Skip to content

Ikoka Handheld compile and LED fixes#35

Closed
andyshinn wants to merge 93 commits intoweebl2000:dev_plusfrom
andyshinn:ashinn/ikoka-compile-fix
Closed

Ikoka Handheld compile and LED fixes#35
andyshinn wants to merge 93 commits intoweebl2000:dev_plusfrom
andyshinn:ashinn/ikoka-compile-fix

Conversation

@andyshinn
Copy link

  • Fix compiling on Ikoka Handheld
  • Fix LED inverted
  • Add status LED

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:43
Copy link

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 updates the Ikoka Handheld NRF52 variant configuration to fix build settings and align LED behavior with the board’s wiring, including adding a dedicated status LED pin.

Changes:

  • Fix Ikoka Handheld build configuration by setting the PlatformIO board + linker script and correcting env inheritance.
  • Fix inverted LED logic by updating LED_STATE_ON for this variant.
  • Add a PIN_STATUS_LED definition and initialize it during board startup.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
variants/ikoka_handheld_nrf/variant.h Updates LED active state and adds PIN_STATUS_LED mapping.
variants/ikoka_handheld_nrf/platformio.ini Adds board/ldscript config and fixes env extends to use the correct base section.
variants/ikoka_handheld_nrf/IkokaNrf52Board.cpp Initializes the status LED pin at startup.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

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