Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -13326,6 +13326,9 @@
}
}
},
"Authenticate packets when possible, but accept unsigned traffic for maximum compatibility." : {
"comment" : "Description of the Compatible packet authenticity policy."
},
"Auto-Fix Channel" : {
"comment" : "A button label that initiates the process of automatically fixing the TAK server's primary communication channel.",
"isCommentAutoGenerated" : true,
Expand Down Expand Up @@ -14582,6 +14585,9 @@
}
}
},
"Balanced — Prefer authenticated" : {
"comment" : "Balanced packet authenticity policy option."
},
Comment thread
jamesarich marked this conversation as resolved.
"Bandwidth" : {
"localizations" : {
"da" : {
Expand Down Expand Up @@ -22047,6 +22053,9 @@
}
}
},
"Compatible — Accept unsigned" : {
"comment" : "Compatible packet authenticity policy option."
},
"Complete Device Setup" : {
"localizations" : {
"de" : {
Expand Down Expand Up @@ -34631,6 +34640,12 @@
}
}
},
"Enable Strict" : {
"comment" : "Confirms enabling the Strict packet authenticity policy."
},
"Enable Strict authentication?" : {
"comment" : "Strict packet authenticity confirmation title."
},
"Enable TAK Server" : {
"localizations" : {
"de" : {
Expand Down Expand Up @@ -64794,6 +64809,9 @@
}
}
},
"Only show and process cryptographically authenticated mesh packets. Older nodes and oversized packets may disappear." : {
"comment" : "Description of the Strict packet authenticity policy."
},
"Open %@ documentation" : {
"localizations" : {
"de" : {
Expand Down Expand Up @@ -67040,6 +67058,7 @@
}
}
},
"Packet Authenticity" : {},
"Packet Count" : {
"localizations" : {
"de" : {
Expand Down Expand Up @@ -72192,6 +72211,7 @@
}
}
},
"Protection Level" : {},
"Provide Confirmation" : {
"localizations" : {
"de" : {
Expand Down Expand Up @@ -74939,6 +74959,9 @@
}
}
},
"Recommended. Reject unsigned downgrade attempts from nodes known to sign." : {
"comment" : "Description of the Balanced packet authenticity policy."
},
"Recorded trace route paths showing the hops a message took through the mesh to reach this node." : {
"localizations" : {
"de" : {
Expand Down Expand Up @@ -92612,6 +92635,12 @@
}
}
},
"Strict ignores every remote mesh packet that is not cryptographically authenticated. Older firmware, licensed or ham nodes without PKI keys, and oversized packets may disappear. PKI-authenticated direct messages remain available." : {
"comment" : "Warning shown before enabling the Strict packet authenticity policy."
},
"Strict — Require authentication" : {
"comment" : "Strict packet authenticity policy option."
},
"Strikethrough" : {
"comment" : "VoiceOver: strikethrough formatting toolbar button"
},
Expand Down Expand Up @@ -97924,6 +97953,9 @@
}
}
},
"This connected device does not support packet signature verification." : {
"comment" : "Summary shown when the connected radio lacks XEdDSA packet signature verification."
},
"This conversation will be deleted." : {
"localizations" : {
"da" : {
Expand Down Expand Up @@ -98100,6 +98132,12 @@
}
}
},
"This device has not reported whether it supports packet signature verification. Update its firmware to configure this setting." : {
"comment" : "Note shown when a radio has not reported its packet signature verification capability."
},
"This device reported a packet authenticity policy that this app version does not recognize." : {
"comment" : "Description shown for an unrecognized packet authenticity policy."
},
"This device will send out range test messages on the selected interval." : {
"localizations" : {
"da" : {
Expand Down Expand Up @@ -102774,6 +102812,9 @@
}
}
},
"Unknown policy" : {
"comment" : "Fallback title for an unrecognized packet authenticity policy."
},
"Unmessagable" : {
"localizations" : {
"de" : {
Expand Down
71 changes: 71 additions & 0 deletions Meshtastic/Extensions/Protobufs/Config+PacketSignaturePolicy.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
//
// Config+PacketSignaturePolicy.swift
// Meshtastic
//
// Display accessors for the firmware packet authenticity policy (design#121, protobufs#983).
//
// The labels and summaries are byte-identical to the Android/Desktop strings in
// `PacketAuthenticitySetting.kt` (Meshtastic-Android#6178) so the two clients describe the same
// device setting the same way.
//
// Terminology note: these say "authenticated"/"authentication" rather than "signed" because Strict
// accepts a packet authenticated *either* by a verified XEdDSA signature *or* by successful PKI
// decryption. "Signed" would wrongly imply authenticated PKI direct messages are rejected.
//

import Foundation
import MeshtasticProtobufs

extension Config.SecurityConfig.PacketSignaturePolicy {
/// The policies offered by the picker, weakest to strongest. `.UNRECOGNIZED` is never offered —
/// `pickerOptions(includingCurrent:)` appends it only when a device is already reporting it.
static let packetAuthenticityOptions: [Self] = [.compatible, .balanced, .strict]

/// Picker contents, guaranteed to contain `current`.
///
/// SwiftUI pickers render incorrectly when the bound selection has no matching tag, so a device
/// on newer firmware reporting a policy this app version does not know must still be listed.
static func pickerOptions(includingCurrent current: Self) -> [Self] {
packetAuthenticityOptions.contains(current)
? packetAuthenticityOptions
: packetAuthenticityOptions + [current]
}

var packetAuthenticityTitle: String {
switch self {
case .compatible:
return String(localized: "Compatible — Accept unsigned", comment: "Compatible packet authenticity policy option.")
case .balanced:
return String(localized: "Balanced — Prefer authenticated", comment: "Balanced packet authenticity policy option.")
case .strict:
return String(localized: "Strict — Require authentication", comment: "Strict packet authenticity policy option.")
case .UNRECOGNIZED:
return String(localized: "Unknown policy", comment: "Fallback title for an unrecognized packet authenticity policy.")
}
}

var packetAuthenticityDescription: String {
switch self {
case .compatible:
return String(
localized: "Authenticate packets when possible, but accept unsigned traffic for maximum compatibility.",
comment: "Description of the Compatible packet authenticity policy."
)
case .balanced:
return String(
localized: "Recommended. Reject unsigned downgrade attempts from nodes known to sign.",
comment: "Description of the Balanced packet authenticity policy."
)
case .strict:
return String(
localized: "Only show and process cryptographically authenticated mesh packets. Older nodes and oversized packets may disappear.",
comment: "Description of the Strict packet authenticity policy."
)
case .UNRECOGNIZED:
return String(
localized: "This device reported a packet authenticity policy that this app version does not recognize.",
comment: "Description shown for an unrecognized packet authenticity policy."
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import SwiftData
import MeshtasticProtobufs

extension SecurityConfigEntity {
/// The persisted packet authenticity policy decoded back into its protobuf enum.
///
/// `init?(rawValue:)` maps values this app version does not know to `.UNRECOGNIZED` rather than
/// `nil`, so a policy set by newer firmware survives a read/write round trip instead of being
/// silently reset to Compatible.
var storedPacketSignaturePolicy: Config.SecurityConfig.PacketSignaturePolicy {
Config.SecurityConfig.PacketSignaturePolicy(rawValue: Int(packetSignaturePolicy)) ?? .compatible
}
}
3 changes: 3 additions & 0 deletions Meshtastic/Model/ConfigModels.swift
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,9 @@ final class SecurityConfigEntity {
var bluetoothLoggingEnabled: Bool = false
var debugLogApiEnabled: Bool = false
var isManaged: Bool = false
/// Raw value of `Config.SecurityConfig.PacketSignaturePolicy`. 0 (Compatible) is the
/// protobuf default, so an absent field and an unconfigured entity agree.
var packetSignaturePolicy: Int32 = 0
var privateKey: Data?
var publicKey: Data?
var serialEnabled: Bool = false
Expand Down
4 changes: 4 additions & 0 deletions Meshtastic/Model/DeviceMetadataEntity.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ final class DeviceMetadataEntity {
var hasBluetooth: Bool = false
var hasEthernet: Bool = false
var hasWifi: Bool = false
/// Whether this firmware build includes XEdDSA packet signature verification.
/// Read-only capability: gates the Packet Authenticity policy selector in Security config.
var hasXeddsa: Bool = false
var hwModel: String?
var positionFlags: Int32 = 0
var role: Int32 = 0
Expand All @@ -34,6 +37,7 @@ final class DeviceMetadataEntity {
hasWifi = metadata.hasWifi_p
hasBluetooth = metadata.hasBluetooth_p
hasEthernet = metadata.hasEthernet_p
hasXeddsa = metadata.hasXeddsa_p
role = Int32(metadata.role.rawValue)
positionFlags = Int32(truncatingIfNeeded: metadata.positionFlags)
excludedModules = Int32(truncatingIfNeeded: metadata.excludedModules)
Expand Down
1 change: 1 addition & 0 deletions Meshtastic/Persistence/NodeBackupManager+Import.swift
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ extension NodeBackupManager {
dst.hasBluetooth = src.hasBluetooth
dst.hasEthernet = src.hasEthernet
dst.hasWifi = src.hasWifi
dst.hasXeddsa = src.hasXeddsa
dst.hwModel = src.hwModel
dst.positionFlags = src.positionFlags
dst.role = src.role
Expand Down
2 changes: 2 additions & 0 deletions Meshtastic/Persistence/UpdateSwiftData.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1160,6 +1160,7 @@ extension MeshPackets {
newSecurityConfig.serialEnabled = config.serialEnabled
newSecurityConfig.debugLogApiEnabled = config.debugLogApiEnabled
newSecurityConfig.adminChannelEnabled = config.adminChannelEnabled
newSecurityConfig.packetSignaturePolicy = Int32(config.packetSignaturePolicy.rawValue)
fetchedNode[0].securityConfig = newSecurityConfig
} else {
fetchedNode[0].securityConfig?.publicKey = config.publicKey
Expand All @@ -1177,6 +1178,7 @@ extension MeshPackets {
fetchedNode[0].securityConfig?.serialEnabled = config.serialEnabled
fetchedNode[0].securityConfig?.debugLogApiEnabled = config.debugLogApiEnabled
fetchedNode[0].securityConfig?.adminChannelEnabled = config.adminChannelEnabled
fetchedNode[0].securityConfig?.packetSignaturePolicy = Int32(config.packetSignaturePolicy.rawValue)
}
if sessionPasskey?.count != 0 {
fetchedNode[0].sessionPasskey = sessionPasskey
Expand Down
6 changes: 5 additions & 1 deletion Meshtastic/Resources/docs/developer/swiftdata.html
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,11 @@ <h3><code>EventFirmwareEntity</code> — off-device event branding cache</h3>
required no new <code>VersionedSchema</code>/<code>MigrationStage</code> (see below).</p>
<h2>Schema Migrations</h2>
<p>When you add, rename, or remove properties on a <code>@Model</code> type, you must provide a migration. Schema files live in <code>Meshtastic/Model/Schema/</code>.</p>
<div class="tips-callout"><strong>Note — V1 is unreleased.</strong> While <code>MeshtasticSchemaV1</code> remains the initial, unshipped version, additive <code>@Model</code> changes go <strong>directly into V1</strong> rather than a new versioned schema + stage (see the comment in <code>MeshtasticMigrationPlan.swift</code>). For example, the air-quality particulate-matter fields on <code>TelemetryEntity</code> (<code>pm10/25/100Standard</code>, <code>pm10/25/100Environmental</code>) were added in place. Start adding <code>VersionedSchema</code> versions and migration stages only once V1 has shipped.</div>
<div class="tips-callout"><strong>Note — V1 is unreleased.</strong> While <code>MeshtasticSchemaV1</code> remains the initial, unshipped version, additive <code>@Model</code> changes go <strong>directly into V1</strong> rather than a new versioned schema + stage (see the comment in <code>MeshtasticMigrationPlan.swift</code>). For example, the air-quality particulate-matter fields on <code>TelemetryEntity</code> (<code>pm10/25/100Standard</code>, <code>pm10/25/100Environmental</code>) were added in place, as were <code>SecurityConfigEntity.packetSignaturePolicy</code> and <code>DeviceMetadataEntity.hasXeddsa</code> for the packet authenticity policy. Start adding <code>VersionedSchema</code> versions and migration stages only once V1 has shipped.</div>
<p>Give an added property a default that matches what an absent value means on the wire, so a row
written before the property existed and a device that never reported it agree. <code>packetSignaturePolicy</code>
defaults to <code>0</code> because <code>PACKET_SIGNATURE_POLICY_COMPATIBLE</code> is the protobuf zero value, so an
unconfigured row is not silently treated as running a stricter receive policy than the radio is.</p>
<h3>Adding a New Schema Version</h3>
<ol>
<li>Create <code>Meshtastic/Model/Schema/MeshtasticSchemaV2.swift</code> with the updated models:</li>
Expand Down
1 change: 1 addition & 0 deletions Meshtastic/Resources/docs/developer/whats-new.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ <h1>What's New</h1>
**Month YYYY** — [Page or area](relative/path.md) — One sentence on what changed architecturally or procedurally.
Show roughly the last 12 months of changes; archive entries older than a year by removing them.
-->
<p><strong>Jul 2026</strong> — <a href="swiftdata.html">SwiftData</a> — Protobufs bumped to <code>b6ad0e5</code> (protobufs#983) for the packet authenticity policy: <code>Config.SecurityConfig.packet_signature_policy</code> (field 9, <code>COMPATIBLE</code>/<code>BALANCED</code>/<code>STRICT</code>) and the read-only <code>DeviceMetadata.has_xeddsa</code> capability (field 14). Both map to additive V1 properties — <code>SecurityConfigEntity.packetSignaturePolicy</code> and <code>DeviceMetadataEntity.hasXeddsa</code> — so no new <code>VersionedSchema</code>/<code>MigrationStage</code> was required. Also additive from the same bump: <code>interdevice</code> SD-card commands, the <code>HM330X</code> telemetry sensor, the <code>FAB</code> firmware edition, and an EU_868 deprecation comment. Display accessors for the policy live in <code>Extensions/Protobufs/Config+PacketSignaturePolicy.swift</code>, the first file in that directory.</p>
<p><strong>Jul 2026</strong> — <a href="codebase.html">Codebase Guide</a> — <code>Meshtastic.xcodeproj</code> is now generated from <code>project.yml</code> via XcodeGen instead of being hand-maintained; most source directories (including <code>Meshtastic/Views/</code>) are Xcode 16 synchronized folders, so new files no longer need any project-file editing. A CI check (<code>xcodegen-drift.yml</code>) regenerates the project on every PR and fails if the committed project drifts from <code>project.yml</code>.</p>
<p><strong>Jul 2026</strong> — <a href="architecture.html">Architecture</a> — Protobufs resynced to upstream <code>master</code> (v2.7.26-97-g9d589c1): added the <code>MEDIUM_TURBO</code> 500 kHz modem preset (wired through <code>ModemPresets</code> and its bandwidth/SNR/label switches, gated to 2.8+ firmware via <code>requiresFirmware2_8</code>), the <code>LORA_OTA_APP</code> PortNum (logged in the inbound packet dispatch), the <code>MESHBEACON_CONFIG</code> admin module-config type, the <code>SPA06</code> telemetry sensor, <code>DeviceProfile.is_unmessagable</code>/<code>is_licensed</code> fields, the Heltec <code>RC32</code>/<code>RC52</code>/<code>RCC6</code> hardware models, and the <code>TRACKER_T1000_E_PRO</code> → <code>MESH_TRACKER_X1</code> hardware-model rename. Everything else is additive; no entity-mapping or migration changes were required.</p>
<p><strong>Jun 2026</strong> — <a href="lora-region-presets.html">LoRa Region Presets</a> — New page documenting the 2.8 <code>FromRadio.region_presets</code> map: decode/flatten into <code>RegionPresetInfo</code>, storage on <code>AccessoryManager</code>, the <code>selectable(supports2_8:)</code> firmware gating, and the LoRa config UI integration.</p>
Expand Down
Loading
Loading