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