Files
ZephCore/zephcore/adapters/ble/ble_dfu.cpp
T
liquidraver 38f815a0a1 server roles, event loops, companion transports, storage and prefs.json
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
2026-09-25 08:34:21 +02:00

135 lines
5.5 KiB
C++

/*
* SPDX-License-Identifier: MIT
* ZephCore BLE - legacy Nordic/Adafruit buttonless DFU service
*
* Split out of ZephyrBLE.cpp. The service symbol name keeps the GATT handle
* order (see below); the jump goes through zephcore_ble_dfu_request().
*/
#include <zephyr/kernel.h>
#include <zephyr/sys/util.h>
#include <zephyr/logging/log.h>
LOG_MODULE_DECLARE(zephcore_ble, CONFIG_ZEPHCORE_BLE_LOG_LEVEL);
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/gatt.h>
#include <zephyr/bluetooth/uuid.h>
#include <zephyr/settings/settings.h>
#include <zephyr/sys/byteorder.h>
#include <string.h>
#include "ble_internal.h"
/* ========== Legacy Nordic/Adafruit buttonless DFU service ==========
*
* Mirrors Adafruit BLEDfu (Arduino MeshCore >=1.15.0) so the same DFU tools
* interoperate: a paired phone writes 0x01 to the control point and the device
* resets into the bootloader's BLE OTA mode. We use the *unbonded* OTA reset
* (GPREGRET 0xA8, same as `start ota`): the bootloader comes up as a fresh DFU
* target and the tool re-scans for it (the legacy buttonless flow). Adafruit's
* 0xB1 bonded-resume path needs SoftDevice peer-data enrollment we can't
* replicate on Zephyr, so the phone makes a fresh connection to the bootloader.
*
* The service exposes the FULL legacy DFU shape Adafruit ships (not just the
* control point): iOS's LegacyDFUService treats the DFU Packet (1532) char as
* a *required* characteristic — discovery fails (DFU "instantly fails") without
* it — and reads the DFU Revision (1534 = 0x0001 "app mode") to classify the
* device as an application that supports the buttonless jump (missing → the app
* can't identify the device → shows it nameless). Packet is a no-op here: the
* real image upload happens in the bootloader, not the running app. All three
* chars sit behind AUTHEN, matching the Arduino companion's service-wide MITM
* floor (`bledfu.setPermission(SECMODE_ENC_WITH_MITM, ...)`); a bonded phone's
* DFU app reuses the OS-level bond, an unpaired stranger is rejected.
*
* NOTE on the service symbol name: Zephyr registers static GATT services in
* the order ld's SORT_BY_NAME emits them — i.e. alphabetically by the symbol
* passed to BT_GATT_SERVICE_DEFINE. This service MUST sort *after*
* `secure_nus_svc` so the NUS attribute handles stay fixed; otherwise every
* NUS handle shifts and bonded phones with cached handles get ATT 0x03
* (Write Not Permitted) on the NUS RX write. Hence the `secure_nus_svc_dfu`
* name (a string sorts before its own extensions, so NUS keeps the low range).
*/
static struct bt_uuid_128 dfu_svc_uuid = BT_UUID_INIT_128(
BT_UUID_128_ENCODE(0x00001530, 0x1212, 0xefde, 0x1523, 0x785feabcd123));
static struct bt_uuid_128 dfu_ctrl_uuid = BT_UUID_INIT_128(
BT_UUID_128_ENCODE(0x00001531, 0x1212, 0xefde, 0x1523, 0x785feabcd123));
static struct bt_uuid_128 dfu_packet_uuid = BT_UUID_INIT_128(
BT_UUID_128_ENCODE(0x00001532, 0x1212, 0xefde, 0x1523, 0x785feabcd123));
static struct bt_uuid_128 dfu_revision_uuid = BT_UUID_INIT_128(
BT_UUID_128_ENCODE(0x00001534, 0x1212, 0xefde, 0x1523, 0x785feabcd123));
/* DFU Revision = 0x0001 (DFU_REV_APPMODE), little-endian — tells the DFU app
* "this is an application that supports the buttonless jump to bootloader". */
static const uint8_t dfu_revision[2] = { 0x01, 0x00 };
static void dfu_jump_work_fn(struct k_work *work);
static K_WORK_DELAYABLE_DEFINE(dfu_jump_work, dfu_jump_work_fn);
static void dfu_jump_work_fn(struct k_work *work)
{
ARG_UNUSED(work);
zephcore_ble_dfu_request(); /* sets GPREGRET + resets; never returns */
}
static ssize_t dfu_ctrl_write(struct bt_conn *conn, const struct bt_gatt_attr *attr,
const void *buf, uint16_t len, uint16_t offset, uint8_t flags)
{
ARG_UNUSED(conn);
ARG_UNUSED(attr);
ARG_UNUSED(offset);
ARG_UNUSED(flags);
/* Adafruit BLEDfu jump command: first byte 0x01 (1-2 byte write). */
if (len >= 1 && ((const uint8_t *)buf)[0] == 0x01) {
LOG_INF("buttonless DFU requested - rebooting to BLE OTA");
/* Defer so the ATT write response flushes and the DFU tool can
* arm its disconnect/rescan before we reset. */
k_work_schedule(&dfu_jump_work, K_MSEC(250));
}
return len;
}
static void dfu_ctrl_ccc_changed(const struct bt_gatt_attr *attr, uint16_t value)
{
ARG_UNUSED(attr);
ARG_UNUSED(value);
}
/* DFU Packet — present only so the DFU library's characteristic discovery
* succeeds; the actual image transfer happens in the bootloader, not here. */
static ssize_t dfu_packet_write(struct bt_conn *conn, const struct bt_gatt_attr *attr,
const void *buf, uint16_t len, uint16_t offset, uint8_t flags)
{
ARG_UNUSED(conn);
ARG_UNUSED(attr);
ARG_UNUSED(buf);
ARG_UNUSED(offset);
ARG_UNUSED(flags);
return len; /* no-op in app mode */
}
static ssize_t dfu_revision_read(struct bt_conn *conn, const struct bt_gatt_attr *attr,
void *buf, uint16_t len, uint16_t offset)
{
return bt_gatt_attr_read(conn, attr, buf, len, offset,
dfu_revision, sizeof(dfu_revision));
}
BT_GATT_SERVICE_DEFINE(secure_nus_svc_dfu,
BT_GATT_PRIMARY_SERVICE(&dfu_svc_uuid),
BT_GATT_CHARACTERISTIC(&dfu_ctrl_uuid.uuid,
BT_GATT_CHRC_WRITE | BT_GATT_CHRC_NOTIFY,
BT_GATT_PERM_WRITE_AUTHEN,
NULL, dfu_ctrl_write, NULL),
BT_GATT_CCC(dfu_ctrl_ccc_changed,
BT_GATT_PERM_READ_AUTHEN | BT_GATT_PERM_WRITE_AUTHEN),
BT_GATT_CHARACTERISTIC(&dfu_packet_uuid.uuid,
BT_GATT_CHRC_WRITE_WITHOUT_RESP,
BT_GATT_PERM_WRITE_AUTHEN,
NULL, dfu_packet_write, NULL),
BT_GATT_CHARACTERISTIC(&dfu_revision_uuid.uuid,
BT_GATT_CHRC_READ,
BT_GATT_PERM_READ_AUTHEN,
dfu_revision_read, NULL, NULL),
);