From 08d5cce6ad61a7de1a3b69a4803d4b94f51fead1 Mon Sep 17 00:00:00 2001 From: ruv Date: Sun, 24 May 2026 19:27:33 -0400 Subject: [PATCH] feat(adr-118/p1.10): frame parser trailing-bytes contract (296/296 GREEN) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Iter 43. Pins BfldFrame::from_bytes behavior on buffers carrying bytes past `BFLD_HEADER_SIZE + header.payload_len`. The parser currently accepts these and silently slices to the declared length. Useful when the transport (UDP MTU padding, ESP-NOW trailer alignment) adds noise the application layer doesn't strip. Pinning this behavior makes any future tightening (reject as MalformedFrame) a deliberate, traceable policy change rather than silent breakage. Added (in tests/frame_trailing_bytes.rs, 6 named tests): parser_accepts_buffer_with_one_trailing_byte (smoke: one extra 0xFF byte tolerated; payload.last() != Some(0xFF)) parser_accepts_many_trailing_bytes (256 trailing bytes — UDP MTU padding scale) parsed_payload_round_trips_back_to_typed_payload_with_trailing_bytes_present *** Sanity: trailing-bytes leniency must not corrupt the section parser downstream. from_bytes → parse_payload still yields the original BfldPayload byte-for-byte. *** header_only_buffer_at_exactly_header_size_with_zero_payload_len_succeeds (boundary: empty-payload frame is exactly 86 bytes) header_only_buffer_with_trailing_bytes_but_zero_payload_len_ignores_them (100 trailing bytes; parsed.payload stays empty) trailing_bytes_do_not_affect_crc_validation_when_payload_intact (CRC is over payload bytes only; 32 trailing bytes leave CRC intact and parse succeeds) ADR-124 status (iter step 0 sibling check): - docs/adr/ADR-124-rvagent-mcp-ruvector-npm-integration.md unchanged at 431 lines. SENSE-BRIDGE scope remains orthogonal. ACs progressed: - ADR-119 wire-format parser contract: trailing-bytes tolerance is now an explicit, tested behavior. Operators building stream-based frame readers (where multiple frames concatenate) know the parser treats `header.payload_len` as authoritative, not buffer.len(). Test config: - cargo test --no-default-features → 90 passed (frame_trailing_bytes cfg-out) - cargo test → 296 passed (290 + 6) Out of scope (next iter target): - PR-readiness pivot: CHANGELOG, witness bundle, AC closeout table. Co-Authored-By: claude-flow --- .../tests/frame_trailing_bytes.rs | 105 ++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 v2/crates/wifi-densepose-bfld/tests/frame_trailing_bytes.rs diff --git a/v2/crates/wifi-densepose-bfld/tests/frame_trailing_bytes.rs b/v2/crates/wifi-densepose-bfld/tests/frame_trailing_bytes.rs new file mode 100644 index 00000000..3dc11efb --- /dev/null +++ b/v2/crates/wifi-densepose-bfld/tests/frame_trailing_bytes.rs @@ -0,0 +1,105 @@ +//! `BfldFrame::from_bytes` trailing-bytes contract. Pins the current +//! behavior: the parser reads exactly `header.payload_len` bytes after the +//! header and silently ignores anything past `BFLD_HEADER_SIZE + +//! header.payload_len`. This matches how the parser is used in iter-4 +//! through iter-15: callers hand a sliced buffer that may include framing +//! noise (UDP MTU padding, ESP-NOW trailer alignment), and the parser +//! extracts only what the header declares. +//! +//! If a future iter decides to tighten this (reject trailing bytes as +//! `MalformedFrame`), updating this test makes the policy change deliberate +//! and traceable rather than silent. + +#![cfg(feature = "std")] + +use wifi_densepose_bfld::{BfldFrame, BfldFrameHeader, BfldPayload, BFLD_HEADER_SIZE}; + +fn frame_with_typed_payload() -> BfldFrame { + let payload = BfldPayload { + compressed_angle_matrix: vec![0x11; 32], + amplitude_proxy: vec![0x22; 16], + phase_proxy: vec![0x33; 16], + snr_vector: vec![0x44; 8], + csi_delta: None, + vendor_extension: vec![], + }; + BfldFrame::from_payload(BfldFrameHeader::empty(), &payload) +} + +#[test] +fn parser_accepts_buffer_with_one_trailing_byte() { + let frame = frame_with_typed_payload(); + let mut bytes = frame.to_bytes(); + let canonical_len = bytes.len(); + bytes.push(0xFF); + let parsed = BfldFrame::from_bytes(&bytes).expect("trailing byte must be tolerated"); + assert_eq!( + parsed.payload.len(), + { parsed.header.payload_len } as usize, + "parsed payload size must equal header.payload_len, not buffer.len() - HEADER", + ); + // Implicit: the trailing 0xFF byte is NOT in parsed.payload. + assert_ne!(parsed.payload.last().copied(), Some(0xFF)); + let _ = canonical_len; // sanity anchor +} + +#[test] +fn parser_accepts_many_trailing_bytes() { + let frame = frame_with_typed_payload(); + let mut bytes = frame.to_bytes(); + bytes.extend_from_slice(&[0xCC; 256]); + let parsed = BfldFrame::from_bytes(&bytes).expect("256 trailing bytes must be tolerated"); + assert_eq!(parsed.payload.len(), { parsed.header.payload_len } as usize); +} + +#[test] +fn parsed_payload_round_trips_back_to_typed_payload_with_trailing_bytes_present() { + // The trailing-bytes parser leniency must not corrupt the section parser + // downstream. After from_bytes + parse_payload, the typed payload should + // match the original BfldPayload byte-for-byte. + let original_payload = BfldPayload { + compressed_angle_matrix: vec![0x11; 32], + amplitude_proxy: vec![0x22; 16], + phase_proxy: vec![0x33; 16], + snr_vector: vec![0x44; 8], + csi_delta: None, + vendor_extension: vec![], + }; + let frame = BfldFrame::from_payload(BfldFrameHeader::empty(), &original_payload); + let mut bytes = frame.to_bytes(); + bytes.extend_from_slice(&[0xEE; 64]); + let parsed_frame = BfldFrame::from_bytes(&bytes).unwrap(); + let parsed_payload = parsed_frame.parse_payload().expect("typed payload parse"); + assert_eq!(parsed_payload, original_payload); +} + +#[test] +fn header_only_buffer_at_exactly_header_size_with_zero_payload_len_succeeds() { + let header = BfldFrameHeader::empty(); + let frame = BfldFrame::new(header, Vec::new()); + let bytes = frame.to_bytes(); + assert_eq!(bytes.len(), BFLD_HEADER_SIZE, "empty-payload frame is exactly header size"); + let parsed = BfldFrame::from_bytes(&bytes).expect("parse"); + assert!(parsed.payload.is_empty()); +} + +#[test] +fn header_only_buffer_with_trailing_bytes_but_zero_payload_len_ignores_them() { + let header = BfldFrameHeader::empty(); + let frame = BfldFrame::new(header, Vec::new()); + let mut bytes = frame.to_bytes(); + bytes.extend_from_slice(&[0xAA; 100]); + let parsed = BfldFrame::from_bytes(&bytes).expect("parse"); + assert_eq!({ parsed.header.payload_len }, 0); + assert!(parsed.payload.is_empty(), "trailing bytes must not leak into payload"); +} + +#[test] +fn trailing_bytes_do_not_affect_crc_validation_when_payload_intact() { + let frame = frame_with_typed_payload(); + let mut bytes = frame.to_bytes(); + let crc_before_extension = { frame.header.payload_crc32 }; + bytes.extend_from_slice(&[0xFF; 32]); + let parsed = BfldFrame::from_bytes(&bytes).expect("CRC over payload-only must still match"); + assert_eq!({ parsed.header.payload_crc32 }, crc_before_extension); +}