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}