| Field | Value |
|---|---|
| Status | Accepted |
| Date | 2026-03-01 |
| Deciders | ruv |
| Relates to | ADR-029 (RuvSense Multistatic), ADR-030 (Persistent Field Model), ADR-031 (RuView Sensing-First RF), ADR-018 (ESP32 Implementation), ADR-012 (ESP32 Mesh) |
A security audit of the RuvSense multistatic sensing stack (ADR-029 through ADR-031) identified seven findings across the TDM synchronization layer, CSI frame transport, NDP injection, coherence gating, cross-room tracking, NVS credential handling, and firmware concurrency model. Three severity levels were assigned: HIGH (1 finding), MEDIUM (3 findings), LOW (3 findings).
The findings fall into three categories:
- Missing cryptographic authentication -- The TDM SyncBeacon and CSI frame formats lack any message authentication, allowing rogue nodes to inject spoofed beacons or frames into the mesh.
- Unbounded or unprotected resources -- The NDP injection path has no rate limiter, the coherence gate recalibration state has no timeout cap, and the cross-room transition log grows without bound.
- Memory safety on embedded targets -- NVS credential buffers are not zeroed after use, and static mutable globals in the CSI collector are accessed from both ESP32-S3 cores without synchronization.
The primary threat actor is a rogue ESP32 node on the same LAN subnet or within WiFi range of the mesh. The attack surface is the UDP broadcast plane used for sync beacons, CSI frames, and NDP injection.
| Threat | STRIDE | Impact | Exploitability |
|---|---|---|---|
| Fake SyncBeacon injection | Spoofing, Tampering | Full mesh desynchronization, no pose output | Low skill, rogue ESP32 on LAN |
| CSI frame spoofing | Spoofing, Tampering | Corrupted pose estimation, phantom occupants | Low skill, UDP packet injection |
| NDP RF flooding | Denial of Service | Channel saturation, loss of CSI data | Low skill, repeated NDP calls |
| Coherence gate stall | Denial of Service | Indefinite recalibration, frozen output | Requires sustained interference |
| Transition log exhaustion | Denial of Service | OOM on aggregator after extended operation | Passive, no attacker needed |
| Credential stack residue | Information Disclosure | WiFi password recoverable from RAM dump | Physical access to device |
| Dual-core data race | Tampering, DoS | Corrupted CSI frames, undefined behavior | Passive, no attacker needed |
- ESP32-S3 has limited CPU budget: cryptographic operations must complete within the 1 ms guard interval between TDM slots.
- HMAC-SHA256 on ESP32-S3 (hardware-accelerated via
mbedtls) completes in approximately 15 us for 24-byte payloads -- well within budget. - SipHash-2-4 completes in approximately 2 us for 64-byte payloads on ESP32-S3 -- suitable for per-frame MAC.
- No TLS or TCP is available on the sensing data path (UDP broadcast for latency).
- Pre-shared key (PSK) model is acceptable because all nodes in a mesh deployment are provisioned by the same operator.
Harden the multistatic mesh with six measures: beacon authentication, frame integrity, NDP rate limiting, bounded buffers, memory safety, and key management. All changes are backward-compatible: unauthenticated frames are accepted during a migration window controlled by a security_level NVS parameter.
Finding: The 16-byte SyncBeacon wire format (crates/wifi-densepose-hardware/src/esp32/tdm.rs) has no cryptographic authentication. A rogue node can inject fake beacons to desynchronize the TDM mesh.
Solution: Extend the SyncBeacon wire format from 16 bytes to 28 bytes by adding a 4-byte monotonic nonce and an 8-byte HMAC-SHA256 truncated tag.
Authenticated SyncBeacon wire format (28 bytes):
[0..7] cycle_id (LE u64)
[8..11] cycle_period_us (LE u32)
[12..13] drift_correction (LE i16)
[14..15] reserved
[16..19] nonce (LE u32, monotonically increasing)
[20..27] hmac_tag (HMAC-SHA256 truncated to 8 bytes)
HMAC computation:
key = 16-byte pre-shared mesh key (stored in NVS, namespace "mesh_sec")
message = beacon[0..20] (first 20 bytes: payload + nonce)
tag = HMAC-SHA256(key, message)[0..8] (truncated to 8 bytes)
Nonce and replay protection:
- The coordinator maintains a monotonically increasing 32-bit nonce counter, incremented on every beacon.
- Each receiver maintains a
last_accepted_nonceper sender. A beacon is accepted only ifnonce > last_accepted_nonce - REPLAY_WINDOW, whereREPLAY_WINDOW = 16(accounts for packet reordering over UDP). - Nonce overflow (after 2^32 beacons at 20 Hz = ~6.8 years) triggers a mandatory key rotation.
Implementation location: crates/wifi-densepose-hardware/src/esp32/tdm.rs -- extend SyncBeacon::to_bytes() and SyncBeacon::from_bytes() to produce/consume the 28-byte authenticated format. Add SyncBeacon::verify() method.
Finding: The ADR-018 CSI frame format has no cryptographic MAC. Frames can be spoofed or tampered with in transit.
Solution: Add an 8-byte SipHash-2-4 tag to the CSI frame header. SipHash is chosen over HMAC-SHA256 for per-frame MAC because it is 7x faster on ESP32 for short messages (approximately 2 us vs 15 us) and provides sufficient integrity for non-secret data.
Extended CSI frame header (28 bytes, was 20):
[0..3] Magic: 0xC5110002 (bumped from 0xC5110001 to signal auth)
[4] Node ID
[5] Number of antennas
[6..7] Number of subcarriers (LE u16)
[8..11] Frequency MHz (LE u32)
[12..15] Sequence number (LE u32)
[16] RSSI (i8)
[17] Noise floor (i8)
[18..19] Reserved
[20..27] siphash_tag (SipHash-2-4 over [0..20] + IQ data)
SipHash key derivation:
siphash_key = HMAC-SHA256(mesh_key, "csi-frame-siphash")[0..16]
The SipHash key is derived once at boot from the mesh key and cached in memory.
Implementation locations:
firmware/esp32-csi-node/main/csi_collector.c-- compute SipHash tag incsi_serialize_frame(), bump magic constant.crates/wifi-densepose-hardware/src/esp32/-- add frame verification in the aggregator's frame parser.
Finding: csi_inject_ndp_frame() in firmware/esp32-csi-node/main/csi_collector.c has no rate limiter. Uncontrolled NDP injection can flood the RF channel.
Solution: Token-bucket rate limiter with configurable parameters stored in NVS.
// Token bucket parameters (defaults)
#define NDP_RATE_MAX_TOKENS 20 // burst capacity
#define NDP_RATE_REFILL_HZ 20 // sustained rate: 20 NDP/sec
#define NDP_RATE_REFILL_US (1000000 / NDP_RATE_REFILL_HZ)
typedef struct {
uint32_t tokens; // current token count
uint32_t max_tokens; // bucket capacity
uint32_t refill_interval_us; // microseconds per token
int64_t last_refill_us; // last refill timestamp
} ndp_rate_limiter_t;csi_inject_ndp_frame() returns ESP_ERR_NOT_ALLOWED when the bucket is empty. The rate limiter parameters are configurable via NVS keys ndp_max_tokens and ndp_refill_hz.
Implementation location: firmware/esp32-csi-node/main/csi_collector.c -- add ndp_rate_limiter_t state and check in csi_inject_ndp_frame().
Finding: The Recalibrate state in crates/wifi-densepose-signal/src/ruvsense/coherence_gate.rs can be held indefinitely. A sustained interference source could keep the system in perpetual recalibration, preventing any output.
Solution: Add a configurable max_recalibrate_duration to GatePolicyConfig (default: 30 seconds = 600 frames at 20 Hz). When the recalibration duration exceeds this cap, the gate transitions to a ForcedAccept state with inflated noise (10x), allowing degraded-but-available output.
pub enum GateDecision {
Accept { noise_multiplier: f32 },
PredictOnly,
Reject,
Recalibrate { stale_frames: u64 },
/// Recalibration timed out. Accept with heavily inflated noise.
ForcedAccept { noise_multiplier: f32, stale_frames: u64 },
}New config field:
pub struct GatePolicyConfig {
// ... existing fields ...
/// Maximum frames in Recalibrate before forcing accept. Default: 600 (30s at 20Hz).
pub max_recalibrate_frames: u64,
/// Noise multiplier for ForcedAccept. Default: 10.0.
pub forced_accept_noise: f32,
}Implementation location: crates/wifi-densepose-signal/src/ruvsense/coherence_gate.rs -- extend GateDecision enum, modify GatePolicy::evaluate().
Finding: CrossRoomTracker in crates/wifi-densepose-signal/src/ruvsense/cross_room.rs stores transitions in an unbounded Vec<TransitionEvent>. Over extended operation (days/weeks), this grows without limit.
Solution: Replace the transitions: Vec<TransitionEvent> with a ring buffer that evicts the oldest entry when capacity is reached.
pub struct CrossRoomConfig {
// ... existing fields ...
/// Maximum transitions retained in the ring buffer. Default: 1000.
pub max_transitions: usize,
}The ring buffer is implemented as a VecDeque<TransitionEvent> with a capacity check on push. When transitions.len() >= max_transitions, transitions.pop_front() before pushing. This preserves the append-only audit trail semantics (events are never mutated, only evicted by age).
Implementation location: crates/wifi-densepose-signal/src/ruvsense/cross_room.rs -- change transitions: Vec<TransitionEvent> to transitions: VecDeque<TransitionEvent>, add eviction logic in match_entry().
Finding: nvs_config_load() in firmware/esp32-csi-node/main/nvs_config.c reads the WiFi password into a stack buffer buf which is not zeroed after use. On ESP32-S3, stack memory is not automatically cleared, leaving credentials recoverable via physical memory dump.
Solution: Zero the stack buffer after each NVS string read using explicit_bzero() (available in ESP-IDF via newlib). If explicit_bzero is unavailable, use memset with a volatile pointer to prevent compiler optimization.
/* After each nvs_get_str that may contain credentials: */
explicit_bzero(buf, sizeof(buf));
/* Portable fallback: */
static void secure_zero(void *ptr, size_t len) {
volatile unsigned char *p = (volatile unsigned char *)ptr;
while (len--) { *p++ = 0; }
}Apply to all three nvs_get_str call sites in nvs_config_load() (ssid, password, target_ip).
Implementation location: firmware/esp32-csi-node/main/nvs_config.c -- add explicit_bzero(buf, sizeof(buf)) after each nvs_get_str block.
Finding: csi_collector.c uses static mutable globals (s_sequence, s_cb_count, s_send_ok, s_send_fail, s_hop_index) accessed from both cores of the ESP32-S3 without synchronization. The CSI callback runs on the WiFi task (pinned to core 0 by default), while the main application and hop timer may run on core 1.
Solution: Use C11 _Atomic qualifiers for all shared counters, and a FreeRTOS mutex for the hop table state which requires multi-variable consistency.
#include <stdatomic.h>
static _Atomic uint32_t s_sequence = 0;
static _Atomic uint32_t s_cb_count = 0;
static _Atomic uint32_t s_send_ok = 0;
static _Atomic uint32_t s_send_fail = 0;
static _Atomic uint8_t s_hop_index = 0;
/* Hop table protected by mutex (multi-variable consistency) */
static SemaphoreHandle_t s_hop_mutex = NULL;The mutex is created in csi_collector_init() and taken/released around hop table reads in csi_hop_next_channel() and writes in csi_collector_set_hop_table().
Implementation location: firmware/esp32-csi-node/main/csi_collector.c -- add _Atomic qualifiers, create and use s_hop_mutex.
All cryptographic operations use a single 16-byte pre-shared mesh key stored in NVS.
Provisioning:
NVS namespace: "mesh_sec"
NVS key: "mesh_key"
NVS type: blob (16 bytes)
The key is provisioned during node setup via the existing scripts/provision.py tool, which is extended to generate a random 16-byte key and flash it to all nodes in a deployment.
Key derivation:
beacon_hmac_key = mesh_key (direct, 16 bytes)
frame_siphash_key = HMAC-SHA256(mesh_key, "csi-frame-siphash")[0..16] (derived, 16 bytes)
Key rotation:
- Manual rotation via management command:
provision.py rotate-key --deployment <id>. - The coordinator broadcasts a key rotation event (signed with the old key) containing the new key encrypted with the old key.
- Nodes accept the new key and switch after confirming the next beacon is signed with the new key.
- Rotation is recommended every 90 days or after any node is decommissioned.
Security level NVS parameter:
NVS key: "sec_level"
Values:
0 = permissive (accept unauthenticated frames, log warning)
1 = transitional (accept both authenticated and unauthenticated)
2 = enforcing (reject unauthenticated frames)
Default: 1 (transitional, for backward compatibility during rollout)
| File | Change | Priority |
|---|---|---|
crates/wifi-densepose-hardware/src/esp32/tdm.rs |
Extend SyncBeacon to 28-byte authenticated format, add verify(), nonce tracking, replay window |
P0 |
firmware/esp32-csi-node/main/nvs_config.c |
Add mesh_key and sec_level NVS reads |
P0 |
firmware/esp32-csi-node/main/nvs_config.h |
Add mesh_key[16] and sec_level to nvs_config_t |
P0 |
scripts/provision.py |
Add --mesh-key generation and rotate-key command |
P0 |
| File | Change | Priority |
|---|---|---|
firmware/esp32-csi-node/main/csi_collector.c |
Add SipHash-2-4 tag to frame serialization, NDP rate limiter, _Atomic qualifiers, hop mutex |
P1 |
firmware/esp32-csi-node/main/csi_collector.h |
Update CSI_HEADER_SIZE to 28, add rate limiter config |
P1 |
crates/wifi-densepose-hardware/src/esp32/ |
Add frame verification in aggregator parser | P1 |
| File | Change | Priority |
|---|---|---|
crates/wifi-densepose-signal/src/ruvsense/cross_room.rs |
Replace Vec with VecDeque, add max_transitions config |
P1 |
crates/wifi-densepose-signal/src/ruvsense/coherence_gate.rs |
Add ForcedAccept variant, max_recalibrate_frames config |
P1 |
| File | Change | Priority |
|---|---|---|
firmware/esp32-csi-node/main/nvs_config.c |
Add explicit_bzero() after credential reads |
P2 |
firmware/esp32-csi-node/main/csi_collector.c |
_Atomic counters, s_hop_mutex (if not done in Phase 2) |
P2 |
| ID | Criterion | Test Method |
|---|---|---|
| H1-1 | SyncBeacon::to_bytes() produces 28-byte output with valid HMAC tag |
Unit test: serialize, verify tag matches recomputed HMAC |
| H1-2 | SyncBeacon::verify() rejects beacons with incorrect HMAC tag |
Unit test: flip one bit in tag, verify returns Err |
| H1-3 | SyncBeacon::verify() rejects beacons with replayed nonce outside window |
Unit test: submit nonce = last_accepted - REPLAY_WINDOW - 1, verify rejection |
| H1-4 | SyncBeacon::verify() accepts beacons within replay window |
Unit test: submit nonce = last_accepted - REPLAY_WINDOW + 1, verify acceptance |
| H1-5 | Coordinator nonce increments monotonically across cycles | Unit test: call begin_cycle() 100 times, verify strict monotonicity |
| H1-6 | Backward compatibility: sec_level=0 accepts unauthenticated 16-byte beacons |
Integration test: mixed old/new nodes |
| ID | Criterion | Test Method |
|---|---|---|
| M3-1 | CSI frame with magic 0xC5110002 includes valid 8-byte SipHash tag |
Unit test: serialize frame, verify tag |
| M3-2 | Frame verification rejects frames with tampered IQ data | Unit test: flip one byte in IQ payload, verify rejection |
| M3-3 | SipHash computation completes in < 10 us on ESP32-S3 | Benchmark on target hardware |
| M3-4 | Frame parser accepts old magic 0xC5110001 when sec_level < 2 |
Unit test: backward compatibility |
| ID | Criterion | Test Method |
|---|---|---|
| M4-1 | csi_inject_ndp_frame() succeeds for first max_tokens calls |
Unit test: call 20 times rapidly, all succeed |
| M4-2 | Call 21 returns ESP_ERR_NOT_ALLOWED when bucket is empty |
Unit test: exhaust bucket, verify error |
| M4-3 | Bucket refills at configured rate | Unit test: exhaust, wait refill_interval_us, verify one token available |
| M4-4 | NVS override of ndp_max_tokens and ndp_refill_hz is respected |
Integration test: set NVS values, verify behavior |
| ID | Criterion | Test Method |
|---|---|---|
| M5-1 | GatePolicy::evaluate() returns Recalibrate at max_stale_frames |
Unit test: existing behavior preserved |
| M5-2 | GatePolicy::evaluate() returns ForcedAccept at max_recalibrate_frames |
Unit test: feed max_recalibrate_frames + 1 low-coherence frames |
| M5-3 | ForcedAccept noise multiplier equals forced_accept_noise (default 10.0) |
Unit test: verify noise_multiplier field |
| M5-4 | Default max_recalibrate_frames = 600 (30s at 20 Hz) |
Unit test: verify default config |
| ID | Criterion | Test Method |
|---|---|---|
| L1-1 | CrossRoomTracker::transition_count() never exceeds max_transitions |
Unit test: insert 1500 transitions with max_transitions=1000, verify count=1000 |
| L1-2 | Oldest transitions are evicted first (FIFO) | Unit test: verify first transition is the (N-999)th inserted |
| L1-3 | Default max_transitions = 1000 |
Unit test: verify default config |
| ID | Criterion | Test Method |
|---|---|---|
| L4-1 | Stack buffer buf is zeroed after each nvs_get_str call |
Code review + static analysis (no runtime test feasible) |
| L4-2 | explicit_bzero is used (not plain memset) to prevent compiler optimization |
Code review: verify function call is explicit_bzero or volatile-pointer pattern |
| ID | Criterion | Test Method |
|---|---|---|
| L5-1 | s_sequence, s_cb_count, s_send_ok, s_send_fail are declared _Atomic |
Code review |
| L5-2 | s_hop_mutex is created in csi_collector_init() |
Code review + integration test: init succeeds |
| L5-3 | csi_hop_next_channel() and csi_collector_set_hop_table() acquire/release mutex |
Code review |
| L5-4 | No data races detected under ThreadSanitizer (host-side test build) | cargo test with TSAN on host (for Rust side); QEMU or hardware test for C side |
- Rogue node protection: HMAC-authenticated beacons prevent mesh desynchronization by unauthorized nodes.
- Frame integrity: SipHash MAC detects in-transit tampering of CSI data, preventing phantom occupant injection.
- RF availability: Token-bucket rate limiter prevents NDP flooding from consuming the shared wireless medium.
- Bounded memory: Ring buffer on transition log and timeout cap on recalibration prevent resource exhaustion during long-running deployments.
- Credential hygiene: Zeroed buffers reduce the window for credential recovery from physical memory access.
- Thread safety: Atomic operations and mutex eliminate undefined behavior on dual-core ESP32-S3.
- Backward compatible:
sec_levelparameter allows gradual rollout without breaking existing deployments.
- 12 bytes added to SyncBeacon: 28 bytes vs 16 bytes (75% increase, but still fits in a single UDP packet with room to spare).
- 8 bytes added to CSI frame header: 28 bytes vs 20 bytes (40% increase in header; negligible relative to IQ payload of 128-512 bytes).
- CPU overhead: HMAC-SHA256 adds approximately 15 us per beacon (once per 50 ms cycle = 0.03% CPU). SipHash adds approximately 2 us per frame (at 100 Hz = 0.02% CPU).
- Key management complexity: Mesh key must be provisioned to all nodes and rotated periodically. Lost key requires re-provisioning all nodes.
- Mutex contention: Hop table mutex may add up to 1 us latency to channel hop path. Within guard interval budget.
| Risk | Probability | Impact | Mitigation |
|---|---|---|---|
| HMAC computation exceeds guard interval on older ESP32 (non-S3) | Low | Beacon authentication unusable on legacy hardware | Hardware-accelerated SHA256 is available on all ESP32 variants; benchmark confirms < 50 us |
| Key compromise via side-channel on ESP32 | Very Low | Full mesh authentication bypass | Keys stored in eFuse (ESP32-S3 supports) or encrypted NVS partition |
| ForcedAccept mode produces unacceptably noisy poses | Medium | Degraded pose quality during sustained interference | 10x noise multiplier is configurable; operator can increase or disable |
| SipHash collision (64-bit tag) | Very Low | Single forged frame accepted | 2^-64 probability per frame; attacker cannot iterate at protocol speed |
The original ADR-032 design (Sections 2.1--2.2) uses manual HMAC-SHA256 and SipHash-2-4 over plain UDP. While correct and efficient on constrained ESP32 hardware, this approach has operational drawbacks:
- Manual key rotation: Requires custom key exchange protocol and coordinator broadcast.
- No congestion control: Plain UDP has no backpressure; burst CSI traffic can overwhelm the aggregator.
- No connection migration: Node roaming (e.g., repositioning an ESP32) requires manual reconnect.
- Duplicate replay-window code: Custom nonce tracking duplicates QUIC's built-in replay protection.
For aggregator-class nodes (Raspberry Pi, x86 gateway) that have sufficient CPU and memory, replace the manual crypto layer with midstreamer-quic v0.1.0, which provides:
| Capability | Manual (ADR-032 original) | QUIC (midstreamer-quic) |
|---|---|---|
| Authentication | HMAC-SHA256 truncated 8B | TLS 1.3 AEAD (AES-128-GCM) |
| Frame integrity | SipHash-2-4 tag | QUIC packet-level AEAD |
| Replay protection | Manual nonce + window | QUIC packet numbers (monotonic) |
| Key rotation | Custom coordinator broadcast | TLS 1.3 KeyUpdate message |
| Congestion control | None | QUIC cubic/BBR |
| Connection migration | Not supported | QUIC connection ID migration |
| Multi-stream | N/A | QUIC streams (beacon, CSI, control) |
Constrained devices (ESP32-S3) retain the manual crypto path from Sections 2.1--2.2 as a fallback. The SecurityMode enum selects the transport:
pub enum SecurityMode {
/// Manual HMAC/SipHash over plain UDP (ESP32-S3, ADR-032 original).
ManualCrypto,
/// QUIC transport with TLS 1.3 (aggregator-class nodes).
QuicTransport,
}Three dedicated QUIC streams separate traffic by priority:
| Stream ID | Purpose | Direction | Priority |
|---|---|---|---|
| 0 | Sync beacons | Coordinator -> Nodes | Highest (TDM timing-critical) |
| 1 | CSI frames | Nodes -> Aggregator | High (sensing data) |
| 2 | Control plane | Bidirectional | Normal (config, key rotation, health) |
Beyond QUIC transport, three additional midstreamer crates enhance the sensing pipeline:
-
midstreamer-schedulerv0.1.0 -- Replaces manual timer-based TDM slot scheduling with an ultra-low-latency real-time task scheduler. Provides deterministic slot firing with sub-microsecond jitter. -
midstreamer-temporal-comparev0.1.0 -- Enhances gesture DTW matching (ADR-030 Tier 6) with temporal sequence comparison primitives. Provides optimized Sakoe-Chiba band DTW, LCS, and edit-distance kernels. -
midstreamer-attractorv0.1.0 -- Enhances longitudinal drift detection (ADR-030 Tier 4) with dynamical systems analysis. Detects phase-space attractor shifts that indicate biomechanical regime changes before they manifest as simple metric drift.
The QUIC transport layer is additive, not a replacement:
- ESP32-S3 nodes: Continue using manual HMAC/SipHash over UDP (Sections 2.1--2.2). These devices lack the memory for a full TLS 1.3 stack.
- Aggregator nodes: Use
midstreamer-quicby default. Fall back to manual crypto if QUIC handshake fails (e.g., network partitions). - Mixed deployments: The aggregator auto-detects whether an incoming connection is QUIC (by TLS ClientHello) or plain UDP (by magic byte) and routes accordingly.
| ID | Criterion | Test Method |
|---|---|---|
| Q-1 | QUIC connection established between two nodes within 100ms | Integration test: connect, measure handshake time |
| Q-2 | Beacon stream delivers beacons with < 1ms jitter | Unit test: send 1000 beacons, measure inter-arrival variance |
| Q-3 | CSI stream achieves >= 95% of plain UDP throughput | Benchmark: criterion comparison |
| Q-4 | Connection migration succeeds after simulated IP change | Integration test: rebind, verify stream continuity |
| Q-5 | Fallback to manual crypto when QUIC unavailable | Unit test: reject QUIC, verify ManualCrypto path |
| Q-6 | SecurityMode::ManualCrypto produces identical wire format to ADR-032 original | Unit test: byte-level comparison |
| ADR | Relationship |
|---|---|
| ADR-029 (RuvSense Multistatic) | Hardened: TDM beacon and CSI frame authentication, NDP rate limiting, QUIC transport |
| ADR-030 (Persistent Field Model) | Protected: Coherence gate timeout; transition log bounded; gesture DTW enhanced (midstreamer-temporal-compare); drift detection enhanced (midstreamer-attractor) |
| ADR-031 (RuView RF Mode) | Hardened: Authenticated beacons protect cross-viewpoint synchronization via QUIC streams |
| ADR-018 (ESP32 Implementation) | Extended: CSI frame header bumped to v2 with SipHash tag; backward-compatible magic check |
| ADR-012 (ESP32 Mesh) | Hardened: Mesh key management, NVS credential zeroing, atomic firmware state, QUIC connection migration |
- Aumasson, J.-P. & Bernstein, D.J. (2012). "SipHash: a fast short-input PRF." INDOCRYPT 2012.
- Krawczyk, H. et al. (1997). "HMAC: Keyed-Hashing for Message Authentication." RFC 2104.
- ESP-IDF mbedtls SHA256 hardware acceleration. Espressif Documentation.
- Espressif. "ESP32-S3 Technical Reference Manual." Section 26: SHA Accelerator.
- Turner, J. (2006). "Token Bucket Rate Limiting." RFC 2697 (adapted).
- ADR-029 through ADR-031 (internal).
midstreamer-quicv0.1.0 -- QUIC multi-stream support. crates.io.midstreamer-schedulerv0.1.0 -- Ultra-low-latency real-time task scheduler. crates.io.midstreamer-temporal-comparev0.1.0 -- Temporal sequence comparison. crates.io.midstreamer-attractorv0.1.0 -- Dynamical systems analysis. crates.io.- Iyengar, J. & Thomson, M. (2021). "QUIC: A UDP-Based Multiplexed and Secure Transport." RFC 9000.