bnb/guide/dual_use.rs
1//! Dual-use: compliant by default, deliberately violatable.
2//!
3//! These types are meant for both sides of the wire: the guided path emits and accepts
4//! RFC-correct traffic, but a caller who *wants* to produce or read non-conformant
5//! bytes — for fuzzing, red-teaming, or interop testing — can. The rules that make
6//! that work:
7//!
8//! 1. **Builder defaults are compliant**, but the fields stay settable.
9//! 2. **Parsers accept representable-but-non-compliant values** — unknowns are modelled
10//! as data (`#[catch_all]`, retained flag bits), never hard errors.
11//! 3. **Policy lives on the construction path, never in a parser.** `validate` gates
12//! `build()`; decoding stays permissive.
13//! 4. **Raw constructors never validate** — they are the open escape hatch.
14//!
15//! # Unknowns are preserved, not rejected
16//!
17//! A `#[catch_all]` enum and a flag set both round-trip values they don't have a name
18//! for, so a parser never throws away or rejects representable input:
19//!
20//! ```
21//! use bnb::{BitEnum, Bits, bitflags, u4};
22//!
23//! #[derive(BitEnum, Clone, Copy, Debug, PartialEq, Eq)]
24//! #[bit_enum(u4)]
25//! enum Op { Read, Write, #[catch_all] Other(u4) }
26//!
27//! assert_eq!(Op::from_bits(0xE), Op::Other(u4::new(0xE))); // preserved...
28//! assert_eq!(Op::Other(u4::new(0xE)).into_bits(), 0xE); // ...and round-trips
29//!
30//! #[bitflags(u8)]
31//! #[derive(Clone, Copy)]
32//! struct F { a: bool, b: bool }
33//! assert_eq!(F::from_bits(0b1011).bits(), 0b1011); // undefined bits retained
34//! ```
35//!
36//! # The parser stays permissive; `validate` is construction-only
37//!
38//! `#[bin(validate = …)]` runs only in `build()`. Decoding the very same malformed
39//! bytes still succeeds — so you can parse hostile input for analysis, but can't
40//! *accidentally build* a malformed message:
41//!
42//! ```
43//! use bnb::bin;
44//!
45//! #[bin(big, validate = check)]
46//! #[derive(Debug, PartialEq)]
47//! struct Cell { tag: u8 }
48//!
49//! fn check(c: &Cell) -> Result<(), String> {
50//! if c.tag > 3 { return Err("tag must be 0..=3".into()); }
51//! Ok(())
52//! }
53//!
54//! assert!(Cell::builder().tag(9).build().is_err()); // construction: rejected
55//! assert_eq!(Cell::decode_exact(&[9]).unwrap(), Cell { tag: 9 }); // parser: accepts
56//! ```
57//!
58//! # Raw constructors are the escape hatch
59//!
60//! `from_raw` (on a `#[bitfield]`) and `from_bits` never validate — they let you build
61//! any representable bit pattern on purpose:
62//!
63//! ```
64//! use bnb::{bitfield, u4};
65//! #[bitfield(u8, bits = msb)]
66//! #[derive(Clone, Copy)]
67//! struct B { hi: u4, lo: u4 }
68//!
69//! let weird = B::from_raw(0xFF); // no checks — exactly the bits you asked for
70//! assert_eq!(weird.hi().value(), 0xF);
71//! ```
72//!
73//! # What is still rejected
74//!
75//! Only the *physically unencodable* is refused — never the merely non-conformant. A
76//! value that doesn't fit its field's bits can't be represented, so checked
77//! construction errors (and `new` panics):
78//!
79//! ```
80//! use bnb::u4;
81//! assert!(u4::try_new(0x10).is_err()); // 16 doesn't fit in 4 bits — unencodable
82//! ```
83//!
84//! The one place a *decode* can panic is a [`closed`](super::enums) enum fed an
85//! out-of-set discriminant — which is exactly why `closed` is an explicit opt-in and
86//! the default for untrusted input is `#[catch_all]`.