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