Commit Graph

6 Commits

Author SHA1 Message Date
ruv 5312e3c4a1 feat(adr-118/p1.6): BfldFrame <-> BfldPayload wire integration (39/39 GREEN)
Iter 6. Connects the typed payload parser (iter 5) to the framed
wire format (iter 4): the CRC32 now covers the section-prefixed
payload bytes per ADR-119 §2.2 ("CRC32 covers all section bytes
including length prefixes").

Added:
- BfldFrame::from_payload(header, &BfldPayload) -> Self
  Auto-syncs header.flags HAS_CSI_DELTA bit from payload.csi_delta.is_some(),
  serializes payload via to_bytes(), feeds BfldFrame::new() which computes
  payload_len + payload_crc32 over the section-prefixed bytes.
- BfldFrame::parse_payload(&self) -> Result<BfldPayload, BfldError>
  Reads HAS_CSI_DELTA bit from header.flags and dispatches to
  BfldPayload::from_bytes(&self.payload, expect_csi_delta).

tests/frame_payload_integration.rs (7 named tests, all green):
  from_payload_then_parse_payload_is_identity
  from_payload_autosets_has_csi_delta_flag
  from_payload_clears_has_csi_delta_flag_when_csi_absent
    (verifies the flag is cleared when csi_delta is None even if caller
     pre-set the bit; other flag bits like PRIVACY_MODE are preserved)
  frame_crc_covers_section_prefixed_bytes
    (mutating a byte inside section body trips CRC, not magic/length)
  frame_crc_covers_section_length_prefixes
    (mutating a section length-prefix byte trips CRC before parser ever runs)
  empty_typed_payload_roundtrips
  end_to_end_wire_roundtrip_via_bytes
    (BfldPayload -> from_payload -> to_bytes -> from_bytes -> parse_payload
     is the identity function modulo flag auto-set)

ACs progressed:
- AC5 ↑ — full payload round-trip through the framed bytes (closes
  the round-trip leg from BfldPayload through wire and back).
- AC6 ↑ — same input produces same bytes through both layers.
- AC4 ↑ — CRC mismatch on tampered section bodies and tampered section
  length prefixes both surface as BfldError::Crc, not as silent acceptance
  or as a deeper parser error.

Test config:
- cargo test --no-default-features → 17 passed (integration tests cfg-out)
- cargo test                       → 39 passed (3 + 6 + 7 + 8 + 8 + 7)

Out of scope (next iter target):
- PrivacyGate::demote(frame, target_class) — ADR-120 §2.4 class transition
  transformer with subtle::Zeroize on dropped fields.
- IdentityEmbedding newtype with no Serialize impl (ADR-120 §2.5 / I2).

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-05-24 14:16:54 -04:00
ruv 73ba8d3b27 feat(adr-118/p1.5): payload section parser (BfldPayload) — 32/32 GREEN
Iter 5. Implements ADR-119 §2.2 payload layout: 4-byte LE length prefix
followed by section bytes, in this fixed order:

  compressed_angle_matrix ‖ amplitude_proxy ‖ phase_proxy ‖ snr_vector
   ‖ csi_delta (iff flags.bit0)
   ‖ vendor_extension (length 0 allowed)

Added:
- src/payload.rs (gated on `feature = "std"`):
  * BfldPayload struct with 6 fields (csi_delta: Option<Vec<u8>>)
  * SECTION_PREFIX_LEN const (= 4)
  * to_bytes(include_csi_delta: bool) -> Vec<u8>
  * wire_len(include_csi_delta: bool) -> usize  (predictive, no allocation)
  * from_bytes(&[u8], expect_csi_delta: bool) -> Result<Self, BfldError>
  * push_section / read_section helpers (private)
- BfldError::MalformedSection { offset, reason } variant
- pub use BfldPayload from lib.rs (cfg-gated mirror of BfldFrame)

tests/payload_sections.rs (8 named tests, all green):
  payload_roundtrip_with_csi_delta
  payload_roundtrip_without_csi_delta
  wire_len_matches_to_bytes_length
  empty_payload_has_five_zero_length_sections
  parser_rejects_buffer_shorter_than_first_length_prefix
  parser_rejects_section_body_running_past_buffer_end
  parser_rejects_trailing_bytes_after_vendor_extension
  csi_delta_flag_mismatch_with_payload_is_detectable_via_trailing_bytes

ACs progressed:
- AC5 ↑ — full section-level round-trip preservation (round-trip with and
  without csi_delta both pass).
- AC6 ↑ — deterministic section encoding (length prefixes use to_le_bytes,
  body is byte-stable).
- AC1 partial — section layout now parses with bounded errors; CBFR-specific
  parsing (Phi/Psi Givens decoders) is a separate iter inside extractor.rs.

Test config:
- cargo test --no-default-features → 17 passed (payload module cfg-out)
- cargo test                       → 32 passed (3 + 6 + 7 + 8 + 8)

Out of scope (next iter target):
- Wire integration: feed BfldPayload bytes through BfldFrame::new so the
  header.payload_crc32 covers the section-prefixed bytes per ADR-119 §2.2
  ("CRC32 covers all section bytes including length prefixes").
- A no_std-friendly BfldPayloadRef<'_> borrowing variant (ESP32-S3 path).
- Givens-rotation angle decoder (Phi/Psi extraction from compressed_angle_matrix).

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-05-24 14:07:14 -04:00
ruv 775661b2e8 feat(adr-118/p1.4): BfldFrame (header + payload + CRC32) — 24/24 GREEN
Iter 4. Lands the central wire-format primitive: complete frames with
header + arbitrary-length payload, protected by CRC-32/ISO-HDLC.

Added:
- crc = "3" dependency (CRC-32/ISO-HDLC, same poly as Ethernet / zlib)
- src/frame.rs: CRC32_ALG const and crc32_of_payload(&[u8]) -> u32
- src/frame.rs: BfldFrame { header, payload: Vec<u8> } (gated on `std`)
  * BfldFrame::new(header, payload) — auto-syncs payload_len + payload_crc32
  * BfldFrame::to_bytes() -> Vec<u8> — header LE bytes ‖ payload
  * BfldFrame::from_bytes(&[u8]) -> Result<Self, BfldError>
- BfldError::TruncatedFrame { got, need } variant
- Doc strings on BfldError::Crc and BfldError::PrivacyViolation field names
- tests/frame_roundtrip.rs (7 named tests, gated on feature = "std"):
    frame_roundtrip_preserves_header_and_payload
    frame_new_syncs_payload_len_and_crc
    frame_serialization_is_deterministic
    frame_rejects_payload_crc_mismatch
    frame_rejects_truncated_buffer_smaller_than_header
    frame_rejects_truncated_buffer_smaller_than_payload
    empty_payload_is_valid (CRC of empty payload is 0x00000000)

Test config:
- cargo test --no-default-features → 17 passed (frame_roundtrip cfg-out)
- cargo test (default features = std)  → 24 passed (3+6+7+8)

ADR-119 ACs progressed:
- AC4 partial: bad-magic + bad-version + CRC-mismatch + truncation rejected
  with typed errors; field-level masking lives in the privacy_gate iter.
- AC5: BfldFrame round-trip preserves header + payload + CRC.
- AC6: Identical inputs produce bit-identical bytes (asserted explicitly).

Out of scope (next iter):
- Payload section parser (compressed_angle_matrix, amplitude_proxy, ...)
  — only the byte buffer is opaque so far; sections need length prefixes.
- BfldFrameRef<'_> for ESP32-S3 self-only mode (no-alloc, ADR-123 §2.5).
- PrivacyGate::demote(frame, target_class) transformer (ADR-120 §2.4).

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-05-24 13:58:26 -04:00
ruv eb996294fb feat(adr-118/p1.3): Sink marker traits + PrivacyClass::try_from (17/17 GREEN)
Iter 3. Lands the structural enforcement of ADR-118 invariant I1
("raw BFI never exits the node") and ADR-120 §2.2 ("Sink marker types").

Added:
- src/sink.rs:
  * Sink trait with MIN_CLASS and KIND associated constants
  * LocalSink (Raw OK), NetworkSink (Derived+ only), MatterSink (Anonymous+)
  * Hierarchy: MatterSink: NetworkSink (every Matter sink is a NetworkSink)
  * check_class<S>(class) runtime gate, returns PrivacyViolation{reason:KIND}
  * Zero-sized kind tags: LocalKind / NetworkKind / MatterKind
- PrivacyClass::as_u8() const helper
- TryFrom<u8> for PrivacyClass (0..=3 valid; 4..=255 → InvalidPrivacyClass)
- BfldError::InvalidPrivacyClass(u8) variant

tests/sink_enforcement.rs adds 8 tests:
  privacy_class_try_from_accepts_all_four_valid_bytes
  privacy_class_try_from_rejects_out_of_range_bytes
  privacy_class_byte_roundtrip_is_stable
  local_sink_accepts_all_classes
  network_sink_rejects_raw_frames
  network_sink_accepts_derived_anonymous_restricted
  matter_sink_rejects_raw_and_derived
  matter_sink_accepts_anonymous_and_restricted

Out of scope (next iter):
- BfldFrame (header + payload + section length-prefixes + CRC32 over payload)
  — needs the `crc` crate dependency.
- PrivacyGate::demote(frame, target_class) transformer (ADR-120 §2.4).
- compile-fail test that proves a sink-trait bound rejects Raw at compile
  time — needs `trybuild` integration; deferred to a separate iter.

cargo test -p wifi-densepose-bfld --no-default-features → 17 passed, 0 failed
  (3 frame_header_size + 6 header_roundtrip + 8 sink_enforcement)

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-05-24 13:43:05 -04:00
ruv be4dad6ede feat(adr-118/p1.2): header encode/decode + 6 round-trip tests (9/9 GREEN)
Iter 2 of the BFLD rollout. Adds the canonical little-endian wire form for
BfldFrameHeader with safe (no unsafe) encoders/decoders. Covers ADR-119 AC5
(round-trip preservation), AC6 (deterministic serialization), and partial
AC1 (constant wire size) / AC4 (rejects bad magic + bad version).

Added:
- BfldFrameHeader::empty() — convenience constructor with magic/version set
- BfldFrameHeader::to_le_bytes() -> [u8; 86]
- BfldFrameHeader::from_le_bytes(&[u8; 86]) -> Result<Self, BfldError>
- Field-level doc strings on every header field (clears all 21 missing-docs
  warnings the iter 1 commit logged)
- tests/header_roundtrip.rs — 6 named tests:
    header_roundtrip_preserves_all_fields
    header_serialization_is_deterministic
    header_magic_is_at_offset_zero_little_endian (LE byte order proof)
    parsing_rejects_invalid_magic
    parsing_rejects_unsupported_version
    wire_size_is_constant

Implementation notes:
- Used #[derive(Default)] on BfldFrameHeader so empty() can build cleanly.
- to_le_bytes copies packed fields into locals first to dodge unaligned-
  borrow lints; from_le_bytes uses try_into() on byte slices.
- All field reads/writes are #[forbid(unsafe_code)] compliant.

Out of scope (next iter targets):
- BfldFrame (header + payload sections + section-length prefixes + CRC32
  computation over payload bytes only) — needs the `crc` crate dependency.
- PrivacyGate::demote(...) skeleton (ADR-120 §2.4).
- SinkMarker traits (LocalSink / NetworkSink / MatterSink) — ADR-120 §2.2.

cargo test -p wifi-densepose-bfld --no-default-features → 9 passed, 0 failed

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-05-24 13:38:11 -04:00
ruv c965e3e6c0 feat(adr-118/p1): scaffold wifi-densepose-bfld crate + frame header (3/3 tests GREEN)
Land P1 of the BFLD rollout — the wire-format primitives:

- New workspace member: v2/crates/wifi-densepose-bfld
- PrivacyClass enum (Raw/Derived/Anonymous/Restricted) with allows_network()
  and allows_matter() const helpers reflecting ADR-120 §2.2 and ADR-122 §2.4
- BfldFrameHeader (#[repr(C, packed)]) per ADR-119 §2.1
- BFLD_MAGIC = 0xBF1D_0001, BFLD_VERSION = 1
- BfldError variants for InvalidMagic / UnsupportedVersion / Crc / PrivacyViolation
- soul-signature cargo feature (gated, default OFF) per ADR-118 §1.4
- Compile-time size assertion via static_assertions::const_assert_eq!
- 3 acceptance tests in tests/frame_header_size.rs (all pass)

Bug fix:
- ADR-119 AC1 claimed BfldFrameHeader is 40 bytes. Actual packed layout sums
  to 86 bytes. Updated AC1 and §2.1 prose to match. const_assert in frame.rs
  pins the value structurally — a future field addition that breaks the size
  fails to compile.

Out of scope for this iter (deferred to later P1 commits):
- Field-level missing-docs warnings (21) — addressed alongside accessor helpers
- Payload section parsing — needs the section-length prefix tests
- Round-trip serialize/parse — covered by a fixture-based test in the next iter

cargo test -p wifi-densepose-bfld --no-default-features → 3 passed, 0 failed

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-05-24 13:34:05 -04:00