mirror of
https://github.com/ruvnet/RuView
synced 2026-06-09 10:13:17 +00:00
docs(adr-118): BFLD — Beamforming Feedback Layer for Detection (6 ADRs + research bundle)
Introduce the Beamforming Feedback Layer for Detection: the RuView safety layer
that ingests WiFi BFI, measures identity-leakage risk, and structurally prevents
identity-correlated data from leaving the node by default.
ADRs (6):
- ADR-118: umbrella decision, crate scaffolding, 6-phase rollout (~10.5 wk)
- ADR-119: BfldFrame wire format, magic 0xBF1D_0001, deterministic serialization
- ADR-120: 4 privacy classes, BLAKE3 keyed-hash rotation, #[must_classify] default-deny
- ADR-121: 9-feature identity-risk scoring, coherence gate with hysteresis
- ADR-122: 6 HA entities, 3 Matter clusters, mosquitto ACL, cognitum-v0 federation
- ADR-123: Pi 5 / Nexmon production capture, AX210 dev path, ESP32-S3 self-only fallback
Research bundle (docs/research/BFLD/, 13,544 words):
- SOTA survey covering BFId (KIT, ACM CCS 2025) and LeakyBeam (NDSS 2025)
- Architectural soul: defensive sensing primitive, not surveillance lens
- Six-adversary threat model with attack trees and mitigations
- Privacy-gating mechanics with structural cross-site isolation proof
- Automation/integration surface (HA, Matter, MQTT, federation)
- Concrete implementation plan with reuse map
- Evaluation strategy with red-team protocol on KIT BFId dataset
- Draft ADR, GitHub issue, and public gist
Three structural invariants enforced by the type system, not policy:
I1 — Raw BFI never exits the node
I2 — Identity embedding is in-RAM-only (no Serialize impl)
I3 — Cross-site identity correlation is cryptographically impossible
(per-site BLAKE3 keyed-hash with daily epoch rotation)
References:
https://publikationen.bibliothek.kit.edu/1000185756 (BFId)
https://www.ndss-symposium.org/wp-content/uploads/2025-5-paper.pdf (LeakyBeam)
Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
@@ -0,0 +1,163 @@
|
||||
# ADR-119: BFLD Frame Format and Wire Protocol
|
||||
|
||||
| Field | Value |
|
||||
|-------|-------|
|
||||
| **Status** | Proposed |
|
||||
| **Date** | 2026-05-24 |
|
||||
| **Deciders** | ruv |
|
||||
| **Parent** | [ADR-118](ADR-118-bfld-beamforming-feedback-layer-for-detection.md) |
|
||||
| **Relates to** | [ADR-028](ADR-028-esp32-capability-audit.md) (witness/deterministic proof), [ADR-095](ADR-095-rvcsi-edge-rf-sensing-platform.md) (rvCSI `CsiFrame` schema) |
|
||||
| **Tracking issue** | TBD |
|
||||
|
||||
---
|
||||
|
||||
## 1. Context
|
||||
|
||||
The BFLD pipeline (ADR-118) emits an over-the-wire `BfldFrame` consumed by the RuView aggregator, HA bridge, and witness bundle. The frame must be:
|
||||
|
||||
1. **Deterministic** — identical input ⇒ bit-identical output, so witness hashes survive verification (ADR-028 pattern).
|
||||
2. **Self-describing** — magic + version so future BFLD revisions don't silently corrupt aggregator state.
|
||||
3. **Privacy-classified at the byte level** — the receiver must know the data class before it even parses the payload, so it can drop frames it isn't authorized to handle.
|
||||
4. **Compact** — BFLD nodes may emit at up to 10 Hz; the frame must be small enough for unsharded MQTT and ESP-NOW transport.
|
||||
5. **Endianness-stable** — captures from x86_64 (ruvultra), aarch64 (cognitum-v0, Pi 5 cluster), and Xtensa (ESP32-S3) must produce identical bytes.
|
||||
|
||||
The existing rvCSI `CsiFrame` (ADR-095) is the closest precedent. BFLD reuses the same little-endian convention and the same "validate-before-FFI" posture.
|
||||
|
||||
---
|
||||
|
||||
## 2. Decision
|
||||
|
||||
### 2.1 `BfldFrame` header (40 bytes, little-endian, packed)
|
||||
|
||||
```rust
|
||||
#[repr(C, packed)]
|
||||
pub struct BfldFrameHeader {
|
||||
pub magic: u32, // 0xBF1D_0001
|
||||
pub version: u16, // 1
|
||||
pub flags: u16, // bit0=has_csi_delta, bit1=privacy_mode, bit2-15 reserved
|
||||
pub timestamp_ns: u64, // monotonic capture clock
|
||||
|
||||
pub ap_hash: [u8; 16], // BLAKE3-keyed(site_salt, ap_mac)[0..16]
|
||||
pub sta_hash: [u8; 16], // BLAKE3-keyed(site_salt ‖ day_epoch, sta_mac)[0..16]
|
||||
pub session_id: [u8; 16], // ephemeral, rotated on capture-session boundary
|
||||
|
||||
pub channel: u16, // 802.11 channel number
|
||||
pub bandwidth_mhz: u16, // 20 | 40 | 80 | 160
|
||||
pub rssi_dbm: i16,
|
||||
pub noise_floor_dbm: i16,
|
||||
|
||||
pub n_subcarriers: u16,
|
||||
pub n_tx: u8,
|
||||
pub n_rx: u8,
|
||||
pub quantization: u8, // 0=f32, 1=i16, 2=i8, 3=packed (4-bit nibbles)
|
||||
pub privacy_class: u8, // 0=raw, 1=derived, 2=anonymous, 3=restricted (default 2)
|
||||
|
||||
pub payload_len: u32,
|
||||
pub payload_crc32: u32, // CRC-32/ISO-HDLC over payload bytes only
|
||||
}
|
||||
```
|
||||
|
||||
Total header size: 40 bytes (validated by `static_assertions::const_assert_eq!`).
|
||||
|
||||
### 2.2 Payload structure
|
||||
|
||||
Payload is a length-prefixed sequence of typed sections in this exact order:
|
||||
|
||||
```
|
||||
payload = compressed_angle_matrix
|
||||
‖ amplitude_proxy
|
||||
‖ phase_proxy
|
||||
‖ snr_vector
|
||||
‖ optional_csi_delta (present iff flags.bit0 set)
|
||||
‖ optional_vendor_extension (length 0 allowed)
|
||||
```
|
||||
|
||||
Each section is `[u32 len_le][bytes...]`. The CRC32 covers all section bytes including length prefixes, but **not** the header.
|
||||
|
||||
### 2.3 Privacy-class gating at serialization
|
||||
|
||||
The serializer enforces these rules **before** writing any payload bytes:
|
||||
|
||||
| `privacy_class` | `compressed_angle_matrix` | Identity-derived fields | Notes |
|
||||
|-----------------|---------------------------|-------------------------|-------|
|
||||
| 0 (`raw`) | full | full | **Local-only**, never serialized to a network sink |
|
||||
| 1 (`derived`) | downsampled to 8-bit, top-k subcarriers | full | Operator-acknowledged research mode |
|
||||
| 2 (`anonymous`, **default**) | absent (zero-length section) | absent | Production default |
|
||||
| 3 (`restricted`) | absent | absent + diagnostic-only | Equivalent to class 2 + suppresses `identity_risk_score` on the bus |
|
||||
|
||||
The serializer returns `Err(BfldError::PrivacyViolation)` if the caller attempts to publish a class-0 frame through a network sink. This is enforced by a sink-type marker trait (`LocalSink` vs `NetworkSink`).
|
||||
|
||||
### 2.4 Deterministic serialization
|
||||
|
||||
Three guarantees:
|
||||
|
||||
1. **Field order is fixed** by `#[repr(C, packed)]`.
|
||||
2. **Float quantization is canonical** — `quantization` byte values 1/2/3 use specified round-half-to-even with documented saturation; f32 (value 0) is forbidden over the wire (local-only).
|
||||
3. **CRC32 is computed last**, after all section bytes are placed.
|
||||
|
||||
The witness test in `tests/determinism.rs` captures a 200-frame BFI fixture, serializes it 1,000 times across two threads, and verifies the BLAKE3 of the resulting byte stream is bit-identical.
|
||||
|
||||
### 2.5 Magic value rationale
|
||||
|
||||
`0xBF1D_0001` is chosen so that `bf1d` reads as "BFLD" in hex-dump output, easing wireshark / xxd debugging. The final `0001` is the major version; minor revisions bump `version` field.
|
||||
|
||||
---
|
||||
|
||||
## 3. Consequences
|
||||
|
||||
### Positive
|
||||
|
||||
- 40-byte header + compact payload fits comfortably in a 1500-byte MTU even at 4×4 MIMO with 256 subcarriers.
|
||||
- Serialization is `#[no_std]` compatible — same code can run on ESP32-S3 (when ESP-NOW transport is added under ADR-123 P2).
|
||||
- Witness-bundle integration is direct: the existing `archive/v1/data/proof/verify.py` pattern extends to a `bfld_verify.py` that consumes the same SHA-256 expected-hash file format.
|
||||
|
||||
### Negative
|
||||
|
||||
- `#[repr(C, packed)]` on the header means consumers must use `read_unaligned` — small ergonomic cost, mitigated by a `#[derive(BfldFrameAccess)]` proc-macro.
|
||||
- Reserved flag bits 2-15 lock in future-extension order; any new bit assignment is a version bump.
|
||||
|
||||
### Neutral
|
||||
|
||||
- The vendor-extension section allows downstream RuView cogs (e.g., `cog-pose-estimation`) to attach metadata without a header change, at the cost of CRC scope creep. Vendor sections are explicitly outside the witness hash.
|
||||
|
||||
---
|
||||
|
||||
## 4. Alternatives Considered
|
||||
|
||||
### Alt 1: Protobuf / FlatBuffers
|
||||
|
||||
Rejected: schema evolution overhead, witness-hash instability across protoc versions, ~3× wire bloat for the small fixed-shape fields.
|
||||
|
||||
### Alt 2: CBOR
|
||||
|
||||
Rejected: deterministic CBOR (RFC 8949 §4.2) is achievable but the parser surface is large and tag handling is a footgun for the `no_std` ESP32 path.
|
||||
|
||||
### Alt 3: Variable-width magic / no magic
|
||||
|
||||
Rejected: receivers must distinguish BFLD frames from rvCSI `CsiFrame` and other RuView payloads on shared transports.
|
||||
|
||||
### Alt 4: Move CRC32 to header
|
||||
|
||||
Rejected: CRC must be computed after the payload, so its value would otherwise force a header rewrite; placing it last avoids a buffer-pass-back.
|
||||
|
||||
---
|
||||
|
||||
## 5. Acceptance Criteria
|
||||
|
||||
- [ ] **AC1**: `BfldFrameHeader` size is exactly 40 bytes on x86_64, aarch64, and xtensa-esp32s3.
|
||||
- [ ] **AC2**: 1,000 serializations of a fixed `BfiCapture` fixture produce a bit-identical BLAKE3 hash.
|
||||
- [ ] **AC3**: `privacy_class = 0` frame returned through `NetworkSink::publish()` returns `Err(BfldError::PrivacyViolation)`.
|
||||
- [ ] **AC4**: Payload CRC32 mismatch causes `BfldFrame::parse()` to return `Err(BfldError::Crc)` without exposing partial payload state.
|
||||
- [ ] **AC5**: Round-trip serialize/parse preserves all header fields exactly.
|
||||
- [ ] **AC6**: A frame with `flags.bit0 = 0` (no CSI delta) and an unexpected CSI-delta section is rejected.
|
||||
- [ ] **AC7**: Bench: serialization throughput ≥ 50k frames/sec on a 2025-era M1/M2 / Pi 5 core.
|
||||
|
||||
---
|
||||
|
||||
## 6. References
|
||||
|
||||
- ADR-118 §2 (umbrella decision)
|
||||
- ADR-095 `CsiFrame` (`vendor/rvcsi/crates/rvcsi-core/src/frame.rs`)
|
||||
- CRC-32/ISO-HDLC: `crc = "3"` crate
|
||||
- BLAKE3 keyed mode: `blake3 = "1.5"`
|
||||
- IEEE 802.11-2020 §19.3.12 (Compressed Beamforming Report)
|
||||
Reference in New Issue
Block a user