meshtastic 0.0.186 → 0.0.188

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +2 -2
  3. data/documentation/README.md +2 -1
  4. data/documentation/admin-backup.md +82 -3
  5. data/documentation/admin-config.md +28 -1
  6. data/documentation/admin-firmware-migration.md +171 -0
  7. data/documentation/admin-firmware.md +7 -3
  8. data/documentation/admin.md +16 -0
  9. data/documentation/mesh-interface.md +46 -18
  10. data/documentation/mqtt.md +63 -71
  11. data/documentation/waypoint.md +54 -0
  12. data/lib/meshtastic/admin/backup.rb +44 -5
  13. data/lib/meshtastic/admin/channel.rb +3 -8
  14. data/lib/meshtastic/admin/config.rb +63 -1
  15. data/lib/meshtastic/admin/firmware.rb +43 -10
  16. data/lib/meshtastic/admin/validation.rb +78 -0
  17. data/lib/meshtastic/admin.rb +28 -22
  18. data/lib/meshtastic/config_pb.rb +1 -1
  19. data/lib/meshtastic/mesh_interface.rb +123 -43
  20. data/lib/meshtastic/mesh_pb.rb +2 -1
  21. data/lib/meshtastic/module_config.rb +2 -0
  22. data/lib/meshtastic/module_config_pb.rb +1 -1
  23. data/lib/meshtastic/mqtt.rb +21 -12
  24. data/lib/meshtastic/version.rb +1 -1
  25. data/lib/meshtastic/waypoint.rb +85 -0
  26. data/lib/meshtastic.rb +1 -0
  27. data/spec/lib/meshtastic/admin/backup_spec.rb +153 -9
  28. data/spec/lib/meshtastic/admin/config_spec.rb +70 -1
  29. data/spec/lib/meshtastic/admin/firmware_migration_spec.rb +121 -0
  30. data/spec/lib/meshtastic/admin/firmware_spec.rb +1 -1
  31. data/spec/lib/meshtastic/admin/firmware_target_selection_spec.rb +44 -0
  32. data/spec/lib/meshtastic/admin/validation_spec.rb +89 -0
  33. data/spec/lib/meshtastic/admin_spec.rb +77 -0
  34. data/spec/lib/meshtastic/bluetooth_spec.rb +26 -1
  35. data/spec/lib/meshtastic/mesh_interface_spec.rb +54 -0
  36. data/spec/lib/meshtastic/module_config_spec.rb +1 -1
  37. data/spec/lib/meshtastic/mqtt_spec.rb +165 -0
  38. data/spec/lib/meshtastic/serial_spec.rb +26 -0
  39. data/spec/lib/meshtastic/tcp_spec.rb +29 -0
  40. data/spec/lib/meshtastic/waypoint_spec.rb +55 -0
  41. data/spec/support/channel_crypto_reference.rb +29 -0
  42. data/spec/support/firmware_release_artifact_audit.rb +39 -0
  43. metadata +16 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3e07fc03dd4ac92690b8209db83c3878cedaeeccae799f64d5a66ebcd77c661f
4
- data.tar.gz: b084165dc768b1446b4977a1ab8be30657773b2549c6ef42f699ecb336516890
3
+ metadata.gz: 4acf199373bf9ae044384b1b735b8ce1706716882129fd59feedeeec6f203ece
4
+ data.tar.gz: 7586ab8345b4125963358eeea95df60279221ca8546ab7864ec6d2624db46727
5
5
  SHA512:
6
- metadata.gz: 981f4b515e1820458ddfa5655aeb9d568e72fa92e05b7458b9069dc2b3baeb6bd7ab6c7a61c91e6d74fdd6f34e77c3aa4ae25f8b47c4f202667a8d0aaa147bc3
7
- data.tar.gz: 54c9de46786ccf0052ede5e441cc526a3294eabb098a8c717d477edd46cf28a47d4e4cfa67349a97f067780ff7a056e3d80a6be598495754d967a69729b49135
6
+ metadata.gz: ec0d592b650fe5695c3c364ec571232710c5558375c362f03d41ec2ba57e133b461d2806a144fdc3dd15bb21f9e8028199e279debfe587199a2ca0e8d10c22b6
7
+ data.tar.gz: '097ee4ded46e2d30fc56cf5d4e04324d2b2d38432e4b62d7c6af7105c4f37c602f302c1d50141583600065e65f5986f6c4b81376138a18187433ab08ef3a4e25'
data/Gemfile CHANGED
@@ -6,7 +6,7 @@ source 'https://rubygems.org'
6
6
 
7
7
  gemspec
8
8
 
9
- gem 'bundler', '>=4.0.21'
9
+ gem 'bundler', '>=4.0.22'
10
10
  gem 'bundler-audit', '>=0.9.3'
11
11
  gem 'executable-hooks', '1.7.1'
12
12
  gem 'fiddle', '1.1.8'
@@ -18,7 +18,7 @@ gem 'mqtt', '0.7.0'
18
18
  gem 'rake', '13.4.2'
19
19
  gem 'rdoc', '7.0.4'
20
20
  gem 'rspec', '3.13.2'
21
- gem 'rubocop', '1.91.0'
21
+ gem 'rubocop', '1.92.0'
22
22
  gem 'rubocop-rake', '0.7.1'
23
23
  gem 'rubocop-rspec', '3.10.2'
24
24
  gem 'ruby-dbus', '0.25.0'
@@ -40,6 +40,7 @@ Do not open Serial and Bluetooth to the same radio at once. Always `disconnect`
40
40
  - [Nordic legacy BLE DFU packages](admin-firmware-nordic.md)
41
41
  - [Meshtastic::ModuleConfig](module-config.md)
42
42
  - [Meshtastic::Position](position.md)
43
+ - [Meshtastic::Waypoint](waypoint.md) — circle/box geofences and notification fields
43
44
  - [Meshtastic::Telemetry](telemetry.md)
44
45
  - [Meshtastic::Traceroute](traceroute.md)
45
46
  - [Meshtastic::RemoteHardware](remote-hardware.md)
