@@ -203,6 +203,9 @@ All packets are sent over UDP to the configured aggregator. The magic number in
203203| ` 0xC5110001 ` | CSI Frame (ADR-018) | ~ 20 Hz | Variable | Raw I/Q per subcarrier per antenna |
204204| ` 0xC5110002 ` | Vitals Packet | 1 Hz | 32 bytes | Presence, breathing BPM, heart rate, fall flag, occupancy |
205205| ` 0xC5110004 ` | WASM Output | Event-driven | Variable | Custom events from WASM modules (u8 type + f32 value) |
206+ | ` RVAE ` (` 0x45415652 ` ) | Authenticated radio envelope v1 | Bounded worker | 92 or 128 bytes | Gateway-authenticated wrapper sent over UDP |
207+ | ` 0xC51100B1 ` | BLE Identity Evidence v1 | Bounded passive scan | 36 bytes inner payload | Rotating pseudonym, RSSI, TTL and evidence quality; never sent bare |
208+ | ` RVCS ` (` 0x53435652 ` ) | External Channel Sounding v1 | Companion-defined | 72 bytes inner payload | HMAC-authenticated phase and RTT primitives; never sent bare |
206209
207210### ADR-018 Binary Frame Format
208211
@@ -238,6 +241,92 @@ Offset Size Field
23824128 4 Reserved
239242```
240243
244+ ### Optional BLE and Bluetooth 6 companion path (ADR-341)
245+
246+ Both paths are disabled by default. ESP32-S3 can scan ordinary BLE advertising
247+ metadata and RSSI, but this firmware does ** not** claim that the S3 exposes raw
248+ CTE IQ or native Bluetooth 6 Channel Sounding.
249+
250+ The BLE path accepts only the RuView vendor service token authenticated with a
251+ provisioned 32-byte HMAC key. It discards the advertiser address and raw packet,
252+ then forwards a rotating eight-byte pseudonym with an explicit TTL. Ordinary
253+ iPhone background advertisements do not satisfy this contract and are not an
254+ identity source.
255+
256+ The Channel Sounding path uses a separate capable radio on UART2. Its fixed v1
257+ frame carries sample age, timing uncertainty, phase, RTT, frequency offset,
258+ quality, source session, procedure metadata, sequence, a domain-separated
259+ 128-bit HMAC tag and CRC32. The S3 validates these primitives. Both radio paths
260+ then enter a bounded queue and a second HMAC-protected gateway envelope carrying
261+ the node, random boot nonce, gateway sequence and receive time. Respiration
262+ inference and motion abstention happen on the host. See
263+ [ ` ADR-341 ` ] ( ../../docs/adr/ADR-341-authenticated-ble-and-channel-sounding-fusion.md )
264+ for exact layouts and the rvCSI mapping.
265+
266+ To compile the BLE scanner, first enable ESP-IDF Bluetooth, NimBLE, the observer
267+ role, ` CONFIG_BT_NIMBLE_EXT_SCAN=y ` , ` CONFIG_BT_NIMBLE_EXT_ADV=y ` , and
268+ ` CONFIG_BT_NIMBLE_TRANSPORT_EVT_SIZE=257 ` , then set
269+ ` CONFIG_BLE_IDENTITY_SCAN_ENABLE=y ` . The 50-byte token requires extended
270+ advertising and cannot fit in a legacy advertisement or scan response. The
271+ advertiser should keep its complete advertising data at or below 200 bytes;
272+ incomplete or truncated reports are rejected rather than authenticating a
273+ fragment. The
274+ default scan window is 50 ms per 1000 ms, or 5 percent duty. Firmware refuses
275+ settings above 25 percent. To compile the companion ingress on ESP32-S3, set
276+ ` CONFIG_CHANNEL_SOUNDING_INGRESS_ENABLE=y ` and verify the UART and GPIO choices
277+ against the specific board.
278+
279+ Runtime activation requires separate secrets and remains fail closed:
280+
281+ ``` bash
282+ python firmware/esp32-csi-node/provision.py --port COM7 \
283+ --ssid " YourSSID" --password " YourPass" --target-ip 192.168.1.20 \
284+ --ble-identity-enable 1 --ble-key-id 7 --ble-secret-file ble-key.bin \
285+ --cs-ingress-enable 1 --cs-key-id 9 --cs-source-id 270544960 \
286+ --cs-secret-file cs-key.bin --radio-envelope-key-id 12 \
287+ --radio-envelope-secret-file gateway-key.bin
288+ ```
289+
290+ Each of the three independent key files contains exactly 32 raw bytes or 64
291+ hexadecimal characters. Key
292+ contents are written to NVS but are never printed or persisted in the local
293+ additive provisioning-state JSON. Production devices also require secure boot,
294+ flash encryption and NVS encryption. Re-supply the secret files on every later
295+ provisioning run while either feature remains enabled. Provisioning fails closed
296+ instead of writing a fallback CSV when any secret is present.
297+
298+ The sensing server requires a fourth independent 32-byte host pseudonym key.
299+ On the first boot only, explicitly create the replay snapshot:
300+
301+ ``` bash
302+ RUVIEW_API_TOKEN=" replace-with-a-long-local-token" \
303+ cargo run -p wifi-densepose-sensing-server -- --source auto \
304+ --radio-gateway-node-id 7 --radio-gateway-key-id 12 \
305+ --radio-gateway-secret-file gateway-key.bin \
306+ --radio-host-pseudonym-secret-file host-pseudonym-key.bin \
307+ --radio-replay-state data/radio-replay-v2.json \
308+ --radio-initialize-replay-state \
309+ --radio-cs-key-id 9 --radio-cs-source-id 270544960 \
310+ --radio-cs-secret-file cs-key.bin
311+ ```
312+
313+ Omit ` --radio-initialize-replay-state ` on every subsequent boot. If an
314+ established replay snapshot is lost, rotate all gateway, advertiser, and
315+ companion keys before creating a replacement. Add independent gateways with a
316+ repeatable ` --radio-gateway NODE,KEY,SECRET_PATH ` argument. P4 respiration and
317+ P5 pseudonymous anchor WebSocket exports remain closed by default. Their local
318+ overrides require loopback binding, configured bearer or OAuth authentication,
319+ and a private audit log. The override is deployment authorization, not a
320+ subject consent receipt.
321+
322+ The gateway envelope authenticates integrity and source but does not encrypt
323+ UDP. Use WireGuard, DTLS, or an equivalent confidential transport if observers
324+ on the LAN must not see rotating pseudonyms or Channel Sounding primitives.
325+
326+ The included C and Rust replays are ** SYNTHETIC** . They are not evidence that a
327+ specific board, companion, room, respiration rate or identity-association
328+ accuracy has been validated.
329+
241330---
242331
243332## Building
0 commit comments