1. CONFIG_BT_DEVICE_NAME_GATT_WRITABLE=y removed. The default GAP
Device Name write permission is plain BT_GATT_PERM_WRITE — no
bonding required (Zephyr gap_svc.c:158). Any connected peer
(bonded or not) could rename the device. Worse, a GAP write
updates bt_get_name() but NOT prefs.node_name, so the advertised
name wouldn't track the renamed value. Rename now flows
exclusively through CMD_SET_ADVERT_NAME, which is NUS-protected
(AUTHEN required) and properly propagates via
zephcore_ble_update_name() to prefs + GATT + adv data.
2. CONFIG_BT_DIS_FW_REV_STR synced from "1.13.0" to "v1.15.1-zephyr"
to match CompanionMesh.cpp CMD_DEVICE_QUERY's version string.
Comment added requiring the two to stay in sync.
Three small correctness/polish improvements from BLE audit Phase 2F:
1. Five handlers (CMD_APP_START, CMD_GET_CHANNEL, CMD_SET_CHANNEL,
CMD_DEVICE_QUERY, CMD_SEND_CHANNEL_TXT_MSG) previously responded
with ERR_UNSUPPORTED on short-frame validation failure (because
they fell through to the dispatcher's default break, which the
caller converts to "unknown command"). They now explicitly
sendPacketError(ERR_ILLEGAL_ARG) — the semantically correct code
for "known cmd, bad frame".
2. CMD_SET_TUNING_PARAMS previously returned PACKET_OK on short
frames without applying any change. Now sends ERR_ILLEGAL_ARG so
the phone learns the change didn't take.
3. Added static_assert that CONFIG_ZEPHCORE_BOARD_NAME fits in 40
bytes including its null terminator, so a future too-long board
name fails at build time instead of producing an unterminated
wire-format response.
The wire format reserves a 32-byte name field; if the phone sends 32
non-null bytes, ContactInfo::name has no terminator. Subsequent
LOG_INF/LOG_DBG sites using %s with contact.name then read past the
field into adjacent struct bytes (type, flags, out_path_len, ...)
until the first null. No memory corruption — serializeContact uses
StrHelper::strzcpy which is length-bounded — but log output gets
garbage and a paired peer could probe a few bytes of the struct
through log capture.
Sibling handler CMD_SET_CHANNEL at :1593-1594 already does this
defensively. Match the pattern.
Two polish items from BLE audit Phase 2B:
1. CMD_SEND_TELEMETRY_REQ self-response buffer was uint8_t rsp[96]
with a comment claiming 70 B worst case. Actual worst case at
POWER_MAX_CHANNELS=4 is 82 B; if the channel cap ever grew the
buffer would silently overflow. Replaced with a sizeof-style
expression that tracks POWER_MAX_CHANNELS, plus an 8-byte safety
pad. No size change today (90 vs. 96) but the upper bound auto-
tracks any future bump.
2. CMD_GET_CUSTOM_VARS used `dp += snprintf(dp, 20, ...)` which
advances by the would-be-written length, not bytes actually
written. Currently safe only because gps_interval is capped
≤86400, but if either cap drifted or a new key was added the
length passed to writeFrame would include uninitialized stack
bytes between the truncation point and the (over-advanced) dp.
Now tracks rsp_end, computes remaining per snprintf, and only
advances dp on real progress.
Both are correctness polish, not exploitable today.
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.
1. USB takeover opcode mismatch
ZephyrCompanionUSB.cpp checked payload[0] == 0x03 with a comment
claiming CMD_APP_START, but CMD_APP_START is 0x01 (0x03 is
CMD_SEND_CHANNEL_TXT_MSG). The USB handshake silently dropped the
companion app's first frame on every connection; the app appeared
broken over USB until the user happened to send a channel message.
2. CMD_SET_ADVERT_NAME didn't propagate to BLE adv data
Name changes were persisted to prefs but the advertising payload
and GATT device name kept the old value until reboot. Added
zephcore_ble_update_name() and called it from the handler.
3. No advertising-health watchdog
If bt_le_adv_start() ever failed transiently (HCI timeout,
controller pacing), the device would silently stop advertising
and stay undiscoverable until reboot. Added an adv_running flag
and a 5s watchdog in the companion housekeeping handler that
nudges adv back on if it stops outside a connection. Tracks
Arduino nrf52's equivalent 10s watchdog.
- calibrate_image: revert to datasheet band table. Narrow ±2 MHz
window had truncation bug placing 433/869 MHz operating freq
outside their own calibration windows. DS §9.2.1 confirms cal
is range-validity, not point-precision
- reset_agc: K_FOREVER mutex → K_MSEC(50). Was holding dispatcher
thread up to ~3 s if a long TX/RX held the SPI lock. Now bails
with WARN log; caller retries next maintenance interval.
every LBT-retried flood packet loses its priority (fixed)
witching between LBT and non-LBT mode (or any cad.mode change) could silently skip full reconfiguration and leave the radio in the wrong mode (fixed)
Add .gitattributes rules so .c/.h/.cpp/.hpp are always stored as LF
(prevents EOL drift from editors with autocrlf-true defaults), and
renormalize the 30 source files that had drifted to CRLF in the index.
Pure mechanical change — `git diff --ignore-cr-at-eol` is empty.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
-Removed broken §15.3 implicit-header workaround (wrong addresses, wrong bit, wrong condition)
-Initialize data->rx_boost_enabled from config->rx_boosted (DTS) instead of hard-coded false
-Re-apply RX gain after the first SetRx in lora_recv_async to match restart_rx / recv_duty_cycle
-Re-issue CalibrateImage(operating_freq) after Calibrate(ALL) to keep image-rejection band correct on EU868 / 433 MHz / 779 MHz
-rx_cb_gen converted from uint32_t to atomic_t — atomic_set/get/inc at all 13 sites; local snapshot is now atomic_val_t