@@ -67,4 +68,4 @@ Do not open Serial and Bluetooth to the same radio at once. Always `disconnect`
67
68
  - [Meshtastic::Util](util.md)
68
69
  - [Generated protobuf types](protobufs.md)
69
70
 
70
- Protobuf message classes that this gem reopens (`Channel`, `Config`, `Position`, `Telemetry`, `ModuleConfig`, `Paxcount`) must not define class methods named `send` or `encode` (those belong to protobuf). Use `get`/`set`, `transmit`, `build`, or `request` instead.
71
+ Protobuf message classes that this gem reopens (`Channel`, `Config`, `Position`, `Waypoint`, `Telemetry`, `ModuleConfig`, `Paxcount`) must not define class methods named `send` or `encode` (those belong to protobuf). Use `get`/`set`, `transmit`, `build`, or `request` instead.
@@ -47,7 +47,7 @@ JSON selection defaults (binary-specific differences are listed below):
47
47
  - `include_owner: true`: only `long_name`, `short_name`, `is_licensed`.
48
48
  Never clone User ID, MAC, hardware model, public key, role or messaging
49
49
  capability metadata. Role belongs to device configuration; security keys
50
- belong to explicitly selected security configuration.
50
+ belong to security configuration (included in the default selection).
51
51
  - `config_types:` defaults to `DEVICE_CONFIG`, `POSITION_CONFIG`, `POWER_CONFIG`,
52
52
  `NETWORK_CONFIG`, `DISPLAY_CONFIG`, `LORA_CONFIG`, `BLUETOOTH_CONFIG`,
53
53
  `SECURITY_CONFIG`. Use a narrower list for firmware that lacks sections.
@@ -75,18 +75,94 @@ resolved or pinned. A disk write/sync failure raises and can leave a partial
75
75
  0600 file; it is never reported as a successful backup. File mode protection is
76
76
  not encryption and does not protect against privileged host access.
77
77
 
