move statusLED to its own class and allow using for repeater#36
move statusLED to its own class and allow using for repeater#36andyshinn wants to merge 90 commits intoweebl2000:dev_plusfrom
Conversation
The GC1109 FEM needs its VFEM_Ctrl pin held HIGH during deep sleep to keep the LNA active, enabling proper RX sensitivity for wake-on-packet. Without this, the LNA is unpowered during sleep and RX wake sensitivity is degraded by ~17dB. Release RTC holds in begin() after configuring GPIO registers (not before) to ensure glitch-free pin transitions on wake. Trade-off: ~6.5mA additional sleep current for significantly improved wake-on-packet range.
- runtime auto-detection of v4.3 board (KCT8103L FEM) vs V4.0-V4.2 (GC1109) via GPIO2 pull level - different TX/RX path using PGIO5 for KCT8103L, GPIO46 CPS for GC1109 - hold both FEMs active for RX - report Heltec V4.3 in manufacturer name
Use millisHasNowPassed() (2's complement safe) instead of direct comparison, consistent with the repeater's sleep timing logic. Co-Authored-By: Wessel <weebl@users.noreply.github.com>
When BLE is enabled but no phone has been connected for 60 seconds, enter a 12s sleep / 3s awake cycle to conserve power while remaining discoverable. On wake, BLE advertising is restarted so phones can reconnect. The cycle exits immediately when a connection is detected. Adds hasPendingConnection() to BaseSerialInterface (defaults to isConnected()). The ESP32 BLE override uses getConnectedCount() > 0 to detect mid-bonding connections, preventing sleep during the authentication handshake. Guarded by #ifndef WIFI_SSID — WiFi builds are unaffected.
The onContactResponse handler copies peer response data into out_frame (MAX_FRAME_SIZE + 1 bytes) without checking whether the data fits. A peer response with len close to MAX_PACKET_PAYLOAD (184) writes up to 188 bytes into the 173-byte buffer, overflowing by 15 bytes. This affects the status response, telemetry response, and binary response code paths. A malicious peer can trigger the overflow by sending a large response payload, corrupting the stack. Cap each memcpy to the remaining space in out_frame before copying.
The TRACE forwarding path appends an SNR byte to pkt->path via path_len++, but the guard only checked path_len < MAX_PATH_SIZE. When path_len entered as MAX_PATH_SIZE - 1, the write was in-bounds but left path_len equal to MAX_PATH_SIZE, which could cause off-by-one issues in downstream code that uses path_len as an index. Change the guard to path_len + 1 < MAX_PATH_SIZE so there is always room for the append without path_len reaching MAX_PATH_SIZE.
Add ChaCha20-Poly1305 AEAD decryption with 4-byte auth tag for peer messages and group channels, falling back to ECB for backward compatibility. Sending remains ECB-only in this phase. - Per-message key derivation: HMAC-SHA256(secret, nonce||dest||src) - Direction-dependent keys prevent bidirectional keystream reuse - 12-byte IV from nonce + dest_hash + src_hash - Advertise AEAD capability via feat1 bit 0 in adverts - Track peer AEAD support in ContactInfo.flags - Seed aead_nonce from HW RNG on contact creation and load
Send ChaChaPoly-encrypted messages to peers with CONTACT_FLAG_AEAD set, and try AEAD decode first for those peers (avoiding 1/65536 ECB false-positive). Legacy peers continue to use ECB in both directions. - Add aead_nonce parameter to createDatagram/createPathReturn (default 0 = ECB) - Add getPeerFlags/getPeerNextAeadNonce virtual methods for decode-order selection - Add ContactInfo::nextAeadNonce() helper (returns nonce++ if AEAD, 0 otherwise) - Update all BaseChatMesh send paths to pass nonce for AEAD-capable peers - Adaptive decode order: AEAD-first for known AEAD peers, ECB-first for others
The header's route type bits (PH_ROUTE_MASK) are zero when createDatagram/createPathReturn encrypt with AEAD, but get changed to ROUTE_TYPE_FLOOD (1) or ROUTE_TYPE_DIRECT (2) by sendFlood/sendDirect afterwards. The receiver builds assoc from the received header (with route bits set), so the tag check always fails and every AEAD packet is silently dropped. Mask out route type bits in assoc data on all 5 encrypt/decrypt sites. Also track AEAD decode success to enable peer capability auto-detection.
- Fix potential unsigned overflow in createDatagram size check by subtracting constants from MAX_PACKET_PAYLOAD instead of adding to data_len - Add upper-bound validation on src_len and assoc_len in aeadEncrypt and aeadDecrypt - Log peer name on AEAD nonce wraparound for debug builds
Prevent nonce reuse after reboots by persisting per-peer nonce counters to a dedicated /nonces (companion) or /s_nonces (server) file. On dirty reset (power-on, watchdog, brownout), nonces are bumped by NONCE_BOOT_BUMP (100) to cover any unpersisted messages. Clean wakes (deep sleep, software restart) load nonces as-is. - Add nonce persistence to BaseChatMesh (companion) and ClientACL (server) - Add wasDirtyReset() helper to ArduinoHelpers.h for platform-specific reset reason detection (ESP32/NRF52) - Add onBeforeReboot() callback to CommonCLI for pre-reboot nonce flush - Wire nonce persistence into all firmware variants: companion radio, repeater, room server, and sensor - Only clear dirty flag on successful file write
The fs->remove("/contacts3.bak") before the rename sequence creates a
vulnerability window: if power is lost right after removing the backup
but before the rename completes, both the backup and primary file could
be lost. The remove is unnecessary since rename() on both SPIFFS and
LittleFS replaces the target if it already exists.
readFrom reads the header byte, transport codes (4 bytes), and path_len from the source buffer before any length validation. With a short input, these reads go past the end of the buffer. Add upfront length checks: minimum 2 bytes overall, transport codes require 4 additional bytes, and path must fit before the remaining payload.
The HMAC check in MACThenDecrypt used standard memcmp(), which short-circuits on the first mismatched byte. This makes the comparison time dependent on how many bytes of the MAC are correct, leaking information through a timing side-channel. With a 2-byte MAC (65,536 possible values), an attacker on a local interface (serial, BLE, or WiFi) can measure response latency to distinguish "first byte wrong" from "first byte correct, second wrong". This reduces a brute-force from 65,536 attempts down to roughly 384 (256 + 128 on average), making MAC forgery practical. An attacker could use this to forge packets that pass MAC verification without knowing the shared secret, allowing them to inject arbitrary messages that appear to come from a trusted peer. Replace memcmp with a constant-time XOR-accumulate loop so the comparison always takes the same time regardless of which bytes match.
Replace the fixed-size hash table with an LRU cache using actual timestamps instead of timeout-based eviction. Some busy nodes see more than 128 packets before duplicates arrive, so LRU ordering provides better eviction behavior.
…yload bounds checks
…y-one fix and payload_len>=9 check
…-detection' into dev_plus
… bounds checks (3 sites)
… simpler deep_sleep_lna)
…n-bucket' into dev_plus
There was a problem hiding this comment.
Pull request overview
This PR extracts the status LED blink/alert behavior into a reusable StatusLED helper so it can be used outside the companion UI (notably on repeaters) to aid debugging by showing that the main loop is still running.
Changes:
- Added
StatusLEDhelper class undersrc/helpers/. - Wired the new
StatusLEDintoexamples/simple_repeatermain loop. - Replaced the companion UI’s ad-hoc LED handler (both
ui-origandui-new) with the sharedStatusLEDclass.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
src/helpers/StatusLED.h |
Introduces a reusable LED blink/alert helper. |
examples/simple_repeater/main.cpp |
Instantiates and runs StatusLED when PIN_STATUS_LED is available. |
examples/companion_radio/ui-orig/UITask.h |
Adds a StatusLED member and constructor initialization. |
examples/companion_radio/ui-orig/UITask.cpp |
Removes legacy LED handler and drives the new StatusLED with _msgcount as alert signal. |
examples/companion_radio/ui-new/UITask.h |
Replaces legacy LED state variables with a StatusLED member. |
examples/companion_radio/ui-new/UITask.cpp |
Removes legacy LED handler and drives the new StatusLED with _msgcount as alert signal. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| #ifdef PIN_STATUS_LED | ||
| #include <helpers/StatusLED.h> | ||
| #endif |
There was a problem hiding this comment.
#include <helpers/StatusLED.h> is wrapped in #ifdef PIN_STATUS_LED but it appears before any header that guarantees PIN_STATUS_LED is defined (e.g., ../AbstractUITask.h includes <Arduino.h> later). If PIN_STATUS_LED becomes defined only after those later includes, the #include is skipped while the later StatusLED status_led; member is still compiled, which will break the build due to an unknown type. Move the StatusLED.h include to a point after PIN_STATUS_LED is known (or include StatusLED.h unconditionally and keep only the member/usage under #ifdef).
| #ifdef PIN_STATUS_LED | |
| #include <helpers/StatusLED.h> | |
| #endif | |
| #include <helpers/StatusLED.h> |
This moves the StatusLED class outside of companion so that it can also be used on repeaters. Good for debugging a repeater that may have failed to see if the loop is still running.