Skip to content

move statusLED to its own class and allow using for repeater#36

Closed
andyshinn wants to merge 90 commits intoweebl2000:dev_plusfrom
andyshinn:ashinn/status-led-repeater-weebl
Closed

move statusLED to its own class and allow using for repeater#36
andyshinn wants to merge 90 commits intoweebl2000:dev_plusfrom
andyshinn:ashinn/status-led-repeater-weebl

Conversation

@andyshinn
Copy link
Copy Markdown

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.

weebl2000 and others added 30 commits February 28, 2026 19:05
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.
weebl2000 and others added 25 commits March 2, 2026 16:35
Copilot AI review requested due to automatic review settings March 2, 2026 21:46
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 StatusLED helper class under src/helpers/.
  • Wired the new StatusLED into examples/simple_repeater main loop.
  • Replaced the companion UI’s ad-hoc LED handler (both ui-orig and ui-new) with the shared StatusLED class.

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.

Comment on lines +11 to +13
#ifdef PIN_STATUS_LED
#include <helpers/StatusLED.h>
#endif
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#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).

Suggested change
#ifdef PIN_STATUS_LED
#include <helpers/StatusLED.h>
#endif
#include <helpers/StatusLED.h>

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants