diff --git a/apps/web/public/i18n/locales/en/config.json b/apps/web/public/i18n/locales/en/config.json index c1da8f4a5..d04a99807 100644 --- a/apps/web/public/i18n/locales/en/config.json +++ b/apps/web/public/i18n/locales/en/config.json @@ -483,6 +483,26 @@ "description": "Settings for the Security configuration", "title": "Security", "button_backupKey": "Backup Key", + "packetAuthenticity": { + "label": "Packet authenticity", + "description": "Configure the radio's receive policy for every remote mesh packet it can decrypt.", + "protectionLevel": "Protection level", + "unavailable": "Unavailable while disconnected or when the firmware does not report XEdDSA verification support.", + "options": { + "compatible": { + "label": "Compatible — Accept unsigned", + "description": "Verify XEdDSA signatures when present, but accept unsigned remote packets for maximum compatibility." + }, + "balanced": { + "label": "Balanced — Prefer authenticated", + "description": "Reject unsigned, signable broadcasts from nodes known to sign while keeping legacy unsigned remote traffic." + }, + "strict": { + "label": "Strict — Require authenticated", + "description": "Only show and process remote mesh packets verified by XEdDSA or successfully authenticated by PKI. Sender authentication does not prove coordinates or freshness." + } + } + }, "adminChannelEnabled": { "description": "Allow incoming device control over the insecure legacy admin channel", "label": "Legacy Admin channel" diff --git a/apps/web/public/i18n/locales/en/dialog.json b/apps/web/public/i18n/locales/en/dialog.json index 5d8e36b40..1988a21e7 100644 --- a/apps/web/public/i18n/locales/en/dialog.json +++ b/apps/web/public/i18n/locales/en/dialog.json @@ -29,6 +29,11 @@ "description": "Are you sure you want to regenerate the pre-shared key?", "regenerate": "Regenerate" }, + "packetAuthenticityStrict": { + "title": "Require authenticated packets?", + "description": "Strict accepts only remotely received mesh packets with a verified XEdDSA signature or successful PKI authentication. PKI-authenticated direct messages remain accepted. Older firmware, licensed/ham nodes without signing keys, and oversized unsigned packets may disappear, reducing mesh visibility. Authentication verifies a cryptographic identity; it does not prove that content is truthful or fresh.", + "confirm": "Enable Strict" + }, "addConnection": { "title": "Add connection", "description": "Choose a connection type and fill in the details", diff --git a/apps/web/src/components/Form/FormSelect.tsx b/apps/web/src/components/Form/FormSelect.tsx index dc801a920..ae2e28667 100644 --- a/apps/web/src/components/Form/FormSelect.tsx +++ b/apps/web/src/components/Form/FormSelect.tsx @@ -22,6 +22,7 @@ export interface SelectFieldProps extends BaseFormBuilderProps { enumValue: { [s: string]: string | number; }; + optionLabels?: Record; formatEnumName?: boolean; }; } @@ -51,6 +52,7 @@ export function SelectInput({ const { enumValue, + optionLabels, formatEnumName, defaultValue, className, @@ -113,7 +115,8 @@ export function SelectInput({ {optionsEnumValues.map(([name, val]) => ( - {formatEnumName ? formatEnumDisplay(name) : name} + {optionLabels?.[name] ?? + (formatEnumName ? formatEnumDisplay(name) : name)} ))} diff --git a/apps/web/src/components/PageComponents/Settings/Security/PacketAuthenticityPolicyField.test.tsx b/apps/web/src/components/PageComponents/Settings/Security/PacketAuthenticityPolicyField.test.tsx new file mode 100644 index 000000000..2035ce23a --- /dev/null +++ b/apps/web/src/components/PageComponents/Settings/Security/PacketAuthenticityPolicyField.test.tsx @@ -0,0 +1,72 @@ +import { Protobuf } from "@meshtastic/sdk"; +import { render, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { useForm } from "react-hook-form"; +import { describe, expect, it, vi } from "vitest"; +import type { RawSecurity } from "@app/validation/config/security.ts"; +import { + PACKET_SIGNATURE_POLICY_OPTIONS, + PacketAuthenticityPolicyField, +} from "./PacketAuthenticityPolicyField.tsx"; + +const Policy = Protobuf.Config.Config_SecurityConfig_PacketSignaturePolicy; + +const Harness = ({ supported }: { supported: boolean }) => { + const { control } = useForm({ + defaultValues: { packetSignaturePolicy: Policy.COMPATIBLE }, + }); + + return ( + + ); +}; + +describe("PacketAuthenticityPolicyField", () => { + it("disables configuration and explains the unavailable capability", () => { + render(); + + expect( + screen.getByRole("combobox", { name: "Protection level" }), + ).toBeDisabled(); + expect( + screen.getByText(/disconnected.*XEdDSA verification support/i), + ).toBeInTheDocument(); + }); + + it("presents Compatible, Balanced, and Strict in explicit product order", async () => { + const user = userEvent.setup(); + render(); + + expect(Object.keys(PACKET_SIGNATURE_POLICY_OPTIONS)).toEqual([ + "COMPATIBLE", + "BALANCED", + "STRICT", + ]); + + await user.click( + screen.getByRole("combobox", { name: "Protection level" }), + ); + + expect( + screen.getAllByRole("option").map((option) => option.textContent), + ).toEqual([ + "Compatible — Accept unsigned", + "Balanced — Prefer authenticated", + "Strict — Require authenticated", + ]); + }); + + it("shows Compatible compatibility copy by default", () => { + render(); + + expect( + screen.getByText( + /accept unsigned remote packets for maximum compatibility/i, + ), + ).toBeInTheDocument(); + }); +}); diff --git a/apps/web/src/components/PageComponents/Settings/Security/PacketAuthenticityPolicyField.tsx b/apps/web/src/components/PageComponents/Settings/Security/PacketAuthenticityPolicyField.tsx new file mode 100644 index 000000000..94d960d5d --- /dev/null +++ b/apps/web/src/components/PageComponents/Settings/Security/PacketAuthenticityPolicyField.tsx @@ -0,0 +1,73 @@ +import { SelectInput } from "@components/Form/FormSelect.tsx"; +import { FieldWrapper } from "@components/Form/FormWrapper.tsx"; +import { Protobuf } from "@meshtastic/sdk"; +import type { Control } from "react-hook-form"; +import { useWatch } from "react-hook-form"; +import { useTranslation } from "react-i18next"; +import type { RawSecurity } from "@app/validation/config/security.ts"; +import { PACKET_SIGNATURE_POLICY_OPTIONS } from "./packetAuthenticityPolicy.ts"; + +const Policy = Protobuf.Config.Config_SecurityConfig_PacketSignaturePolicy; + +export { PACKET_SIGNATURE_POLICY_OPTIONS } from "./packetAuthenticityPolicy.ts"; + +const POLICY_DESCRIPTION_KEYS: Record< + Protobuf.Config.Config_SecurityConfig_PacketSignaturePolicy, + string +> = { + [Policy.COMPATIBLE]: + "security.packetAuthenticity.options.compatible.description", + [Policy.BALANCED]: "security.packetAuthenticity.options.balanced.description", + [Policy.STRICT]: "security.packetAuthenticity.options.strict.description", +}; + +interface PacketAuthenticityPolicyFieldProps { + control: Control; + supported: boolean; + validateSelection: (policyKey: string) => Promise; +} + +export const PacketAuthenticityPolicyField = ({ + control, + supported, + validateSelection, +}: PacketAuthenticityPolicyFieldProps) => { + const { t } = useTranslation("config"); + const selectedPolicy = useWatch({ + control, + name: "packetSignaturePolicy", + }); + const description = supported + ? t(POLICY_DESCRIPTION_KEYS[selectedPolicy] ?? POLICY_DESCRIPTION_KEYS[0]) + : t("security.packetAuthenticity.unavailable"); + + return ( + + + control={control} + disabled={!supported} + field={{ + type: "select", + name: "packetSignaturePolicy", + label: t("security.packetAuthenticity.protectionLevel"), + validate: validateSelection, + properties: { + enumValue: PACKET_SIGNATURE_POLICY_OPTIONS, + optionLabels: { + COMPATIBLE: t( + "security.packetAuthenticity.options.compatible.label", + ), + BALANCED: t("security.packetAuthenticity.options.balanced.label"), + STRICT: t("security.packetAuthenticity.options.strict.label"), + }, + "aria-label": t("security.packetAuthenticity.protectionLevel"), + }, + }} + /> + + ); +}; diff --git a/apps/web/src/components/PageComponents/Settings/Security/PacketAuthenticityStrictDialog.test.tsx b/apps/web/src/components/PageComponents/Settings/Security/PacketAuthenticityStrictDialog.test.tsx new file mode 100644 index 000000000..732687061 --- /dev/null +++ b/apps/web/src/components/PageComponents/Settings/Security/PacketAuthenticityStrictDialog.test.tsx @@ -0,0 +1,43 @@ +import { fireEvent, render, screen } from "@testing-library/react"; +import { describe, expect, it, vi } from "vitest"; +import { PacketAuthenticityStrictDialog } from "./PacketAuthenticityStrictDialog.tsx"; + +describe("PacketAuthenticityStrictDialog", () => { + it("explains Strict authentication and visibility consequences", () => { + render( + , + ); + + expect(screen.getByText(/verified XEdDSA signature/i)).toBeInTheDocument(); + expect( + screen.getByText(/successful PKI authentication/i), + ).toBeInTheDocument(); + expect(screen.getByText(/older firmware/i)).toBeInTheDocument(); + expect(screen.getByText(/licensed\/ham nodes/i)).toBeInTheDocument(); + expect(screen.getByText(/oversized unsigned packets/i)).toBeInTheDocument(); + }); + + it("exposes explicit confirm and cancel actions", () => { + const onConfirm = vi.fn(); + const onCancel = vi.fn(); + render( + , + ); + + fireEvent.click(screen.getByRole("button", { name: "Enable Strict" })); + fireEvent.click(screen.getByRole("button", { name: "Cancel" })); + + expect(onConfirm).toHaveBeenCalledOnce(); + expect(onCancel).toHaveBeenCalledOnce(); + }); +}); diff --git a/apps/web/src/components/PageComponents/Settings/Security/PacketAuthenticityStrictDialog.tsx b/apps/web/src/components/PageComponents/Settings/Security/PacketAuthenticityStrictDialog.tsx new file mode 100644 index 000000000..0544dd7b4 --- /dev/null +++ b/apps/web/src/components/PageComponents/Settings/Security/PacketAuthenticityStrictDialog.tsx @@ -0,0 +1,32 @@ +import { DialogWrapper } from "@components/Dialog/DialogWrapper.tsx"; +import { useTranslation } from "react-i18next"; + +interface PacketAuthenticityStrictDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; + onConfirm: () => void; + onCancel: () => void; +} + +export const PacketAuthenticityStrictDialog = ({ + open, + onOpenChange, + onConfirm, + onCancel, +}: PacketAuthenticityStrictDialogProps) => { + const { t } = useTranslation("dialog"); + + return ( + + ); +}; diff --git a/apps/web/src/components/PageComponents/Settings/Security/Security.test.ts b/apps/web/src/components/PageComponents/Settings/Security/Security.test.ts new file mode 100644 index 000000000..c37a68664 --- /dev/null +++ b/apps/web/src/components/PageComponents/Settings/Security/Security.test.ts @@ -0,0 +1,67 @@ +import { Protobuf } from "@meshtastic/sdk"; +import { describe, expect, it, vi } from "vitest"; +import type { RawSecurity } from "@app/validation/config/security.ts"; +import { + submitSecurityConfig, + toFormShape, + toSecurityPayload, +} from "./Security.tsx"; + +const Policy = Protobuf.Config.Config_SecurityConfig_PacketSignaturePolicy; + +const rawSecurity = ( + packetSignaturePolicy: RawSecurity["packetSignaturePolicy"], +) => + ({ + isManaged: false, + adminChannelEnabled: false, + debugLogApiEnabled: false, + serialEnabled: true, + packetSignaturePolicy, + privateKey: "", + publicKey: "", + adminKey: ["", "", ""], + }) satisfies RawSecurity; + +describe("Security packet authenticity persistence", () => { + it("maps an absent firmware policy to Compatible", () => { + const form = toFormShape({} as Protobuf.Config.Config_SecurityConfig); + + expect(form.packetSignaturePolicy).toBe(Policy.COMPATIBLE); + }); + + it.each([Policy.COMPATIBLE, Policy.BALANCED, Policy.STRICT])( + "preserves policy %s when mapping firmware config into the form", + (packetSignaturePolicy) => { + const form = toFormShape({ + packetSignaturePolicy, + } as Protobuf.Config.Config_SecurityConfig); + + expect(form.packetSignaturePolicy).toBe(packetSignaturePolicy); + }, + ); + + it.each([Policy.COMPATIBLE, Policy.BALANCED, Policy.STRICT])( + "preserves policy %s when mapping the form to a security payload", + (packetSignaturePolicy) => { + expect( + toSecurityPayload(rawSecurity(packetSignaturePolicy)) + .packetSignaturePolicy, + ).toBe(packetSignaturePolicy); + }, + ); + + it.each([Policy.COMPATIBLE, Policy.BALANCED, Policy.STRICT])( + "submits policy %s through the security radio section", + (packetSignaturePolicy) => { + const setRadioSection = vi.fn(); + + submitSecurityConfig(setRadioSection, rawSecurity(packetSignaturePolicy)); + + expect(setRadioSection).toHaveBeenCalledWith( + "security", + expect.objectContaining({ packetSignaturePolicy }), + ); + }, + ); +}); diff --git a/apps/web/src/components/PageComponents/Settings/Security/Security.tsx b/apps/web/src/components/PageComponents/Settings/Security/Security.tsx index 152286d79..4a898ed08 100644 --- a/apps/web/src/components/PageComponents/Settings/Security/Security.tsx +++ b/apps/web/src/components/PageComponents/Settings/Security/Security.tsx @@ -11,14 +11,21 @@ import { DynamicForm, type DynamicFormFormInit, } from "@components/Form/DynamicForm.tsx"; -import { useDevice } from "@core/stores"; +import { useDevice, useIsConnected } from "@core/stores"; import { getX25519PrivateKey, getX25519PublicKey } from "@core/utils/x25519.ts"; import { Protobuf } from "@meshtastic/sdk"; -import { useConfigEditor, useSignal } from "@meshtastic/sdk-react"; +import { + useConfigEditor, + useMeshDevice, + useSignal, +} from "@meshtastic/sdk-react"; import { fromByteArray, toByteArray } from "base64-js"; import { useEffect, useState } from "react"; import { type DefaultValues, useForm } from "react-hook-form"; import { useTranslation } from "react-i18next"; +import { PacketAuthenticityPolicyField } from "./PacketAuthenticityPolicyField.tsx"; +import { PacketAuthenticityStrictDialog } from "./PacketAuthenticityStrictDialog.tsx"; +import { usePacketAuthenticityConfirmation } from "./usePacketAuthenticityConfirmation.ts"; interface SecurityConfigProps { onFormInit: DynamicFormFormInit; @@ -30,7 +37,10 @@ const EMPTY_RADIO_SIGNAL = { subscribe: () => () => {}, } as const; -const toFormShape = ( +const PacketSignaturePolicy = + Protobuf.Config.Config_SecurityConfig_PacketSignaturePolicy; + +export const toFormShape = ( cfg: Protobuf.Config.Config_SecurityConfig | undefined, ) => ({ ...cfg, @@ -41,13 +51,49 @@ const toFormShape = ( fromByteArray(cfg?.adminKey?.at(1) ?? new Uint8Array(0)), fromByteArray(cfg?.adminKey?.at(2) ?? new Uint8Array(0)), ], + packetSignaturePolicy: + cfg?.packetSignaturePolicy ?? PacketSignaturePolicy.COMPATIBLE, +}); + +export const toSecurityPayload = (data: RawSecurity): ParsedSecurity => ({ + ...data, + privateKey: toByteArray(data.privateKey), + publicKey: toByteArray(data.publicKey), + adminKey: [ + toByteArray(data.adminKey.at(0) ?? ""), + toByteArray(data.adminKey.at(1) ?? ""), + toByteArray(data.adminKey.at(2) ?? ""), + ], }); +type SetSecuritySection = ( + section: "security", + payload: Protobuf.Config.Config_SecurityConfig, +) => void; + +export const submitSecurityConfig = ( + setRadioSection: SetSecuritySection, + data: RawSecurity, +) => { + setRadioSection( + "security", + toSecurityPayload(data) as unknown as Protobuf.Config.Config_SecurityConfig, + ); +}; + export const Security = ({ onFormInit }: SecurityConfigProps) => { useWaitForConfig({ configCase: "security" }); - const { config, setDialogOpen, getEffectiveConfig } = useDevice(); + const { config, setDialogOpen, getEffectiveConfig, connectionId } = + useDevice(); + const isConnected = useIsConnected(connectionId ?? -1); const editor = useConfigEditor(); + const { metadata } = useMeshDevice(); + const canConfigurePacketAuthenticity = + isConnected && metadata?.hasXeddsa === true; + const packetAuthenticityConfirmation = usePacketAuthenticityConfirmation( + canConfigurePacketAuthenticity, + ); const radio = useSignal(editor?.radio ?? EMPTY_RADIO_SIGNAL); const effective = radio.security ?? @@ -79,19 +125,9 @@ export const Security = ({ onFormInit }: SecurityConfigProps) => { const onSubmit = (data: RawSecurity) => { if (!formState.isReady || !editor) return; - const payload: ParsedSecurity = { - ...data, - privateKey: toByteArray(data.privateKey), - publicKey: toByteArray(data.publicKey), - adminKey: [ - toByteArray(data.adminKey.at(0) ?? ""), - toByteArray(data.adminKey.at(1) ?? ""), - toByteArray(data.adminKey.at(2) ?? ""), - ], - }; - editor.setRadioSection( - "security", - payload as unknown as Protobuf.Config.Config_SecurityConfig, + submitSecurityConfig( + editor.setRadioSection.bind(editor) as SetSecuritySection, + data, ); }; @@ -132,6 +168,20 @@ export const Security = ({ onFormInit }: SecurityConfigProps) => { propMethods={formMethods} onSubmit={onSubmit} fieldGroups={[ + { + label: t("security.packetAuthenticity.label"), + description: t("security.packetAuthenticity.description"), + fields: [], + footer: ( + + ), + }, { label: t("security.directMessageKey.label"), description: t("security.directMessageKey.description"), @@ -288,6 +338,13 @@ export const Security = ({ onFormInit }: SecurityConfigProps) => { setManagedModeDialogOpen(false); }} /> + + ); }; diff --git a/apps/web/src/components/PageComponents/Settings/Security/packetAuthenticityPolicy.ts b/apps/web/src/components/PageComponents/Settings/Security/packetAuthenticityPolicy.ts new file mode 100644 index 000000000..e52a1ef4a --- /dev/null +++ b/apps/web/src/components/PageComponents/Settings/Security/packetAuthenticityPolicy.ts @@ -0,0 +1,15 @@ +import { Protobuf } from "@meshtastic/sdk"; + +const Policy = Protobuf.Config.Config_SecurityConfig_PacketSignaturePolicy; + +export const PACKET_SIGNATURE_POLICY_OPTIONS = { + COMPATIBLE: Policy.COMPATIBLE, + BALANCED: Policy.BALANCED, + STRICT: Policy.STRICT, +} as const; + +export type PacketSignaturePolicyKey = + keyof typeof PACKET_SIGNATURE_POLICY_OPTIONS; + +export const STRICT_PACKET_SIGNATURE_POLICY_KEY = + "STRICT" satisfies PacketSignaturePolicyKey; diff --git a/apps/web/src/components/PageComponents/Settings/Security/usePacketAuthenticityConfirmation.test.tsx b/apps/web/src/components/PageComponents/Settings/Security/usePacketAuthenticityConfirmation.test.tsx new file mode 100644 index 000000000..753438120 --- /dev/null +++ b/apps/web/src/components/PageComponents/Settings/Security/usePacketAuthenticityConfirmation.test.tsx @@ -0,0 +1,76 @@ +import { act, renderHook } from "@testing-library/react"; +import { describe, expect, it } from "vitest"; +import { usePacketAuthenticityConfirmation } from "./usePacketAuthenticityConfirmation.ts"; + +describe("usePacketAuthenticityConfirmation", () => { + it("allows supported non-Strict choices without confirmation", async () => { + const { result } = renderHook(() => + usePacketAuthenticityConfirmation(true), + ); + + await expect(result.current.validateSelection("BALANCED")).resolves.toBe( + true, + ); + expect(result.current.strictDialogOpen).toBe(false); + }); + + it("requires confirmation before accepting Strict", async () => { + const { result } = renderHook(() => + usePacketAuthenticityConfirmation(true), + ); + let pending!: Promise; + + act(() => { + pending = result.current.validateSelection("STRICT"); + }); + expect(result.current.strictDialogOpen).toBe(true); + + act(() => result.current.confirmStrict()); + await expect(pending).resolves.toBe(true); + expect(result.current.strictDialogOpen).toBe(false); + }); + + it("keeps the current policy when Strict is canceled", async () => { + const { result } = renderHook(() => + usePacketAuthenticityConfirmation(true), + ); + let pending!: Promise; + + act(() => { + pending = result.current.validateSelection("STRICT"); + }); + act(() => result.current.cancelStrict()); + + await expect(pending).resolves.toBe(false); + }); + + it("rejects Strict if capability disappears during confirmation", async () => { + const { result, rerender } = renderHook( + ({ supported }) => usePacketAuthenticityConfirmation(supported), + { initialProps: { supported: true } }, + ); + let pending!: Promise; + + act(() => { + pending = result.current.validateSelection("STRICT"); + }); + rerender({ supported: false }); + + await expect(pending).resolves.toBe(false); + expect(result.current.strictDialogOpen).toBe(false); + }); + + it("rejects every policy change when the capability is unavailable", async () => { + const { result } = renderHook(() => + usePacketAuthenticityConfirmation(false), + ); + + await expect(result.current.validateSelection("COMPATIBLE")).resolves.toBe( + false, + ); + await expect(result.current.validateSelection("STRICT")).resolves.toBe( + false, + ); + expect(result.current.strictDialogOpen).toBe(false); + }); +}); diff --git a/apps/web/src/components/PageComponents/Settings/Security/usePacketAuthenticityConfirmation.ts b/apps/web/src/components/PageComponents/Settings/Security/usePacketAuthenticityConfirmation.ts new file mode 100644 index 000000000..88fae603d --- /dev/null +++ b/apps/web/src/components/PageComponents/Settings/Security/usePacketAuthenticityConfirmation.ts @@ -0,0 +1,67 @@ +import { useCallback, useEffect, useRef, useState } from "react"; +import { STRICT_PACKET_SIGNATURE_POLICY_KEY } from "./packetAuthenticityPolicy.ts"; + +export const usePacketAuthenticityConfirmation = (hasXeddsa: boolean) => { + const [strictDialogOpen, setStrictDialogOpen] = useState(false); + const pendingResolution = useRef<((approved: boolean) => void) | null>(null); + const capability = useRef(hasXeddsa); + capability.current = hasXeddsa; + + const resolvePending = useCallback((approved: boolean) => { + const resolve = pendingResolution.current; + pendingResolution.current = null; + setStrictDialogOpen(false); + resolve?.(approved && capability.current); + }, []); + + const validateSelection = useCallback( + (policyKey: string): Promise => { + if (!hasXeddsa) { + return Promise.resolve(false); + } + + if (policyKey !== STRICT_PACKET_SIGNATURE_POLICY_KEY) { + return Promise.resolve(true); + } + + pendingResolution.current?.(false); + setStrictDialogOpen(true); + + return new Promise((resolve) => { + pendingResolution.current = resolve; + }); + }, + [hasXeddsa], + ); + + const handleDialogOpenChange = useCallback( + (open: boolean) => { + if (!open) { + resolvePending(false); + } + }, + [resolvePending], + ); + + useEffect(() => { + if (!hasXeddsa) { + resolvePending(false); + } + }, [hasXeddsa, resolvePending]); + + useEffect( + () => () => { + pendingResolution.current?.(false); + pendingResolution.current = null; + }, + [], + ); + + return { + strictDialogOpen, + validateSelection, + confirmStrict: () => resolvePending(true), + cancelStrict: () => resolvePending(false), + handleDialogOpenChange, + }; +}; diff --git a/apps/web/src/components/generic/Filter/useFilterNode.test.ts b/apps/web/src/components/generic/Filter/useFilterNode.test.ts index 1ed8fa00d..d199683ff 100644 --- a/apps/web/src/components/generic/Filter/useFilterNode.test.ts +++ b/apps/web/src/components/generic/Filter/useFilterNode.test.ts @@ -16,6 +16,7 @@ function createMockNode(): Protobuf.Mesh.NodeInfo { isMuted: false, hopsAway: 2, isKeyManuallyVerified: false, + hasXeddsaSigned: false, user: { $typeName: "meshtastic.User", id: "!12345678", diff --git a/apps/web/src/validation/config/security.test.ts b/apps/web/src/validation/config/security.test.ts index abc8edda9..53035754d 100644 --- a/apps/web/src/validation/config/security.test.ts +++ b/apps/web/src/validation/config/security.test.ts @@ -1,7 +1,11 @@ +import { create, fromBinary, toBinary } from "@bufbuild/protobuf"; +import { Protobuf } from "@meshtastic/sdk"; import { fromByteArray } from "base64-js"; import { describe, expect, it } from "vitest"; import { ParsedSecuritySchema, RawSecuritySchema } from "./security.ts"; +const Policy = Protobuf.Config.Config_SecurityConfig_PacketSignaturePolicy; + function makeBase64OfLength(len: number): string { return fromByteArray(new Uint8Array(len)); } @@ -15,6 +19,7 @@ describe("RawSecuritySchema", () => { adminChannelEnabled: true, debugLogApiEnabled: false, serialEnabled: true, + packetSignaturePolicy: Policy.BALANCED, privateKey: validKey, publicKey: validKey, adminKey: [validKey, "", ""], @@ -28,6 +33,7 @@ describe("RawSecuritySchema", () => { adminChannelEnabled: true, debugLogApiEnabled: false, serialEnabled: true, + packetSignaturePolicy: Policy.BALANCED, privateKey: "badkey", publicKey: validKey, adminKey: [validKey, "", ""], @@ -46,6 +52,7 @@ describe("RawSecuritySchema", () => { adminChannelEnabled: true, debugLogApiEnabled: false, serialEnabled: true, + packetSignaturePolicy: Policy.BALANCED, privateKey: validKey, publicKey: validKey, adminKey: ["", "", ""], @@ -66,6 +73,7 @@ describe("RawSecuritySchema", () => { adminChannelEnabled: true, debugLogApiEnabled: false, serialEnabled: true, + packetSignaturePolicy: Policy.BALANCED, privateKey: validKey, publicKey: validKey, adminKey: [validKey, "", ""], @@ -83,6 +91,7 @@ describe("ParsedSecuritySchema", () => { adminChannelEnabled: true, debugLogApiEnabled: false, serialEnabled: true, + packetSignaturePolicy: Policy.BALANCED, privateKey: validKey, publicKey: validKey, adminKey: [validKey, new Uint8Array(), new Uint8Array()], @@ -96,6 +105,7 @@ describe("ParsedSecuritySchema", () => { adminChannelEnabled: true, debugLogApiEnabled: false, serialEnabled: true, + packetSignaturePolicy: Policy.BALANCED, privateKey: validKey, publicKey: validKey, adminKey: [new Uint8Array(), new Uint8Array(), new Uint8Array()], @@ -110,3 +120,22 @@ describe("ParsedSecuritySchema", () => { } }); }); + +describe("packet signature policy protobuf", () => { + it("defaults an absent policy to Compatible", () => { + const security = create(Protobuf.Config.Config_SecurityConfigSchema); + + expect(security.packetSignaturePolicy).toBe(Policy.COMPATIBLE); + }); + + it.each([Policy.COMPATIBLE, Policy.BALANCED, Policy.STRICT])( + "serializes and decodes policy %s", + (packetSignaturePolicy) => { + const schema = Protobuf.Config.Config_SecurityConfigSchema; + const security = create(schema, { packetSignaturePolicy }); + const decoded = fromBinary(schema, toBinary(schema, security)); + + expect(decoded.packetSignaturePolicy).toBe(packetSignaturePolicy); + }, + ); +}); diff --git a/apps/web/src/validation/config/security.ts b/apps/web/src/validation/config/security.ts index 5ff91831f..497a9af50 100644 --- a/apps/web/src/validation/config/security.ts +++ b/apps/web/src/validation/config/security.ts @@ -1,9 +1,13 @@ import { type ZodType, z } from "zod/v4"; +import { Protobuf } from "@meshtastic/sdk"; import { makePskHelpers } from "./../pskSchema.ts"; const { stringSchema, bytesSchema, isValidKey } = makePskHelpers([32]); // 256-bit const isManagedRequiredMsg = "formValidation.required.managed"; +const PacketSignaturePolicyEnum = z.enum( + Protobuf.Config.Config_SecurityConfig_PacketSignaturePolicy, +); function makeSecuritySchema( keyMaker: (optional: boolean) => ZodType, @@ -14,6 +18,7 @@ function makeSecuritySchema( adminChannelEnabled: z.boolean(), debugLogApiEnabled: z.boolean(), serialEnabled: z.boolean(), + packetSignaturePolicy: PacketSignaturePolicyEnum, privateKey: keyMaker(false), publicKey: keyMaker(false), diff --git a/packages/protobufs/meshtastic/config.proto b/packages/protobufs/meshtastic/config.proto index aa5264e2c..e33fb7c22 100644 --- a/packages/protobufs/meshtastic/config.proto +++ b/packages/protobufs/meshtastic/config.proto @@ -1286,6 +1286,29 @@ message Config { } message SecurityConfig { + /* + * Controls how the device authenticates remotely received mesh packets. + */ + enum PacketSignaturePolicy { + /* + * Accept unsigned packets for maximum compatibility while still rejecting malformed or invalid signatures. + * This is the default to avoid legacy nodes dropping signed packets during rebroadcast. + */ + PACKET_SIGNATURE_POLICY_COMPATIBLE = 0; + + /* + * Prefer authenticated packets while retaining compatibility with unsigned packets from nodes not known to sign. + * Rejects unsigned, signable broadcasts from nodes that have previously signed. + */ + PACKET_SIGNATURE_POLICY_BALANCED = 1; + + /* + * Accept only packets authenticated by a verified XEdDSA signature or successful PKI decryption. + * Unsigned, malformed, invalid, or unverifiable packets are ignored. + */ + PACKET_SIGNATURE_POLICY_STRICT = 2; + } + /* * The public key of the user's device. * Sent out to other nodes on the mesh to allow them to compute a shared secret key. @@ -1324,6 +1347,11 @@ message Config { * Allow incoming device control over the insecure legacy admin channel. */ bool admin_channel_enabled = 8; + + /* + * Determines the packet signature policy applied to remotely received mesh packets. + */ + PacketSignaturePolicy packet_signature_policy = 9; } /* diff --git a/packages/protobufs/meshtastic/mesh.proto b/packages/protobufs/meshtastic/mesh.proto index ecfdadd9a..a29111c8d 100644 --- a/packages/protobufs/meshtastic/mesh.proto +++ b/packages/protobufs/meshtastic/mesh.proto @@ -2675,6 +2675,12 @@ message DeviceMetadata { * (bitwise OR of ExcludedModules) */ uint32 excluded_modules = 12; + + /* + * Indicates whether this firmware build includes XEdDSA packet signature verification. + * This is a read-only capability and must be false when XEdDSA is not compiled in. + */ + bool has_xeddsa = 14; } /* diff --git a/packages/protobufs/packages/ts/dist/meshtastic/config_pb.ts b/packages/protobufs/packages/ts/dist/meshtastic/config_pb.ts index 0b8be50a2..3de54eebf 100644 --- a/packages/protobufs/packages/ts/dist/meshtastic/config_pb.ts +++ b/packages/protobufs/packages/ts/dist/meshtastic/config_pb.ts @@ -1,4 +1,4 @@ -// @generated by protoc-gen-es v2.12.0 with parameter "target=ts" +// @generated by protoc-gen-es v2.12.1 with parameter "target=ts" // @generated from file meshtastic/config.proto (package meshtastic, syntax proto3) /* eslint-disable */ @@ -12,7 +12,7 @@ import type { Message } from "@bufbuild/protobuf"; * Describes the file meshtastic/config.proto. */ export const file_meshtastic_config: GenFile = /*@__PURE__*/ - fileDesc("ChdtZXNodGFzdGljL2NvbmZpZy5wcm90bxIKbWVzaHRhc3RpYyLlLAoGQ29uZmlnEjEKBmRldmljZRgBIAEoCzIfLm1lc2h0YXN0aWMuQ29uZmlnLkRldmljZUNvbmZpZ0gAEjUKCHBvc2l0aW9uGAIgASgLMiEubWVzaHRhc3RpYy5Db25maWcuUG9zaXRpb25Db25maWdIABIvCgVwb3dlchgDIAEoCzIeLm1lc2h0YXN0aWMuQ29uZmlnLlBvd2VyQ29uZmlnSAASMwoHbmV0d29yaxgEIAEoCzIgLm1lc2h0YXN0aWMuQ29uZmlnLk5ldHdvcmtDb25maWdIABIzCgdkaXNwbGF5GAUgASgLMiAubWVzaHRhc3RpYy5Db25maWcuRGlzcGxheUNvbmZpZ0gAEi0KBGxvcmEYBiABKAsyHS5tZXNodGFzdGljLkNvbmZpZy5Mb1JhQ29uZmlnSAASNwoJYmx1ZXRvb3RoGAcgASgLMiIubWVzaHRhc3RpYy5Db25maWcuQmx1ZXRvb3RoQ29uZmlnSAASNQoIc2VjdXJpdHkYCCABKAsyIS5tZXNodGFzdGljLkNvbmZpZy5TZWN1cml0eUNvbmZpZ0gAEjkKCnNlc3Npb25rZXkYCSABKAsyIy5tZXNodGFzdGljLkNvbmZpZy5TZXNzaW9ua2V5Q29uZmlnSAASLwoJZGV2aWNlX3VpGAogASgLMhoubWVzaHRhc3RpYy5EZXZpY2VVSUNvbmZpZ0gAGvYGCgxEZXZpY2VDb25maWcSMgoEcm9sZRgBIAEoDjIkLm1lc2h0YXN0aWMuQ29uZmlnLkRldmljZUNvbmZpZy5Sb2xlEhoKDnNlcmlhbF9lbmFibGVkGAIgASgIQgIYARITCgtidXR0b25fZ3BpbxgEIAEoDRITCgtidXp6ZXJfZ3BpbxgFIAEoDRJJChByZWJyb2FkY2FzdF9tb2RlGAYgASgOMi8ubWVzaHRhc3RpYy5Db25maWcuRGV2aWNlQ29uZmlnLlJlYnJvYWRjYXN0TW9kZRIgChhub2RlX2luZm9fYnJvYWRjYXN0X3NlY3MYByABKA0SIgoaZG91YmxlX3RhcF9hc19idXR0b25fcHJlc3MYCCABKAgSFgoKaXNfbWFuYWdlZBgJIAEoCEICGAESHAoUZGlzYWJsZV90cmlwbGVfY2xpY2sYCiABKAgSDQoFdHpkZWYYCyABKAkSHgoWbGVkX2hlYXJ0YmVhdF9kaXNhYmxlZBgMIAEoCBI/CgtidXp6ZXJfbW9kZRgNIAEoDjIqLm1lc2h0YXN0aWMuQ29uZmlnLkRldmljZUNvbmZpZy5CdXp6ZXJNb2RlItQBCgRSb2xlEgoKBkNMSUVOVBAAEg8KC0NMSUVOVF9NVVRFEAESCgoGUk9VVEVSEAISFQoNUk9VVEVSX0NMSUVOVBADGgIIARIQCghSRVBFQVRFUhAEGgIIARILCgdUUkFDS0VSEAUSCgoGU0VOU09SEAYSBwoDVEFLEAcSEQoNQ0xJRU5UX0hJRERFThAIEhIKDkxPU1RfQU5EX0ZPVU5EEAkSDwoLVEFLX1RSQUNLRVIQChIPCgtST1VURVJfTEFURRALEg8KC0NMSUVOVF9CQVNFEAwicwoPUmVicm9hZGNhc3RNb2RlEgcKA0FMTBAAEhUKEUFMTF9TS0lQX0RFQ09ESU5HEAESDgoKTE9DQUxfT05MWRACEg4KCktOT1dOX09OTFkQAxIICgROT05FEAQSFgoSQ09SRV9QT1JUTlVNU19PTkxZEAUiaQoKQnV6emVyTW9kZRIPCgtBTExfRU5BQkxFRBAAEgwKCERJU0FCTEVEEAESFgoSTk9USUZJQ0FUSU9OU19PTkxZEAISDwoLU1lTVEVNX09OTFkQAxITCg9ESVJFQ1RfTVNHX09OTFkQBBqRBQoOUG9zaXRpb25Db25maWcSHwoXcG9zaXRpb25fYnJvYWRjYXN0X3NlY3MYASABKA0SKAogcG9zaXRpb25fYnJvYWRjYXN0X3NtYXJ0X2VuYWJsZWQYAiABKAgSFgoOZml4ZWRfcG9zaXRpb24YAyABKAgSFwoLZ3BzX2VuYWJsZWQYBCABKAhCAhgBEhsKE2dwc191cGRhdGVfaW50ZXJ2YWwYBSABKA0SHAoQZ3BzX2F0dGVtcHRfdGltZRgGIAEoDUICGAESFgoOcG9zaXRpb25fZmxhZ3MYByABKA0SDwoHcnhfZ3BpbxgIIAEoDRIPCgd0eF9ncGlvGAkgASgNEigKIGJyb2FkY2FzdF9zbWFydF9taW5pbXVtX2Rpc3RhbmNlGAogASgNEi0KJWJyb2FkY2FzdF9zbWFydF9taW5pbXVtX2ludGVydmFsX3NlY3MYCyABKA0SEwoLZ3BzX2VuX2dwaW8YDCABKA0SOwoIZ3BzX21vZGUYDSABKA4yKS5tZXNodGFzdGljLkNvbmZpZy5Qb3NpdGlvbkNvbmZpZy5HcHNNb2RlIqsBCg1Qb3NpdGlvbkZsYWdzEgkKBVVOU0VUEAASDAoIQUxUSVRVREUQARIQCgxBTFRJVFVERV9NU0wQAhIWChJHRU9JREFMX1NFUEFSQVRJT04QBBIHCgNET1AQCBIJCgVIVkRPUBAQEg0KCVNBVElOVklFVxAgEgoKBlNFUV9OTxBAEg4KCVRJTUVTVEFNUBCAARIMCgdIRUFESU5HEIACEgoKBVNQRUVEEIAEIjUKB0dwc01vZGUSDAoIRElTQUJMRUQQABILCgdFTkFCTEVEEAESDwoLTk9UX1BSRVNFTlQQAhqEAgoLUG93ZXJDb25maWcSFwoPaXNfcG93ZXJfc2F2aW5nGAEgASgIEiYKHm9uX2JhdHRlcnlfc2h1dGRvd25fYWZ0ZXJfc2VjcxgCIAEoDRIfChdhZGNfbXVsdGlwbGllcl9vdmVycmlkZRgDIAEoAhIbChN3YWl0X2JsdWV0b290aF9zZWNzGAQgASgNEhAKCHNkc19zZWNzGAYgASgNEg8KB2xzX3NlY3MYByABKA0SFQoNbWluX3dha2Vfc2VjcxgIIAEoDRIiChpkZXZpY2VfYmF0dGVyeV9pbmFfYWRkcmVzcxgJIAEoDRIYChBwb3dlcm1vbl9lbmFibGVzGCAgASgEGuUDCg1OZXR3b3JrQ29uZmlnEhQKDHdpZmlfZW5hYmxlZBgBIAEoCBIRCgl3aWZpX3NzaWQYAyABKAkSEAoId2lmaV9wc2sYBCABKAkSEgoKbnRwX3NlcnZlchgFIAEoCRITCgtldGhfZW5hYmxlZBgGIAEoCBJCCgxhZGRyZXNzX21vZGUYByABKA4yLC5tZXNodGFzdGljLkNvbmZpZy5OZXR3b3JrQ29uZmlnLkFkZHJlc3NNb2RlEkAKC2lwdjRfY29uZmlnGAggASgLMisubWVzaHRhc3RpYy5Db25maWcuTmV0d29ya0NvbmZpZy5JcFY0Q29uZmlnEhYKDnJzeXNsb2dfc2VydmVyGAkgASgJEhkKEWVuYWJsZWRfcHJvdG9jb2xzGAogASgNEhQKDGlwdjZfZW5hYmxlZBgLIAEoCBpGCgpJcFY0Q29uZmlnEgoKAmlwGAEgASgHEg8KB2dhdGV3YXkYAiABKAcSDgoGc3VibmV0GAMgASgHEgsKA2RucxgEIAEoByIjCgtBZGRyZXNzTW9kZRIICgRESENQEAASCgoGU1RBVElDEAEiNAoNUHJvdG9jb2xGbGFncxIQCgxOT19CUk9BRENBU1QQABIRCg1VRFBfQlJPQURDQVNUEAEawggKDURpc3BsYXlDb25maWcSFgoOc2NyZWVuX29uX3NlY3MYASABKA0SVgoKZ3BzX2Zvcm1hdBgCIAEoDjI+Lm1lc2h0YXN0aWMuQ29uZmlnLkRpc3BsYXlDb25maWcuRGVwcmVjYXRlZEdwc0Nvb3JkaW5hdGVGb3JtYXRCAhgBEiEKGWF1dG9fc2NyZWVuX2Nhcm91c2VsX3NlY3MYAyABKA0SHQoRY29tcGFzc19ub3J0aF90b3AYBCABKAhCAhgBEhMKC2ZsaXBfc2NyZWVuGAUgASgIEjwKBXVuaXRzGAYgASgOMi0ubWVzaHRhc3RpYy5Db25maWcuRGlzcGxheUNvbmZpZy5EaXNwbGF5VW5pdHMSNwoEb2xlZBgHIAEoDjIpLm1lc2h0YXN0aWMuQ29uZmlnLkRpc3BsYXlDb25maWcuT2xlZFR5cGUSQQoLZGlzcGxheW1vZGUYCCABKA4yLC5tZXNodGFzdGljLkNvbmZpZy5EaXNwbGF5Q29uZmlnLkRpc3BsYXlNb2RlEhQKDGhlYWRpbmdfYm9sZBgJIAEoCBIdChV3YWtlX29uX3RhcF9vcl9tb3Rpb24YCiABKAgSUAoTY29tcGFzc19vcmllbnRhdGlvbhgLIAEoDjIzLm1lc2h0YXN0aWMuQ29uZmlnLkRpc3BsYXlDb25maWcuQ29tcGFzc09yaWVudGF0aW9uEhUKDXVzZV8xMmhfY2xvY2sYDCABKAgSGgoSdXNlX2xvbmdfbm9kZV9uYW1lGA0gASgIEh4KFmVuYWJsZV9tZXNzYWdlX2J1YmJsZXMYDiABKAgiKwodRGVwcmVjYXRlZEdwc0Nvb3JkaW5hdGVGb3JtYXQSCgoGVU5VU0VEEAAiKAoMRGlzcGxheVVuaXRzEgoKBk1FVFJJQxAAEgwKCElNUEVSSUFMEAEifwoIT2xlZFR5cGUSDQoJT0xFRF9BVVRPEAASEAoMT0xFRF9TU0QxMzA2EAESDwoLT0xFRF9TSDExMDYQAhIPCgtPTEVEX1NIMTEwNxADEhcKE09MRURfU0gxMTA3XzEyOF8xMjgQBBIXChNPTEVEX1NIMTEwN19ST1RBVEVEEAUiQQoLRGlzcGxheU1vZGUSCwoHREVGQVVMVBAAEgwKCFRXT0NPTE9SEAESDAoISU5WRVJURUQQAhIJCgVDT0xPUhADIroBChJDb21wYXNzT3JpZW50YXRpb24SDQoJREVHUkVFU18wEAASDgoKREVHUkVFU185MBABEg8KC0RFR1JFRVNfMTgwEAISDwoLREVHUkVFU18yNzAQAxIWChJERUdSRUVTXzBfSU5WRVJURUQQBBIXChNERUdSRUVTXzkwX0lOVkVSVEVEEAUSGAoUREVHUkVFU18xODBfSU5WRVJURUQQBhIYChRERUdSRUVTXzI3MF9JTlZFUlRFRBAHGvkKCgpMb1JhQ29uZmlnEhIKCnVzZV9wcmVzZXQYASABKAgSPwoMbW9kZW1fcHJlc2V0GAIgASgOMikubWVzaHRhc3RpYy5Db25maWcuTG9SYUNvbmZpZy5Nb2RlbVByZXNldBIRCgliYW5kd2lkdGgYAyABKA0SFQoNc3ByZWFkX2ZhY3RvchgEIAEoDRITCgtjb2RpbmdfcmF0ZRgFIAEoDRIYChBmcmVxdWVuY3lfb2Zmc2V0GAYgASgCEjgKBnJlZ2lvbhgHIAEoDjIoLm1lc2h0YXN0aWMuQ29uZmlnLkxvUmFDb25maWcuUmVnaW9uQ29kZRIRCglob3BfbGltaXQYCCABKA0SEgoKdHhfZW5hYmxlZBgJIAEoCBIQCgh0eF9wb3dlchgKIAEoBRITCgtjaGFubmVsX251bRgLIAEoDRIbChNvdmVycmlkZV9kdXR5X2N5Y2xlGAwgASgIEh4KFnN4MTI2eF9yeF9ib29zdGVkX2dhaW4YDSABKAgSGgoSb3ZlcnJpZGVfZnJlcXVlbmN5GA4gASgCEhcKD3BhX2Zhbl9kaXNhYmxlZBgPIAEoCBIXCg9pZ25vcmVfaW5jb21pbmcYZyADKA0SEwoLaWdub3JlX21xdHQYaCABKAgSGQoRY29uZmlnX29rX3RvX21xdHQYaSABKAgSQAoMZmVtX2xuYV9tb2RlGGogASgOMioubWVzaHRhc3RpYy5Db25maWcuTG9SYUNvbmZpZy5GRU1fTE5BX01vZGUSFwoPc2VyaWFsX2hhbF9vbmx5GGsgASgIIsQDCgpSZWdpb25Db2RlEgkKBVVOU0VUEAASBgoCVVMQARIKCgZFVV80MzMQAhIKCgZFVV84NjgQAxIGCgJDThAEEgYKAkpQEAUSBwoDQU5aEAYSBgoCS1IQBxIGCgJUVxAIEgYKAlJVEAkSBgoCSU4QChIKCgZOWl84NjUQCxIGCgJUSBAMEgsKB0xPUkFfMjQQDRIKCgZVQV80MzMQDhIKCgZVQV84NjgQDxIKCgZNWV80MzMQEBIKCgZNWV85MTkQERIKCgZTR185MjMQEhIKCgZQSF80MzMQExIKCgZQSF84NjgQFBIKCgZQSF85MTUQFRILCgdBTlpfNDMzEBYSCgoGS1pfNDMzEBcSCgoGS1pfODYzEBgSCgoGTlBfODY1EBkSCgoGQlJfOTAyEBoSCwoHSVRVMV8yTRAbEgsKB0lUVTJfMk0QHBIKCgZFVV84NjYQHRIKCgZFVV84NzQQHhIKCgZFVV85MTcQHxIMCghFVV9OXzg2OBAgEgsKB0lUVTNfMk0QIRINCglJVFUxXzcwQ00QIhINCglJVFUyXzcwQ00QIxINCglJVFUzXzcwQ00QJBIOCgpJVFUyXzEyNUNNECUimwIKC01vZGVtUHJlc2V0Eg0KCUxPTkdfRkFTVBAAEhEKCUxPTkdfU0xPVxABGgIIARIWCg5WRVJZX0xPTkdfU0xPVxACGgIIARIPCgtNRURJVU1fU0xPVxADEg8KC01FRElVTV9GQVNUEAQSDgoKU0hPUlRfU0xPVxAFEg4KClNIT1JUX0ZBU1QQBhIRCg1MT05HX01PREVSQVRFEAcSDwoLU0hPUlRfVFVSQk8QCBIOCgpMT05HX1RVUkJPEAkSDQoJTElURV9GQVNUEAoSDQoJTElURV9TTE9XEAsSDwoLTkFSUk9XX0ZBU1QQDBIPCgtOQVJST1dfU0xPVxANEg0KCVRJTllfRkFTVBAOEg0KCVRJTllfU0xPVxAPIjoKDEZFTV9MTkFfTW9kZRIMCghESVNBQkxFRBAAEgsKB0VOQUJMRUQQARIPCgtOT1RfUFJFU0VOVBACGq0BCg9CbHVldG9vdGhDb25maWcSDwoHZW5hYmxlZBgBIAEoCBI8CgRtb2RlGAIgASgOMi4ubWVzaHRhc3RpYy5Db25maWcuQmx1ZXRvb3RoQ29uZmlnLlBhaXJpbmdNb2RlEhEKCWZpeGVkX3BpbhgDIAEoDSI4CgtQYWlyaW5nTW9kZRIOCgpSQU5ET01fUElOEAASDQoJRklYRURfUElOEAESCgoGTk9fUElOEAIatgEKDlNlY3VyaXR5Q29uZmlnEhIKCnB1YmxpY19rZXkYASABKAwSEwoLcHJpdmF0ZV9rZXkYAiABKAwSEQoJYWRtaW5fa2V5GAMgAygMEhIKCmlzX21hbmFnZWQYBCABKAgSFgoOc2VyaWFsX2VuYWJsZWQYBSABKAgSHQoVZGVidWdfbG9nX2FwaV9lbmFibGVkGAYgASgIEh0KFWFkbWluX2NoYW5uZWxfZW5hYmxlZBgIIAEoCBoSChBTZXNzaW9ua2V5Q29uZmlnQhEKD3BheWxvYWRfdmFyaWFudEJiChRvcmcubWVzaHRhc3RpYy5wcm90b0IMQ29uZmlnUHJvdG9zWiJnaXRodWIuY29tL21lc2h0YXN0aWMvZ28vZ2VuZXJhdGVkqgIUTWVzaHRhc3RpYy5Qcm90b2J1ZnO6AgBiBnByb3RvMw", [file_meshtastic_device_ui]); + fileDesc("ChdtZXNodGFzdGljL2NvbmZpZy5wcm90bxIKbWVzaHRhc3RpYyLLLgoGQ29uZmlnEjEKBmRldmljZRgBIAEoCzIfLm1lc2h0YXN0aWMuQ29uZmlnLkRldmljZUNvbmZpZ0gAEjUKCHBvc2l0aW9uGAIgASgLMiEubWVzaHRhc3RpYy5Db25maWcuUG9zaXRpb25Db25maWdIABIvCgVwb3dlchgDIAEoCzIeLm1lc2h0YXN0aWMuQ29uZmlnLlBvd2VyQ29uZmlnSAASMwoHbmV0d29yaxgEIAEoCzIgLm1lc2h0YXN0aWMuQ29uZmlnLk5ldHdvcmtDb25maWdIABIzCgdkaXNwbGF5GAUgASgLMiAubWVzaHRhc3RpYy5Db25maWcuRGlzcGxheUNvbmZpZ0gAEi0KBGxvcmEYBiABKAsyHS5tZXNodGFzdGljLkNvbmZpZy5Mb1JhQ29uZmlnSAASNwoJYmx1ZXRvb3RoGAcgASgLMiIubWVzaHRhc3RpYy5Db25maWcuQmx1ZXRvb3RoQ29uZmlnSAASNQoIc2VjdXJpdHkYCCABKAsyIS5tZXNodGFzdGljLkNvbmZpZy5TZWN1cml0eUNvbmZpZ0gAEjkKCnNlc3Npb25rZXkYCSABKAsyIy5tZXNodGFzdGljLkNvbmZpZy5TZXNzaW9ua2V5Q29uZmlnSAASLwoJZGV2aWNlX3VpGAogASgLMhoubWVzaHRhc3RpYy5EZXZpY2VVSUNvbmZpZ0gAGvYGCgxEZXZpY2VDb25maWcSMgoEcm9sZRgBIAEoDjIkLm1lc2h0YXN0aWMuQ29uZmlnLkRldmljZUNvbmZpZy5Sb2xlEhoKDnNlcmlhbF9lbmFibGVkGAIgASgIQgIYARITCgtidXR0b25fZ3BpbxgEIAEoDRITCgtidXp6ZXJfZ3BpbxgFIAEoDRJJChByZWJyb2FkY2FzdF9tb2RlGAYgASgOMi8ubWVzaHRhc3RpYy5Db25maWcuRGV2aWNlQ29uZmlnLlJlYnJvYWRjYXN0TW9kZRIgChhub2RlX2luZm9fYnJvYWRjYXN0X3NlY3MYByABKA0SIgoaZG91YmxlX3RhcF9hc19idXR0b25fcHJlc3MYCCABKAgSFgoKaXNfbWFuYWdlZBgJIAEoCEICGAESHAoUZGlzYWJsZV90cmlwbGVfY2xpY2sYCiABKAgSDQoFdHpkZWYYCyABKAkSHgoWbGVkX2hlYXJ0YmVhdF9kaXNhYmxlZBgMIAEoCBI/CgtidXp6ZXJfbW9kZRgNIAEoDjIqLm1lc2h0YXN0aWMuQ29uZmlnLkRldmljZUNvbmZpZy5CdXp6ZXJNb2RlItQBCgRSb2xlEgoKBkNMSUVOVBAAEg8KC0NMSUVOVF9NVVRFEAESCgoGUk9VVEVSEAISFQoNUk9VVEVSX0NMSUVOVBADGgIIARIQCghSRVBFQVRFUhAEGgIIARILCgdUUkFDS0VSEAUSCgoGU0VOU09SEAYSBwoDVEFLEAcSEQoNQ0xJRU5UX0hJRERFThAIEhIKDkxPU1RfQU5EX0ZPVU5EEAkSDwoLVEFLX1RSQUNLRVIQChIPCgtST1VURVJfTEFURRALEg8KC0NMSUVOVF9CQVNFEAwicwoPUmVicm9hZGNhc3RNb2RlEgcKA0FMTBAAEhUKEUFMTF9TS0lQX0RFQ09ESU5HEAESDgoKTE9DQUxfT05MWRACEg4KCktOT1dOX09OTFkQAxIICgROT05FEAQSFgoSQ09SRV9QT1JUTlVNU19PTkxZEAUiaQoKQnV6emVyTW9kZRIPCgtBTExfRU5BQkxFRBAAEgwKCERJU0FCTEVEEAESFgoSTk9USUZJQ0FUSU9OU19PTkxZEAISDwoLU1lTVEVNX09OTFkQAxITCg9ESVJFQ1RfTVNHX09OTFkQBBqRBQoOUG9zaXRpb25Db25maWcSHwoXcG9zaXRpb25fYnJvYWRjYXN0X3NlY3MYASABKA0SKAogcG9zaXRpb25fYnJvYWRjYXN0X3NtYXJ0X2VuYWJsZWQYAiABKAgSFgoOZml4ZWRfcG9zaXRpb24YAyABKAgSFwoLZ3BzX2VuYWJsZWQYBCABKAhCAhgBEhsKE2dwc191cGRhdGVfaW50ZXJ2YWwYBSABKA0SHAoQZ3BzX2F0dGVtcHRfdGltZRgGIAEoDUICGAESFgoOcG9zaXRpb25fZmxhZ3MYByABKA0SDwoHcnhfZ3BpbxgIIAEoDRIPCgd0eF9ncGlvGAkgASgNEigKIGJyb2FkY2FzdF9zbWFydF9taW5pbXVtX2Rpc3RhbmNlGAogASgNEi0KJWJyb2FkY2FzdF9zbWFydF9taW5pbXVtX2ludGVydmFsX3NlY3MYCyABKA0SEwoLZ3BzX2VuX2dwaW8YDCABKA0SOwoIZ3BzX21vZGUYDSABKA4yKS5tZXNodGFzdGljLkNvbmZpZy5Qb3NpdGlvbkNvbmZpZy5HcHNNb2RlIqsBCg1Qb3NpdGlvbkZsYWdzEgkKBVVOU0VUEAASDAoIQUxUSVRVREUQARIQCgxBTFRJVFVERV9NU0wQAhIWChJHRU9JREFMX1NFUEFSQVRJT04QBBIHCgNET1AQCBIJCgVIVkRPUBAQEg0KCVNBVElOVklFVxAgEgoKBlNFUV9OTxBAEg4KCVRJTUVTVEFNUBCAARIMCgdIRUFESU5HEIACEgoKBVNQRUVEEIAEIjUKB0dwc01vZGUSDAoIRElTQUJMRUQQABILCgdFTkFCTEVEEAESDwoLTk9UX1BSRVNFTlQQAhqEAgoLUG93ZXJDb25maWcSFwoPaXNfcG93ZXJfc2F2aW5nGAEgASgIEiYKHm9uX2JhdHRlcnlfc2h1dGRvd25fYWZ0ZXJfc2VjcxgCIAEoDRIfChdhZGNfbXVsdGlwbGllcl9vdmVycmlkZRgDIAEoAhIbChN3YWl0X2JsdWV0b290aF9zZWNzGAQgASgNEhAKCHNkc19zZWNzGAYgASgNEg8KB2xzX3NlY3MYByABKA0SFQoNbWluX3dha2Vfc2VjcxgIIAEoDRIiChpkZXZpY2VfYmF0dGVyeV9pbmFfYWRkcmVzcxgJIAEoDRIYChBwb3dlcm1vbl9lbmFibGVzGCAgASgEGuUDCg1OZXR3b3JrQ29uZmlnEhQKDHdpZmlfZW5hYmxlZBgBIAEoCBIRCgl3aWZpX3NzaWQYAyABKAkSEAoId2lmaV9wc2sYBCABKAkSEgoKbnRwX3NlcnZlchgFIAEoCRITCgtldGhfZW5hYmxlZBgGIAEoCBJCCgxhZGRyZXNzX21vZGUYByABKA4yLC5tZXNodGFzdGljLkNvbmZpZy5OZXR3b3JrQ29uZmlnLkFkZHJlc3NNb2RlEkAKC2lwdjRfY29uZmlnGAggASgLMisubWVzaHRhc3RpYy5Db25maWcuTmV0d29ya0NvbmZpZy5JcFY0Q29uZmlnEhYKDnJzeXNsb2dfc2VydmVyGAkgASgJEhkKEWVuYWJsZWRfcHJvdG9jb2xzGAogASgNEhQKDGlwdjZfZW5hYmxlZBgLIAEoCBpGCgpJcFY0Q29uZmlnEgoKAmlwGAEgASgHEg8KB2dhdGV3YXkYAiABKAcSDgoGc3VibmV0GAMgASgHEgsKA2RucxgEIAEoByIjCgtBZGRyZXNzTW9kZRIICgRESENQEAASCgoGU1RBVElDEAEiNAoNUHJvdG9jb2xGbGFncxIQCgxOT19CUk9BRENBU1QQABIRCg1VRFBfQlJPQURDQVNUEAEawggKDURpc3BsYXlDb25maWcSFgoOc2NyZWVuX29uX3NlY3MYASABKA0SVgoKZ3BzX2Zvcm1hdBgCIAEoDjI+Lm1lc2h0YXN0aWMuQ29uZmlnLkRpc3BsYXlDb25maWcuRGVwcmVjYXRlZEdwc0Nvb3JkaW5hdGVGb3JtYXRCAhgBEiEKGWF1dG9fc2NyZWVuX2Nhcm91c2VsX3NlY3MYAyABKA0SHQoRY29tcGFzc19ub3J0aF90b3AYBCABKAhCAhgBEhMKC2ZsaXBfc2NyZWVuGAUgASgIEjwKBXVuaXRzGAYgASgOMi0ubWVzaHRhc3RpYy5Db25maWcuRGlzcGxheUNvbmZpZy5EaXNwbGF5VW5pdHMSNwoEb2xlZBgHIAEoDjIpLm1lc2h0YXN0aWMuQ29uZmlnLkRpc3BsYXlDb25maWcuT2xlZFR5cGUSQQoLZGlzcGxheW1vZGUYCCABKA4yLC5tZXNodGFzdGljLkNvbmZpZy5EaXNwbGF5Q29uZmlnLkRpc3BsYXlNb2RlEhQKDGhlYWRpbmdfYm9sZBgJIAEoCBIdChV3YWtlX29uX3RhcF9vcl9tb3Rpb24YCiABKAgSUAoTY29tcGFzc19vcmllbnRhdGlvbhgLIAEoDjIzLm1lc2h0YXN0aWMuQ29uZmlnLkRpc3BsYXlDb25maWcuQ29tcGFzc09yaWVudGF0aW9uEhUKDXVzZV8xMmhfY2xvY2sYDCABKAgSGgoSdXNlX2xvbmdfbm9kZV9uYW1lGA0gASgIEh4KFmVuYWJsZV9tZXNzYWdlX2J1YmJsZXMYDiABKAgiKwodRGVwcmVjYXRlZEdwc0Nvb3JkaW5hdGVGb3JtYXQSCgoGVU5VU0VEEAAiKAoMRGlzcGxheVVuaXRzEgoKBk1FVFJJQxAAEgwKCElNUEVSSUFMEAEifwoIT2xlZFR5cGUSDQoJT0xFRF9BVVRPEAASEAoMT0xFRF9TU0QxMzA2EAESDwoLT0xFRF9TSDExMDYQAhIPCgtPTEVEX1NIMTEwNxADEhcKE09MRURfU0gxMTA3XzEyOF8xMjgQBBIXChNPTEVEX1NIMTEwN19ST1RBVEVEEAUiQQoLRGlzcGxheU1vZGUSCwoHREVGQVVMVBAAEgwKCFRXT0NPTE9SEAESDAoISU5WRVJURUQQAhIJCgVDT0xPUhADIroBChJDb21wYXNzT3JpZW50YXRpb24SDQoJREVHUkVFU18wEAASDgoKREVHUkVFU185MBABEg8KC0RFR1JFRVNfMTgwEAISDwoLREVHUkVFU18yNzAQAxIWChJERUdSRUVTXzBfSU5WRVJURUQQBBIXChNERUdSRUVTXzkwX0lOVkVSVEVEEAUSGAoUREVHUkVFU18xODBfSU5WRVJURUQQBhIYChRERUdSRUVTXzI3MF9JTlZFUlRFRBAHGvkKCgpMb1JhQ29uZmlnEhIKCnVzZV9wcmVzZXQYASABKAgSPwoMbW9kZW1fcHJlc2V0GAIgASgOMikubWVzaHRhc3RpYy5Db25maWcuTG9SYUNvbmZpZy5Nb2RlbVByZXNldBIRCgliYW5kd2lkdGgYAyABKA0SFQoNc3ByZWFkX2ZhY3RvchgEIAEoDRITCgtjb2RpbmdfcmF0ZRgFIAEoDRIYChBmcmVxdWVuY3lfb2Zmc2V0GAYgASgCEjgKBnJlZ2lvbhgHIAEoDjIoLm1lc2h0YXN0aWMuQ29uZmlnLkxvUmFDb25maWcuUmVnaW9uQ29kZRIRCglob3BfbGltaXQYCCABKA0SEgoKdHhfZW5hYmxlZBgJIAEoCBIQCgh0eF9wb3dlchgKIAEoBRITCgtjaGFubmVsX251bRgLIAEoDRIbChNvdmVycmlkZV9kdXR5X2N5Y2xlGAwgASgIEh4KFnN4MTI2eF9yeF9ib29zdGVkX2dhaW4YDSABKAgSGgoSb3ZlcnJpZGVfZnJlcXVlbmN5GA4gASgCEhcKD3BhX2Zhbl9kaXNhYmxlZBgPIAEoCBIXCg9pZ25vcmVfaW5jb21pbmcYZyADKA0SEwoLaWdub3JlX21xdHQYaCABKAgSGQoRY29uZmlnX29rX3RvX21xdHQYaSABKAgSQAoMZmVtX2xuYV9tb2RlGGogASgOMioubWVzaHRhc3RpYy5Db25maWcuTG9SYUNvbmZpZy5GRU1fTE5BX01vZGUSFwoPc2VyaWFsX2hhbF9vbmx5GGsgASgIIsQDCgpSZWdpb25Db2RlEgkKBVVOU0VUEAASBgoCVVMQARIKCgZFVV80MzMQAhIKCgZFVV84NjgQAxIGCgJDThAEEgYKAkpQEAUSBwoDQU5aEAYSBgoCS1IQBxIGCgJUVxAIEgYKAlJVEAkSBgoCSU4QChIKCgZOWl84NjUQCxIGCgJUSBAMEgsKB0xPUkFfMjQQDRIKCgZVQV80MzMQDhIKCgZVQV84NjgQDxIKCgZNWV80MzMQEBIKCgZNWV85MTkQERIKCgZTR185MjMQEhIKCgZQSF80MzMQExIKCgZQSF84NjgQFBIKCgZQSF85MTUQFRILCgdBTlpfNDMzEBYSCgoGS1pfNDMzEBcSCgoGS1pfODYzEBgSCgoGTlBfODY1EBkSCgoGQlJfOTAyEBoSCwoHSVRVMV8yTRAbEgsKB0lUVTJfMk0QHBIKCgZFVV84NjYQHRIKCgZFVV84NzQQHhIKCgZFVV85MTcQHxIMCghFVV9OXzg2OBAgEgsKB0lUVTNfMk0QIRINCglJVFUxXzcwQ00QIhINCglJVFUyXzcwQ00QIxINCglJVFUzXzcwQ00QJBIOCgpJVFUyXzEyNUNNECUimwIKC01vZGVtUHJlc2V0Eg0KCUxPTkdfRkFTVBAAEhEKCUxPTkdfU0xPVxABGgIIARIWCg5WRVJZX0xPTkdfU0xPVxACGgIIARIPCgtNRURJVU1fU0xPVxADEg8KC01FRElVTV9GQVNUEAQSDgoKU0hPUlRfU0xPVxAFEg4KClNIT1JUX0ZBU1QQBhIRCg1MT05HX01PREVSQVRFEAcSDwoLU0hPUlRfVFVSQk8QCBIOCgpMT05HX1RVUkJPEAkSDQoJTElURV9GQVNUEAoSDQoJTElURV9TTE9XEAsSDwoLTkFSUk9XX0ZBU1QQDBIPCgtOQVJST1dfU0xPVxANEg0KCVRJTllfRkFTVBAOEg0KCVRJTllfU0xPVxAPIjoKDEZFTV9MTkFfTW9kZRIMCghESVNBQkxFRBAAEgsKB0VOQUJMRUQQARIPCgtOT1RfUFJFU0VOVBACGq0BCg9CbHVldG9vdGhDb25maWcSDwoHZW5hYmxlZBgBIAEoCBI8CgRtb2RlGAIgASgOMi4ubWVzaHRhc3RpYy5Db25maWcuQmx1ZXRvb3RoQ29uZmlnLlBhaXJpbmdNb2RlEhEKCWZpeGVkX3BpbhgDIAEoDSI4CgtQYWlyaW5nTW9kZRIOCgpSQU5ET01fUElOEAASDQoJRklYRURfUElOEAESCgoGTk9fUElOEAIanAMKDlNlY3VyaXR5Q29uZmlnEhIKCnB1YmxpY19rZXkYASABKAwSEwoLcHJpdmF0ZV9rZXkYAiABKAwSEQoJYWRtaW5fa2V5GAMgAygMEhIKCmlzX21hbmFnZWQYBCABKAgSFgoOc2VyaWFsX2VuYWJsZWQYBSABKAgSHQoVZGVidWdfbG9nX2FwaV9lbmFibGVkGAYgASgIEh0KFWFkbWluX2NoYW5uZWxfZW5hYmxlZBgIIAEoCBJYChdwYWNrZXRfc2lnbmF0dXJlX3BvbGljeRgJIAEoDjI3Lm1lc2h0YXN0aWMuQ29uZmlnLlNlY3VyaXR5Q29uZmlnLlBhY2tldFNpZ25hdHVyZVBvbGljeSKJAQoVUGFja2V0U2lnbmF0dXJlUG9saWN5EiYKIlBBQ0tFVF9TSUdOQVRVUkVfUE9MSUNZX0NPTVBBVElCTEUQABIkCiBQQUNLRVRfU0lHTkFUVVJFX1BPTElDWV9CQUxBTkNFRBABEiIKHlBBQ0tFVF9TSUdOQVRVUkVfUE9MSUNZX1NUUklDVBACGhIKEFNlc3Npb25rZXlDb25maWdCEQoPcGF5bG9hZF92YXJpYW50QmIKFG9yZy5tZXNodGFzdGljLnByb3RvQgxDb25maWdQcm90b3NaImdpdGh1Yi5jb20vbWVzaHRhc3RpYy9nby9nZW5lcmF0ZWSqAhRNZXNodGFzdGljLlByb3RvYnVmc7oCAGIGcHJvdG8z", [file_meshtastic_device_ui]); /** * @generated from message meshtastic.Config @@ -2287,6 +2287,14 @@ export type Config_SecurityConfig = Message<"meshtastic.Config.SecurityConfig"> * @generated from field: bool admin_channel_enabled = 8; */ adminChannelEnabled: boolean; + + /** + * + * Determines the packet signature policy applied to remotely received mesh packets. + * + * @generated from field: meshtastic.Config.SecurityConfig.PacketSignaturePolicy packet_signature_policy = 9; + */ + packetSignaturePolicy: Config_SecurityConfig_PacketSignaturePolicy; }; /** @@ -2296,6 +2304,47 @@ export type Config_SecurityConfig = Message<"meshtastic.Config.SecurityConfig"> export const Config_SecurityConfigSchema: GenMessage = /*@__PURE__*/ messageDesc(file_meshtastic_config, 0, 7); +/** + * + * Controls how the device authenticates remotely received mesh packets. + * + * @generated from enum meshtastic.Config.SecurityConfig.PacketSignaturePolicy + */ +export enum Config_SecurityConfig_PacketSignaturePolicy { + /** + * + * Accept unsigned packets for maximum compatibility while still rejecting malformed or invalid signatures. + * This is the default to avoid legacy nodes dropping signed packets during rebroadcast. + * + * @generated from enum value: PACKET_SIGNATURE_POLICY_COMPATIBLE = 0; + */ + COMPATIBLE = 0, + + /** + * + * Prefer authenticated packets while retaining compatibility with unsigned packets from nodes not known to sign. + * Rejects unsigned, signable broadcasts from nodes that have previously signed. + * + * @generated from enum value: PACKET_SIGNATURE_POLICY_BALANCED = 1; + */ + BALANCED = 1, + + /** + * + * Accept only packets authenticated by a verified XEdDSA signature or successful PKI decryption. + * Unsigned, malformed, invalid, or unverifiable packets are ignored. + * + * @generated from enum value: PACKET_SIGNATURE_POLICY_STRICT = 2; + */ + STRICT = 2, +} + +/** + * Describes the enum meshtastic.Config.SecurityConfig.PacketSignaturePolicy. + */ +export const Config_SecurityConfig_PacketSignaturePolicySchema: GenEnum = /*@__PURE__*/ + enumDesc(file_meshtastic_config, 0, 7, 0); + /** * * Blank config request, strictly for getting the session key @@ -2311,4 +2360,3 @@ export type Config_SessionkeyConfig = Message<"meshtastic.Config.SessionkeyConfi */ export const Config_SessionkeyConfigSchema: GenMessage = /*@__PURE__*/ messageDesc(file_meshtastic_config, 0, 8); - diff --git a/packages/protobufs/packages/ts/dist/meshtastic/mesh_pb.ts b/packages/protobufs/packages/ts/dist/meshtastic/mesh_pb.ts index 96681d018..79c021f1f 100644 --- a/packages/protobufs/packages/ts/dist/meshtastic/mesh_pb.ts +++ b/packages/protobufs/packages/ts/dist/meshtastic/mesh_pb.ts @@ -24,7 +24,7 @@ import type { Message } from "@bufbuild/protobuf"; * Describes the file meshtastic/mesh.proto. */ export const file_meshtastic_mesh: GenFile = /*@__PURE__*/ - fileDesc("ChVtZXNodGFzdGljL21lc2gucHJvdG8SCm1lc2h0YXN0aWMihwcKCFBvc2l0aW9uEhcKCmxhdGl0dWRlX2kYASABKA9IAIgBARIYCgtsb25naXR1ZGVfaRgCIAEoD0gBiAEBEhUKCGFsdGl0dWRlGAMgASgFSAKIAQESDAoEdGltZRgEIAEoBxI3Cg9sb2NhdGlvbl9zb3VyY2UYBSABKA4yHi5tZXNodGFzdGljLlBvc2l0aW9uLkxvY1NvdXJjZRI3Cg9hbHRpdHVkZV9zb3VyY2UYBiABKA4yHi5tZXNodGFzdGljLlBvc2l0aW9uLkFsdFNvdXJjZRIRCgl0aW1lc3RhbXAYByABKAcSHwoXdGltZXN0YW1wX21pbGxpc19hZGp1c3QYCCABKAUSGQoMYWx0aXR1ZGVfaGFlGAkgASgRSAOIAQESKAobYWx0aXR1ZGVfZ2VvaWRhbF9zZXBhcmF0aW9uGAogASgRSASIAQESDAoEUERPUBgLIAEoDRIMCgRIRE9QGAwgASgNEgwKBFZET1AYDSABKA0SFAoMZ3BzX2FjY3VyYWN5GA4gASgNEhkKDGdyb3VuZF9zcGVlZBgPIAEoDUgFiAEBEhkKDGdyb3VuZF90cmFjaxgQIAEoDUgGiAEBEhMKC2ZpeF9xdWFsaXR5GBEgASgNEhAKCGZpeF90eXBlGBIgASgNEhQKDHNhdHNfaW5fdmlldxgTIAEoDRIRCglzZW5zb3JfaWQYFCABKA0SEwoLbmV4dF91cGRhdGUYFSABKA0SEgoKc2VxX251bWJlchgWIAEoDRIWCg5wcmVjaXNpb25fYml0cxgXIAEoDSJOCglMb2NTb3VyY2USDQoJTE9DX1VOU0VUEAASDgoKTE9DX01BTlVBTBABEhAKDExPQ19JTlRFUk5BTBACEhAKDExPQ19FWFRFUk5BTBADImIKCUFsdFNvdXJjZRINCglBTFRfVU5TRVQQABIOCgpBTFRfTUFOVUFMEAESEAoMQUxUX0lOVEVSTkFMEAISEAoMQUxUX0VYVEVSTkFMEAMSEgoOQUxUX0JBUk9NRVRSSUMQBEINCgtfbGF0aXR1ZGVfaUIOCgxfbG9uZ2l0dWRlX2lCCwoJX2FsdGl0dWRlQg8KDV9hbHRpdHVkZV9oYWVCHgocX2FsdGl0dWRlX2dlb2lkYWxfc2VwYXJhdGlvbkIPCg1fZ3JvdW5kX3NwZWVkQg8KDV9ncm91bmRfdHJhY2siigIKBFVzZXISCgoCaWQYASABKAkSEQoJbG9uZ19uYW1lGAIgASgJEhIKCnNob3J0X25hbWUYAyABKAkSEwoHbWFjYWRkchgEIAEoDEICGAESKwoIaHdfbW9kZWwYBSABKA4yGS5tZXNodGFzdGljLkhhcmR3YXJlTW9kZWwSEwoLaXNfbGljZW5zZWQYBiABKAgSMgoEcm9sZRgHIAEoDjIkLm1lc2h0YXN0aWMuQ29uZmlnLkRldmljZUNvbmZpZy5Sb2xlEhIKCnB1YmxpY19rZXkYCCABKAwSHAoPaXNfdW5tZXNzYWdhYmxlGAkgASgISACIAQFCEgoQX2lzX3VubWVzc2FnYWJsZSJaCg5Sb3V0ZURpc2NvdmVyeRINCgVyb3V0ZRgBIAMoBxITCgtzbnJfdG93YXJkcxgCIAMoBRISCgpyb3V0ZV9iYWNrGAMgAygHEhAKCHNucl9iYWNrGAQgAygFIpkECgdSb3V0aW5nEjMKDXJvdXRlX3JlcXVlc3QYASABKAsyGi5tZXNodGFzdGljLlJvdXRlRGlzY292ZXJ5SAASMQoLcm91dGVfcmVwbHkYAiABKAsyGi5tZXNodGFzdGljLlJvdXRlRGlzY292ZXJ5SAASMQoMZXJyb3JfcmVhc29uGAMgASgOMhkubWVzaHRhc3RpYy5Sb3V0aW5nLkVycm9ySAAi5wIKBUVycm9yEggKBE5PTkUQABIMCghOT19ST1VURRABEgsKB0dPVF9OQUsQAhILCgdUSU1FT1VUEAMSEAoMTk9fSU5URVJGQUNFEAQSEgoOTUFYX1JFVFJBTlNNSVQQBRIOCgpOT19DSEFOTkVMEAYSDQoJVE9PX0xBUkdFEAcSDwoLTk9fUkVTUE9OU0UQCBIUChBEVVRZX0NZQ0xFX0xJTUlUEAkSDwoLQkFEX1JFUVVFU1QQIBISCg5OT1RfQVVUSE9SSVpFRBAhEg4KClBLSV9GQUlMRUQQIhIWChJQS0lfVU5LTk9XTl9QVUJLRVkQIxIZChVBRE1JTl9CQURfU0VTU0lPTl9LRVkQJBIhCh1BRE1JTl9QVUJMSUNfS0VZX1VOQVVUSE9SSVpFRBAlEhcKE1JBVEVfTElNSVRfRVhDRUVERUQQJhIcChhQS0lfU0VORF9GQUlMX1BVQkxJQ19LRVkQJ0IJCgd2YXJpYW50IuUBCgREYXRhEiQKB3BvcnRudW0YASABKA4yEy5tZXNodGFzdGljLlBvcnROdW0SDwoHcGF5bG9hZBgCIAEoDBIVCg13YW50X3Jlc3BvbnNlGAMgASgIEgwKBGRlc3QYBCABKAcSDgoGc291cmNlGAUgASgHEhIKCnJlcXVlc3RfaWQYBiABKAcSEAoIcmVwbHlfaWQYByABKAcSDQoFZW1vamkYCCABKAcSFQoIYml0ZmllbGQYCSABKA1IAIgBARIYChB4ZWRkc2Ffc2lnbmF0dXJlGAogASgMQgsKCV9iaXRmaWVsZCI+Cg9LZXlWZXJpZmljYXRpb24SDQoFbm9uY2UYASABKAQSDQoFaGFzaDEYAiABKAwSDQoFaGFzaDIYAyABKAwiywMKFFN0b3JlRm9yd2FyZFBsdXNQbHVzEk0KEXNmcHBfbWVzc2FnZV90eXBlGAEgASgOMjIubWVzaHRhc3RpYy5TdG9yZUZvcndhcmRQbHVzUGx1cy5TRlBQX21lc3NhZ2VfdHlwZRIUCgxtZXNzYWdlX2hhc2gYAiABKAwSEwoLY29tbWl0X2hhc2gYAyABKAwSEQoJcm9vdF9oYXNoGAQgASgMEg8KB21lc3NhZ2UYBSABKAwSFwoPZW5jYXBzdWxhdGVkX2lkGAYgASgNEhcKD2VuY2Fwc3VsYXRlZF90bxgHIAEoDRIZChFlbmNhcHN1bGF0ZWRfZnJvbRgIIAEoDRIbChNlbmNhcHN1bGF0ZWRfcnh0aW1lGAkgASgNEhMKC2NoYWluX2NvdW50GAogASgNIpUBChFTRlBQX21lc3NhZ2VfdHlwZRISCg5DQU5PTl9BTk5PVU5DRRAAEg8KC0NIQUlOX1FVRVJZEAESEAoMTElOS19SRVFVRVNUEAMSEAoMTElOS19QUk9WSURFEAQSGgoWTElOS19QUk9WSURFX0ZJUlNUSEFMRhAFEhsKF0xJTktfUFJPVklERV9TRUNPTkRIQUxGEAYi4wIKC1JlbW90ZVNoZWxsEioKAm9wGAEgASgOMh4ubWVzaHRhc3RpYy5SZW1vdGVTaGVsbC5PcENvZGUSEgoKc2Vzc2lvbl9pZBgCIAEoDRILCgNzZXEYAyABKA0SDwoHYWNrX3NlcRgEIAEoDRIPCgdwYXlsb2FkGAUgASgMEgwKBGNvbHMYBiABKA0SDAoEcm93cxgHIAEoDRINCgVmbGFncxgIIAEoDRITCgtsYXN0X3R4X3NlcRgJIAEoDRITCgtsYXN0X3J4X3NlcRgKIAEoDSKPAQoGT3BDb2RlEgwKCE9QX1VOU0VUEAASCAoET1BFThABEgkKBUlOUFVUEAISCgoGUkVTSVpFEAMSCQoFQ0xPU0UQBBIICgRQSU5HEAUSBwoDQUNLEAYSCwoHT1BFTl9PSxBAEgoKBk9VVFBVVBBBEgoKBkNMT1NFRBBCEgkKBUVSUk9SEEMSCAoEUE9ORxBEIrwBCghXYXlwb2ludBIKCgJpZBgBIAEoDRIXCgpsYXRpdHVkZV9pGAIgASgPSACIAQESGAoLbG9uZ2l0dWRlX2kYAyABKA9IAYgBARIOCgZleHBpcmUYBCABKA0SEQoJbG9ja2VkX3RvGAUgASgNEgwKBG5hbWUYBiABKAkSEwoLZGVzY3JpcHRpb24YByABKAkSDAoEaWNvbhgIIAEoB0INCgtfbGF0aXR1ZGVfaUIOCgxfbG9uZ2l0dWRlX2kiHwoNU3RhdHVzTWVzc2FnZRIOCgZzdGF0dXMYASABKAkibAoWTXF0dENsaWVudFByb3h5TWVzc2FnZRINCgV0b3BpYxgBIAEoCRIOCgRkYXRhGAIgASgMSAASDgoEdGV4dBgDIAEoCUgAEhAKCHJldGFpbmVkGAQgASgIQhEKD3BheWxvYWRfdmFyaWFudCLnBwoKTWVzaFBhY2tldBIMCgRmcm9tGAEgASgHEgoKAnRvGAIgASgHEg8KB2NoYW5uZWwYAyABKA0SIwoHZGVjb2RlZBgEIAEoCzIQLm1lc2h0YXN0aWMuRGF0YUgAEhMKCWVuY3J5cHRlZBgFIAEoDEgAEgoKAmlkGAYgASgHEg8KB3J4X3RpbWUYByABKAcSDgoGcnhfc25yGAggASgCEhEKCWhvcF9saW1pdBgJIAEoDRIQCgh3YW50X2FjaxgKIAEoCBIxCghwcmlvcml0eRgLIAEoDjIfLm1lc2h0YXN0aWMuTWVzaFBhY2tldC5Qcmlvcml0eRIPCgdyeF9yc3NpGAwgASgFEjMKB2RlbGF5ZWQYDSABKA4yHi5tZXNodGFzdGljLk1lc2hQYWNrZXQuRGVsYXllZEICGAESEAoIdmlhX21xdHQYDiABKAgSEQoJaG9wX3N0YXJ0GA8gASgNEhIKCnB1YmxpY19rZXkYECABKAwSFQoNcGtpX2VuY3J5cHRlZBgRIAEoCBIQCghuZXh0X2hvcBgSIAEoDRISCgpyZWxheV9ub2RlGBMgASgNEhAKCHR4X2FmdGVyGBQgASgNEkYKE3RyYW5zcG9ydF9tZWNoYW5pc20YFSABKA4yKS5tZXNodGFzdGljLk1lc2hQYWNrZXQuVHJhbnNwb3J0TWVjaGFuaXNtEhUKDXhlZGRzYV9zaWduZWQYFiABKAgifgoIUHJpb3JpdHkSCQoFVU5TRVQQABIHCgNNSU4QARIOCgpCQUNLR1JPVU5EEAoSCwoHREVGQVVMVBBAEgwKCFJFTElBQkxFEEYSDAoIUkVTUE9OU0UQUBIICgRISUdIEGQSCQoFQUxFUlQQbhIHCgNBQ0sQeBIHCgNNQVgQfyJCCgdEZWxheWVkEgwKCE5PX0RFTEFZEAASFQoRREVMQVlFRF9CUk9BRENBU1QQARISCg5ERUxBWUVEX0RJUkVDVBACIuoBChJUcmFuc3BvcnRNZWNoYW5pc20SFgoSVFJBTlNQT1JUX0lOVEVSTkFMEAASEgoOVFJBTlNQT1JUX0xPUkEQARIXChNUUkFOU1BPUlRfTE9SQV9BTFQxEAISFwoTVFJBTlNQT1JUX0xPUkFfQUxUMhADEhcKE1RSQU5TUE9SVF9MT1JBX0FMVDMQBBISCg5UUkFOU1BPUlRfTVFUVBAFEhsKF1RSQU5TUE9SVF9NVUxUSUNBU1RfVURQEAYSEQoNVFJBTlNQT1JUX0FQSRAHEhkKFVRSQU5TUE9SVF9VTklDQVNUX1VEUBAIQhEKD3BheWxvYWRfdmFyaWFudCL0AgoITm9kZUluZm8SCwoDbnVtGAEgASgNEh4KBHVzZXIYAiABKAsyEC5tZXNodGFzdGljLlVzZXISJgoIcG9zaXRpb24YAyABKAsyFC5tZXNodGFzdGljLlBvc2l0aW9uEgsKA3NuchgEIAEoAhISCgpsYXN0X2hlYXJkGAUgASgHEjEKDmRldmljZV9tZXRyaWNzGAYgASgLMhkubWVzaHRhc3RpYy5EZXZpY2VNZXRyaWNzEg8KB2NoYW5uZWwYByABKA0SEAoIdmlhX21xdHQYCCABKAgSFgoJaG9wc19hd2F5GAkgASgNSACIAQESEwoLaXNfZmF2b3JpdGUYCiABKAgSEgoKaXNfaWdub3JlZBgLIAEoCBIgChhpc19rZXlfbWFudWFsbHlfdmVyaWZpZWQYDCABKAgSEAoIaXNfbXV0ZWQYDSABKAgSGQoRaGFzX3hlZGRzYV9zaWduZWQYDiABKAhCDAoKX2hvcHNfYXdheSLBAQoKTXlOb2RlSW5mbxITCgtteV9ub2RlX251bRgBIAEoDRIUCgxyZWJvb3RfY291bnQYCCABKA0SFwoPbWluX2FwcF92ZXJzaW9uGAsgASgNEhEKCWRldmljZV9pZBgMIAEoDBIPCgdwaW9fZW52GA0gASgJEjUKEGZpcm13YXJlX2VkaXRpb24YDiABKA4yGy5tZXNodGFzdGljLkZpcm13YXJlRWRpdGlvbhIUCgxub2RlZGJfY291bnQYDyABKA0iwAEKCUxvZ1JlY29yZBIPCgdtZXNzYWdlGAEgASgJEgwKBHRpbWUYAiABKAcSDgoGc291cmNlGAMgASgJEioKBWxldmVsGAQgASgOMhsubWVzaHRhc3RpYy5Mb2dSZWNvcmQuTGV2ZWwiWAoFTGV2ZWwSCQoFVU5TRVQQABIMCghDUklUSUNBTBAyEgkKBUVSUk9SECgSCwoHV0FSTklORxAeEggKBElORk8QFBIJCgVERUJVRxAKEgkKBVRSQUNFEAUiUAoLUXVldWVTdGF0dXMSCwoDcmVzGAEgASgFEgwKBGZyZWUYAiABKA0SDgoGbWF4bGVuGAMgASgNEhYKDm1lc2hfcGFja2V0X2lkGAQgASgNIrAGCglGcm9tUmFkaW8SCgoCaWQYASABKA0SKAoGcGFja2V0GAIgASgLMhYubWVzaHRhc3RpYy5NZXNoUGFja2V0SAASKQoHbXlfaW5mbxgDIAEoCzIWLm1lc2h0YXN0aWMuTXlOb2RlSW5mb0gAEikKCW5vZGVfaW5mbxgEIAEoCzIULm1lc2h0YXN0aWMuTm9kZUluZm9IABIkCgZjb25maWcYBSABKAsyEi5tZXNodGFzdGljLkNvbmZpZ0gAEisKCmxvZ19yZWNvcmQYBiABKAsyFS5tZXNodGFzdGljLkxvZ1JlY29yZEgAEhwKEmNvbmZpZ19jb21wbGV0ZV9pZBgHIAEoDUgAEhIKCHJlYm9vdGVkGAggASgISAASMAoMbW9kdWxlQ29uZmlnGAkgASgLMhgubWVzaHRhc3RpYy5Nb2R1bGVDb25maWdIABImCgdjaGFubmVsGAogASgLMhMubWVzaHRhc3RpYy5DaGFubmVsSAASLgoLcXVldWVTdGF0dXMYCyABKAsyFy5tZXNodGFzdGljLlF1ZXVlU3RhdHVzSAASKgoMeG1vZGVtUGFja2V0GAwgASgLMhIubWVzaHRhc3RpYy5YTW9kZW1IABIuCghtZXRhZGF0YRgNIAEoCzIaLm1lc2h0YXN0aWMuRGV2aWNlTWV0YWRhdGFIABJEChZtcXR0Q2xpZW50UHJveHlNZXNzYWdlGA4gASgLMiIubWVzaHRhc3RpYy5NcXR0Q2xpZW50UHJveHlNZXNzYWdlSAASKAoIZmlsZUluZm8YDyABKAsyFC5tZXNodGFzdGljLkZpbGVJbmZvSAASPAoSY2xpZW50Tm90aWZpY2F0aW9uGBAgASgLMh4ubWVzaHRhc3RpYy5DbGllbnROb3RpZmljYXRpb25IABI0Cg5kZXZpY2V1aUNvbmZpZxgRIAEoCzIaLm1lc2h0YXN0aWMuRGV2aWNlVUlDb25maWdIABI1Cg9sb2NrZG93bl9zdGF0dXMYEiABKAsyGi5tZXNodGFzdGljLkxvY2tkb3duU3RhdHVzSABCEQoPcGF5bG9hZF92YXJpYW50IpMCCg5Mb2NrZG93blN0YXR1cxIvCgVzdGF0ZRgBIAEoDjIgLm1lc2h0YXN0aWMuTG9ja2Rvd25TdGF0dXMuU3RhdGUSEwoLbG9ja19yZWFzb24YAiABKAkSFwoPYm9vdHNfcmVtYWluaW5nGAMgASgNEhkKEXZhbGlkX3VudGlsX2Vwb2NoGAQgASgNEhcKD2JhY2tvZmZfc2Vjb25kcxgFIAEoDSJuCgVTdGF0ZRIVChFTVEFURV9VTlNQRUNJRklFRBAAEhMKD05FRURTX1BST1ZJU0lPThABEgoKBkxPQ0tFRBACEgwKCFVOTE9DS0VEEAMSEQoNVU5MT0NLX0ZBSUxFRBAEEgwKCERJU0FCTEVEEAUi+gMKEkNsaWVudE5vdGlmaWNhdGlvbhIVCghyZXBseV9pZBgBIAEoDUgBiAEBEgwKBHRpbWUYAiABKAcSKgoFbGV2ZWwYAyABKA4yGy5tZXNodGFzdGljLkxvZ1JlY29yZC5MZXZlbBIPCgdtZXNzYWdlGAQgASgJElEKHmtleV92ZXJpZmljYXRpb25fbnVtYmVyX2luZm9ybRgLIAEoCzInLm1lc2h0YXN0aWMuS2V5VmVyaWZpY2F0aW9uTnVtYmVySW5mb3JtSAASUwofa2V5X3ZlcmlmaWNhdGlvbl9udW1iZXJfcmVxdWVzdBgMIAEoCzIoLm1lc2h0YXN0aWMuS2V5VmVyaWZpY2F0aW9uTnVtYmVyUmVxdWVzdEgAEkIKFmtleV92ZXJpZmljYXRpb25fZmluYWwYDSABKAsyIC5tZXNodGFzdGljLktleVZlcmlmaWNhdGlvbkZpbmFsSAASQAoVZHVwbGljYXRlZF9wdWJsaWNfa2V5GA4gASgLMh8ubWVzaHRhc3RpYy5EdXBsaWNhdGVkUHVibGljS2V5SAASNAoPbG93X2VudHJvcHlfa2V5GA8gASgLMhkubWVzaHRhc3RpYy5Mb3dFbnRyb3B5S2V5SABCEQoPcGF5bG9hZF92YXJpYW50QgsKCV9yZXBseV9pZCJeChtLZXlWZXJpZmljYXRpb25OdW1iZXJJbmZvcm0SDQoFbm9uY2UYASABKAQSFwoPcmVtb3RlX2xvbmduYW1lGAIgASgJEhcKD3NlY3VyaXR5X251bWJlchgDIAEoDSJGChxLZXlWZXJpZmljYXRpb25OdW1iZXJSZXF1ZXN0Eg0KBW5vbmNlGAEgASgEEhcKD3JlbW90ZV9sb25nbmFtZRgCIAEoCSJxChRLZXlWZXJpZmljYXRpb25GaW5hbBINCgVub25jZRgBIAEoBBIXCg9yZW1vdGVfbG9uZ25hbWUYAiABKAkSEAoIaXNTZW5kZXIYAyABKAgSHwoXdmVyaWZpY2F0aW9uX2NoYXJhY3RlcnMYBCABKAkiFQoTRHVwbGljYXRlZFB1YmxpY0tleSIPCg1Mb3dFbnRyb3B5S2V5IjEKCEZpbGVJbmZvEhEKCWZpbGVfbmFtZRgBIAEoCRISCgpzaXplX2J5dGVzGAIgASgNIpQCCgdUb1JhZGlvEigKBnBhY2tldBgBIAEoCzIWLm1lc2h0YXN0aWMuTWVzaFBhY2tldEgAEhgKDndhbnRfY29uZmlnX2lkGAMgASgNSAASFAoKZGlzY29ubmVjdBgEIAEoCEgAEioKDHhtb2RlbVBhY2tldBgFIAEoCzISLm1lc2h0YXN0aWMuWE1vZGVtSAASRAoWbXF0dENsaWVudFByb3h5TWVzc2FnZRgGIAEoCzIiLm1lc2h0YXN0aWMuTXF0dENsaWVudFByb3h5TWVzc2FnZUgAEioKCWhlYXJ0YmVhdBgHIAEoCzIVLm1lc2h0YXN0aWMuSGVhcnRiZWF0SABCEQoPcGF5bG9hZF92YXJpYW50IkAKCkNvbXByZXNzZWQSJAoHcG9ydG51bRgBIAEoDjITLm1lc2h0YXN0aWMuUG9ydE51bRIMCgRkYXRhGAIgASgMIocBCgxOZWlnaGJvckluZm8SDwoHbm9kZV9pZBgBIAEoDRIXCg9sYXN0X3NlbnRfYnlfaWQYAiABKA0SJAocbm9kZV9icm9hZGNhc3RfaW50ZXJ2YWxfc2VjcxgDIAEoDRInCgluZWlnaGJvcnMYBCADKAsyFC5tZXNodGFzdGljLk5laWdoYm9yImQKCE5laWdoYm9yEg8KB25vZGVfaWQYASABKA0SCwoDc25yGAIgASgCEhQKDGxhc3RfcnhfdGltZRgDIAEoBxIkChxub2RlX2Jyb2FkY2FzdF9pbnRlcnZhbF9zZWNzGAQgASgNItcCCg5EZXZpY2VNZXRhZGF0YRIYChBmaXJtd2FyZV92ZXJzaW9uGAEgASgJEhwKFGRldmljZV9zdGF0ZV92ZXJzaW9uGAIgASgNEhMKC2NhblNodXRkb3duGAMgASgIEg8KB2hhc1dpZmkYBCABKAgSFAoMaGFzQmx1ZXRvb3RoGAUgASgIEhMKC2hhc0V0aGVybmV0GAYgASgIEjIKBHJvbGUYByABKA4yJC5tZXNodGFzdGljLkNvbmZpZy5EZXZpY2VDb25maWcuUm9sZRIWCg5wb3NpdGlvbl9mbGFncxgIIAEoDRIrCghod19tb2RlbBgJIAEoDjIZLm1lc2h0YXN0aWMuSGFyZHdhcmVNb2RlbBIZChFoYXNSZW1vdGVIYXJkd2FyZRgKIAEoCBIOCgZoYXNQS0MYCyABKAgSGAoQZXhjbHVkZWRfbW9kdWxlcxgMIAEoDSIaCglIZWFydGJlYXQSDQoFbm9uY2UYASABKA0iVQoVTm9kZVJlbW90ZUhhcmR3YXJlUGluEhAKCG5vZGVfbnVtGAEgASgNEioKA3BpbhgCIAEoCzIdLm1lc2h0YXN0aWMuUmVtb3RlSGFyZHdhcmVQaW4iZQoOQ2h1bmtlZFBheWxvYWQSEgoKcGF5bG9hZF9pZBgBIAEoDRITCgtjaHVua19jb3VudBgCIAEoDRITCgtjaHVua19pbmRleBgDIAEoDRIVCg1wYXlsb2FkX2NodW5rGAQgASgMIh8KDXJlc2VuZF9jaHVua3MSDgoGY2h1bmtzGAEgAygNIqoBChZDaHVua2VkUGF5bG9hZFJlc3BvbnNlEhIKCnBheWxvYWRfaWQYASABKA0SGgoQcmVxdWVzdF90cmFuc2ZlchgCIAEoCEgAEhkKD2FjY2VwdF90cmFuc2ZlchgDIAEoCEgAEjIKDXJlc2VuZF9jaHVua3MYBCABKAsyGS5tZXNodGFzdGljLnJlc2VuZF9jaHVua3NIAEIRCg9wYXlsb2FkX3ZhcmlhbnQqrBUKDUhhcmR3YXJlTW9kZWwSCQoFVU5TRVQQABIMCghUTE9SQV9WMhABEgwKCFRMT1JBX1YxEAISEgoOVExPUkFfVjJfMV8xUDYQAxIJCgVUQkVBTRAEEg8KC0hFTFRFQ19WMl8wEAUSDgoKVEJFQU1fVjBQNxAGEgoKBlRfRUNITxAHEhAKDFRMT1JBX1YxXzFQMxAIEgsKB1JBSzQ2MzEQCRIPCgtIRUxURUNfVjJfMRAKEg0KCUhFTFRFQ19WMRALEhgKFExJTFlHT19UQkVBTV9TM19DT1JFEAwSDAoIUkFLMTEyMDAQDRILCgdOQU5PX0cxEA4SEgoOVExPUkFfVjJfMV8xUDgQDxIPCgtUTE9SQV9UM19TMxAQEhQKEE5BTk9fRzFfRVhQTE9SRVIQERIRCg1OQU5PX0cyX1VMVFJBEBISDQoJTE9SQV9UWVBFEBMSCwoHV0lQSE9ORRAUEg4KCldJT19XTTExMTAQFRILCgdSQUsyNTYwEBYSEwoPSEVMVEVDX0hSVV8zNjAxEBcSGgoWSEVMVEVDX1dJUkVMRVNTX0JSSURHRRAYEg4KClNUQVRJT05fRzEQGRIMCghSQUsxMTMxMBAaEhQKEFNFTlNFTE9SQV9SUDIwNDAQGxIQCgxTRU5TRUxPUkFfUzMQHBINCglDQU5BUllPTkUQHRIPCgtSUDIwNDBfTE9SQRAeEg4KClNUQVRJT05fRzIQHxIRCg1MT1JBX1JFTEFZX1YxECASDwoLVF9FQ0hPX1BMVVMQIRIHCgNQUFIQIhIPCgtHRU5JRUJMT0NLUxAjEhEKDU5SRjUyX1VOS05PV04QJBINCglQT1JURFVJTk8QJRIPCgtBTkRST0lEX1NJTRAmEgoKBkRJWV9WMRAnEhUKEU5SRjUyODQwX1BDQTEwMDU5ECgSCgoGRFJfREVWECkSCwoHTTVTVEFDSxAqEg0KCUhFTFRFQ19WMxArEhEKDUhFTFRFQ19XU0xfVjMQLBITCg9CRVRBRlBWXzI0MDBfVFgQLRIXChNCRVRBRlBWXzkwMF9OQU5PX1RYEC4SDAoIUlBJX1BJQ08QLxIbChdIRUxURUNfV0lSRUxFU1NfVFJBQ0tFUhAwEhkKFUhFTFRFQ19XSVJFTEVTU19QQVBFUhAxEgoKBlRfREVDSxAyEg4KClRfV0FUQ0hfUzMQMxIRCg1QSUNPTVBVVEVSX1MzEDQSDwoLSEVMVEVDX0hUNjIQNRISCg5FQllURV9FU1AzMl9TMxA2EhEKDUVTUDMyX1MzX1BJQ08QNxINCglDSEFUVEVSXzIQOBIeChpIRUxURUNfV0lSRUxFU1NfUEFQRVJfVjFfMBA5EiAKHEhFTFRFQ19XSVJFTEVTU19UUkFDS0VSX1YxXzAQOhILCgdVTlBIT05FEDsSDAoIVERfTE9SQUMQPBITCg9DREVCWVRFX0VPUkFfUzMQPRIPCgtUV0NfTUVTSF9WNBA+EhYKEk5SRjUyX1BST01JQ1JPX0RJWRA/Eh8KG1JBRElPTUFTVEVSXzkwMF9CQU5ESVRfTkFOTxBAEhwKGEhFTFRFQ19DQVBTVUxFX1NFTlNPUl9WMxBBEh0KGUhFTFRFQ19WSVNJT05fTUFTVEVSX1QxOTAQQhIdChlIRUxURUNfVklTSU9OX01BU1RFUl9FMjEzEEMSHQoZSEVMVEVDX1ZJU0lPTl9NQVNURVJfRTI5MBBEEhkKFUhFTFRFQ19NRVNIX05PREVfVDExNBBFEhYKElNFTlNFQ0FQX0lORElDQVRPUhBGEhMKD1RSQUNLRVJfVDEwMDBfRRBHEgsKB1JBSzMxNzIQSBIKCgZXSU9fRTUQSRIaChZSQURJT01BU1RFUl85MDBfQkFORElUEEoSEwoPTUUyNUxTMDFfNFkxMFREEEsSGAoUUlAyMDQwX0ZFQVRIRVJfUkZNOTUQTBIVChFNNVNUQUNLX0NPUkVCQVNJQxBNEhEKDU01U1RBQ0tfQ09SRTIQThINCglSUElfUElDTzIQTxISCg5NNVNUQUNLX0NPUkVTMxBQEhEKDVNFRUVEX1hJQU9fUzMQURILCgdNUzI0U0YxEFISDAoIVExPUkFfQzYQUxIPCgtXSVNNRVNIX1RBUBBUEg0KCVJPVVRBU1RJQxBVEgwKCE1FU0hfVEFCEFYSDAoITUVTSExJTksQVxISCg5YSUFPX05SRjUyX0tJVBBYEhAKDFRISU5LTk9ERV9NMRBZEhAKDFRISU5LTk9ERV9NMhBaEg8KC1RfRVRIX0VMSVRFEFsSFQoRSEVMVEVDX1NFTlNPUl9IVUIQXBINCglNVVpJX0JBU0UQXRIWChJIRUxURUNfTUVTSF9QT0NLRVQQXhIUChBTRUVFRF9TT0xBUl9OT0RFEF8SGAoUTk9NQURTVEFSX01FVEVPUl9QUk8QYBINCglDUk9XUEFORUwQYRILCgdMSU5LXzMyEGISGAoUU0VFRURfV0lPX1RSQUNLRVJfTDEQYxIdChlTRUVFRF9XSU9fVFJBQ0tFUl9MMV9FSU5LEGQSDwoLTVVaSV9SMV9ORU8QZRIOCgpUX0RFQ0tfUFJPEGYSEAoMVF9MT1JBX1BBR0VSEGcSFAoQTTVTVEFDS19SRVNFUlZFRBBoEg8KC1dJU01FU0hfVEFHEGkSCwoHUkFLMzMxMhBqEhAKDFRISU5LTk9ERV9NNRBrEhUKEUhFTFRFQ19NRVNIX1NPTEFSEGwSDwoLVF9FQ0hPX0xJVEUQbRINCglIRUxURUNfVjQQbhIPCgtNNVNUQUNLX0M2TBBvEhkKFU01U1RBQ0tfQ0FSRFBVVEVSX0FEVhBwEh4KGkhFTFRFQ19XSVJFTEVTU19UUkFDS0VSX1YyEHESEQoNVF9XQVRDSF9VTFRSQRByEhAKDFRISU5LTk9ERV9NMxBzEhIKDldJU01FU0hfVEFQX1YyEHQSCwoHUkFLMzQwMRB1EgsKB1JBSzY0MjEQdhIQCgxUSElOS05PREVfTTQQdxIQCgxUSElOS05PREVfTTYQeBISCg5NRVNIU1RJQ0tfMTI2MhB5EhAKDFRCRUFNXzFfV0FUVBB6EhQKEFQ1X1MzX0VQQVBFUl9QUk8QexINCglUQkVBTV9CUEYQfBISCg5NSU5JX0VQQVBFUl9TMxB9EhMKD1RESVNQTEFZX1MzX1BSTxB+EhkKFUhFTFRFQ19NRVNIX05PREVfVDA5NhB/EhgKE1RSQUNLRVJfVDEwMDBfRV9QUk8QgAESEQoMVEhJTktOT0RFX003EIEBEhEKDFRISU5LTk9ERV9NOBCCARIRCgxUSElOS05PREVfTTkQgwESEQoMSEVMVEVDX1Y0X1I4EIQBEhgKE0hFTFRFQ19NRVNIX05PREVfVDEQhQESDwoKU1RBVElPTl9HMxCGARITCg5UX0lNUFVMU0VfUExVUxCHARIQCgtUX0VDSE9fQ0FSRBCIARIZChRTRUVFRF9XSU9fVFJBQ0tFUl9MMhCJARIRCgxDUk9XUEFORUxfUDQQigESGQoUSEVMVEVDX01FU0hfVE9XRVJfVjIQiwESEwoOTUVTSE5PTE9HWV9XMTAQjAESDwoKUFJJVkFURV9IVxD/ASosCglDb25zdGFudHMSCAoEWkVSTxAAEhUKEERBVEFfUEFZTE9BRF9MRU4Q6QEqtAIKEUNyaXRpY2FsRXJyb3JDb2RlEggKBE5PTkUQABIPCgtUWF9XQVRDSERPRxABEhQKEFNMRUVQX0VOVEVSX1dBSVQQAhIMCghOT19SQURJTxADEg8KC1VOU1BFQ0lGSUVEEAQSFQoRVUJMT1hfVU5JVF9GQUlMRUQQBRINCglOT19BWFAxOTIQBhIZChVJTlZBTElEX1JBRElPX1NFVFRJTkcQBxITCg9UUkFOU01JVF9GQUlMRUQQCBIMCghCUk9XTk9VVBAJEhIKDlNYMTI2Ml9GQUlMVVJFEAoSEQoNUkFESU9fU1BJX0JVRxALEiAKHEZMQVNIX0NPUlJVUFRJT05fUkVDT1ZFUkFCTEUQDBIiCh5GTEFTSF9DT1JSVVBUSU9OX1VOUkVDT1ZFUkFCTEUQDSp/Cg9GaXJtd2FyZUVkaXRpb24SCwoHVkFOSUxMQRAAEhEKDVNNQVJUX0NJVElaRU4QARIOCgpPUEVOX1NBVUNFEBASCgoGREVGQ09OEBESDwoLQlVSTklOR19NQU4QEhIOCgpIQU1WRU5USU9OEBMSDwoLRElZX0VESVRJT04QfyqAAwoPRXhjbHVkZWRNb2R1bGVzEhEKDUVYQ0xVREVEX05PTkUQABIPCgtNUVRUX0NPTkZJRxABEhEKDVNFUklBTF9DT05GSUcQAhITCg9FWFROT1RJRl9DT05GSUcQBBIXChNTVE9SRUZPUldBUkRfQ09ORklHEAgSFAoQUkFOR0VURVNUX0NPTkZJRxAQEhQKEFRFTEVNRVRSWV9DT05GSUcQIBIUChBDQU5ORURNU0dfQ09ORklHEEASEQoMQVVESU9fQ09ORklHEIABEhoKFVJFTU9URUhBUkRXQVJFX0NPTkZJRxCAAhIYChNORUlHSEJPUklORk9fQ09ORklHEIAEEhsKFkFNQklFTlRMSUdIVElOR19DT05GSUcQgAgSGwoWREVURUNUSU9OU0VOU09SX0NPTkZJRxCAEBIWChFQQVhDT1VOVEVSX0NPTkZJRxCAIBIVChBCTFVFVE9PVEhfQ09ORklHEIBAEhQKDk5FVFdPUktfQ09ORklHEICAAUJgChRvcmcubWVzaHRhc3RpYy5wcm90b0IKTWVzaFByb3Rvc1oiZ2l0aHViLmNvbS9tZXNodGFzdGljL2dvL2dlbmVyYXRlZKoCFE1lc2h0YXN0aWMuUHJvdG9idWZzugIAYgZwcm90bzM", [file_meshtastic_channel, file_meshtastic_config, file_meshtastic_device_ui, file_meshtastic_module_config, file_meshtastic_portnums, file_meshtastic_telemetry, file_meshtastic_xmodem]); + fileDesc("ChVtZXNodGFzdGljL21lc2gucHJvdG8SCm1lc2h0YXN0aWMihwcKCFBvc2l0aW9uEhcKCmxhdGl0dWRlX2kYASABKA9IAIgBARIYCgtsb25naXR1ZGVfaRgCIAEoD0gBiAEBEhUKCGFsdGl0dWRlGAMgASgFSAKIAQESDAoEdGltZRgEIAEoBxI3Cg9sb2NhdGlvbl9zb3VyY2UYBSABKA4yHi5tZXNodGFzdGljLlBvc2l0aW9uLkxvY1NvdXJjZRI3Cg9hbHRpdHVkZV9zb3VyY2UYBiABKA4yHi5tZXNodGFzdGljLlBvc2l0aW9uLkFsdFNvdXJjZRIRCgl0aW1lc3RhbXAYByABKAcSHwoXdGltZXN0YW1wX21pbGxpc19hZGp1c3QYCCABKAUSGQoMYWx0aXR1ZGVfaGFlGAkgASgRSAOIAQESKAobYWx0aXR1ZGVfZ2VvaWRhbF9zZXBhcmF0aW9uGAogASgRSASIAQESDAoEUERPUBgLIAEoDRIMCgRIRE9QGAwgASgNEgwKBFZET1AYDSABKA0SFAoMZ3BzX2FjY3VyYWN5GA4gASgNEhkKDGdyb3VuZF9zcGVlZBgPIAEoDUgFiAEBEhkKDGdyb3VuZF90cmFjaxgQIAEoDUgGiAEBEhMKC2ZpeF9xdWFsaXR5GBEgASgNEhAKCGZpeF90eXBlGBIgASgNEhQKDHNhdHNfaW5fdmlldxgTIAEoDRIRCglzZW5zb3JfaWQYFCABKA0SEwoLbmV4dF91cGRhdGUYFSABKA0SEgoKc2VxX251bWJlchgWIAEoDRIWCg5wcmVjaXNpb25fYml0cxgXIAEoDSJOCglMb2NTb3VyY2USDQoJTE9DX1VOU0VUEAASDgoKTE9DX01BTlVBTBABEhAKDExPQ19JTlRFUk5BTBACEhAKDExPQ19FWFRFUk5BTBADImIKCUFsdFNvdXJjZRINCglBTFRfVU5TRVQQABIOCgpBTFRfTUFOVUFMEAESEAoMQUxUX0lOVEVSTkFMEAISEAoMQUxUX0VYVEVSTkFMEAMSEgoOQUxUX0JBUk9NRVRSSUMQBEINCgtfbGF0aXR1ZGVfaUIOCgxfbG9uZ2l0dWRlX2lCCwoJX2FsdGl0dWRlQg8KDV9hbHRpdHVkZV9oYWVCHgocX2FsdGl0dWRlX2dlb2lkYWxfc2VwYXJhdGlvbkIPCg1fZ3JvdW5kX3NwZWVkQg8KDV9ncm91bmRfdHJhY2siigIKBFVzZXISCgoCaWQYASABKAkSEQoJbG9uZ19uYW1lGAIgASgJEhIKCnNob3J0X25hbWUYAyABKAkSEwoHbWFjYWRkchgEIAEoDEICGAESKwoIaHdfbW9kZWwYBSABKA4yGS5tZXNodGFzdGljLkhhcmR3YXJlTW9kZWwSEwoLaXNfbGljZW5zZWQYBiABKAgSMgoEcm9sZRgHIAEoDjIkLm1lc2h0YXN0aWMuQ29uZmlnLkRldmljZUNvbmZpZy5Sb2xlEhIKCnB1YmxpY19rZXkYCCABKAwSHAoPaXNfdW5tZXNzYWdhYmxlGAkgASgISACIAQFCEgoQX2lzX3VubWVzc2FnYWJsZSJaCg5Sb3V0ZURpc2NvdmVyeRINCgVyb3V0ZRgBIAMoBxITCgtzbnJfdG93YXJkcxgCIAMoBRISCgpyb3V0ZV9iYWNrGAMgAygHEhAKCHNucl9iYWNrGAQgAygFIpkECgdSb3V0aW5nEjMKDXJvdXRlX3JlcXVlc3QYASABKAsyGi5tZXNodGFzdGljLlJvdXRlRGlzY292ZXJ5SAASMQoLcm91dGVfcmVwbHkYAiABKAsyGi5tZXNodGFzdGljLlJvdXRlRGlzY292ZXJ5SAASMQoMZXJyb3JfcmVhc29uGAMgASgOMhkubWVzaHRhc3RpYy5Sb3V0aW5nLkVycm9ySAAi5wIKBUVycm9yEggKBE5PTkUQABIMCghOT19ST1VURRABEgsKB0dPVF9OQUsQAhILCgdUSU1FT1VUEAMSEAoMTk9fSU5URVJGQUNFEAQSEgoOTUFYX1JFVFJBTlNNSVQQBRIOCgpOT19DSEFOTkVMEAYSDQoJVE9PX0xBUkdFEAcSDwoLTk9fUkVTUE9OU0UQCBIUChBEVVRZX0NZQ0xFX0xJTUlUEAkSDwoLQkFEX1JFUVVFU1QQIBISCg5OT1RfQVVUSE9SSVpFRBAhEg4KClBLSV9GQUlMRUQQIhIWChJQS0lfVU5LTk9XTl9QVUJLRVkQIxIZChVBRE1JTl9CQURfU0VTU0lPTl9LRVkQJBIhCh1BRE1JTl9QVUJMSUNfS0VZX1VOQVVUSE9SSVpFRBAlEhcKE1JBVEVfTElNSVRfRVhDRUVERUQQJhIcChhQS0lfU0VORF9GQUlMX1BVQkxJQ19LRVkQJ0IJCgd2YXJpYW50IuUBCgREYXRhEiQKB3BvcnRudW0YASABKA4yEy5tZXNodGFzdGljLlBvcnROdW0SDwoHcGF5bG9hZBgCIAEoDBIVCg13YW50X3Jlc3BvbnNlGAMgASgIEgwKBGRlc3QYBCABKAcSDgoGc291cmNlGAUgASgHEhIKCnJlcXVlc3RfaWQYBiABKAcSEAoIcmVwbHlfaWQYByABKAcSDQoFZW1vamkYCCABKAcSFQoIYml0ZmllbGQYCSABKA1IAIgBARIYChB4ZWRkc2Ffc2lnbmF0dXJlGAogASgMQgsKCV9iaXRmaWVsZCI+Cg9LZXlWZXJpZmljYXRpb24SDQoFbm9uY2UYASABKAQSDQoFaGFzaDEYAiABKAwSDQoFaGFzaDIYAyABKAwiywMKFFN0b3JlRm9yd2FyZFBsdXNQbHVzEk0KEXNmcHBfbWVzc2FnZV90eXBlGAEgASgOMjIubWVzaHRhc3RpYy5TdG9yZUZvcndhcmRQbHVzUGx1cy5TRlBQX21lc3NhZ2VfdHlwZRIUCgxtZXNzYWdlX2hhc2gYAiABKAwSEwoLY29tbWl0X2hhc2gYAyABKAwSEQoJcm9vdF9oYXNoGAQgASgMEg8KB21lc3NhZ2UYBSABKAwSFwoPZW5jYXBzdWxhdGVkX2lkGAYgASgNEhcKD2VuY2Fwc3VsYXRlZF90bxgHIAEoDRIZChFlbmNhcHN1bGF0ZWRfZnJvbRgIIAEoDRIbChNlbmNhcHN1bGF0ZWRfcnh0aW1lGAkgASgNEhMKC2NoYWluX2NvdW50GAogASgNIpUBChFTRlBQX21lc3NhZ2VfdHlwZRISCg5DQU5PTl9BTk5PVU5DRRAAEg8KC0NIQUlOX1FVRVJZEAESEAoMTElOS19SRVFVRVNUEAMSEAoMTElOS19QUk9WSURFEAQSGgoWTElOS19QUk9WSURFX0ZJUlNUSEFMRhAFEhsKF0xJTktfUFJPVklERV9TRUNPTkRIQUxGEAYi4wIKC1JlbW90ZVNoZWxsEioKAm9wGAEgASgOMh4ubWVzaHRhc3RpYy5SZW1vdGVTaGVsbC5PcENvZGUSEgoKc2Vzc2lvbl9pZBgCIAEoDRILCgNzZXEYAyABKA0SDwoHYWNrX3NlcRgEIAEoDRIPCgdwYXlsb2FkGAUgASgMEgwKBGNvbHMYBiABKA0SDAoEcm93cxgHIAEoDRINCgVmbGFncxgIIAEoDRITCgtsYXN0X3R4X3NlcRgJIAEoDRITCgtsYXN0X3J4X3NlcRgKIAEoDSKPAQoGT3BDb2RlEgwKCE9QX1VOU0VUEAASCAoET1BFThABEgkKBUlOUFVUEAISCgoGUkVTSVpFEAMSCQoFQ0xPU0UQBBIICgRQSU5HEAUSBwoDQUNLEAYSCwoHT1BFTl9PSxBAEgoKBk9VVFBVVBBBEgoKBkNMT1NFRBBCEgkKBUVSUk9SEEMSCAoEUE9ORxBEIrwBCghXYXlwb2ludBIKCgJpZBgBIAEoDRIXCgpsYXRpdHVkZV9pGAIgASgPSACIAQESGAoLbG9uZ2l0dWRlX2kYAyABKA9IAYgBARIOCgZleHBpcmUYBCABKA0SEQoJbG9ja2VkX3RvGAUgASgNEgwKBG5hbWUYBiABKAkSEwoLZGVzY3JpcHRpb24YByABKAkSDAoEaWNvbhgIIAEoB0INCgtfbGF0aXR1ZGVfaUIOCgxfbG9uZ2l0dWRlX2kiHwoNU3RhdHVzTWVzc2FnZRIOCgZzdGF0dXMYASABKAkibAoWTXF0dENsaWVudFByb3h5TWVzc2FnZRINCgV0b3BpYxgBIAEoCRIOCgRkYXRhGAIgASgMSAASDgoEdGV4dBgDIAEoCUgAEhAKCHJldGFpbmVkGAQgASgIQhEKD3BheWxvYWRfdmFyaWFudCLnBwoKTWVzaFBhY2tldBIMCgRmcm9tGAEgASgHEgoKAnRvGAIgASgHEg8KB2NoYW5uZWwYAyABKA0SIwoHZGVjb2RlZBgEIAEoCzIQLm1lc2h0YXN0aWMuRGF0YUgAEhMKCWVuY3J5cHRlZBgFIAEoDEgAEgoKAmlkGAYgASgHEg8KB3J4X3RpbWUYByABKAcSDgoGcnhfc25yGAggASgCEhEKCWhvcF9saW1pdBgJIAEoDRIQCgh3YW50X2FjaxgKIAEoCBIxCghwcmlvcml0eRgLIAEoDjIfLm1lc2h0YXN0aWMuTWVzaFBhY2tldC5Qcmlvcml0eRIPCgdyeF9yc3NpGAwgASgFEjMKB2RlbGF5ZWQYDSABKA4yHi5tZXNodGFzdGljLk1lc2hQYWNrZXQuRGVsYXllZEICGAESEAoIdmlhX21xdHQYDiABKAgSEQoJaG9wX3N0YXJ0GA8gASgNEhIKCnB1YmxpY19rZXkYECABKAwSFQoNcGtpX2VuY3J5cHRlZBgRIAEoCBIQCghuZXh0X2hvcBgSIAEoDRISCgpyZWxheV9ub2RlGBMgASgNEhAKCHR4X2FmdGVyGBQgASgNEkYKE3RyYW5zcG9ydF9tZWNoYW5pc20YFSABKA4yKS5tZXNodGFzdGljLk1lc2hQYWNrZXQuVHJhbnNwb3J0TWVjaGFuaXNtEhUKDXhlZGRzYV9zaWduZWQYFiABKAgifgoIUHJpb3JpdHkSCQoFVU5TRVQQABIHCgNNSU4QARIOCgpCQUNLR1JPVU5EEAoSCwoHREVGQVVMVBBAEgwKCFJFTElBQkxFEEYSDAoIUkVTUE9OU0UQUBIICgRISUdIEGQSCQoFQUxFUlQQbhIHCgNBQ0sQeBIHCgNNQVgQfyJCCgdEZWxheWVkEgwKCE5PX0RFTEFZEAASFQoRREVMQVlFRF9CUk9BRENBU1QQARISCg5ERUxBWUVEX0RJUkVDVBACIuoBChJUcmFuc3BvcnRNZWNoYW5pc20SFgoSVFJBTlNQT1JUX0lOVEVSTkFMEAASEgoOVFJBTlNQT1JUX0xPUkEQARIXChNUUkFOU1BPUlRfTE9SQV9BTFQxEAISFwoTVFJBTlNQT1JUX0xPUkFfQUxUMhADEhcKE1RSQU5TUE9SVF9MT1JBX0FMVDMQBBISCg5UUkFOU1BPUlRfTVFUVBAFEhsKF1RSQU5TUE9SVF9NVUxUSUNBU1RfVURQEAYSEQoNVFJBTlNQT1JUX0FQSRAHEhkKFVRSQU5TUE9SVF9VTklDQVNUX1VEUBAIQhEKD3BheWxvYWRfdmFyaWFudCL0AgoITm9kZUluZm8SCwoDbnVtGAEgASgNEh4KBHVzZXIYAiABKAsyEC5tZXNodGFzdGljLlVzZXISJgoIcG9zaXRpb24YAyABKAsyFC5tZXNodGFzdGljLlBvc2l0aW9uEgsKA3NuchgEIAEoAhISCgpsYXN0X2hlYXJkGAUgASgHEjEKDmRldmljZV9tZXRyaWNzGAYgASgLMhkubWVzaHRhc3RpYy5EZXZpY2VNZXRyaWNzEg8KB2NoYW5uZWwYByABKA0SEAoIdmlhX21xdHQYCCABKAgSFgoJaG9wc19hd2F5GAkgASgNSACIAQESEwoLaXNfZmF2b3JpdGUYCiABKAgSEgoKaXNfaWdub3JlZBgLIAEoCBIgChhpc19rZXlfbWFudWFsbHlfdmVyaWZpZWQYDCABKAgSEAoIaXNfbXV0ZWQYDSABKAgSGQoRaGFzX3hlZGRzYV9zaWduZWQYDiABKAhCDAoKX2hvcHNfYXdheSLBAQoKTXlOb2RlSW5mbxITCgtteV9ub2RlX251bRgBIAEoDRIUCgxyZWJvb3RfY291bnQYCCABKA0SFwoPbWluX2FwcF92ZXJzaW9uGAsgASgNEhEKCWRldmljZV9pZBgMIAEoDBIPCgdwaW9fZW52GA0gASgJEjUKEGZpcm13YXJlX2VkaXRpb24YDiABKA4yGy5tZXNodGFzdGljLkZpcm13YXJlRWRpdGlvbhIUCgxub2RlZGJfY291bnQYDyABKA0iwAEKCUxvZ1JlY29yZBIPCgdtZXNzYWdlGAEgASgJEgwKBHRpbWUYAiABKAcSDgoGc291cmNlGAMgASgJEioKBWxldmVsGAQgASgOMhsubWVzaHRhc3RpYy5Mb2dSZWNvcmQuTGV2ZWwiWAoFTGV2ZWwSCQoFVU5TRVQQABIMCghDUklUSUNBTBAyEgkKBUVSUk9SECgSCwoHV0FSTklORxAeEggKBElORk8QFBIJCgVERUJVRxAKEgkKBVRSQUNFEAUiUAoLUXVldWVTdGF0dXMSCwoDcmVzGAEgASgFEgwKBGZyZWUYAiABKA0SDgoGbWF4bGVuGAMgASgNEhYKDm1lc2hfcGFja2V0X2lkGAQgASgNIrAGCglGcm9tUmFkaW8SCgoCaWQYASABKA0SKAoGcGFja2V0GAIgASgLMhYubWVzaHRhc3RpYy5NZXNoUGFja2V0SAASKQoHbXlfaW5mbxgDIAEoCzIWLm1lc2h0YXN0aWMuTXlOb2RlSW5mb0gAEikKCW5vZGVfaW5mbxgEIAEoCzIULm1lc2h0YXN0aWMuTm9kZUluZm9IABIkCgZjb25maWcYBSABKAsyEi5tZXNodGFzdGljLkNvbmZpZ0gAEisKCmxvZ19yZWNvcmQYBiABKAsyFS5tZXNodGFzdGljLkxvZ1JlY29yZEgAEhwKEmNvbmZpZ19jb21wbGV0ZV9pZBgHIAEoDUgAEhIKCHJlYm9vdGVkGAggASgISAASMAoMbW9kdWxlQ29uZmlnGAkgASgLMhgubWVzaHRhc3RpYy5Nb2R1bGVDb25maWdIABImCgdjaGFubmVsGAogASgLMhMubWVzaHRhc3RpYy5DaGFubmVsSAASLgoLcXVldWVTdGF0dXMYCyABKAsyFy5tZXNodGFzdGljLlF1ZXVlU3RhdHVzSAASKgoMeG1vZGVtUGFja2V0GAwgASgLMhIubWVzaHRhc3RpYy5YTW9kZW1IABIuCghtZXRhZGF0YRgNIAEoCzIaLm1lc2h0YXN0aWMuRGV2aWNlTWV0YWRhdGFIABJEChZtcXR0Q2xpZW50UHJveHlNZXNzYWdlGA4gASgLMiIubWVzaHRhc3RpYy5NcXR0Q2xpZW50UHJveHlNZXNzYWdlSAASKAoIZmlsZUluZm8YDyABKAsyFC5tZXNodGFzdGljLkZpbGVJbmZvSAASPAoSY2xpZW50Tm90aWZpY2F0aW9uGBAgASgLMh4ubWVzaHRhc3RpYy5DbGllbnROb3RpZmljYXRpb25IABI0Cg5kZXZpY2V1aUNvbmZpZxgRIAEoCzIaLm1lc2h0YXN0aWMuRGV2aWNlVUlDb25maWdIABI1Cg9sb2NrZG93bl9zdGF0dXMYEiABKAsyGi5tZXNodGFzdGljLkxvY2tkb3duU3RhdHVzSABCEQoPcGF5bG9hZF92YXJpYW50IpMCCg5Mb2NrZG93blN0YXR1cxIvCgVzdGF0ZRgBIAEoDjIgLm1lc2h0YXN0aWMuTG9ja2Rvd25TdGF0dXMuU3RhdGUSEwoLbG9ja19yZWFzb24YAiABKAkSFwoPYm9vdHNfcmVtYWluaW5nGAMgASgNEhkKEXZhbGlkX3VudGlsX2Vwb2NoGAQgASgNEhcKD2JhY2tvZmZfc2Vjb25kcxgFIAEoDSJuCgVTdGF0ZRIVChFTVEFURV9VTlNQRUNJRklFRBAAEhMKD05FRURTX1BST1ZJU0lPThABEgoKBkxPQ0tFRBACEgwKCFVOTE9DS0VEEAMSEQoNVU5MT0NLX0ZBSUxFRBAEEgwKCERJU0FCTEVEEAUi+gMKEkNsaWVudE5vdGlmaWNhdGlvbhIVCghyZXBseV9pZBgBIAEoDUgBiAEBEgwKBHRpbWUYAiABKAcSKgoFbGV2ZWwYAyABKA4yGy5tZXNodGFzdGljLkxvZ1JlY29yZC5MZXZlbBIPCgdtZXNzYWdlGAQgASgJElEKHmtleV92ZXJpZmljYXRpb25fbnVtYmVyX2luZm9ybRgLIAEoCzInLm1lc2h0YXN0aWMuS2V5VmVyaWZpY2F0aW9uTnVtYmVySW5mb3JtSAASUwofa2V5X3ZlcmlmaWNhdGlvbl9udW1iZXJfcmVxdWVzdBgMIAEoCzIoLm1lc2h0YXN0aWMuS2V5VmVyaWZpY2F0aW9uTnVtYmVyUmVxdWVzdEgAEkIKFmtleV92ZXJpZmljYXRpb25fZmluYWwYDSABKAsyIC5tZXNodGFzdGljLktleVZlcmlmaWNhdGlvbkZpbmFsSAASQAoVZHVwbGljYXRlZF9wdWJsaWNfa2V5GA4gASgLMh8ubWVzaHRhc3RpYy5EdXBsaWNhdGVkUHVibGljS2V5SAASNAoPbG93X2VudHJvcHlfa2V5GA8gASgLMhkubWVzaHRhc3RpYy5Mb3dFbnRyb3B5S2V5SABCEQoPcGF5bG9hZF92YXJpYW50QgsKCV9yZXBseV9pZCJeChtLZXlWZXJpZmljYXRpb25OdW1iZXJJbmZvcm0SDQoFbm9uY2UYASABKAQSFwoPcmVtb3RlX2xvbmduYW1lGAIgASgJEhcKD3NlY3VyaXR5X251bWJlchgDIAEoDSJGChxLZXlWZXJpZmljYXRpb25OdW1iZXJSZXF1ZXN0Eg0KBW5vbmNlGAEgASgEEhcKD3JlbW90ZV9sb25nbmFtZRgCIAEoCSJxChRLZXlWZXJpZmljYXRpb25GaW5hbBINCgVub25jZRgBIAEoBBIXCg9yZW1vdGVfbG9uZ25hbWUYAiABKAkSEAoIaXNTZW5kZXIYAyABKAgSHwoXdmVyaWZpY2F0aW9uX2NoYXJhY3RlcnMYBCABKAkiFQoTRHVwbGljYXRlZFB1YmxpY0tleSIPCg1Mb3dFbnRyb3B5S2V5IjEKCEZpbGVJbmZvEhEKCWZpbGVfbmFtZRgBIAEoCRISCgpzaXplX2J5dGVzGAIgASgNIpQCCgdUb1JhZGlvEigKBnBhY2tldBgBIAEoCzIWLm1lc2h0YXN0aWMuTWVzaFBhY2tldEgAEhgKDndhbnRfY29uZmlnX2lkGAMgASgNSAASFAoKZGlzY29ubmVjdBgEIAEoCEgAEioKDHhtb2RlbVBhY2tldBgFIAEoCzISLm1lc2h0YXN0aWMuWE1vZGVtSAASRAoWbXF0dENsaWVudFByb3h5TWVzc2FnZRgGIAEoCzIiLm1lc2h0YXN0aWMuTXF0dENsaWVudFByb3h5TWVzc2FnZUgAEioKCWhlYXJ0YmVhdBgHIAEoCzIVLm1lc2h0YXN0aWMuSGVhcnRiZWF0SABCEQoPcGF5bG9hZF92YXJpYW50IkAKCkNvbXByZXNzZWQSJAoHcG9ydG51bRgBIAEoDjITLm1lc2h0YXN0aWMuUG9ydE51bRIMCgRkYXRhGAIgASgMIocBCgxOZWlnaGJvckluZm8SDwoHbm9kZV9pZBgBIAEoDRIXCg9sYXN0X3NlbnRfYnlfaWQYAiABKA0SJAocbm9kZV9icm9hZGNhc3RfaW50ZXJ2YWxfc2VjcxgDIAEoDRInCgluZWlnaGJvcnMYBCADKAsyFC5tZXNodGFzdGljLk5laWdoYm9yImQKCE5laWdoYm9yEg8KB25vZGVfaWQYASABKA0SCwoDc25yGAIgASgCEhQKDGxhc3RfcnhfdGltZRgDIAEoBxIkChxub2RlX2Jyb2FkY2FzdF9pbnRlcnZhbF9zZWNzGAQgASgNIusCCg5EZXZpY2VNZXRhZGF0YRIYChBmaXJtd2FyZV92ZXJzaW9uGAEgASgJEhwKFGRldmljZV9zdGF0ZV92ZXJzaW9uGAIgASgNEhMKC2NhblNodXRkb3duGAMgASgIEg8KB2hhc1dpZmkYBCABKAgSFAoMaGFzQmx1ZXRvb3RoGAUgASgIEhMKC2hhc0V0aGVybmV0GAYgASgIEjIKBHJvbGUYByABKA4yJC5tZXNodGFzdGljLkNvbmZpZy5EZXZpY2VDb25maWcuUm9sZRIWCg5wb3NpdGlvbl9mbGFncxgIIAEoDRIrCghod19tb2RlbBgJIAEoDjIZLm1lc2h0YXN0aWMuSGFyZHdhcmVNb2RlbBIZChFoYXNSZW1vdGVIYXJkd2FyZRgKIAEoCBIOCgZoYXNQS0MYCyABKAgSGAoQZXhjbHVkZWRfbW9kdWxlcxgMIAEoDRISCgpoYXNfeGVkZHNhGA4gASgIIhoKCUhlYXJ0YmVhdBINCgVub25jZRgBIAEoDSJVChVOb2RlUmVtb3RlSGFyZHdhcmVQaW4SEAoIbm9kZV9udW0YASABKA0SKgoDcGluGAIgASgLMh0ubWVzaHRhc3RpYy5SZW1vdGVIYXJkd2FyZVBpbiJlCg5DaHVua2VkUGF5bG9hZBISCgpwYXlsb2FkX2lkGAEgASgNEhMKC2NodW5rX2NvdW50GAIgASgNEhMKC2NodW5rX2luZGV4GAMgASgNEhUKDXBheWxvYWRfY2h1bmsYBCABKAwiHwoNcmVzZW5kX2NodW5rcxIOCgZjaHVua3MYASADKA0iqgEKFkNodW5rZWRQYXlsb2FkUmVzcG9uc2USEgoKcGF5bG9hZF9pZBgBIAEoDRIaChByZXF1ZXN0X3RyYW5zZmVyGAIgASgISAASGQoPYWNjZXB0X3RyYW5zZmVyGAMgASgISAASMgoNcmVzZW5kX2NodW5rcxgEIAEoCzIZLm1lc2h0YXN0aWMucmVzZW5kX2NodW5rc0gAQhEKD3BheWxvYWRfdmFyaWFudCqsFQoNSGFyZHdhcmVNb2RlbBIJCgVVTlNFVBAAEgwKCFRMT1JBX1YyEAESDAoIVExPUkFfVjEQAhISCg5UTE9SQV9WMl8xXzFQNhADEgkKBVRCRUFNEAQSDwoLSEVMVEVDX1YyXzAQBRIOCgpUQkVBTV9WMFA3EAYSCgoGVF9FQ0hPEAcSEAoMVExPUkFfVjFfMVAzEAgSCwoHUkFLNDYzMRAJEg8KC0hFTFRFQ19WMl8xEAoSDQoJSEVMVEVDX1YxEAsSGAoUTElMWUdPX1RCRUFNX1MzX0NPUkUQDBIMCghSQUsxMTIwMBANEgsKB05BTk9fRzEQDhISCg5UTE9SQV9WMl8xXzFQOBAPEg8KC1RMT1JBX1QzX1MzEBASFAoQTkFOT19HMV9FWFBMT1JFUhAREhEKDU5BTk9fRzJfVUxUUkEQEhINCglMT1JBX1RZUEUQExILCgdXSVBIT05FEBQSDgoKV0lPX1dNMTExMBAVEgsKB1JBSzI1NjAQFhITCg9IRUxURUNfSFJVXzM2MDEQFxIaChZIRUxURUNfV0lSRUxFU1NfQlJJREdFEBgSDgoKU1RBVElPTl9HMRAZEgwKCFJBSzExMzEwEBoSFAoQU0VOU0VMT1JBX1JQMjA0MBAbEhAKDFNFTlNFTE9SQV9TMxAcEg0KCUNBTkFSWU9ORRAdEg8KC1JQMjA0MF9MT1JBEB4SDgoKU1RBVElPTl9HMhAfEhEKDUxPUkFfUkVMQVlfVjEQIBIPCgtUX0VDSE9fUExVUxAhEgcKA1BQUhAiEg8KC0dFTklFQkxPQ0tTECMSEQoNTlJGNTJfVU5LTk9XThAkEg0KCVBPUlREVUlOTxAlEg8KC0FORFJPSURfU0lNECYSCgoGRElZX1YxECcSFQoRTlJGNTI4NDBfUENBMTAwNTkQKBIKCgZEUl9ERVYQKRILCgdNNVNUQUNLECoSDQoJSEVMVEVDX1YzECsSEQoNSEVMVEVDX1dTTF9WMxAsEhMKD0JFVEFGUFZfMjQwMF9UWBAtEhcKE0JFVEFGUFZfOTAwX05BTk9fVFgQLhIMCghSUElfUElDTxAvEhsKF0hFTFRFQ19XSVJFTEVTU19UUkFDS0VSEDASGQoVSEVMVEVDX1dJUkVMRVNTX1BBUEVSEDESCgoGVF9ERUNLEDISDgoKVF9XQVRDSF9TMxAzEhEKDVBJQ09NUFVURVJfUzMQNBIPCgtIRUxURUNfSFQ2MhA1EhIKDkVCWVRFX0VTUDMyX1MzEDYSEQoNRVNQMzJfUzNfUElDTxA3Eg0KCUNIQVRURVJfMhA4Eh4KGkhFTFRFQ19XSVJFTEVTU19QQVBFUl9WMV8wEDkSIAocSEVMVEVDX1dJUkVMRVNTX1RSQUNLRVJfVjFfMBA6EgsKB1VOUEhPTkUQOxIMCghURF9MT1JBQxA8EhMKD0NERUJZVEVfRU9SQV9TMxA9Eg8KC1RXQ19NRVNIX1Y0ED4SFgoSTlJGNTJfUFJPTUlDUk9fRElZED8SHwobUkFESU9NQVNURVJfOTAwX0JBTkRJVF9OQU5PEEASHAoYSEVMVEVDX0NBUFNVTEVfU0VOU09SX1YzEEESHQoZSEVMVEVDX1ZJU0lPTl9NQVNURVJfVDE5MBBCEh0KGUhFTFRFQ19WSVNJT05fTUFTVEVSX0UyMTMQQxIdChlIRUxURUNfVklTSU9OX01BU1RFUl9FMjkwEEQSGQoVSEVMVEVDX01FU0hfTk9ERV9UMTE0EEUSFgoSU0VOU0VDQVBfSU5ESUNBVE9SEEYSEwoPVFJBQ0tFUl9UMTAwMF9FEEcSCwoHUkFLMzE3MhBIEgoKBldJT19FNRBJEhoKFlJBRElPTUFTVEVSXzkwMF9CQU5ESVQQShITCg9NRTI1TFMwMV80WTEwVEQQSxIYChRSUDIwNDBfRkVBVEhFUl9SRk05NRBMEhUKEU01U1RBQ0tfQ09SRUJBU0lDEE0SEQoNTTVTVEFDS19DT1JFMhBOEg0KCVJQSV9QSUNPMhBPEhIKDk01U1RBQ0tfQ09SRVMzEFASEQoNU0VFRURfWElBT19TMxBREgsKB01TMjRTRjEQUhIMCghUTE9SQV9DNhBTEg8KC1dJU01FU0hfVEFQEFQSDQoJUk9VVEFTVElDEFUSDAoITUVTSF9UQUIQVhIMCghNRVNITElOSxBXEhIKDlhJQU9fTlJGNTJfS0lUEFgSEAoMVEhJTktOT0RFX00xEFkSEAoMVEhJTktOT0RFX00yEFoSDwoLVF9FVEhfRUxJVEUQWxIVChFIRUxURUNfU0VOU09SX0hVQhBcEg0KCU1VWklfQkFTRRBdEhYKEkhFTFRFQ19NRVNIX1BPQ0tFVBBeEhQKEFNFRUVEX1NPTEFSX05PREUQXxIYChROT01BRFNUQVJfTUVURU9SX1BSTxBgEg0KCUNST1dQQU5FTBBhEgsKB0xJTktfMzIQYhIYChRTRUVFRF9XSU9fVFJBQ0tFUl9MMRBjEh0KGVNFRUVEX1dJT19UUkFDS0VSX0wxX0VJTksQZBIPCgtNVVpJX1IxX05FTxBlEg4KClRfREVDS19QUk8QZhIQCgxUX0xPUkFfUEFHRVIQZxIUChBNNVNUQUNLX1JFU0VSVkVEEGgSDwoLV0lTTUVTSF9UQUcQaRILCgdSQUszMzEyEGoSEAoMVEhJTktOT0RFX001EGsSFQoRSEVMVEVDX01FU0hfU09MQVIQbBIPCgtUX0VDSE9fTElURRBtEg0KCUhFTFRFQ19WNBBuEg8KC001U1RBQ0tfQzZMEG8SGQoVTTVTVEFDS19DQVJEUFVURVJfQURWEHASHgoaSEVMVEVDX1dJUkVMRVNTX1RSQUNLRVJfVjIQcRIRCg1UX1dBVENIX1VMVFJBEHISEAoMVEhJTktOT0RFX00zEHMSEgoOV0lTTUVTSF9UQVBfVjIQdBILCgdSQUszNDAxEHUSCwoHUkFLNjQyMRB2EhAKDFRISU5LTk9ERV9NNBB3EhAKDFRISU5LTk9ERV9NNhB4EhIKDk1FU0hTVElDS18xMjYyEHkSEAoMVEJFQU1fMV9XQVRUEHoSFAoQVDVfUzNfRVBBUEVSX1BSTxB7Eg0KCVRCRUFNX0JQRhB8EhIKDk1JTklfRVBBUEVSX1MzEH0SEwoPVERJU1BMQVlfUzNfUFJPEH4SGQoVSEVMVEVDX01FU0hfTk9ERV9UMDk2EH8SGAoTVFJBQ0tFUl9UMTAwMF9FX1BSTxCAARIRCgxUSElOS05PREVfTTcQgQESEQoMVEhJTktOT0RFX004EIIBEhEKDFRISU5LTk9ERV9NORCDARIRCgxIRUxURUNfVjRfUjgQhAESGAoTSEVMVEVDX01FU0hfTk9ERV9UMRCFARIPCgpTVEFUSU9OX0czEIYBEhMKDlRfSU1QVUxTRV9QTFVTEIcBEhAKC1RfRUNIT19DQVJEEIgBEhkKFFNFRUVEX1dJT19UUkFDS0VSX0wyEIkBEhEKDENST1dQQU5FTF9QNBCKARIZChRIRUxURUNfTUVTSF9UT1dFUl9WMhCLARITCg5NRVNITk9MT0dZX1cxMBCMARIPCgpQUklWQVRFX0hXEP8BKiwKCUNvbnN0YW50cxIICgRaRVJPEAASFQoQREFUQV9QQVlMT0FEX0xFThDpASq0AgoRQ3JpdGljYWxFcnJvckNvZGUSCAoETk9ORRAAEg8KC1RYX1dBVENIRE9HEAESFAoQU0xFRVBfRU5URVJfV0FJVBACEgwKCE5PX1JBRElPEAMSDwoLVU5TUEVDSUZJRUQQBBIVChFVQkxPWF9VTklUX0ZBSUxFRBAFEg0KCU5PX0FYUDE5MhAGEhkKFUlOVkFMSURfUkFESU9fU0VUVElORxAHEhMKD1RSQU5TTUlUX0ZBSUxFRBAIEgwKCEJST1dOT1VUEAkSEgoOU1gxMjYyX0ZBSUxVUkUQChIRCg1SQURJT19TUElfQlVHEAsSIAocRkxBU0hfQ09SUlVQVElPTl9SRUNPVkVSQUJMRRAMEiIKHkZMQVNIX0NPUlJVUFRJT05fVU5SRUNPVkVSQUJMRRANKn8KD0Zpcm13YXJlRWRpdGlvbhILCgdWQU5JTExBEAASEQoNU01BUlRfQ0lUSVpFThABEg4KCk9QRU5fU0FVQ0UQEBIKCgZERUZDT04QERIPCgtCVVJOSU5HX01BThASEg4KCkhBTVZFTlRJT04QExIPCgtESVlfRURJVElPThB/KoADCg9FeGNsdWRlZE1vZHVsZXMSEQoNRVhDTFVERURfTk9ORRAAEg8KC01RVFRfQ09ORklHEAESEQoNU0VSSUFMX0NPTkZJRxACEhMKD0VYVE5PVElGX0NPTkZJRxAEEhcKE1NUT1JFRk9SV0FSRF9DT05GSUcQCBIUChBSQU5HRVRFU1RfQ09ORklHEBASFAoQVEVMRU1FVFJZX0NPTkZJRxAgEhQKEENBTk5FRE1TR19DT05GSUcQQBIRCgxBVURJT19DT05GSUcQgAESGgoVUkVNT1RFSEFSRFdBUkVfQ09ORklHEIACEhgKE05FSUdIQk9SSU5GT19DT05GSUcQgAQSGwoWQU1CSUVOVExJR0hUSU5HX0NPTkZJRxCACBIbChZERVRFQ1RJT05TRU5TT1JfQ09ORklHEIAQEhYKEVBBWENPVU5URVJfQ09ORklHEIAgEhUKEEJMVUVUT09USF9DT05GSUcQgEASFAoOTkVUV09SS19DT05GSUcQgIABQmAKFG9yZy5tZXNodGFzdGljLnByb3RvQgpNZXNoUHJvdG9zWiJnaXRodWIuY29tL21lc2h0YXN0aWMvZ28vZ2VuZXJhdGVkqgIUTWVzaHRhc3RpYy5Qcm90b2J1ZnO6AgBiBnByb3RvMw", [file_meshtastic_channel, file_meshtastic_config, file_meshtastic_device_ui, file_meshtastic_module_config, file_meshtastic_portnums, file_meshtastic_telemetry, file_meshtastic_xmodem]); /** * @@ -3110,6 +3110,15 @@ export type DeviceMetadata = Message<"meshtastic.DeviceMetadata"> & { * @generated from field: uint32 excluded_modules = 12; */ excludedModules: number; + + /** + * + * Indicates whether this firmware build includes XEdDSA packet signature verification. + * This is a read-only capability and must be false when XEdDSA is not compiled in. + * + * @generated from field: bool has_xeddsa = 14; + */ + hasXeddsa: boolean; }; /** diff --git a/packages/sdk/package.json b/packages/sdk/package.json index aa2e8d380..d43aa174c 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -48,7 +48,7 @@ }, "dependencies": { "@bufbuild/protobuf": "^2.12.1", - "@meshtastic/protobufs": "jsr:^2.7.18", + "@meshtastic/protobufs": "workspace:*", "@preact/signals-core": "^1.14.4", "better-result": "^2.9.2", "crc": "npm:crc@^4.3.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9a6cc9a68..707001872 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -342,8 +342,8 @@ importers: specifier: ^2.12.1 version: 2.12.1 '@meshtastic/protobufs': - specifier: jsr:^2.7.18 - version: '@jsr/meshtastic__protobufs@2.7.20' + specifier: workspace:* + version: link:../protobufs '@preact/signals-core': specifier: ^1.14.4 version: 1.14.4