Skip to main content

bnb/
codecs.rs

1//! Ready-made field codecs for [`parse_with`]/[`write_with`] — LEB128 varints,
2//! NUL-terminated C strings, and length-prefixed strings.
3//!
4//! Reference them by path from a [`#[bin]`](macro@crate::bin) field instead of
5//! hand-rolling the read/write pair:
6//!
7//! | codec | wire form | field type | attributes |
8//! |---|---|---|---|
9//! | [`leb128`] | 7-bit groups, high-bit continuation | `u8`…`u128` | `parse_with = bnb::codecs::leb128::parse`, `write_with = bnb::codecs::leb128::write` |
10//! | [`cstring`] | bytes, `0x00` terminator | `Vec<u8>` / `String` | `parse_with = bnb::codecs::cstring::parse` (or `parse_utf8`), `write_with = …::write` (or `write_utf8`) |
11//! | [`prefixed`] | length prefix, then UTF-8 bytes | `String` | `parse_with = bnb::codecs::prefixed::parse_string::<_, u16>`, `write_with = …::write_string::<_, u16>` |
12//!
13//! Every codec follows the crate's dual-use doctrine: **parse rejects only the
14//! unrepresentable** (an overflowing varint, invalid UTF-8 destined for a `String`) and
15//! **write refuses only what could not round-trip** (an embedded NUL in a C string, a
16//! length exceeding its prefix). Nothing here validates *meaning* — that stays with
17//! `validate` at construction.
18//!
19//! The fn contract (what a hand-rolled codec must also satisfy): a parse fn is
20//! `fn(&mut impl Source) -> Result<T, BitError>`, a write fn is
21//! `fn(&T, &mut impl Sink) -> Result<(), BitError>` — see the
22//! [`guide::directives`](crate::guide::directives) `parse_with` section for rolling your
23//! own.
24//!
25//! Using the same codec on many fields? Wrap it **once** as a per-type newtype —
26//! `#[bin(codec = bnb::codecs::leb128)] struct Varint(pub u64);` — and use the type as
27//! a plain field everywhere (see the guide's "Per-type codecs" section).
28//!
29//! Naming note: this is **not** the `codec` module — that one (under the `tokio`
30//! feature) is the async `Decoder`/`Encoder` adapter for framed transports; *this*
31//! module is the library of ready-made *field* codecs.
32//!
33//! [`parse_with`]: crate::guide::directives
34//! [`write_with`]: crate::guide::directives
35
36/// Unsigned LEB128 (`varint`) — 7 payload bits per byte, low group first, high bit set
37/// while more bytes follow. The wire format of protobuf `varint`, WebAssembly, DWARF.
38///
39/// Generic over the field's integer width via [`Varint`]: the declared field type pins
40/// the width, so the same `parse`/`write` pair serves `u16` and `u64` fields alike.
41/// Decoding is **bounded and overflow-checked** — a value too large for the field or a
42/// continuation run longer than the width allows is a clean [`BitError`], never a panic
43/// or a silent wrap. Non-minimal encodings (e.g. `0x80 0x00` for zero) are accepted:
44/// permissive parse, canonical (minimal) write.
45///
46/// ```
47/// use bnb::bin;
48///
49/// #[bin(big)]
50/// #[derive(Debug, PartialEq)]
51/// struct Record {
52///     #[br(parse_with = bnb::codecs::leb128::parse)]
53///     #[bw(write_with = bnb::codecs::leb128::write)]
54///     length: u32, // width inferred from the field type
55///     #[br(parse_with = bnb::codecs::leb128::parse)]
56///     #[bw(write_with = bnb::codecs::leb128::write)]
57///     timestamp: u64,
58/// }
59///
60/// let r = Record { length: 300, timestamp: 1 };
61/// let bytes = r.to_bytes().unwrap();
62/// assert_eq!(bytes, [0xAC, 0x02, 0x01]); // 300 = 0b10_0101100 → AC 02; 1 → 01
63/// assert_eq!(Record::decode_exact(&bytes).unwrap(), r);
64/// ```
65///
66/// [`BitError`]: crate::BitError
67pub mod leb128 {
68    use crate::bitstream::{BitError, Sink, Source, sealed};
69    use crate::field::Bits;
70    use alloc::format;
71
72    /// The integer widths readable/writable as LEB128 — `u8`, `u16`, `u32`, `u64`,
73    /// `u128`. Sealed: LEB128 is byte-granular, so the primitive widths are the whole
74    /// set (a `uN` field wants the next wider primitive).
75    #[diagnostic::on_unimplemented(
76        message = "`{Self}` cannot be read/written as a LEB128 varint",
77        note = "supported widths: u8, u16, u32, u64, u128 — declare a `uN` field as the next wider primitive",
78        note = "this trait is sealed — the supported widths are built in"
79    )]
80    pub trait Varint: Bits + sealed::Sealed {}
81
82    impl Varint for u8 {}
83    impl Varint for u16 {}
84    impl Varint for u32 {}
85    impl Varint for u64 {}
86    impl Varint for u128 {}
87
88    /// Reads one LEB128 value; the target width comes from the field's declared type.
89    ///
90    /// # Errors
91    /// [`Convert`](crate::bitstream::ErrorKind::Convert) when the value overflows the
92    /// width or the continuation run exceeds the width's maximum byte count;
93    /// [`UnexpectedEof`](crate::bitstream::ErrorKind::UnexpectedEof) when the input ends
94    /// mid-varint.
95    pub fn parse<S: Source, T: Varint>(r: &mut S) -> Result<T, BitError> {
96        let start = r.bit_pos();
97        // ceil(BITS / 7): 2 / 3 / 5 / 10 / 19 bytes for u8 / u16 / u32 / u64 / u128.
98        let max_bytes = T::BITS.div_ceil(7);
99        let mut value: u128 = 0;
100        let mut shift: u32 = 0;
101        for _ in 0..max_bytes {
102            let byte: u8 = r.read()?;
103            let group = u128::from(byte & 0x7F);
104            // The loop bound keeps `shift ≤ 7·(max_bytes−1) < T::BITS`, so `T::BITS −
105            // shift` never underflows; when the group carries bits past the width,
106            // reject instead of wrapping (the hand-rolled version this replaces shifted
107            // unbounded — a debug panic on hostile input).
108            if shift + 7 > T::BITS && (group >> (T::BITS - shift)) != 0 {
109                return Err(BitError::convert(
110                    format!("LEB128 value overflows u{}", T::BITS),
111                    start,
112                ));
113            }
114            value |= group << shift;
115            if byte & 0x80 == 0 {
116                return Ok(T::from_bits(value));
117            }
118            shift += 7;
119        }
120        Err(BitError::convert(
121            format!(
122                "unterminated LEB128: no final byte within {max_bytes} bytes (u{})",
123                T::BITS
124            ),
125            start,
126        ))
127    }
128
129    /// Writes one LEB128 value in canonical (minimal) form.
130    ///
131    /// # Errors
132    /// Only what the underlying [`Sink`] reports (e.g. a bounded buffer running out).
133    pub fn write<K: Sink, T: Varint>(v: &T, w: &mut K) -> Result<(), BitError> {
134        let mut value = v.into_bits();
135        loop {
136            let mut byte = (value & 0x7F) as u8;
137            value >>= 7;
138            if value != 0 {
139                byte |= 0x80;
140            }
141            w.write(byte)?;
142            if value == 0 {
143                return Ok(());
144            }
145        }
146    }
147}
148
149/// NUL-terminated C strings — bytes until (and consuming) a `0x00` terminator.
150///
151/// Two forms: raw bytes (`parse`/`write` over `Vec<u8>` — permissive, pairs with
152/// [`#[try_str]`](macro@crate::bin) for display) and UTF-8 (`parse_utf8`/`write_utf8`
153/// over `String` — decode errors on invalid UTF-8, which a `String` physically cannot
154/// hold). The terminator is excluded from the value; write appends it, and **errors on
155/// an embedded NUL** — the wire image would decode back truncated, so it cannot
156/// round-trip.
157///
158/// ```
159/// use bnb::bin;
160///
161/// #[bin(big)]
162/// #[derive(Debug, PartialEq)]
163/// struct Entry {
164///     id: u8,
165///     #[br(parse_with = bnb::codecs::cstring::parse)]
166///     #[bw(write_with = bnb::codecs::cstring::write)]
167///     #[try_str]
168///     name: Vec<u8>,
169/// }
170///
171/// let e = Entry { id: 7, name: b"alpha".to_vec() };
172/// let bytes = e.to_bytes().unwrap();
173/// assert_eq!(bytes, [7, b'a', b'l', b'p', b'h', b'a', 0]);
174/// assert_eq!(Entry::decode_exact(&bytes).unwrap(), e);
175/// ```
176pub mod cstring {
177    use crate::bitstream::{BitError, Sink, Source};
178    use alloc::format;
179    use alloc::string::String;
180    use alloc::vec::Vec;
181
182    /// Reads bytes until a `0x00` terminator (consumed, excluded from the value).
183    /// Permissive: any byte sequence is accepted.
184    ///
185    /// # Errors
186    /// [`UnexpectedEof`](crate::bitstream::ErrorKind::UnexpectedEof) when the input ends
187    /// before a terminator.
188    pub fn parse<S: Source>(r: &mut S) -> Result<Vec<u8>, BitError> {
189        let mut v = Vec::new();
190        loop {
191            let b: u8 = r.read()?;
192            if b == 0 {
193                return Ok(v);
194            }
195            v.push(b);
196        }
197    }
198
199    /// Writes the bytes followed by the `0x00` terminator.
200    ///
201    /// # Errors
202    /// [`Convert`](crate::bitstream::ErrorKind::Convert) on an embedded NUL — the
203    /// decoded value would stop there, so the wire image could not round-trip.
204    pub fn write<K: Sink>(v: &[u8], w: &mut K) -> Result<(), BitError> {
205        if let Some(i) = v.iter().position(|&b| b == 0) {
206            return Err(BitError::convert(
207                format!("embedded NUL at byte {i}: a NUL-terminated string cannot represent it"),
208                w.bit_pos(),
209            ));
210        }
211        w.write_bytes(v)?;
212        w.write(0u8)
213    }
214
215    /// [`parse`], then UTF-8-validates into a `String`.
216    ///
217    /// # Errors
218    /// As [`parse`], plus [`Convert`](crate::bitstream::ErrorKind::Convert) on invalid
219    /// UTF-8 (a `String` cannot represent it; keep a `Vec<u8>` field + [`parse`] to
220    /// preserve arbitrary bytes).
221    pub fn parse_utf8<S: Source>(r: &mut S) -> Result<String, BitError> {
222        let start = r.bit_pos();
223        let bytes = parse(r)?;
224        String::from_utf8(bytes)
225            .map_err(|e| BitError::convert(format!("invalid UTF-8 in string field: {e}"), start))
226    }
227
228    /// Writes the string's UTF-8 bytes followed by the terminator (via [`write`], so an
229    /// embedded `'\0'` is rejected the same way).
230    ///
231    /// # Errors
232    /// As [`write`].
233    pub fn write_utf8<K: Sink>(s: &str, w: &mut K) -> Result<(), BitError> {
234        write(s.as_bytes(), w)
235    }
236}
237
238/// Length-prefixed UTF-8 strings — a [`CountPrefix`](crate::bitstream::CountPrefix) integer
239/// counting the **bytes**, then that many bytes of UTF-8.
240///
241/// Generic over the prefix type: any type the
242/// [`#[brw(count_prefix = …)]`](macro@crate::bin) directive accepts, including the
243/// arbitrary-width `uN`s (a `u12` prefix is 12 bits on the wire). Pick it with a
244/// turbofish — the cursor parameter stays inferred:
245///
246/// ```
247/// use bnb::bin;
248///
249/// #[bin(big)]
250/// #[derive(Debug, PartialEq)]
251/// struct Label {
252///     #[br(parse_with = bnb::codecs::prefixed::parse_string::<_, u16>)]
253///     #[bw(write_with = bnb::codecs::prefixed::write_string::<_, u16>)]
254///     title: String,
255/// }
256///
257/// let l = Label { title: "hi".into() };
258/// let bytes = l.to_bytes().unwrap();
259/// assert_eq!(bytes, [0x00, 0x02, b'h', b'i']); // u16 byte length, then UTF-8
260/// assert_eq!(Label::decode_exact(&bytes).unwrap(), l);
261/// ```
262///
263/// For length-prefixed **bytes** (`Vec<u8>`), use the
264/// [`#[brw(count_prefix = …)]`](macro@crate::bin) directive instead — same wire form,
265/// one attribute. Write is **checked**: a string longer than the prefix's range is a
266/// [`BitError`](crate::BitError), never a wrapped length. (On 32-bit targets a `u64`/
267/// `u128` prefix narrows through `usize` on read — see
268/// [`CountPrefix::to_count`](crate::bitstream::CountPrefix::to_count).)
269pub mod prefixed {
270    use crate::bitstream::{BitError, CountPrefix, Sink, Source};
271    use alloc::format;
272    use alloc::string::{String, ToString};
273
274    /// Reads a `P` byte-length prefix, then that many bytes, UTF-8-validated.
275    ///
276    /// No pre-allocation from the untrusted length — bytes are pushed as read, bounded
277    /// by the input, so a hostile huge prefix is a fast
278    /// [`UnexpectedEof`](crate::bitstream::ErrorKind::UnexpectedEof), not an allocation.
279    ///
280    /// # Errors
281    /// `UnexpectedEof` when the input is shorter than the prefix promises;
282    /// [`Convert`](crate::bitstream::ErrorKind::Convert) on invalid UTF-8.
283    pub fn parse_string<S: Source, P: CountPrefix>(r: &mut S) -> Result<String, BitError> {
284        let start = r.bit_pos();
285        let n = r.read::<P>()?.to_count();
286        // `read_bytes` pushes as it reads — nothing pre-allocated from the untrusted `n`.
287        let bytes = r.read_bytes(n)?;
288        String::from_utf8(bytes)
289            .map_err(|e| BitError::convert(format!("invalid UTF-8 in string field: {e}"), start))
290    }
291
292    /// Writes the `P` byte-length prefix, then the string's UTF-8 bytes.
293    ///
294    /// # Errors
295    /// [`Convert`](crate::bitstream::ErrorKind::Convert) when the byte length exceeds
296    /// the prefix's range (checked — never a silently wrapped length).
297    pub fn write_string<K: Sink, P: CountPrefix>(s: &str, w: &mut K) -> Result<(), BitError> {
298        let prefix = P::try_from_count(s.len())
299            .map_err(|e| BitError::convert(e.to_string(), w.bit_pos()))?;
300        w.write(prefix)?;
301        w.write_bytes(s.as_bytes())
302    }
303}
304
305#[cfg(test)]
306mod unit {
307    use super::*;
308    use crate::bitstream::{BitReader, BitWriter, CountPrefix, ErrorKind};
309    use crate::u12;
310    use alloc::vec;
311    use alloc::vec::Vec;
312
313    fn encode_with<T: ?Sized>(
314        f: impl Fn(&T, &mut BitWriter) -> Result<(), crate::BitError>,
315        v: &T,
316    ) -> Vec<u8> {
317        let mut w = BitWriter::new();
318        f(v, &mut w).unwrap();
319        w.into_bytes()
320    }
321
322    // ——— leb128 ———
323
324    fn leb_round_trip<T: leb128::Varint + PartialEq + core::fmt::Debug>(v: T) {
325        let mut w = BitWriter::new();
326        leb128::write(&v, &mut w).unwrap();
327        let bytes = w.into_bytes();
328        let mut r = BitReader::new(&bytes);
329        assert_eq!(leb128::parse::<_, T>(&mut r).unwrap(), v);
330        assert_eq!(r.remaining_bits(), 0, "consumed exactly the varint");
331    }
332
333    #[test]
334    fn leb128_round_trips_per_width() {
335        for v in [0u8, 1, 127, 128, u8::MAX] {
336            leb_round_trip(v);
337        }
338        for v in [0u16, 127, 128, 0x3FFF, u16::MAX] {
339            leb_round_trip(v);
340        }
341        for v in [0u32, 300, u32::MAX] {
342            leb_round_trip(v);
343        }
344        for v in [0u64, 300, 1_000_000, u64::MAX] {
345            leb_round_trip(v);
346        }
347        for v in [0u128, u128::from(u64::MAX) + 1, u128::MAX] {
348            leb_round_trip(v);
349        }
350    }
351
352    #[test]
353    fn leb128_golden_bytes() {
354        assert_eq!(encode_with(leb128::write, &300u64), [0xAC, 0x02]);
355        assert_eq!(encode_with(leb128::write, &1u32), [0x01]);
356    }
357
358    #[test]
359    fn leb128_accepts_non_minimal() {
360        // 0x80 0x00 is zero with a redundant continuation — permissive parse takes it.
361        let mut r = BitReader::new(&[0x80, 0x00]);
362        assert_eq!(leb128::parse::<_, u8>(&mut r).unwrap(), 0);
363    }
364
365    #[test]
366    fn leb128_overflow_is_an_error_not_a_panic() {
367        // u8: second byte may only carry one bit. 0xFF 0x01 = 255 fits; 0xFF 0x02 doesn't.
368        let mut r = BitReader::new(&[0xFF, 0x01]);
369        assert_eq!(leb128::parse::<_, u8>(&mut r).unwrap(), 255);
370        let mut r = BitReader::new(&[0xFF, 0x02]);
371        let err = leb128::parse::<_, u8>(&mut r).unwrap_err();
372        assert!(
373            matches!(&err.kind, ErrorKind::Convert { message } if message.contains("overflows u8")),
374            "got {err:?}"
375        );
376        assert_eq!(err.at, 0, "error points at the varint's start");
377
378        // u32: the 5th byte may carry 4 bits; 0x1F sets bit 33 → overflow.
379        let mut r = BitReader::new(&[0xFF, 0xFF, 0xFF, 0xFF, 0x1F]);
380        let err = leb128::parse::<_, u32>(&mut r).unwrap_err();
381        assert!(
382            matches!(&err.kind, ErrorKind::Convert { message } if message.contains("overflows u32"))
383        );
384
385        // u128: 19th byte may carry 2 bits (18·7 = 126); 0x03 fits, 0x04 overflows.
386        let mut ok = vec![0xFF; 18];
387        ok.push(0x03);
388        let mut r = BitReader::new(&ok);
389        assert_eq!(leb128::parse::<_, u128>(&mut r).unwrap(), u128::MAX);
390        let mut over = vec![0xFF; 18];
391        over.push(0x04);
392        let mut r = BitReader::new(&over);
393        assert!(leb128::parse::<_, u128>(&mut r).is_err());
394    }
395
396    #[test]
397    fn leb128_hostile_continuation_run_is_bounded() {
398        // 11 continuation bytes against a u64 (max 10): the old hand-rolled loop shifted
399        // past 63 and panicked in debug — the shipped codec errors cleanly.
400        let bytes = [0x80u8; 11];
401        let mut r = BitReader::new(&bytes);
402        let err = leb128::parse::<_, u64>(&mut r).unwrap_err();
403        assert!(
404            matches!(&err.kind, ErrorKind::Convert { message } if message.contains("unterminated")),
405            "got {err:?}"
406        );
407    }
408
409    #[test]
410    fn leb128_eof_mid_varint() {
411        let mut r = BitReader::new(&[0x80, 0x80]);
412        let err = leb128::parse::<_, u64>(&mut r).unwrap_err();
413        assert!(matches!(err.kind, ErrorKind::UnexpectedEof { .. }));
414    }
415
416    // ——— cstring ———
417
418    #[test]
419    fn cstring_round_trips() {
420        for s in [&b""[..], b"a", b"alpha", &[0xFF, 0xFE]] {
421            let bytes = encode_with(cstring::write, s);
422            assert_eq!(bytes.last(), Some(&0));
423            let mut r = BitReader::new(&bytes);
424            assert_eq!(cstring::parse(&mut r).unwrap(), s);
425        }
426    }
427
428    #[test]
429    fn cstring_eof_before_terminator() {
430        let mut r = BitReader::new(b"hi");
431        let err = cstring::parse(&mut r).unwrap_err();
432        assert!(matches!(err.kind, ErrorKind::UnexpectedEof { .. }));
433    }
434
435    #[test]
436    fn cstring_write_rejects_embedded_nul() {
437        let mut w = BitWriter::new();
438        let err = cstring::write(&[1, 0, 2], &mut w).unwrap_err();
439        assert!(
440            matches!(&err.kind, ErrorKind::Convert { message } if message.contains("byte 1")),
441            "got {err:?}"
442        );
443        // The str form delegates, so it rejects the same way.
444        let mut w = BitWriter::new();
445        assert!(cstring::write_utf8("a\0b", &mut w).is_err());
446    }
447
448    #[test]
449    fn cstring_utf8_forms() {
450        let bytes = encode_with(cstring::write_utf8, "héllo");
451        let mut r = BitReader::new(&bytes);
452        assert_eq!(cstring::parse_utf8(&mut r).unwrap(), "héllo");
453
454        let mut r = BitReader::new(&[0xFF, 0x00]);
455        let err = cstring::parse_utf8(&mut r).unwrap_err();
456        assert!(matches!(&err.kind, ErrorKind::Convert { message } if message.contains("UTF-8")));
457    }
458
459    // ——— prefixed ———
460
461    fn prefixed_round_trip<P: CountPrefix>(s: &str) {
462        let mut w = BitWriter::new();
463        prefixed::write_string::<_, P>(s, &mut w).unwrap();
464        let bytes = w.into_bytes();
465        let mut r = BitReader::new(&bytes);
466        assert_eq!(prefixed::parse_string::<_, P>(&mut r).unwrap(), s);
467    }
468
469    #[test]
470    fn prefixed_round_trips_across_prefix_types() {
471        for s in ["", "x", "hello", "héllo wörld"] {
472            prefixed_round_trip::<u8>(s);
473            prefixed_round_trip::<u16>(s);
474            prefixed_round_trip::<u32>(s);
475            prefixed_round_trip::<u12>(s); // a uN prefix: 12 bits on the wire
476        }
477    }
478
479    #[test]
480    fn prefixed_u12_is_bit_native() {
481        let mut w = BitWriter::new();
482        prefixed::write_string::<_, u12>("hi", &mut w).unwrap();
483        // 12-bit length (2) + 'h' + 'i' = 12 + 16 bits = 28 bits → 4 bytes padded.
484        let bytes = w.into_bytes();
485        assert_eq!(bytes[0], 0x00); // high 8 of the 12-bit length
486        assert_eq!(bytes[1] >> 4, 0x2); // low 4 of the length
487    }
488
489    #[test]
490    fn prefixed_write_overflow_is_checked() {
491        let long = "x".repeat(256);
492        let mut w = BitWriter::new();
493        let err = prefixed::write_string::<_, u8>(&long, &mut w).unwrap_err();
494        assert!(
495            matches!(&err.kind, ErrorKind::Convert { message } if message.contains("256")),
496            "got {err:?}"
497        );
498        let long = "x".repeat(4096);
499        let mut w = BitWriter::new();
500        assert!(prefixed::write_string::<_, u12>(&long, &mut w).is_err());
501    }
502
503    #[test]
504    fn prefixed_hostile_length_is_eof_not_alloc() {
505        // Prefix promises u32::MAX bytes; only 3 follow. Push-per-byte means a fast EOF.
506        let mut wire = vec![0xFF, 0xFF, 0xFF, 0xFF];
507        wire.extend_from_slice(b"abc");
508        let mut r = BitReader::new(&wire);
509        let err = prefixed::parse_string::<_, u32>(&mut r).unwrap_err();
510        assert!(matches!(err.kind, ErrorKind::UnexpectedEof { .. }));
511    }
512
513    #[test]
514    fn prefixed_invalid_utf8_is_convert() {
515        let wire = [0x02, 0xFF, 0xFE];
516        let mut r = BitReader::new(&wire);
517        let err = prefixed::parse_string::<_, u8>(&mut r).unwrap_err();
518        assert!(matches!(&err.kind, ErrorKind::Convert { message } if message.contains("UTF-8")));
519    }
520
521    #[test]
522    fn error_display_texts() {
523        let mut w = BitWriter::new();
524        let e = prefixed::write_string::<_, u8>(&"x".repeat(300), &mut w).unwrap_err();
525        assert_eq!(
526            e.to_string(),
527            "conversion failed: value 300 does not fit in 8 bits at bit 0"
528        );
529    }
530}