78
+ ## Restore safety policy (firmware v2.8.1.8e6a88d)
79
+
80
+ Validation is pinned to firmware commit
81
+ [`8e6a88d06f44cad26f1e8d7cd402939ccacb9b4c`](https://github.com/meshtastic/firmware/blob/8e6a88d06f44cad26f1e8d7cd402939ccacb9b4c/src/modules/AdminModule.cpp)
82
+ and protobuf commit `8f97d66a63ce10cfb12f94203e361691647f5ad3`.
83
+ The same semantic validator runs for Admin setters, JSON backups and binary
84
+ profiles before session acquisition or writes. It rejects rather than truncates:
85
+
86
+ - Owner long names over **24 UTF-8 bytes**, short names over four bytes, embedded
87
+ NULs and nonempty ASCII-whitespace-only names. Empty owner names mean unchanged
88
+ on this firmware, not clearing. The User license flag has no presence: an
89
+ omitted flag in an owner write is false, not a read/merge patch.
90
+ - Ham callsigns must be nonblank and at most seven bytes; the optional suffix is
91
+ at most fourteen bytes and short name at most four. The composed `CALL//suffix`
92
+ is checked against 24 bytes. Blank nonempty optional names are rejected rather
93
+ than silently ignored.
94
+ - Unknown enum values, including numeric values accepted by protobuf; channel
95
+ indexes outside 0–7, unknown roles, names of twelve or more bytes/NUL, and PSKs
96
+ other than 0, 1, 16 or 32 raw bytes. PRIMARY is allowed at any valid slot for
97
+ direct/JSON writes; binary URL ordering still requires primary slot zero.
98
+ - Config/ModuleConfig without exactly one selected section. A selected empty
99
+ section such as `device: {}` remains valid and means protobuf defaults.
100
+ Sessionkey is request-only and UI requires the dedicated store operation.
101
+ - Security keys other than zero or 32 bytes, or more than three admin keys.
102
+ Size validation does **not** authenticate a keypair or prove public/private
103
+ correspondence; firmware derives/normalizes identity state.
104
+
105
+ **Compatibility change:** `identity_keys: :reject` is the import default. Any
106
+ nonempty `security.private_key` **or** `security.public_key` causes rejection of
107
+ an entire restore before radio I/O. Use `identity_keys: :restore` only when
108
+ intentionally cloning/restoring that identity. It sends the exact input; there
109
+ is no silent stripping, rewriting or `:preserve` mode. To leave security entirely
110
+ untouched, remove the complete security record offline or omit it from export.
111
+ Excluding the owner record/owner ID does **not** exclude security identity keys;
112
+ SECURITY_CONFIG is part of the default export selection. Admin authorization
113
+ keys are secret-bearing configuration too, but are not the node identity keypair.
114
+
115
+ **Privacy opt-in:** `allow_privacy_changes: false` is the import default. Set it
116
+ to `true` explicitly for any PositionConfig (even an empty/default section), fixed
117
+ position, positive channel position precision, channel uplink, enabled MQTT,
118
+ MQTT map reporting, or enabled telemetry broadcasts. In both JSON and binary
119
+ profiles, any true TelemetryConfig flag below requires consent:
120
+
121
+ - `device_telemetry_enabled`
122
+ - `environment_measurement_enabled`
123
+ - `air_quality_enabled`
124
+ - `power_measurement_enabled`
125
+ - `health_measurement_enabled`
126
+
127
+ These flags match the pinned protobuf revision above and current generated
128
+ descriptors. An empty/default telemetry section, false broadcast flags, nonzero
129
+ intervals alone, and on-device screen/Fahrenheit preferences do not require
130
+ telemetry consent. Disabling device mesh telemetry does not disable metrics sent
131
+ to a connected client. This is a conservative restore guard, not a complete
132
+ privacy assessment of every firmware module. It does not fetch existing state to infer
133
+ whether a setting is already enabled. No values are silently disabled or
134
+ redacted. The report warns that these settings may disclose location/traffic or
135
+ device, environment, air quality, power and health metrics, without printing values,
136
+ and firmware may clamp public-channel position precision. Both consent options
137
+ are required for relevant **dry runs too**; neither changes the backup bytes.
138
+ Direct Admin setters are explicit low-level writes and do not use these backup
139
+ consent flags.
140
+
141
+ Absent security private keys preserve an existing firmware keypair; they do not
142
+ request an identity reset. Owner/license, LoRa region and security changes can
143
+ migrate identity and change the node number; network/Bluetooth changes can break
144
+ the connection. Reports expose `reconnect_required` conservatively for these
145
+ sections and warn the caller to reconnect and rediscover the target. The importer
146
+ does **not** reconnect, infer a new target, replay writes, or claim persisted
147
+ state. Optional readback still uses the original connection/target and can fail
148
+ after an identity change. `persistence_verified` always remains false. Verify
149
+ all selected sections on a newly established connection before claiming durable
150
+ restoration. Hardware/reconnect verification is outside this implementation.
151
+
78
152
  ## Validate, plan and import
79
153
 
80
154
  ```ruby
81
155
  plan = Meshtastic::Admin::Backup.import(
82
156
  transport_obj: connection,
83
157
  path: '/private/directory/node-backup.json',
158
+ allow_privacy_changes: true, # reviewed location/sharing restoration
84
159
  dry_run: true
85
160
  )
86
161
 
87
162
  result = Meshtastic::Admin::Backup.import(
88
163
  transport_obj: connection,
89
164
  backup: snapshot[:backup], # use exactly one of backup: or path:
165
+ allow_privacy_changes: true, # explicitly restore reviewed location/sharing
90
166
  edit_transaction: true, # ONLY when target firmware supports it
91
167
  verify: true,
92
168
  timeout: 10
@@ -160,7 +236,8 @@ snapshot = Meshtastic::Admin::Backup.export(
160
236
  snapshot.slice(:status, :format, :count)
161
237
  # snapshot[:backup] is a raw ASCII-8BIT String, not JSON/base64 or a protobuf object.
162
238
  # Pass it directly to Backup.import(transport_obj: connection,
163
- # backup: snapshot[:backup], dry_run: true).
239
+ # backup: snapshot[:backup], dry_run: true,
240
+ # allow_privacy_changes: true).
164
241
  ```
165
242
 
166
243
  Binary returns `{ status: :exported, format: :device_profile, count: Integer,
@@ -216,6 +293,7 @@ plan = Meshtastic::Admin::Backup.import(
216
293
  transport_obj: connection,
217
294
  path: '/private/directory/nodeConfig.cfg',
218
295
  format: :device_profile,
296
+ allow_privacy_changes: true, # if restoring reviewed position/sharing sections
219
297
  dry_run: true
220
298
  )
221
299
  # Only section/slot identities and counts are returned; no profile contents.
@@ -226,6 +304,7 @@ plan = Meshtastic::Admin::Backup.import(
226
304
  transport_obj: connection,
227
305
  backup: binary_profile_string,
228
306
  format: :device_profile,
307
+ allow_privacy_changes: true, # if restoring reviewed position/sharing sections
229
308
  dry_run: true
230
309
  )
231
310
  ```
@@ -291,7 +370,7 @@ no-replay, secret-handling and persistence limitations apply.
291
370
  ACK, sends each section once with a correlated ACK wait (or matching timeout
292
371
  readback as described below), then sends
293
372
  `commit_edit_settings` and waits again. Current upstream
294
- [AdminModule.cpp](https://github.com/meshtastic/firmware/blob/master/src/modules/AdminModule.cpp)
373
+ [AdminModule.cpp](https://github.com/meshtastic/firmware/blob/8e6a88d06f44cad26f1e8d7cd402939ccacb9b4c/src/modules/AdminModule.cpp)
295
374
  implements a begin flag and commit save of configuration/module/device/channel/
296
375
  node database segments. Commit also disables Bluetooth. **This is not an atomic
297
376
  rollback facility**: firmware may apply values before commit, UI has its own
@@ -29,7 +29,7 @@ ModuleConfig is a different protobuf: use `Admin.get_module_config` / `Admin.set
29
29
 
30
30
  Read/write helpers pass through Admin options, including `transport_obj: connection`, `to`, `from`, numeric transport `channel`, `want_ack`, `want_response`, `hop_limit`, and `session_passkey`. Use a supported connected transport. Session authentication and remote-node routing follow [Admin](admin.md).
31
31
 
32
- A return value means transport submission, **not confirmed persistence**. These methods inherit Admin's automatic remote session-key acquisition, but do not wait for write acknowledgments or read settings back. Obtain the prior configuration, edit it, write, and request it again to confirm; configuration writes replace a whole section rather than patching only non-default fields. Omitting a field in a Hash can reset that setting to its protobuf default. Firmware version and hardware determine which fields are applied, and writes may reboot/disconnect the node.
32
+ For the section getters/setters above, a return value means transport submission, **not confirmed persistence**. These methods inherit Admin's automatic remote session-key acquisition, but do not wait for write acknowledgments or read settings back. Obtain the prior configuration, edit it, write, and request it again to confirm; configuration writes replace a whole section rather than patching only non-default fields. Omitting a field in a Hash can reset that setting to its protobuf default. Firmware version and hardware determine which fields are applied, and writes may reboot/disconnect the node.
33
33
 
34
34
  ## Example
35
35
 
@@ -50,6 +50,33 @@ Meshtastic::Admin::Config.set_device_ui(
50
50
 
51
51
  Ruby protobuf's `display` reader collides with `Object#display`; inspect `config['display']`, not `config.display`. Do not log configuration objects: network/security settings and session passkeys can contain secrets.
52
52
 
53
+ ## Signature policy and receive-only read/modify/readback
54
+
55
+ Two helpers perform **fresh get → single set → fresh get**, rather than replacing a whole section with defaults:
56
+
57
+ ```ruby
58
+ result = Meshtastic::Admin::Config.set_packet_signature_policy(
59
+ transport_obj: connection,
60
+ policy: :PACKET_SIGNATURE_POLICY_BALANCED,
61
+ timeout: 10
62
+ )
63
+
64
+ result = Meshtastic::Admin::Config.set_receive_only(
65
+ transport_obj: connection,
66
+ enabled: true, # writes LoRaConfig.tx_enabled = false; false re-enables TX
67
+ timeout: 10
68
+ )
69
+ ```
70
+
71
+ - `policy:` accepts the known `Config::SecurityConfig::PacketSignaturePolicy` symbols (`PACKET_SIGNATURE_POLICY_COMPATIBLE`, `PACKET_SIGNATURE_POLICY_BALANCED`, `PACKET_SIGNATURE_POLICY_STRICT`) or their known integer values. Unknown enums fail before any request. Compatible accepts unsigned traffic, balanced rejects unsigned signable broadcasts from previously signing nodes, strict requires authenticated traffic; strict can exclude older nodes.
72
+ - Both require a radio receive queue; MQTT is unsupported. Receive-only is deliberately restricted to the **connected local node**: disabling a remote node's radio TX would prevent reliable remote readback and recovery. This is LoRa TX disablement, not a new firmware role or a host-side packet filter.
73
+ - Current section fields are copied and only the requested field is changed. Security readback may contain private keys locally; do not log the returned data. Pinned firmware redacts remote private-key readback and preserves the existing identity keypair on a key-omitting security write.
74
+ - The return value contains `acknowledgment:` (the setter's Admin result), `readback:` (the final correlated Admin result), `matched:` (whether the requested field matches), and `reboot_requested: false`. A firmware build without signature support may normalize the policy to compatible; `matched: false` exposes that instead of claiming success. Inspect the returned section for other firmware-normalized changes.
75
+ - Helpers always wait and generate a fresh ID for each request, irrespective of supplied `wait`/`request_id`. `timeout` applies to each request. Pause other queue consumers throughout the sequence; do not run concurrent configuration editors.
76
+ - No explicit reboot, implicit commit, retry, or mutation replay is issued. **Firmware itself can reboot or disable BLE on a section write**, so a timeout/disconnect is not evidence of rollback. Reconnect and issue `Admin.request(get_config_request: :SECURITY_CONFIG)` or `:LORA_CONFIG` before deciding what to do next. `matched` is current readback, not post-reboot persistence or host cryptographic verification. An already-open firmware edit transaction remains the caller's responsibility.
77
+
78
+ These helpers are pinned to **v2.8.1.8e6a88d**: [config.proto](https://github.com/meshtastic/protobufs/blob/8f97d66a63ce10cfb12f94203e361691647f5ad3/meshtastic/config.proto) and [AdminModule.cpp](https://github.com/meshtastic/firmware/blob/8e6a88d06f44cad26f1e8d7cd402939ccacb9b4c/src/modules/AdminModule.cpp). Offline framing/readback tests establish host behavior, not live firmware application.
79
+
53
80
  ## Protocol evidence
54
81
 
55
82
  - [Official Config schema](https://github.com/meshtastic/protobufs/blob/master/meshtastic/config.proto): all ten variants and request-only SessionkeyConfig.
@@ -0,0 +1,171 @@
1
+ # Firmware v2.8.1 migration and installer matrix
2
+
3
+ ## Exact release boundary
4
+
5
+ This delivery targets **`v2.8.1.8e6a88d`**, firmware commit
6
+ `8e6a88d06f44cad26f1e8d7cd402939ccacb9b4c`. The expected PhoneAPI version is
7
+ **`2.8.1.8e6a88d`** (no leading `v`). The firmware's actual protobuf submodule
8
+ pin is **`8f97d66a63ce10cfb12f94203e361691647f5ad3`**, not whichever protobuf
9
+ revision happens to be current on `develop`.
10
+
11
+ Sources: [exact release and migration warnings](https://github.com/meshtastic/firmware/releases/tag/v2.8.1.8e6a88d),
12
+ [firmware protobuf gitlink](https://github.com/meshtastic/firmware/tree/8e6a88d06f44cad26f1e8d7cd402939ccacb9b4c/protobufs),
13
+ [pinned protobuf definitions](https://github.com/meshtastic/protobufs/tree/8f97d66a63ce10cfb12f94203e361691647f5ad3).
14
+
15
+ **2.8.0 was revoked** for a packet-signing defect; its signatures are not
16
+ compatible with 2.8.1. Do not substitute 2.8.0, a bare `2.8.1`, a different
17
+ build suffix, or cached metadata for this release. `verify_reboot` is a generic
18
+ exact-string verifier, not a firmware downloader/revocation service: the caller
19
+ must pin `expected_version: '2.8.1.8e6a88d'` rather than pass an untrusted value
20
+ reported by the device itself.
21
+
22
+ ## Implemented installer versus board support
23
+
24
+ A board/chip label is not sufficient authorization or compatibility evidence.
25
+ Select the actual board variant, image type, installed loader and physical
26
+ endpoint. There is no automatic board selection, fallback or universal flasher.
27
+ All rows below are **hardware-unverified**. Parser acceptance does not prove that
28
+ a firmware image is safe for a specific radio, partition layout or bootloader.
29
+
30
+ | Chip / representative board | Artifact | Implemented loader / data transport | Exact-release scope and restrictions |
31
+ | --- | --- | --- | --- |
32
+ | ESP32 (e.g. T-Beam), ESP32-S3 (e.g. Heltec V3) | Matching application BIN | `:esp_rom`, USB/UART ROM | Explicit `chip`, `port`, application `offset >= 0x10000`, flash capacity; identify chip/security before erase; flash MD5 verification. No factory/merged images, stub, secure/encrypted targets or full erase. These two release artifacts were not sampled in this audit. |
33
+ | ESP32 / ESP32-S3 with compatible unified loader already installed | Matching application-update BIN | `:unified_wifi` TCP 3232 or `:unified_ble` custom GATT | Separate preparation via `request_ota`, NVS hash pin and final loader `OK`. Not PhoneAPI TCP 4403/BLE. No automatic loader installation or image/board compatibility inference. |
34
+ | ESP32-C3 / Heltec HT62 SX1262 | Application BIN | `:esp_rom`, USB/UART ROM | Exact-release application parser accepted; factory BIN deliberately rejected. Do not infer unified-loader C3 support from a release's `bleota-c3.bin`. |
35
+ | nRF52840 / RAK4631 | Intel HEX | `:swd`, trusted local OpenOCD executable and interface/target Tcl files | Exact-release parser accepted; explicit nRF52840 FICR/geometry checks before programming, `verify_image` and reset. No automatic recovery mass erase. |
36
+ | nRF52840 / RAK4631 | UF2 | `:uf2`, explicitly selected Linux mounted bootloader volume | Backend exists for application range `0x27000...0xf4000` and exact Board-ID/family; **this exact-release UF2 is rejected**, since it starts at `0x26000`. Do not trim blocks or relax bounds blindly. |
37
+ | nRF52840 / legacy Adafruit SDK11 DFU | Application-only legacy ZIP + DAT inside ZIP | `:nordic_dfu`, separate BLE DFU GATT | Backend exists, but **this release's RAK4631 OTA ZIP is rejected**: its manifest includes `dfu_version: 0.5`, while the current parser permits only the application member. This is a parser compatibility gap, not evidence the package uses Secure DFU. No conversion is attempted. |
38
+ | RP2040 / RAK11310 | UF2 | `:uf2`, explicitly selected Linux mounted bootloader volume | Exact-release parser accepted with family `0xe48bff56` and explicit 2 MiB capacity. Require selected volume's Board-ID and correct actual capacity. Copy/fsync is submission, not flash verification. |
39
+ | ESP32-C6 | Release BIN | **Unsupported** | Not in native ROM chip set; no claimed unified-loader support. |
40
+ | RP2350 (including Ethernet OTA boards) | Release UF2 / Ethernet OTA | **Unsupported** | RP2040 support does not imply RP2350 families or its OTA protocol. |
41
+ | STM32 / STM32WL | Release artifacts | **Unsupported** | nRF52840 OpenOCD support does not imply a generic STM32 installer. |
42
+ | Nordic Secure DFU, serial Nordic DFU | Secure/signed ZIP or other package | **Unsupported** | Not the implemented legacy Adafruit BLE protocol. |
43
+ | Any radio via MQTT or PhoneAPI XModem | Any image | **Unsupported image transport** | MQTT Admin preparation is distinct from upload; no synchronous MQTT reboot verifier. PhoneAPI XModem is filesystem transfer. |
44
+
45
+ Format/protocol and verifier option mismatches fail before an installer is
46
+ invoked. Backend-specific target/image validation occurs before opening a
47
+ programmer, GATT loader or destination where implemented. ROM/SWD hardware chip
48
+ checks necessarily occur after connecting but before erase. Unified OTA cannot
49
+ prove board/image compatibility: the operator must select it explicitly.
50
+
51
+ No backend offers a `full_erase:` option. The release recommends full erase and
52
+ flash **if** upgrading causes bootloops/crashes, after exporting configuration.
53
+ That recovery requires a separately authorized, board-specific tool/workflow;
54
+ this gem must not report a normal application-region write as a full erase.
55
+
56
+ ## Backup and migration consent
57
+
58
+ 1. Before a destructive operation, obtain explicit authorization for the board,
59
+ endpoint, selected release/artifact, preparation/reboot, and recovery method.
60
+ Capture the old node number and a **fresh local owner public key** over the
61
+ trusted connection; retain the raw key only in memory/private backup. Do not
62
+ print keys, profiles or complete Admin packets. Close competing subscribers.
63
+ 2. Export and privately store a [host backup](admin-backup.md), JSON or binary
64
+ DeviceProfile `.cfg`. A backup can contain private identity keys, channel PSKs,
65
+ credentials, coordinates and PINs. Restrict access; do not attach it to logs.
66
+ Excluding owner metadata does **not** exclude `security` identity keys.
67
+ 3. Review a restore offline/dry-run before sending. `identity_keys: :reject` is
68
+ the default; explicitly choose `identity_keys: :restore` only for an intended
69
+ identity restoration. Privacy-sensitive position, telemetry and MQTT-sharing
70
+ restores require separate `allow_privacy_changes: true`, including dry runs.
71
+ Consent is not implicit in possessing a backup. No keys/settings are silently
72
+ stripped to make a restore pass. A key replacement/full erase without key
73
+ restoration will not satisfy continuity with the original identity.
74
+ 4. Review the release changes: key-derived node IDs replace MAC-derived IDs;
75
+ owner long names are limited to 24 UTF-8 bytes plus NUL; position/telemetry
76
+ broadcasts are opt-in; precise position is restricted on known-key channels;
77
+ first-time US region selection on the device screen now defaults to
78
+ **LongTurbo**, not LongFast. Choose compatible region/preset deliberately.
79
+ Do not blindly enable old sharing settings or replay a 2.7 backup wholesale.
80
+ 5. After the authorized installation and any restore, reconnect and read back the
81
+ requested state. ACKs, loader checksums and UF2 copy success are distinct from
82
+ application boot health and persisted configuration. Keep interrupted writes
83
+ ambiguous; never automatically replay a mutation or raw firmware bytes.
84
+
85
+ ## Key-continuous post-reboot verification
86
+
87
+ `expected_node:` remains a **strict post-reboot numeric ID** check. Do not put the
88
+ old 2.7 ID there when a 2.8 key-derived ID transition is expected. Instead provide
89
+ a trusted pre-upgrade `expected_public_key:` (exactly 32 nonzero raw bytes) and
90
+ `previous_node:` (old numeric unicast ID for mapping/reporting):
91
+
92
+ ```ruby
93
+ result = Meshtastic::Admin::Firmware.verify_reboot(
94
+ transport: :serial,
95
+ connection: { block_dev: '/dev/ttyACM0', baud: 115_200 },
96
+ expected_version: '2.8.1.8e6a88d',
97
+ expected_public_key: trusted_pre_upgrade_public_key, # do not log this value
98
+ previous_node: old_node_number,
99
+ timeout: 90
100
+ )
101
+ # Use only selected report fields; never print the backup or request options.
102
+ puts "#{result[:previous_node_id]} -> #{result[:node_id]}"
103
+ ```
104
+
105
+ The same options work with explicitly selected application TCP or Bluetooth
106
+ connections, or as the `verify:` Hash on `install`. `previous_node` requires key
107
+ continuity; it is not a destination override. The verifier:
108
+
109
+ - reconnects with the selected transport and waits for configuration completion;
110
+ - fixes the connected local node as the target, then requests fresh **owner and
111
+ metadata** with independent request-ID/source correlation;
112
+ - compares the public key and exact firmware string, rejecting another key,
113
+ stale/wrong-source replies and a local ID change during verification;
114
+ - never scans or retries arbitrary mesh nodes to find an apparent match;
115
+ - reports `identity: :public_key_continuity`, `previous_node_num`, `node_num`,
116
+ fixed-width `previous_node_id` / `node_id`, and `node_changed`; it returns no
117
+ owner/key material and does not log keys or echo them in identity errors.
118
+
119
+ If both `expected_node` and `expected_public_key` are given, **both must match**.
120
+ An advanced `reconnect:` callback must return a newly connected handle of the
121
+ selected transport with configuration requested; freshness of that callback's
122
+ connection is the caller's responsibility. Cached handle metadata never suffices.
123
+ This is device-reported continuity on a trusted local connection, **not** a
124
+ cryptographic challenge, signed boot attestation or RF-delivery test. Reports do
125
+ not rewrite application destination maps: review and explicitly update them.
126
+
127
+ ## Executed exact-release parser audit (no hardware)
128
+
129
+ Official archive members were retrieved from the exact release using Ruby
130
+ `open-uri`, extracted in memory with Ruby `rubyzip`, then passed to the production
131
+ Ruby parsers only. No Python, flasher, serial port, BLE radio, programmer or mount
132
+ was opened. Samples were not modified to obtain a pass.
133
+
134
+ | Official member | Bytes | Production parser result |
135
+ | --- | ---: | --- |
136
+ | `firmware-heltec-ht62-esp32c3-sx1262-2.8.1.8e6a88d.bin` | 2119088 | Accepted: ESP image/chip/checksum/SHA |
137
+ | `firmware-heltec-ht62-esp32c3-sx1262-2.8.1.8e6a88d.factory.bin` | 2184624 | Rejected: merged/trailing data |
138
+ | `firmware-rak4631-2.8.1.8e6a88d.hex` | 1966833 | Accepted: nRF52840 HEX |
139
+ | `firmware-rak4631-2.8.1.8e6a88d.uf2` | 1398784 | Rejected: 16 blocks below supported application start |
140
+ | `firmware-rak4631-2.8.1.8e6a88d-ota.zip` | 700163 | Rejected: manifest shape (`dfu_version`) |
141
+ | `firmware-rak11310-2.8.1.8e6a88d.uf2` | 1611776 | Accepted: RP2040 UF2 |
142
+
143
+ Reproduce without hardware: retrieve `firmware-esp32c3-2.8.1.8e6a88d.zip`,
144
+ `firmware-nrf52840-2.8.1.8e6a88d.zip` and `firmware-rp2040-2.8.1.8e6a88d.zip`
145
+ from the linked release; extract only the six named members to a private local
146
+ directory, then run:
147
+
148
+ ```sh
149
+ bundle exec ruby -Ilib spec/support/firmware_release_artifact_audit.rb /path/to/extracted-members
150
+ ```
151
+
152
+ This opt-in audit pins each member's SHA-256, asserts the documented acceptance
153
+ **or rejection**, and prints a JSON report. It is separate from the default spec
154
+ suite and does not download artifacts itself. Rejections are real compatibility
155
+ gaps, not verified installers. Hashes identify retrieved bytes, not publisher
156
+ signatures. Firmware archives/binaries are not bundled in this repository.
157
+
158
+ `firmware_migration_spec.rb` uses production ToRadio/AdminMessage/FromRadio codecs
159
+ and synchronous Admin correlation over mocked Serial/BLE/TCP hardware writes.
160
+ It covers changed mapping, exact suffix/revoked version rejection, stale cached
161
+ metadata, unrelated source/request preservation, key mismatch, strict numeric
162
+ expectation, reconnect transport mismatch and preflash option validation.
163
+ Existing firmware specs additionally exercise a real loopback PhoneAPI TCP
164
+ configuration/metadata exchange. `firmware_target_selection_spec.rb` guards
165
+ unsupported chips/protocols, full-erase claims and preflash format selection.
166
+
167
+ **Delivery step 6 remains blocked** pending the user's endpoint selection and
168
+ explicit live-operation authorization. No hardware has been contacted, staged,
169
+ prepared, erased, flashed or verified by this step.
170
+
171
+ [Installer API and transport details](admin-firmware.md)
@@ -2,6 +2,8 @@
2
2
 
3
3
  **PhoneAPI XModem is not a firmware flashing protocol.** The old implementation incorrectly sent padded XModem data after an OTA admin request and could report completion without any device acknowledgement. That path is now rejected, including MQTT's former silent request-only `install`.
4
4
 
5
+ For the exact **v2.8.1.8e6a88d** release, see the [migration checklist and installer matrix](admin-firmware-migration.md): firmware/protobuf pins, public-key identity continuity, backup/privacy consent, actual release-parser results and unsupported board families. Parser/loader success is not hardware verification.
6
+
5
7
  ## Supported operations
6
8
 
7
9
  | Operation | What the Ruby implementation actually does |
@@ -120,8 +122,9 @@ result = Meshtastic::Admin::Firmware.install(
120
122
  verify: {
121
123
  transport: :bluetooth,
122
124
  connection: { address: 'AA:BB:CC:DD:EE:FF', adapter: 'hci0' }, # application address
123
- expected_version: '2.7.1.example', # exact version from your chosen release
124
- expected_node: 0xaabbccdd,
125
+ expected_version: '2.8.1.8e6a88d', # exact intended release, including suffix
126
+ expected_public_key: trusted_pre_upgrade_public_key, # raw 32 bytes, never log
127
+ previous_node: 0xaabbccdd, # report mapping; not a strict post-reboot ID check
125
128
  timeout: 90
126
129
  }
127
130
  )
@@ -138,7 +141,8 @@ For tests or an alternative Ruby GATT implementation, unified BLE accepts `backe
138
141
 
139
142
  - Required: `transport: :tcp | :bluetooth | :serial`, `expected_version:` (exact nonempty String).
140
143
  - Production default: `connection:` Hash for a **new** application connection, explicitly specifying `host`, `address`, or `block_dev`, respectively. TCP uses PhoneAPI port **4403**, not updater port 3232; set `connection[:port]` only for a custom PhoneAPI port. Do not supply an existing socket/handle.
141
- - Optional: `expected_node:` numeric node ID, `timeout:` whole-operation deadline (60 seconds), `reboot_delay:` initial wait (3 seconds).
144
+ - Optional: `expected_node:` strict post-reboot numeric node ID, `timeout:` whole-operation deadline (60 seconds), `reboot_delay:` initial wait (3 seconds).
145
+ - Migration identity: `expected_public_key:` trusted pre-upgrade raw 32-byte public key adds a fresh source/request-correlated local owner readback. `previous_node:` requires this key expectation and reports old/new fixed-width node IDs and `node_changed`; it does not retarget another node. Both an explicit `expected_node` and key expectation must match if supplied together. No keys are returned/logged; key comparison is device-reported continuity, not cryptographic attestation. See the [2.8.1 migration guide](admin-firmware-migration.md).
142
146
  - Optional advanced `reconnect:` callable receives `{transport:, connection:, timeout:}` and must return a newly connected handle of the selected transport, with configuration requested. The verifier still waits for configuration and issues a fresh Admin metadata request; a callback cannot substitute a cached metadata Hash. The returned handle is closed afterward.
143
147
  - Pre-metadata connection/configuration I/O failures retry every 0.25 seconds within the total deadline. Metadata errors/version mismatches do not cause a reflash or get converted to success.
144
148
  - A fresh source/request-ID-matched `get_device_metadata_response` is required. Cached `handle[:metadata]`, configuration metadata, loader VERSION, a routing ACK or successful port open cannot satisfy verification.
@@ -87,6 +87,22 @@ After reboot, reconnect, complete the transport handshake, and make this request
87
87
  - `request(wait: false, ...)` retains the old `{ request_id:, result: }` submission-only API, including MQTT. Use your existing receive loop with `response` in that mode. Automatic remote session acquisition can still wait unless an explicit key or `auto_session: false` is supplied.
88
88
  - IDs are generated or supplied with `request_id:` (2 through `0xffffffff`), overriding `last_packet_id`. Use fresh IDs; deliberately reusing one cannot distinguish a stale reply. ID 1 is excluded because transport predecessor zero requests a random ID.
89
89
 
90
+ ### Delivery evidence and trust boundary (v2.8.1.8e6a88d)
91
+
92
+ Synchronous `request` adds `delivery` and `security_provenance`:
93
+
94
+ | `delivery` | Meaning |
95
+ | --- | --- |
96
+ | `:acknowledged` | Target-correlated successful routing ACK without valid device-reported ACK proof. Includes absent, invalid, unknown, and no-key proof statuses. |
97
+ | `:device_verified` | Connected radio reports `ACK_PROOF_VALID` on the successful routing ACK matching the actual request ID **and addressed target**. This is a **device report**, not host cryptographic verification. |
98
+ | `:readback` | Expected Admin response matched request ID and target. This is not an ACK proof or a persistence check. |
99
+
100
+ `security_provenance` contains `source: :radio`, `verification: :device_reported`, and `reported: { ack_proof_status:, xeddsa_signed: }`. Raw enum values are retained, including unknown integers. A signature report **does not** substitute for ACK proof; invalid/no-key/absent proof must not be displayed as verified delivery. A local ACK cannot establish remote delivery. Even valid proof does not establish configuration persistence, application execution, or post-reboot health.
101
+
102
+ Trust is assigned only inside the synchronous receive path using the already-classified radio handle's queue. `response` on caller-provided bytes (even a `FromRadio` object) instead reports `source: :unknown, verification: :untrusted`; caller-supplied provenance flags cannot promote it. MQTT publication or broker-supplied signature/proof fields cannot produce `:device_verified`. `wait: false` remains submission-only and has no verified-delivery result. Do not inject external/broker traffic into a radio handle's private receive queue.
103
+
104
+ Pinned evidence: [ReliableRouter.cpp](https://github.com/meshtastic/firmware/blob/8e6a88d06f44cad26f1e8d7cd402939ccacb9b4c/src/mesh/ReliableRouter.cpp) and [mesh.proto](https://github.com/meshtastic/protobufs/blob/8f97d66a63ce10cfb12f94203e361691647f5ad3/meshtastic/mesh.proto), firmware tag **v2.8.1.8e6a88d**. ACK proof is advisory on that release; legacy/no-proof ACKs remain distinguishable rather than being silently upgraded or retried.
105
+
90
106
  ### Automatic sessions
91
107
 
92
108
  Remote state-changing `send` calls (including convenience setters, reboot, and firmware commands) automatically request `get_config_request: :SESSIONKEY_CONFIG` before transmitting when no passkey was supplied. Acquisition must return a correlated `get_config_response` containing exactly eight passkey bytes; otherwise the write is not sent. Reads and local PhoneAPI operations do not need acquisition. An explicit eight-byte `session_passkey:` or a passkey already in `message:` bypasses acquisition; `auto_session: false` explicitly disables it. For MQTT provide the key obtained through your own authorized receive workflow.
@@ -14,13 +14,13 @@ Packet builder used by Serial, Bluetooth, TCP, and MQTT. Instantiated internally
14
14
  - `send_data` / `send_text`
15
15
  - `send_to_radio` / `send_to_mqtt` — serialize only
16
16
  - `decode_payload` — shared protobuf/text/binary decoding; [complete port inventory](#receive-payload-coverage)
17
- - `decrypt_packet` — selected channel AES-CTR only; ciphertext preserved on failure
17
+ - `decrypt_packet` — exact channel AES-CTR or AES-CCM (AEAD); ciphertext preserved on failure
18
18
  - `help` / `authors`
19
19
 
20
20
  On Serial/Bluetooth/TCP, transports pass `psks: nil` so the radio owns channel crypto. MQTT must pass `psks`.
21
21
 
22
- `send_data` and `send_packet` preserve `pki_encrypted: true` and `public_key:`
23
- (32 raw bytes) for remote administrative requests. The connected radio performs
22
+ `send_text`, `send_data` and `send_packet` preserve `pki_encrypted: true` and `public_key:`
23
+ (32 raw bytes) for radio-owned direct messaging and remote administrative requests. The connected radio performs
24
24
  the public-key encryption; the Ruby client does not encrypt these packets itself.
25
25
  Explicit PKI requests reject MQTT and host-side PSK encryption instead of silently
26
26
  falling back to channel encryption. Supplying a recipient key does not grant admin
@@ -46,7 +46,7 @@ text_bytes = mesh.send_text(
46
46
  envelope = mesh.send_text(
47
47
  from: '!c0ffee00',
48
48
  to: '!ffffffff',
49
- channel: 93,
49
+ channel_id: 'LongFast', # exact name; firmware hash is computed
50
50
  text: 'Hello MQTT',
51
51
  via: :mqtt,
52
52
  psks: { LongFast: 'AQ==' }
@@ -184,21 +184,49 @@ This is schema dispatch, not decompression of arbitrary bytes.
184
184
 
185
185
  ### Encryption boundary
186
186
 
187
- `decrypt_packet(message:, psks:, channel:)` preserves already-decoded packets,
188
- even if PKI metadata is present. MQTT selects the exact `ServiceEnvelope.channel_id`
187
+ `decrypt_packet(message:, psks:, channel:, source:)` preserves already-decoded payloads,
188
+ even if PKI metadata is present, but always attaches host-created security provenance.
189
+ MQTT passes `source: :mqtt` and selects the exact `ServiceEnvelope.channel_id`
189
190
  (string or symbol PSK key), using the topic channel only when the envelope omits it.
190
- It never falls back from an unknown channel to LongFast. Radio ciphertext carries
191
- an eight-bit XOR hash of channel name and key, not a local channel slot; only a
192
- unique matching configured name/key is selected. Hash collisions are refused.
193
- Supply the actual firmware channel name and full Base64 16- or 32-byte key.
194
- Subscription wrappers also accept the existing `LongFast: 'AQ=='` shorthand.
195
-
196
- PKI ciphertext is never fed to an AES channel cipher. Missing/ambiguous/invalid keys
197
- and malformed decrypted `Data` preserve ciphertext with `:decryption_error`.
198
- Successful channel decoding retains the existing `encrypted: :decrypted` marker.
199
- AES-CTR is unauthenticated: valid protobuf parsing is not proof the key was correct
200
- or the sender authentic. Device-owned private-key decryption remains the radio's
201
- responsibility. No private-key discovery or downgrade fallback is performed.
191
+ Unknown names never fall back. Radio ciphertext uses the unique eight-bit XOR hash
192
+ of name and expanded key, additionally XOR `0xAE` for an AEAD channel. Hash collisions
193
+ and duplicate string/symbol key names are refused.
194
+
195
+ Supply `psks: { Name: base64_key }` for legacy CTR or
196
+ `psks: { Name: { psk: base64_key, use_aead: true } }` for CCM. Modes are configured
197
+ per key, not detected by trial. Firmware aliases 1–255 expand on any channel name;
198
+ otherwise require full 16/32-byte keys. Unlike firmware, arbitrary short keys are
199
+ not padded and inherited secondary keys must be resolved by the caller.
200
+
201
+ MQTT sends accept `channel_id:` (mandatory with multiple keys); they compute the
202
+ firmware hash instead of using the old arbitrary numeric `channel:`. MQTT refuses
203
+ plaintext fallback, explicit recipient PKI options, and nonbroadcast text, including
204
+ text supplied as `Data`. Other channel-addressed application Data is not PKI.
205
+ Serial/Bluetooth/TCP retain device channel-slot semantics and pass `psks: nil`.
206
+
207
+ PKI ciphertext is never fed to an AES channel cipher. Missing/ambiguous/invalid keys,
208
+ mode/hash mismatches, failed CCM tags and malformed decrypted Data preserve ciphertext
209
+ with `:decryption_error`. Success retains `encrypted: :decrypted`. No fallback cipher
210
+ or key is tried. AES-CTR parsing is not authentication.
211
+
212
+ `:security_provenance` records `source: :radio, verification: :device_reported` for
213
+ the three radio subscriptions (the direct helper defaults to `source: :radio`).
214
+ The original `xeddsa_signed` and `ack_proof_status` are retained and copied under
215
+ `:reported`. These are the connected device's reports, not host cryptographic checks.
216
+ For MQTT, `source: :mqtt, verification: :untrusted`; those fields are removed from
217
+ the top-level packet and preserved only under `:reported`. Broker-created provenance
218
+ is overwritten. Successful host decryption adds `channel_authentication: :aead` or
219
+ `:none`; channel-key possession does not prove sender identity, ACK validity or freshness.
220
+ Raw FromRadio/protobuf APIs do not add this host-only metadata.
221
+
222
+ Channel crypto is pinned to **v2.8.1.8e6a88d**, firmware
223
+ `8e6a88d06f44cad26f1e8d7cd402939ccacb9b4c`, protobuf
224
+ `8f97d66a63ce10cfb12f94203e361691647f5ad3`. CCM uses a 13-byte nonce
225
+ `packet_id64le || from32le || 00`, AAD `from32le || to32le`, and appends a 12-byte tag.
226
+ CTR uses the same prefix in a 16-byte nonce. See [MQTT](mqtt.md#pinned-firmware-interoperability)
227
+ for source links, key selection, OpenSSL requirements, nonce-reuse limits and examples.
228
+ No host XEdDSA signing/verification, PKI encryption/decryption or ACK-proof verification
229
+ is implemented by these channel helpers.
202
230
 
203
231
  ### Verification
204
232