Skip to main content

bnb/guide/
directives.rs

1//! Field-directive reference — `#[br]` (read), `#[bw]` (write), and the standalone
2//! field attributes, one runnable example each.
3//!
4//! These appear on the fields of a [`#[bin]`](super::bin_codec) struct (or a bare
5//! `#[derive(BitDecode/BitEncode)]`). Struct-level options (`magic`, `ctx`, `validate`,
6//! `big`/`little`, …) are covered in [`bin_codec`](super::bin_codec).
7//!
8//! # `count` — a length-driven `Vec`
9//!
10//! `#[br(count = <expr>)]` reads that many elements into a `Vec<T>`; the expression may
11//! name an earlier field. On write, every element is emitted (the length is the
12//! caller's to track — usually with `temp`+`calc`, below).
13//!
14//! ```
15//! use bnb::bin;
16//! #[bin(big)]
17//! #[derive(Debug, PartialEq)]
18//! struct List { len: u8, #[br(count = len)] items: Vec<u16> }
19//!
20//! let v = List { len: 2, items: vec![0xAABB, 0xCCDD] };
21//! assert_eq!(v.to_bytes().unwrap(), [0x02, 0xAA, 0xBB, 0xCC, 0xDD]);
22//! assert_eq!(List::decode_exact(&[0x02, 0xAA, 0xBB, 0xCC, 0xDD]).unwrap(), v);
23//! ```
24//!
25//! # `temp` + `calc` — derived, never stored
26//!
27//! `#[br(temp)]` reads a value into a local that later directives can use, but does not
28//! store it; its `#[bw(calc = <expr>)]` recomputes it on write. Together they keep a
29//! length/count from ever drifting from the data it describes.
30//!
31//! ```
32//! use bnb::bin;
33//! #[bin(big)]
34//! #[derive(Debug, PartialEq)]
35//! struct Msg {
36//!     #[br(temp)]
37//!     #[bw(calc = self.items.len() as u8)]
38//!     n: u8,
39//!     #[br(count = n)]
40//!     items: Vec<u8>,
41//! }
42//!
43//! let m = Msg { items: vec![10, 20, 30] };   // no `n` field to set
44//! assert_eq!(m.to_bytes().unwrap(), [0x03, 10, 20, 30]);
45//! assert_eq!(Msg::decode_exact(&[0x02, 5, 6]).unwrap().items, vec![5, 6]);
46//! ```
47//!
48//! # `if` — a conditional `Option`
49//!
50//! `#[br(if(<cond>))]` on an `Option<T>` reads `Some` when the condition (over earlier
51//! fields) holds, else `None`. On write, the `Option`'s presence drives whether it is
52//! emitted.
53//!
54//! ```
55//! use bnb::bin;
56//! #[bin(big)]
57//! #[derive(Debug, PartialEq)]
58//! struct Opt { has_ext: u8, #[br(if(has_ext != 0))] ext: Option<u16> }
59//!
60//! let with = Opt { has_ext: 1, ext: Some(0xBEEF) };
61//! let without = Opt { has_ext: 0, ext: None };
62//! assert_eq!(with.to_bytes().unwrap(), [0x01, 0xBE, 0xEF]);
63//! assert_eq!(without.to_bytes().unwrap(), [0x00]);
64//! assert_eq!(Opt::decode_exact(&[0x01, 0xBE, 0xEF]).unwrap(), with);
65//! assert_eq!(Opt::decode_exact(&[0x00]).unwrap(), without);
66//! ```
67//!
68//! # `map` / `try_map` — transform the wire value
69//!
70//! `#[br(map = <f>)]` reads the wire value (its type inferred from `f`'s argument) and
71//! maps it to the field type; `#[bw(map = <f>)]` is the inverse on write. Use them to
72//! store a friendly type while the wire keeps a raw encoding.
73//!
74//! ```
75//! use bnb::bin;
76//! #[bin(big)]
77//! #[derive(Debug, PartialEq)]
78//! struct Reading {
79//!     // wire: u8 biased by 40; stored: signed °C.
80//!     #[br(map = |raw: u8| raw as i16 - 40)]
81//!     #[bw(map = |c: &i16| (*c + 40) as u8)]
82//!     celsius: i16,
83//! }
84//!
85//! let r = Reading { celsius: 10 };
86//! assert_eq!(r.to_bytes().unwrap(), [0x32]);              // 10 + 40 = 50
87//! assert_eq!(Reading::decode_exact(&[0x32]).unwrap(), r);
88//! ```
89//!
90//! `try_map` is the fallible form — the converter returns a `Result`, and an error
91//! becomes a decode error (no panic):
92//!
93//! ```
94//! use bnb::bin;
95//! #[bin(big, read_only)]
96//! #[derive(Debug, PartialEq)]
97//! struct Checked {
98//!     #[br(try_map = |raw: u8| if raw < 100 { Ok(raw) } else { Err("out of range") })]
99//!     pct: u8,
100//! }
101//! assert_eq!(Checked::decode_exact(&[42]).unwrap().pct, 42);
102//! assert!(Checked::decode_exact(&[200]).is_err());        // converter rejected it
103//! ```
104//!
105//! # `parse_with` / `write_with` — a custom codec escape hatch
106//!
107//! When a field's shape needs arbitrary logic, supply your own functions. `parse_with`
108//! takes `fn(&mut impl Source) -> Result<T, BitError>`; `write_with` takes
109//! `fn(&T, &mut impl Sink) -> Result<(), BitError>`.
110//!
111//! ```
112//! use bnb::{bin, BitError, Sink, Source};
113//!
114//! fn read_pascal<S: Source>(r: &mut S) -> Result<String, BitError> {
115//!     let len: u8 = r.read()?;
116//!     let mut s = String::new();
117//!     for _ in 0..len { s.push(r.read::<u8>()? as char); }
118//!     Ok(s)
119//! }
120//! fn write_pascal<K: Sink>(s: &String, w: &mut K) -> Result<(), BitError> {
121//!     w.write(s.len() as u8)?;
122//!     for &b in s.as_bytes() { w.write(b)?; }
123//!     Ok(())
124//! }
125//!
126//! #[bin(big)]
127//! #[derive(Debug, PartialEq)]
128//! struct Named {
129//!     #[br(parse_with = read_pascal)]
130//!     #[bw(write_with = write_pascal)]
131//!     name: String,
132//! }
133//!
134//! let n = Named { name: "Hi".into() };
135//! assert_eq!(n.to_bytes().unwrap(), [0x02, b'H', b'i']);
136//! assert_eq!(Named::decode_exact(&[0x02, b'H', b'i']).unwrap(), n);
137//! ```
138//!
139//! # `brw(ignore)` — a field neither read nor written
140//!
141//! `#[brw(ignore)]` consumes no wire bits: the field is `Default::default()` on read and
142//! skipped on write. Use it for derived/scratch state you want on the struct but not on
143//! the wire. It is spelled with `brw` (not `br`) because it applies to **both**
144//! directions.
145//!
146//! ```
147//! use bnb::bin;
148//! #[bin(big)]
149//! #[derive(Debug, PartialEq)]
150//! struct Parsed { raw: u8, #[brw(ignore)] note: u32 }
151//!
152//! let p = Parsed { raw: 7, note: 999 };
153//! assert_eq!(p.to_bytes().unwrap(), [0x07]);             // note not written
154//! assert_eq!(Parsed::decode_exact(&[0x07]).unwrap(), Parsed { raw: 7, note: 0 });
155//! ```
156//!
157//! # `reserved` / `reserved_with` — fixed wire bits with a spec value
158//!
159//! A reserved field is a normal **stored** field with a known *spec value*: the type's
160//! zero for `#[reserved]`, the given expression for `#[reserved_with(<expr>)]` (e.g. a
161//! must-be-one pattern). On the default path it reads and writes its *actual* value —
162//! so you can observe a peer's reserved bits and override them — while the builder
163//! defaults it to the spec value (so it isn't required) and the `spec_*` codecs use the
164//! spec value instead.
165//!
166//! ```
167//! use bnb::bin;
168//! #[bin(big)]
169//! #[derive(Debug, PartialEq)]
170//! struct R {
171//!     a: u8,
172//!     #[reserved] pad: u8,                 // spec value 0x00
173//!     #[reserved_with(0xFFu8)] ones: u8,   // spec value 0xFF
174//!     b: u8,
175//! }
176//!
177//! // The builder makes the reserved fields optional, defaulting to their spec values.
178//! let r = R::builder().a(1).b(2).build().unwrap();
179//! assert_eq!(r.to_bytes().unwrap(), [0x01, 0x00, 0xFF, 0x02]);
180//!
181//! // Decode captures the actual reserved bits; spec_decode reports the expected ones.
182//! let actual = R::decode_exact(&[0x01, 0x55, 0x55, 0x02]).unwrap();
183//! assert_eq!((actual.pad, actual.ones), (0x55, 0x55));
184//! let spec = R::spec_decode_exact(&[0x01, 0x55, 0x55, 0x02]).unwrap();
185//! assert_eq!((spec.pad, spec.ones), (0x00, 0xFF));     // the spec values
186//! assert_eq!(spec.to_spec_bytes().unwrap(), [0x01, 0x00, 0xFF, 0x02]);
187//! ```
188//!
189//! # `pad_*` / `align_*` — forward positioning
190//!
191//! `#[br(pad_before = <bits>)]` / `pad_after` skip a bit count around a field;
192//! `align_before` / `align_after` skip to the next byte boundary. Bit/byte amounts come
193//! from the [`prelude`](crate::prelude) (`1.bytes()`, `4.bits()`).
194//!
195//! ```
196//! use bnb::{bin, prelude::*};
197//! #[bin(big)]
198//! #[derive(Debug, PartialEq)]
199//! struct P { a: u8, #[br(pad_before = 1u32.bytes())] b: u8 }
200//!
201//! let p = P { a: 1, b: 2 };
202//! assert_eq!(p.to_bytes().unwrap(), [0x01, 0x00, 0x02]); // one zero pad byte
203//! assert_eq!(P::decode_exact(&[0x01, 0x99, 0x02]).unwrap(), p); // pad skipped on read
204//! ```
205//!
206//! ```
207//! use bnb::bin;
208//! #[bin(big)]
209//! #[derive(Debug, PartialEq)]
210//! struct A { flag: bool, #[br(align_before)] val: u8 }  // val starts on a byte boundary
211//!
212//! let a = A { flag: true, val: 0x2A };
213//! assert_eq!(a.to_bytes().unwrap(), [0x80, 0x2A]); // flag in the high bit, then val
214//! assert_eq!(A::decode_exact(&[0x80, 0x2A]).unwrap(), a);
215//! ```
216//!
217//! # `restore_position` — peek without consuming
218//!
219//! `#[br(restore_position)]` reads the field, then rewinds the cursor so later fields
220//! re-read the same bytes (e.g. peek a discriminant, then read the full record). The
221//! field is not re-emitted on write — the overlapping field owns those bytes. It needs
222//! a seekable source, so `decode_from` on a forward-only stream is a compile error; the
223//! slice paths (`decode`/`peek`/`decode_exact`) always qualify.
224//!
225//! ```
226//! use bnb::bin;
227//! #[bin(big)]
228//! #[derive(Debug, PartialEq)]
229//! struct Peeked {
230//!     #[br(restore_position)] tag: u8, // peek the first byte...
231//!     full: u16,                       // ...then read it as the high byte of a u16
232//! }
233//!
234//! let p = Peeked::decode_exact(&[0xAB, 0xCD]).unwrap();
235//! assert_eq!(p.tag, 0xAB);
236//! assert_eq!(p.full, 0xABCD);
237//! assert_eq!(p.to_bytes().unwrap(), [0xAB, 0xCD]); // `full` emits the bytes; `tag` does not
238//! ```
239//!
240//! # `seek` — read at an absolute offset (pointer-following)
241//!
242//! `#[br(seek = <bits>)]` jumps the cursor to an **absolute** bit offset before reading
243//! the field — the building block for offset tables and pointer chains. Bit/byte amounts
244//! come from the [`prelude`](crate::prelude) (`ptr.bytes()`, `n.bits()`). It is read-side
245//! (the writer is append-only); pair it with `restore_position` to read at the offset and
246//! return so later fields continue in order. Like `restore_position` it seeks, so
247//! `decode_from` on a forward-only stream is a compile error; the slice paths qualify.
248//!
249//! ```
250//! use bnb::{bin, prelude::*};
251//! #[bin(big)]
252//! #[derive(Debug, PartialEq)]
253//! struct Ptr {
254//!     ptr: u8,                                     // byte offset of `target`
255//!     #[br(seek = ptr.bytes(), restore_position)]
256//!     target: u8,                                  // read at `ptr`, then rewind
257//!     next: u8,                                    // continues right after `ptr`
258//! }
259//!
260//! // `peek` doesn't require full consumption (seek/restore leave the tail untouched).
261//! let p = Ptr::peek(&[0x03, 0x11, 0x22, 0xAB]).unwrap();
262//! assert_eq!((p.ptr, p.target, p.next), (3, 0xAB, 0x11));
263//! ```
264//!
265//! On encode the seek is a no-op (the writer appends), so a *relocated* layout won't
266//! round-trip through the default encoder — emit such formats with `write_with` /
267//! `write_only`, where you control placement.
268//!
269//! # `dbg` — trace a field as it decodes
270//!
271//! `#[br(dbg)]` emits a [`tracing`](https://docs.rs/tracing) event as the field is read,
272//! carrying its start bit offset and decoded value (the field type must be `Debug`). It
273//! is a read-side diagnostic — no extra bits are consumed and encode is unaffected. The
274//! event is at `TRACE` level under the `bnb::dbg` target, so you can surface just these
275//! with `RUST_LOG=bnb::dbg=trace` (the application installs the subscriber; libraries
276//! only emit).
277//!
278//! ```
279//! use bnb::bin;
280//! #[bin(big)]
281//! #[derive(Debug, PartialEq)]
282//! struct Framed { tag: u8, #[br(dbg)] len: u16 }
283//!
284//! // Decoding is identical with or without `dbg`; it just also traces `len`.
285//! let f = Framed::decode_exact(&[0x01, 0x00, 0x2A]).unwrap();
286//! assert_eq!(f, Framed { tag: 1, len: 42 });
287//! ```