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}