Share nRF52 checked-record reads and command-family lookups without changing
storage formats, error protection, fleet authorization, or delegated scopes.
Use the existing bounded Merkle proof workspace for qualified ESP32 repeaters
while retaining source capacity, wire format, and every enabled feature.
Restore advert tab/CRLF delimiters and expose nested qualification build logs.
Validation: all 1932 native CI test cases; exact canonical RAK4631 Full,
Wio-E5 Mini, Station G3 Full MQTT, and Wireless Tracker Full wrappers;
linked RAK crypto/workspace checks and retained Reader code equivalence;
focused persistence, authorization, source-proof, and RAM policy regressions.
Allow Auto, SSD1306 or SH1106 through display.controller on display-enabled RAK4631 and RAK3401 roles. Persist the choice before restart without changing the active screen policy, and let saved Auto override legacy build defaults.
Share checked settings I/O and the existing atomic writer to keep Full Companion within its flash boundary while retaining features and existing record formats. Cover save failures, backup recovery, startup selection and ambiguous OLED replies.
Validation: 35 targeted host tests passed. RAK4631 Full Companion and RAK3401 unified LoRa OTA repeater build and package checks passed with release-length version strings.
Default fresh settings to sound off and vibration on, and keep existing saved choices. Apply changes immediately across all three UIs and make custom notification patterns respect the same mute controls.
Keep the Wio Mini Companion within its existing flash boundary by storing its LED switch in a reserved preference byte and shortening CLI help without removing commands. Add coverage for live output changes, persistence, and storage failures.
Adapted from https://github.com/meshcore-dev/MeshCore/pull/3603.
Co-authored-by: R0ck <develop@hectospark.co.uk>
Add get leds and set leds on|off for Companion, repeater, room server and
sensor builds. Preserve the setting across reboots and cover GPIO, RGB,
Bluetooth, notification and expander indicators without changing radio
amplifier controls or screen backlights.
RAK Companions blink while advertising and keep the Bluetooth LED off once
connected, including late timer callbacks.
Adapted from meshcore-dev/MeshCore#3605 and #2694.
Validated with host regressions and firmware builds for SenseCAP Solar,
RAK4631, T096, Heltec V4, T-Beam, Xiao S3, Pico W and Wio E5 Mini.
Co-authored-by: R0ck <develop@hectospark.co.uk>
Co-authored-by: Nick Dunklee <nick@dunklee.org>
Use the committed node credential for setup WiFi, WebConfig and browser OTA. Add persistent ESP32 WiFi Companion management credentials without changing preferences layouts, revoke stale browser sessions on rotation, retain scoped save receipts, and coordinate credential changes with flash uploads. Keep external-service credentials and trusted TCP protocol compatibility independent. Update live join QR codes, failure handling and documentation.
Validated 51 native cases, 158 related host and browser methods, and serial Full V4 Companion, Station G3 Repeater, ESP32-C6 Room Server and V4 Sensor firmware builds.
Stage STM32 preference prefixes and checked footers in one atomic image, reject declared tails that encounter premature EOF, and share the typed CLI rollback helper. Use the proven Wio Mini code-layout flags on Wio E5 and Tiny Relay without changing features or memory boundaries. All four STM32 targets and RAK4631 Full pass at the full 1.18.0.1 release version length.
Deduplicate validation and preserve the published preference bytes and transactional saves. Correct Ikoka Handheld Companion flash bounds and document QSPI Room migration. Qualify STM32 and RAK4631 at the full 1.18.0.1 release version length.
Integrate fractional Companion timezones and conditional Bluetooth pages while preserving the existing preference byte layout, transactional saves, and Full transport selector. Add migration, rollback, invalid-value, and UI profile regressions.
Upstream MeshCore, and this fork up to v1.17.1.6 except ESP32 Full
builds, rewrote /contacts3 in place, one record after another. A reset
or a full volume during that save leaves whole records followed by part
of the next one. Upstream reads such a file up to the short read. Here
readableContactFile() treats any size that is not a multiple of 152
bytes as unreadable, and since 2311160a9 the bounded recovery then
deletes /contacts3. On the first boot after an upgrade, three whole
records plus one stray byte became zero contacts.
Invariant: on ESP32, RP2040 and STM32, when /contacts3 holds at least
one whole record followed by part of one, and /contacts3.bak does not
exist, the whole records load and nothing is written or deleted at
boot. The next ordinary save publishes only whole records through the
existing transaction. Unchanged: whole-record images; a backup still
wins over an incomplete primary, including a backup that is unreadable
or of unknown presence at boot, which keeps its retry at the recovery
boundary; read errors keep the bounded recovery; a file with no whole
record is still discarded; nRF52 is not touched.
selectContactFile() makes this check only after the strict checks of
both names fail, through a new partial_tail mode of
readableContactFile(). loadContacts() drops its own size check; its
whole-record loop bound already stops before the tail.
Add test_companion_contact_partial_tail.py, which runs the real load,
recovery and save paths on the uncached ESP32, RP2040 and STM32
harness and on the ESP32 Full cache harness under ASan/UBSan. On the
old code all four fail with zero contacts and /contacts3 deleted. It
catches 14 of 15 mutations of this change, including a full revert;
the other drops a guard whose value is read only when the file handle
is valid. Two cases in the uncached storage fixture used a one-byte
truncation as their unreadable image; they now use a file shorter than
one record, so they still exercise the discard path.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
POST /api/room checked the room password on every request with no failure
limit, and a browser may send a new identity token each time. Any host that
reached WebConfig could guess the room admin password, which is also the
radio and /api/login admin password, at main-loop speed.
One budget now covers all browser tokens, with /api/login's numbers: at most
five distinct wrong passwords in each thirty-second window. After that, a
request carrying a password is refused without checking it until the window
ends. Wrong passwords count in read-only rooms too. Empty passwords are never
counted or refused, so public read-only access keeps working. The last four
wrong passwords are remembered by 8-byte SHA-256 digest and not counted
again, so a tab still polling with an old or mistyped password cannot use up
the budget. Successful sign-ins do not refund failures. Radio login is
unchanged.
test_room_web_service.py gains PASSWORD_LIMIT, which runs the real
processRoomRequest: refusal after five failures from five tokens; identical
replies for wrong, guest and admin passwords while refused, with no password
check (calls to authorizeRoomLogin are counted); no lock extension from 300
refused requests; the 29999/30000 ms boundary and the first window after
boot; read-only and empty-password cases; no refund on success; repeated,
rotating and read-only stale passwords; slow distinct failures ageing out;
and a guard that the digest ring stays in bounds. It fails on the previous
processRoomRequest at the sixth guess.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Wi-Fi, LoRa first-stage and Partition Expander bridges serve an
unauthenticated /update behind an AP whose password was fixed and published,
so anyone in range during a migration could upload firmware while the private
key is staged. Use the old node's saved admin password as the WPA2
passphrase, read from /com_prefs or /node_prefs at offset 56 as the firmware
loads it. The operator already used that password to start the old updater.
Fall back to the published password, with a serial warning, when the saved
admin password is missing, unreadable, shorter than 8 characters or not
printable ASCII, so no node becomes unreachable. The legacy filesystem is
mounted without formatting and the password is never printed. Migration
admission, flash sequencing, /update gating and the USB bridge are unchanged.
Update both guides and the package README.
Add a sanitizer-backed host test that compiles the bridge's actual Wi-Fi
block against fake SPIFFS and WiFi: both boots, file precedence, WPA2 length
and terminator bounds, printable characters, stock 1.10/1.11 file sizes,
truncated fields, and mount, seek and partial-read failures. It fails 8 of 8
on the previous code and catches 27 of 27 code mutations. Verified 13 related
host test files and the esp32_s3_4mb_partition_migrator build.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On a room server the guest password grants Read/Write, which the login
replay store treats as a role that needs a durable record. Every new
guest-password identity therefore took one of the 128 permanent slots,
and once they were used up the store failed closed for every new
identity, including a new administrator device, until the filesystem was
erased. Rooms often publish the guest password.
A guest-password Read/Write session (one without explicitly assigned
permissions) now uses the policy public readers already use: it must
beat its in-boot timestamp and any record the identity already has,
which is still advanced, but it never creates a new record.
Administrator logins and explicitly assigned roles keep the durable
reservation and still fail closed on a full store.
Tests: test_room_login_replay_capacity.py runs the real room login
handler against the real ClientACL replay store with ASan/UBSan: guest
churn cannot lock out a new admin or assigned writer, a former admin's
record still binds guest-password logins, and a store full of privileged
records still refuses a new admin. It fails 2 of 3 on the previous code.
Two expectations in test_room_login_integration.py and the login profile
contract follow the new policy.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>