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, or the `count_prefix` sugar).
13//!
14//! **The count obligation.** The expression drives *decode* sizing only — encode trusts
15//! the `Vec` and writes everything it holds. If a stored count (or a `ctx` param, which
16//! doesn't even exist at encode time) disagrees with `len()`, the emitted bytes won't
17//! round-trip. Keeping them consistent is the constructor's job: derive the count
18//! (`count_prefix` / `temp`+`calc`) where the layout allows, or enforce it with
19//! construction-side `validate` (see the `ctx_length` example). The flip side is
20//! deliberate: a *stored* count field can disagree on purpose — that's how dual-use
21//! code forges adversarial frames.
22//!
23//! ```
24//! use bnb::bin;
25//! #[bin(big)]
26//! #[derive(Debug, PartialEq)]
27//! struct List { len: u8, #[br(count = len)] items: Vec<u16> }
28//!
29//! let v = List { len: 2, items: vec![0xAABB, 0xCCDD] };
30//! assert_eq!(v.to_bytes().unwrap(), [0x02, 0xAA, 0xBB, 0xCC, 0xDD]);
31//! assert_eq!(List::decode_exact(&[0x02, 0xAA, 0xBB, 0xCC, 0xDD]).unwrap(), v);
32//! ```
33//!
34//! # `temp` + `calc` — derived, never stored
35//!
36//! `#[br(temp)]` reads a value into a local that later directives can use, but does not
37//! store it; its `#[bw(calc = <expr>)]` recomputes it on write. Together they keep a
38//! length/count from ever drifting from the data it describes.
39//!
40//! ```
41//! use bnb::bin;
42//! #[bin(big)]
43//! #[derive(Debug, PartialEq)]
44//! struct Msg {
45//! #[br(temp)]
46//! #[bw(calc = self.items.len() as u8)]
47//! n: u8,
48//! #[br(count = n)]
49//! items: Vec<u8>,
50//! }
51//!
52//! let m = Msg { items: vec![10, 20, 30] }; // no `n` field to set
53//! assert_eq!(m.to_bytes().unwrap(), [0x03, 10, 20, 30]);
54//! assert_eq!(Msg::decode_exact(&[0x02, 5, 6]).unwrap().items, vec![5, 6]);
55//! ```
56//!
57//! # `br(calc)` — a decode-computed field (the read-side dual of `bw(calc)`)
58//!
59//! `#[br(calc = <expr>)]` binds a **stored** field from earlier fields on decode (no
60//! wire read) and writes **nothing** on encode — its bytes live in the raw fields it
61//! derives from. It resolves a field whose typed meaning depends on a field *later* on
62//! the wire without a look-ahead: read the raw bits into `temp`s in wire order, then
63//! `calc` the typed field from the full set. (The shape of NXDN's LICH and DMR's
64//! directional tables, where a direction bit read near the end selects how earlier
65//! bits are interpreted.)
66//!
67//! ```
68//! use bnb::{bin, u2, u3};
69//!
70//! #[bin]
71//! #[derive(Debug, PartialEq)]
72//! struct Frame {
73//! header: u3,
74//! #[br(temp)]
75//! #[bw(calc = u2::new(self.level & 0b11))] // raw bits, recomputed from `level`
76//! raw: u2,
77//! outbound: bool, // the context — read *after* `raw`
78//! trailing: u2,
79//! // `level` is the raw value when outbound, or that value plus four when inbound;
80//! // it needs `outbound`, which is read after `raw`. Resolved after the whole
81//! // record is read; writes nothing (its bytes are `raw`'s).
82//! #[br(calc = if outbound { raw.value() } else { raw.value() + 4 })]
83//! level: u8,
84//! }
85//!
86//! // `raw` is a temp, so the literal omits it and sets only the semantic `level`.
87//! let inbound = Frame { header: u3::new(0b101), outbound: false, trailing: u2::new(0), level: 6 };
88//! let bytes = inbound.to_bytes().unwrap();
89//! assert_eq!(bytes.len(), 1);
90//! assert_eq!(Frame::decode_exact(&bytes).unwrap(), inbound); // round-trips
91//!
92//! // The later `outbound` bit changes what the same raw bits mean: level 2 and level 6
93//! // both store raw `0b10`, distinguished only by `outbound`.
94//! let outbound = Frame { header: u3::new(0b101), outbound: true, trailing: u2::new(0), level: 2 };
95//! assert_eq!(Frame::decode_exact(&outbound.to_bytes().unwrap()).unwrap().level, 2);
96//! ```
97//!
98//! # `count_prefix` — the length-prefixed count, in one line
99//!
100//! A count immediately followed by the elements it counts is the most common shape of
101//! the `temp`+`calc`+`count` triad above — so `#[brw(count_prefix = <Ty>)]` on the `Vec`
102//! generates the whole triad. The prefix is read into a hidden local, sizes the `Vec`,
103//! and is recomputed from `len()` on write: derived, never stored, can never drift.
104//!
105//! ```
106//! use bnb::bin;
107//! #[bin(big)]
108//! #[derive(Debug, PartialEq)]
109//! struct Msg {
110//! #[brw(count_prefix = u8)]
111//! items: Vec<u8>,
112//! }
113//!
114//! let m = Msg { items: vec![10, 20, 30] };
115//! assert_eq!(m.to_bytes().unwrap(), [0x03, 10, 20, 30]); // same wire as the triad
116//! assert_eq!(Msg::decode_exact(&[0x02, 5, 6]).unwrap().items, vec![5, 6]);
117//! ```
118//!
119//! Any [`Bits`](crate::Bits) type works as the prefix — a `uN` occupies its declared
120//! width, so a `count_prefix = u12` puts a true 12-bit count on the wire. Encode is
121//! **checked**: a collection too long for the prefix is a [`BitError`](crate::BitError)
122//! (`Convert`), never a silently wrapped count (which is what a hand-written
123//! `as u8` calc would do).
124//!
125//! ```
126//! # use bnb::bin;
127//! # #[bin(big)]
128//! # #[derive(Debug, PartialEq)]
129//! # struct Msg { #[brw(count_prefix = u8)] items: Vec<u8> }
130//! let too_long = Msg { items: vec![0; 300] }; // 300 > u8::MAX
131//! assert!(too_long.to_bytes().is_err()); // checked, not truncated
132//! ```
133//!
134//! `count_prefix` is *adjacent* (immediately before its `Vec`) and derive-only (never
135//! stored, so never overridable). When the count sits **away from** its data (DNS's
136//! `qdcount`…`arcount` header block) or measures **bytes** rather than elements (DNS's
137//! `rdlength`), or when you want to *forge* a disagreeing length — reach for [`WireLen`],
138//! below.
139//!
140//! # `WireLen` — an auto-deriving, overridable length/count
141//!
142//! [`WireLen<T>`](crate::WireLen) is a length field that is either `auto()` (derive at
143//! encode — the default) or `set(n)` (an explicit value). Decode always yields `Set`, so a
144//! plain `to_bytes()` is correct by default *and* `decode → encode` is byte-identical (a
145//! forged length survives a round-trip). It's the dual-use, non-adjacent, byte-or-element
146//! counterpart to `count_prefix`:
147//!
148//! - **Same-struct**: `#[bw(auto_len = count(<field>))]` (element count) or
149//! `#[bw(auto_len = bytes(<field>))]` (encoded byte length) on the `WireLen` field.
150//! - **Cross-struct**: `#[bin(auto_len(<field>.<nested> = count(<source>), …))]` on the
151//! enclosing struct — a count nested in a sub-struct that sizes a sibling collection. The
152//! targeted sub-struct must be `Clone` (it is filled through a clone at encode), and an
153//! `auto_len` target may carry only positioning directives, no other codec directive.
154//!
155//! ```
156//! use bnb::{bin, WireLen};
157//!
158//! #[bin(big)]
159//! # #[derive(Debug, PartialEq)]
160//! struct Framed {
161//! #[bw(auto_len = count(items))] // Auto → items.len(); Set(n) → n (a forgery)
162//! len: WireLen<u16>,
163//! #[br(count = len.to_count())]
164//! items: Vec<u8>,
165//! }
166//!
167//! let m = Framed { len: WireLen::auto(), items: vec![1, 2, 3] };
168//! assert_eq!(m.to_bytes().unwrap(), [0, 3, 1, 2, 3]); // auto-derived
169//! let forged = Framed { len: WireLen::set(9), items: vec![1, 2, 3] };
170//! assert_eq!(forged.to_bytes().unwrap(), [0, 9, 1, 2, 3]); // the lie is written
171//! ```
172//!
173//! Derivation is **checked** (a too-long collection is a [`BitError`](crate::BitError), never
174//! a truncation), and a `WireLen`-auto field is optional in the builder (defaults to
175//! `auto()`), so you never mention it unless forging.
176//!
177//! # `if` — a conditional `Option`
178//!
179//! `#[br(if(<cond>))]` on an `Option<T>` reads `Some` when the condition (over earlier
180//! fields) holds, else `None`. On write, the `Option`'s presence drives whether it is
181//! emitted.
182//!
183//! ```
184//! use bnb::bin;
185//! #[bin(big)]
186//! #[derive(Debug, PartialEq)]
187//! struct Opt { has_ext: u8, #[br(if(has_ext != 0))] ext: Option<u16> }
188//!
189//! let with = Opt { has_ext: 1, ext: Some(0xBEEF) };
190//! let without = Opt { has_ext: 0, ext: None };
191//! assert_eq!(with.to_bytes().unwrap(), [0x01, 0xBE, 0xEF]);
192//! assert_eq!(without.to_bytes().unwrap(), [0x00]);
193//! assert_eq!(Opt::decode_exact(&[0x01, 0xBE, 0xEF]).unwrap(), with);
194//! assert_eq!(Opt::decode_exact(&[0x00]).unwrap(), without);
195//! ```
196//!
197//! # `assert` — a decode-time guard
198//!
199//! `#[br(assert(<expr>))]` runs after the field is read (and mapped): if the expression —
200//! over this and any earlier field — is false, decode fails with a position-aware
201//! [`Convert`](crate::bitstream::ErrorKind::Convert) error naming the field. An optional
202//! message takes format args: `assert(<expr>, "fmt {}", args…)`. Multiple asserts run in
203//! order.
204//!
205//! **Doctrine note.** The parser is permissive by default — it never rejects representable
206//! input. `assert` is the *explicit opt-in* for values that are unrepresentable in your
207//! domain (an impossible version, a violated framing invariant) — the same rejection family
208//! as `magic`, closed enums, and `try_map`. It is **read-only**: no `bw` inverse is needed,
209//! and encode still writes whatever is stored — you can forge what you would not accept
210//! (dual-use). For *semantic* validity, use struct-level `validate` (construction-side,
211//! never the parser); for a genuine type conversion, use `try_map`.
212//!
213//! ```
214//! use bnb::bin;
215//! use bnb::bitstream::ErrorKind;
216//! #[bin(big)]
217//! #[derive(Debug, PartialEq)]
218//! struct Event {
219//! #[br(assert((1..=2).contains(&version), "unsupported version {}", version))]
220//! version: u8,
221//! id: u16,
222//! }
223//!
224//! assert!(Event::decode_exact(&[0x02, 0x00, 0x07]).is_ok());
225//! let err = Event::decode_exact(&[0x09, 0x00, 0x07]).unwrap_err();
226//! assert_eq!(err.field, Some("version"));
227//! assert!(matches!(err.kind, ErrorKind::Convert { .. }));
228//! // Encode is untouched by the guard — forging stays possible:
229//! assert_eq!(Event { version: 9, id: 7 }.to_bytes().unwrap(), [0x09, 0x00, 0x07]);
230//! ```
231//!
232//! # `map` / `try_map` — transform the wire value
233//!
234//! `#[br(map = <f>)]` reads the wire value (its type inferred from `f`'s argument) and
235//! maps it to the field type; `#[bw(map = <f>)]` is the inverse on write. Use them to
236//! store a friendly type while the wire keeps a raw encoding.
237//!
238//! ```
239//! use bnb::bin;
240//! #[bin(big)]
241//! #[derive(Debug, PartialEq)]
242//! struct Reading {
243//! // wire: u8 biased by 40; stored: signed °C.
244//! #[br(map = |raw: u8| raw as i16 - 40)]
245//! #[bw(map = |c: &i16| (*c + 40) as u8)]
246//! celsius: i16,
247//! }
248//!
249//! let r = Reading { celsius: 10 };
250//! assert_eq!(r.to_bytes().unwrap(), [0x32]); // 10 + 40 = 50
251//! assert_eq!(Reading::decode_exact(&[0x32]).unwrap(), r);
252//! ```
253//!
254//! `try_map` is the fallible form — the converter returns a `Result`, and an error
255//! becomes a decode error (no panic):
256//!
257//! ```
258//! use bnb::bin;
259//! #[bin(big, read_only)]
260//! #[derive(Debug, PartialEq)]
261//! struct Checked {
262//! #[br(try_map = |raw: u8| if raw < 100 { Ok(raw) } else { Err("out of range") })]
263//! pct: u8,
264//! }
265//! assert_eq!(Checked::decode_exact(&[42]).unwrap().pct, 42);
266//! assert!(Checked::decode_exact(&[200]).is_err()); // converter rejected it
267//! ```
268//!
269//! # `parse_with` / `write_with` — a custom codec escape hatch
270//!
271//! When a field's shape needs arbitrary logic, supply your own functions. `parse_with`
272//! takes `fn(&mut impl Source) -> Result<T, BitError>`; `write_with` takes
273//! `fn(&T, &mut impl Sink) -> Result<(), BitError>`.
274//!
275//! ```
276//! use bnb::{bin, BitError, Sink, Source};
277//!
278//! fn read_pascal<S: Source>(r: &mut S) -> Result<String, BitError> {
279//! let len: u8 = r.read()?;
280//! let mut s = String::new();
281//! for _ in 0..len { s.push(r.read::<u8>()? as char); }
282//! Ok(s)
283//! }
284//! fn write_pascal<K: Sink>(s: &String, w: &mut K) -> Result<(), BitError> {
285//! w.write(s.len() as u8)?;
286//! for &b in s.as_bytes() { w.write(b)?; }
287//! Ok(())
288//! }
289//!
290//! #[bin(big)]
291//! #[derive(Debug, PartialEq)]
292//! struct Named {
293//! #[br(parse_with = read_pascal)]
294//! #[bw(write_with = write_pascal)]
295//! name: String,
296//! }
297//!
298//! let n = Named { name: "Hi".into() };
299//! assert_eq!(n.to_bytes().unwrap(), [0x02, b'H', b'i']);
300//! assert_eq!(Named::decode_exact(&[0x02, b'H', b'i']).unwrap(), n);
301//! ```
302//!
303//! ## Ready-made codecs — [`bnb::codecs`](crate::codecs)
304//!
305//! Before rolling your own, check the shipped library: LEB128 varints, NUL-terminated
306//! C strings, and length-prefixed strings, all referenced by path. (The `read_pascal`
307//! above is exactly what [`codecs::prefixed`](crate::codecs::prefixed) ships — checked
308//! and UTF-8-validated.)
309//!
310//! ```
311//! use bnb::bin;
312//! #[bin(big)]
313//! #[derive(Debug, PartialEq)]
314//! struct Packet {
315//! #[br(parse_with = bnb::codecs::leb128::parse)]
316//! #[bw(write_with = bnb::codecs::leb128::write)]
317//! length: u64,
318//! #[br(parse_with = bnb::codecs::prefixed::parse_string::<_, u8>)]
319//! #[bw(write_with = bnb::codecs::prefixed::write_string::<_, u8>)]
320//! name: String,
321//! }
322//!
323//! let p = Packet { length: 300, name: "Hi".into() };
324//! assert_eq!(p.to_bytes().unwrap(), [0xAC, 0x02, 0x02, b'H', b'i']);
325//! assert_eq!(Packet::decode_exact(&p.to_bytes().unwrap()).unwrap(), p);
326//! ```
327//!
328//! ## Shared encode state — [`Sink::scratch`](crate::Sink::scratch)
329//!
330//! A `write_with` sees only its own field and the `Sink`. When a codec needs mutable state
331//! shared across *every* field of a message — a **back-reference / compression dictionary**
332//! (e.g. DNS name compression: a repeated name becomes a pointer to the first occurrence) —
333//! attach it to the sink with [`BitWriter::with_scratch`](crate::BitWriter::with_scratch) and
334//! reach it via `w.scratch()` + [`downcast_mut`](core::any::Any::downcast_mut). The sink is the
335//! one `&mut` threaded through all fields, so the value is visible to them all; it is dropped on
336//! `into_bytes` and never written. (Scope the borrow — copy what you need out before calling
337//! `w.write`.)
338//!
339//! ```
340//! use bnb::{bin, BitEncode, BitWriter, Sink, BitError, Source};
341//!
342//! // First write of a byte records its offset; a repeat emits a 0xFF marker + that offset.
343//! fn backref<K: Sink>(v: &u8, w: &mut K) -> Result<(), BitError> {
344//! let at = (w.bit_pos() / 8) as u8;
345//! let prior = w.scratch()
346//! .and_then(|s| s.downcast_mut::<std::collections::HashMap<u8, u8>>())
347//! .and_then(|d| match d.get(v).copied() { Some(o) => Some(o), None => { d.insert(*v, at); None } });
348//! match prior { Some(o) => { w.write(0xFFu8)?; w.write(o) } None => w.write(*v) }
349//! }
350//! fn read<S: Source>(r: &mut S) -> Result<u8, BitError> { r.read() }
351//!
352//! #[bin(big)]
353//! struct M { #[bw(write_with = backref)] #[br(parse_with = read)] a: u8,
354//! #[bw(write_with = backref)] #[br(parse_with = read)] b: u8 }
355//!
356//! let mut w = BitWriter::new().with_scratch(Box::new(std::collections::HashMap::<u8, u8>::new()));
357//! M { a: 7, b: 7 }.bit_encode(&mut w).unwrap();
358//! assert_eq!(w.into_bytes(), [7, 0xFF, 0x00]); // second 7 → back-reference to offset 0
359//! ```
360//!
361//! ## Per-type codecs — `#[bin(codec = …)]` newtypes
362//!
363//! When the same codec applies to *many* fields, hoist it onto a **newtype**: a
364//! single-field tuple struct whose wire form is owned by the fn pair. The type then
365//! carries its codec everywhere — fields need no attributes at all (just
366//! `#[brw(variable)]`, below).
367//!
368//! ```
369//! use bnb::bin;
370//!
371//! /// A LEB128-encoded u64 — annotate once, use as a plain field forever.
372//! #[bin(codec = bnb::codecs::leb128)] // the module's `parse`/`write` pair…
373//! #[derive(Debug, Clone, Copy, PartialEq)]
374//! pub struct Varint(pub u64);
375//! // …or any fns: #[bin(codec(parse = <f>, write = <f>))] — turbofish welcome,
376//! // e.g. parse = bnb::codecs::prefixed::parse_string::<_, u16>.
377//!
378//! #[bin(big)]
379//! #[derive(Debug, PartialEq)]
380//! struct Frame {
381//! kind: u8,
382//! #[brw(variable)] // a variable-length type in an otherwise-fixed parent
383//! length: Varint, // ← the codec travels with the type
384//! crc: u16,
385//! }
386//!
387//! let f = Frame { kind: 1, length: Varint(300), crc: 0xBEEF };
388//! assert_eq!(f.to_bytes().unwrap(), [0x01, 0xAC, 0x02, 0xBE, 0xEF]);
389//! assert_eq!(Frame::decode_exact(&f.to_bytes().unwrap()).unwrap(), f);
390//! assert_eq!(u64::from(Varint(300)), 300); // `From` both ways comes generated
391//! ```
392//!
393//! The newtype gets `BitDecode`/`BitEncode`, the slice entry points
394//! (`decode_exact`/`decode_all`/`to_bytes`, at its own declared `big`/`little`/
395//! `bits` — a *field* of this type decodes through the parent's cursor), and
396//! `From` conversions both ways. It emits **no `FixedBitLen`** — a codec's wire form
397//! is assumed variable; a genuinely fixed-width codec adds the one-line manual impl
398//! (see [`mapping`](super::mapping)). `read_only`/`write_only` narrow the direction
399//! (the paren form may then omit the unneeded fn). For a one-off field, plain
400//! `parse_with`/`write_with` stays the right tool.
401//!
402//! ## `#[brw(variable)]` — a variable-length field in a fixed parent
403//!
404//! A struct with no `Vec` or codec directives normally derives `FixedBitLen` by
405//! summing its fields — which fails to compile against a variable-length custom type
406//! (the error names the missing `FixedBitLen`). `#[brw(variable)]` declares the truth:
407//! this field's width isn't fixed, so the parent never claims to be. It's harmlessly
408//! redundant on a field that is already variable (a `Vec`, a directive-bearing field).
409//!
410//! # `brw(ignore)` — a field neither read nor written
411//!
412//! `#[brw(ignore)]` consumes no wire bits: the field is `Default::default()` on read and
413//! skipped on write. Use it for derived/scratch state you want on the struct but not on
414//! the wire. It is spelled with `brw` (not `br`) because it applies to **both**
415//! directions.
416//!
417//! ```
418//! use bnb::bin;
419//! #[bin(big)]
420//! #[derive(Debug, PartialEq)]
421//! struct Parsed { raw: u8, #[brw(ignore)] note: u32 }
422//!
423//! let p = Parsed { raw: 7, note: 999 };
424//! assert_eq!(p.to_bytes().unwrap(), [0x07]); // note not written
425//! assert_eq!(Parsed::decode_exact(&[0x07]).unwrap(), Parsed { raw: 7, note: 0 });
426//! ```
427//!
428//! # `reserved` / `reserved_with` — fixed wire bits with a spec value
429//!
430//! A reserved field is a normal **stored** field with a known *spec value*: the type's
431//! zero for `#[reserved]`, the given expression for `#[reserved_with(<expr>)]` (e.g. a
432//! must-be-one pattern). On the default path it reads and writes its *actual* value —
433//! so you can observe a peer's reserved bits and override them — while the builder
434//! defaults it to the spec value (so it isn't required) and the `spec_*` codecs use the
435//! spec value instead.
436//!
437//! ```
438//! use bnb::bin;
439//! #[bin(big)]
440//! #[derive(Debug, PartialEq)]
441//! struct R {
442//! a: u8,
443//! #[reserved] pad: u8, // spec value 0x00
444//! #[reserved_with(0xFFu8)] ones: u8, // spec value 0xFF
445//! b: u8,
446//! }
447//!
448//! // The builder makes the reserved fields optional, defaulting to their spec values.
449//! let r = R::builder().a(1).b(2).build().unwrap();
450//! assert_eq!(r.to_bytes().unwrap(), [0x01, 0x00, 0xFF, 0x02]);
451//!
452//! // Decode is verbatim — it captures the actual reserved bits off the wire...
453//! let actual = R::decode_exact(&[0x01, 0x55, 0x55, 0x02]).unwrap();
454//! assert_eq!((actual.pad, actual.ones), (0x55, 0x55));
455//! assert_eq!(actual.to_bytes().unwrap(), [0x01, 0x55, 0x55, 0x02]); // re-emitted as-is
456//! // ...while `to_canonical_bytes` writes the reserved fields' spec values instead.
457//! assert_eq!(actual.to_canonical_bytes().unwrap(), [0x01, 0x00, 0xFF, 0x02]);
458//! ```
459//!
460//! # `pad_*` / `align_*` — forward positioning
461//!
462//! `#[br(pad_before = <bits>)]` / `pad_after` skip a bit count around a field;
463//! `align_before` / `align_after` skip to the next byte boundary. Bit/byte amounts come
464//! from the [`prelude`](crate::prelude) (`1.bytes()`, `4.bits()`).
465//!
466//! ```
467//! use bnb::{bin, prelude::*};
468//! #[bin(big)]
469//! #[derive(Debug, PartialEq)]
470//! struct P { a: u8, #[br(pad_before = 1u32.bytes())] b: u8 }
471//!
472//! let p = P { a: 1, b: 2 };
473//! assert_eq!(p.to_bytes().unwrap(), [0x01, 0x00, 0x02]); // one zero pad byte
474//! assert_eq!(P::decode_exact(&[0x01, 0x99, 0x02]).unwrap(), p); // pad skipped on read
475//! ```
476//!
477//! ```
478//! use bnb::bin;
479//! #[bin(big)]
480//! #[derive(Debug, PartialEq)]
481//! struct A { flag: bool, #[br(align_before)] val: u8 } // val starts on a byte boundary
482//!
483//! let a = A { flag: true, val: 0x2A };
484//! assert_eq!(a.to_bytes().unwrap(), [0x80, 0x2A]); // flag in the high bit, then val
485//! assert_eq!(A::decode_exact(&[0x80, 0x2A]).unwrap(), a);
486//! ```
487//!
488//! # `restore_position` — peek without consuming
489//!
490//! `#[br(restore_position)]` reads the field, then rewinds the cursor so later fields
491//! re-read the same bytes (e.g. peek a discriminant, then read the full record). The
492//! field is not re-emitted on write — the overlapping field owns those bytes. It needs
493//! a seekable source, so `decode` on a forward-only stream is a compile error; the
494//! slice paths (`decode_exact`/`decode_all`/`peek`) always qualify.
495//!
496//! ```
497//! use bnb::bin;
498//! #[bin(big)]
499//! #[derive(Debug, PartialEq)]
500//! struct Peeked {
501//! #[br(restore_position)] tag: u8, // peek the first byte...
502//! full: u16, // ...then read it as the high byte of a u16
503//! }
504//!
505//! let p = Peeked::decode_exact(&[0xAB, 0xCD]).unwrap();
506//! assert_eq!(p.tag, 0xAB);
507//! assert_eq!(p.full, 0xABCD);
508//! assert_eq!(p.to_bytes().unwrap(), [0xAB, 0xCD]); // `full` emits the bytes; `tag` does not
509//! ```
510//!
511//! # `seek` — read at an absolute offset (pointer-following)
512//!
513//! `#[br(seek = <bits>)]` jumps the cursor to an **absolute** bit offset before reading
514//! the field — the building block for offset tables and pointer chains. Bit/byte amounts
515//! come from the [`prelude`](crate::prelude) (`ptr.bytes()`, `n.bits()`). It is read-side
516//! (the writer is append-only); pair it with `restore_position` to read at the offset and
517//! return so later fields continue in order. Like `restore_position` it seeks, so
518//! `decode` on a forward-only stream is a compile error; the slice paths qualify.
519//!
520//! ```
521//! use bnb::{bin, prelude::*};
522//! #[bin(big)]
523//! #[derive(Debug, PartialEq)]
524//! struct Ptr {
525//! ptr: u8, // byte offset of `target`
526//! #[br(seek = ptr.bytes(), restore_position)]
527//! target: u8, // read at `ptr`, then rewind
528//! next: u8, // continues right after `ptr`
529//! }
530//!
531//! // `peek` doesn't require full consumption (seek/restore leave the tail untouched).
532//! let p = Ptr::peek(&[0x03, 0x11, 0x22, 0xAB]).unwrap();
533//! assert_eq!((p.ptr, p.target, p.next), (3, 0xAB, 0x11));
534//! ```
535//!
536//! On encode the seek is a no-op (the writer appends), so a *relocated* layout won't
537//! round-trip through the default encoder — emit such formats with `write_with` /
538//! `write_only`, where you control placement.
539//!
540//! # `dbg` — trace a field as it decodes
541//!
542//! `#[br(dbg)]` emits a [`tracing`](https://docs.rs/tracing) event as the field is read,
543//! carrying its start bit offset and decoded value (the field type must be `Debug`). It
544//! is a read-side diagnostic — no extra bits are consumed and encode is unaffected. The
545//! event is at `TRACE` level under the `bnb::dbg` target, so you can surface just these
546//! with `RUST_LOG=bnb::dbg=trace` (the application installs the subscriber; libraries
547//! only emit).
548//!
549//! ```
550//! use bnb::bin;
551//! #[bin(big)]
552//! #[derive(Debug, PartialEq)]
553//! struct Framed { tag: u8, #[br(dbg)] len: u16 }
554//!
555//! // Decoding is identical with or without `dbg`; it just also traces `len`.
556//! let f = Framed::decode_exact(&[0x01, 0x00, 0x2A]).unwrap();
557//! assert_eq!(f, Framed { tag: 1, len: 42 });
558//! ```
559//!
560//! # `try_str` — render a byte buffer as a string in `Debug`
561//!
562//! `#[try_str]` is a **rendering hint**, not a codec directive: a byte-buffer field (`Vec<u8>`
563//! / `[u8; N]`) prints in `Debug` as a quoted, escaped **string** when its bytes are valid
564//! UTF-8, and falls back to **hex bytes** otherwise — all-or-nothing, never lossy (no `�`). It
565//! changes nothing on the wire: the field still stores raw bytes (sized by `count`, etc.), so
566//! the parser stays permissive — a non-UTF-8 value decodes fine, it just renders as bytes.
567//! `Debug` is what `tracing`'s `?` and `{:#?}` use, so this is what tidies up log output.
568//!
569//! ```
570//! use bnb::bin;
571//! #[bin(big)]
572//! #[derive(Debug, PartialEq, Eq)]
573//! struct Record {
574//! id: u8,
575//! #[br(temp)] #[bw(calc = self.name.len() as u8)] len: u8,
576//! #[br(count = len)] #[try_str] name: Vec<u8>,
577//! }
578//!
579//! let text = Record { id: 1, name: b"hi".to_vec() };
580//! assert!(format!("{text:?}").contains(r#"name: "hi""#)); // valid UTF-8 -> "hi"
581//!
582//! let bin = Record { id: 2, name: vec![0xC0, 0xDE] };
583//! assert!(format!("{bin:?}").contains("name: [c0, de]")); // not UTF-8 -> hex bytes
584//! ```