Skip to content

fix(firmware): batch of P3 OTA/DFU cleanups from the #5915 audit#5916

Merged
jamesarich merged 1 commit into
mainfrom
claude/xenodochial-torvalds-7bd21a
Jun 23, 2026
Merged

fix(firmware): batch of P3 OTA/DFU cleanups from the #5915 audit#5916
jamesarich merged 1 commit into
mainfrom
claude/xenodochial-torvalds-7bd21a

Conversation

@jamesarich

Copy link
Copy Markdown
Collaborator

Low-priority (P3) follow-ups surfaced during the firmware-update audit and hardware testing that produced #5915 (which fixed the higher-severity issues). Each item below is independent and was implemented as the smallest self-contained change; the hardware-validated OTA/DFU happy paths are unchanged.

🐛 Bug Fixes

  • ESP32 BLE OTA handshake fragment-count waitBleOtaTransport.startOta drove handshake completion off responsesReceived >= packetsSent (the same fragment-count model fix(firmware): harden ESP32 OTA + nRF DFU update paths (hardware-validated) #5915 removed from streamFirmware). Safe at the negotiated MTU 512 but latent at very low MTU. It now completes only on an explicit OK and tolerates interim ERASING, exactly like the fixed streaming loop.
  • IPv6 routing heuristicEsp32OtaUpdateHandler split BLE-vs-WiFi on target.contains(":"), which misclassifies an IPv6 literal as a BLE MAC. Routing now uses a strict 6-octet MAC match (isBleMacAddress); IPv6/hostname targets correctly route to WiFi.
  • Manifest md5/bytes not verifiedFirmwareRetriever.resolveFromManifest logged but never verified the downloaded .bin against the manifest. It now checks size, then md5 (FirmwareHashUtil.calculateMd5Hex, pure-Okio so it stays in commonMain); a mismatch falls back to the filename heuristics. This surfaces a bad download before the disruptive reboot-into-OTA dance rather than failing device-side mid-flash.
  • Empty-firmware slow fail — a 0-byte image skipped the streaming loop and then waited the full 10s VERIFICATION_TIMEOUT before failing. streamFirmware now fails immediately with TransferFailed("Firmware is empty").

🌟 UX

  • Serial+ESP32 / TCP+nRF52 dead-end — these transport/device combinations have no update path (getHandler throws), yet the screen still rendered a working "Update" button (method = Unknown) that only failed on press. The ViewModel now maps TCP+non-ESP32 to Unknown, and the Ready screen shows a proactive "firmware updates are not supported over this connection" message instead of an actionable button. New string firmware_update_unsupported_transport.

🧹 Chores / Hardening

  • DFU multi-image gapDfuZipParser transfers only the primary image (app > softdevice_bootloader > bootloader > softdevice). This is correct for app-only Meshtastic OTA zips, but a combined app+SD+BL package would silently flash only one. The limitation is now documented and guarded: the parser logs a WARN when a package declares more than one image. (Multi-image sequencing is intentionally out of scope.)
  • Misleading buttonless-DFU warningSecureDfuTransport.triggerButtonless logged a WARN about a stale bond on the normal success path (the WITH_RESPONSE write times out because the device reboots before the ATT ACK — expected and handled). Downgraded to debug and reworded; the Forget+Re-pair guidance moved to connectToDfuMode's scan-failure error, where it actually indicates a problem.

Testing Performed

Baseline: ./gradlew spotlessApply spotlessCheck detekt :feature:firmware:allTests — green (429 tests).

Added/updated commonTest coverage:

  • BleOtaTransportTeststreamFirmware fails immediately on empty firmware (existing startOta OK/ERASING tests already cover the handshake change).
  • Esp32OtaRoutingTest (new) — isBleMacAddress accepts MACs and rejects IPv4, IPv6 literals, and hostnames.
  • CommonFirmwareRetrieverTest — manifest artifact accepted on matching md5+size; rejected (and falls back) on md5 mismatch and on size mismatch.
  • DfuZipParserTest — priority image is chosen when multiple images are present.
  • FirmwareUpdateViewModelTest — TCP + nRF52 resolves to Unknown.

Seven independent low-priority issues surfaced during the firmware-update
audit + hardware testing that produced #5915. Each is a minimal,
self-contained fix; no behavior change on the validated happy paths.

- ESP32 BLE OTA handshake completes on an explicit OK / tolerates ERASING
  instead of counting fragments (latent low-MTU hang).
- Route ESP32 OTA BLE-vs-WiFi on a strict MAC match so an IPv6 literal is
  no longer misclassified as a BLE address.
- Show a proactive "unsupported on this connection" state for Serial+ESP32
  / TCP+nRF52 instead of an Update button that only fails on press.
- Verify downloaded ESP32 firmware against the manifest md5/bytes.
- Warn (and document) that combined multi-image DFU zips flash only the
  primary image.
- Fail immediately on empty firmware instead of waiting out the 10s
  verification timeout.
- Downgrade the buttonless-DFU success-path WARN to debug and move the
  Forget+Re-pair guidance to the DFU-mode scan-failure path.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions github-actions Bot added the bugfix PR tag label Jun 23, 2026
@jamesarich jamesarich enabled auto-merge June 23, 2026 18:32
@jamesarich jamesarich added this pull request to the merge queue Jun 23, 2026
Merged via the queue into main with commit 61c8a3f Jun 23, 2026
22 checks passed
@jamesarich jamesarich deleted the claude/xenodochial-torvalds-7bd21a branch June 23, 2026 18:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bugfix PR tag

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant