- gps: servers honour gps_enabled at boot (was lost on reboot; zc.gps_set
upgrades older prefs to on once); sleep lock only while acquiring;
gps off works on boards without a power line (UART sleep); UI toggles
and fixes handled on the main thread; configure only after a good init;
observer parks its GPS; one interval clamp (clampGpsInterval); one
nav-mode knob (room servers on Air530Z now stationary); GNSS UARTE from DT
- gps: split into ZephyrGPSManager / gps_power / gps_module_cfg
(+gps_internal.h); dead driver branches, gps_get_diag, sats_* removed
- clock: ZephyrRTCClock writes the hardware RTC itself (clkreboot now
resets it); MESH_EVENT_RTC_SAVE gone
- power: one power-off path (zephcore_power_off, ZephyrBoard::powerOff),
poweroff/shutdown CLI, get pwrmgt.*, crash record kept across reboot,
battery reading cached 10 s, fuel gauge on the current API
- telemetry: upstream SensorManager + CayenneLPP (rounded to nearest),
one builder for all roles; per-sensor channels, MCU temp always,
guest gating and inverse mask on servers; sensor list/get/set work;
BME280/BMP280 also probed at 0x77
- CLI: gps in upstream's form, get gps detail, gps sync
Server roles (slice 7)
- CommonCLI split into upstream's shape; region CLI moved into CommonCLI;
ClientACL, RegionMap, TransportKeyStore and both server meshes in
upstream shape over an Arduino compat shim (helpers/compat: Arduino.h,
Stream/Print, Serial, SHA256, FILESYSTEM/File over Zephyr fs)
- room server processAck port; `get acl` prints as upstream
- room: default name Room-XXXX, display/input rotation, live radio UI refresh
Event loops (slice 7b)
- repeater and room server share server_main_common with a ServerRole
descriptor; shared event bits in src/mesh_events.h
- room server deadline-driven (idle wakes 2.2/s -> 0.08/s); companion
loop() runs on housekeeping (closes the lazy contact-flush gap)
- `get loop.wakes` in debug builds (CONFIG_ZEPHCORE_LOOP_WAKE_STATS)
Companion transports and WiFi (slice 8)
- upstream MultiSerialInterface: BLE, USB/UART and TCP are each a
BaseSerialInterface, every connected transport gets every frame;
CompanionSerial holds a lossless reply; shared frame_txq for BLE/TCP
- SerialCompanionTransport and LinuxTCPTransport replaced by
TcpCompanionTransport; ZephyrBLE split (gatt layout, DFU)
- WiFi companion (upstream wifi.ssid/pwd/enabled CLI, TCP port 5000) on
boards declaring `capabilities: wifi` in zephcore.yml: the PSRAM
ESP32-S3 boards, plus Heltec V3 (140 contacts), Wireless Tracker (130)
and both C6 boards (280); ESP32-S3 without PSRAM gets bounded WiFi
buffers and a measured 72 KB heap; PSRAM boards' internal heap 72 KB
- TCP queues: own depth (ZEPHCORE_TCP_QUEUE_SIZE=8), RX waits instead
of dropping
- RAM: companion ClientACL dropped; main/BT-TX/TCP stacks sized from
measured peaks (-7 KB nRF companions, -9 KB ESP32 companions)
- ESP32 WiFi stopped working under ~200 frames/s unicast. Fixed by:
patches/zephyr/0017 never block the WiFi task in the RX callback;
patches/zephyr/0018 lock shared_multi_heap (heap corruption with the
WiFi heap in PSRAM); patches/modules/hal_espressif/0001 backport of
hal_espressif af97fb02df (PSRAM realloc)
Storage and prefs (slice 9)
- prefs are now prefs.json through upstream's ConfigSerializer (ported
verbatim), with upstream's key names for shared fields and ZephCore's
own under "zc"; on first boot the binary new_prefs / repeater prefs is
migrated and kept, so a downgrade still boots with the old settings
- helpers/PrefsCodec: both legacy binary layouts (companion 272 B,
server 311 B) as Zephyr-free codecs; sanitizeNodePrefs is the only
validator
- fix: server prefs could be replaced by a truncated file when a write
failed; identity, prefs and channel writes go through one checked
atomic replace (ZephyrFsUtil)
- fix: a corrupt cad_auto byte turned adaptive CAD off instead of the
default on
- fix: a deliberate 300 s GPS duty on a repeater was rewritten to 48 h
on every boot (that migration now runs on legacy files only)
- one identity loader for all roles (IdentityFile); an unusable key
file is always parked as .bad before regenerating
- server store no longer stamps repeater defaults on room server and
observer; companion first-boot volume policy moved into the store
(adoptVolume)
- ZephyrDataStore 1435 -> ~850 lines
Tracks upstream meshcore-dev/MeshCore#2556 — passwords sent as
plaintext over encrypted links are vulnerable to evil-twin/phishing
attacks (attacker advertises a repeater with the same name but a
different pubkey; user picks the wrong one and types the password).
The structural fix is a protocol change (PAKE adoption — SPAKE2,
OPAQUE, or HMAC-with-both-pubkeys) and must land synchronously
across all implementations. Diverging unilaterally would break
interop with Arduino-based companions and repeaters, so we wait
for upstream.
Two within-protocol mitigations applied server-side:
1. Constant-time password comparison. Replaced strcmp() in
RepeaterMesh::handleLoginReq with a local ct_memeq() helper.
Pads the received password to the full 16-byte storage size,
XOR-accumulates byte differences with no early exit. Compares
both admin and guest passwords unconditionally so timing is
identical regardless of which (if any) the attempt resembled.
Eliminates the timing oracle that lets an already-MITM
attacker recover the stored password byte-by-byte.
2. Failed-login rate limit. New login_fail_limiter(4, 180)
RateLimiter — 4 wrong-password attempts per 180s, matching the
existing anon_limiter pattern. Hitting the cap trips a distinct
LOG_WRN so operators see active brute-force attempts in logs.
Global rate (not per-sender) — simpler, no ACL state bloat;
trade-off documented in CRYPTO_AUDIT_INDEX.md.
What's NOT fixed: the wire protocol still carries plaintext
passwords. The evil-twin attack itself remains possible; these
mitigations raise the attacker's cost (no timing leak, no
brute-force at line rate) but don't replace the structural fix.
UI-side defenses (TOFU warnings on duplicate names, pubkey
fingerprint display) are valuable companion-side mitigations
but out of scope for this audit's server-side commit.
Both mesh::Packet::writePath and ::copyPath did a raw memcpy of the
decoded hash_count*hash_size bytes from src to dest with no bound on
src. Two call sites used phone-supplied or LoRa-anon-supplied buffers
where the path_len byte was attacker-controlled:
- CompanionMesh CMD_SEND_CHANNEL_DATA accepted len>=4 and called
writePath with no src bound; a paired phone could leak up to ~65
bytes of syswq stack into the outgoing LoRa channel-data frame.
- RepeaterMesh handleAnonRegionsReq / handleAnonOwnerReq /
handleAnonClockReq read reply_path_len from an unauthenticated
LoRa anon-request payload and called copyPath without any src
bound. Any LoRa neighbor could leak repeater stack into the
reply path.
Hardened the API: both functions now require an explicit src_len
and reject (return 0) when the decoded byte count exceeds it.
Updated all 14 call sites across Packet/Mesh/Dispatcher/BaseChatMesh/
CompanionMesh/RepeaterMesh. Trusted callers (internal MAX_PATH_SIZE
buffers) pass MAX_PATH_SIZE; untrusted callers pass real remaining
length. Added len-5 plumbing through the anon-handler signatures.
CMD_SEND_CHANNEL_DATA also gained a local len>=5 + path_bytes
sanity check for early rejection.
stolen from Zephyr main:
1. Issue StopTimerOnPreamble=1 before SetRxDutyCycle so the chip's
timer is not reset on every preamble detect (per §13.1 of the
datasheet). Without this, duty cycle effectively never sleeps in
noisy RF and current draw spikes.
2. On IRQ_RX_TX_TIMEOUT during duty-cycle RX, re-arm via
sx126x_restart_rx() instead of falling through to set_sleep().
The old path silently killed duty cycle after the first preamble
false-positive.
3. On recv_duty_cycle(NULL) cancel, wake the radio before issuing
SetStandby — BUSY stays asserted during the sleep phase and the
standby command was being dropped.
Also adds a dc_timeout_restarts atomic counter incremented on the Fix 2
path, exposed end-to-end: sx126x_ext.h accessors → LoRaRadioBase vtable
→ SX126xRadio override → CommonCLICallbacks → RepeaterMesh. Query via
`get dc.restarts` on the repeater CLI; cleared by `clear stats`. High
values indicate a noisy environment or a too-loose preamble threshold.
(+increase ESP BT stack because future zephyr pin advance will trip that mine)
GPS fixes no longer overwrite prefs lat/lon — adverts use only
manually-set coordinates (via gps setloc or companion app)
Precise GPS position served via telemetry requests only
getNodeLat()/getNodeLon() returns live GPS fix
Last good fix persisted to /lfs/gps_pos — survives reboots
| Command | Description |
|---------|-------------|
| `gps` | Show GPS status (`on` or `off`) |
| `gps on` | Enable GPS module |
| `gps off` | Disable GPS module |
| `gps setloc` | Update stored latitude/longitude from current GPS fix |
| `gps advert` | Show current location advertising policy |
| `gps advert none` | Do not include location in advertisements |
| `gps advert share` | Include live GPS location in advertisements |
| `gps advert prefs` | Include stored lat/lon from prefs in advertisements |