1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
//! **varint** — the shipped **LEB128** codec, [`bnb::codecs::leb128`]: variable-length
//! integers (the encoding protobuf, DWARF, WASM, and git use), two ways. Per **field**:
//! one `parse_with`/`write_with` attribute pair, generic over the width (the field's
//! declared type — `u32` vs `u64` below — pins it). Per **type**: a `#[bin(codec = …)]`
//! newtype that carries the codec with it, so fields need no attributes at all (just
//! `#[brw(variable)]` in a fixed parent). Decoding is **bounded and overflow-checked**:
//! a hostile continuation run is a clean error, where a naive hand-rolled reader would
//! shift unbounded and panic in debug builds.
//!
//! (Rolling your own codec is still first-class — see the `parse_with` section of
//! [`bnb::guide::directives`], and `dns.rs` for a real one with DNS name compression.)
//!
//! Run with: `cargo run -p bitsandbytes --example varint`
use bnb::bin;
use bnb::bitstream::ErrorKind;
#[bin(big)]
#[derive(Debug, PartialEq, Eq, Clone)]
struct Record {
kind: u8,
#[br(parse_with = bnb::codecs::leb128::parse)]
#[bw(write_with = bnb::codecs::leb128::write)]
length: u32, // one codec, two widths — the field type decides
#[br(parse_with = bnb::codecs::leb128::parse)]
#[bw(write_with = bnb::codecs::leb128::write)]
timestamp: u64,
}
// --- the per-type form: annotate once, use as a plain field everywhere ----------
/// A LEB128-encoded u64 — the codec travels with the type.
#[bin(codec = bnb::codecs::leb128)]
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
struct Varint(u64);
/// `kind` and `crc` are fixed-width; `#[brw(variable)]` tells the parent its width
/// isn't fixed (so it doesn't try to claim `FixedBitLen`).
#[bin(big)]
#[derive(Debug, PartialEq, Eq, Clone)]
struct Frame {
kind: u8,
#[brw(variable)]
length: Varint, // no codec attributes — the type owns them
crc: u16,
}
fn main() {
// Small values pack into one byte; large ones grow only as needed — the point of LEB128.
for &(length, timestamp) in &[(0u32, 0u64), (127, 128), (300, 1_000_000), (u32::MAX, 1)] {
let r = Record {
kind: 1,
length,
timestamp,
};
let bytes = r.to_bytes().unwrap();
println!(
"len={length} ts={timestamp} -> {} bytes {bytes:02x?}",
bytes.len()
);
assert_eq!(Record::decode_exact(&bytes).unwrap(), r);
}
// Bounded decode: a continuation run longer than the width allows (a u32 fits in
// 5 LEB128 bytes) is a clean, position-aware error — never a panic, never a wrap.
let hostile = [0x01, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80]; // kind, then 6×"more follows"
let err = Record::decode_exact(&hostile).unwrap_err();
assert!(
matches!(&err.kind, ErrorKind::Convert { message } if message.contains("unterminated"))
);
println!("hostile continuation run -> {err}");
// Overflow is checked per width: 5 full bytes overflow a u32 field.
let too_wide = [0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0x1F, 0x00];
let err = Record::decode_exact(&too_wide).unwrap_err();
assert_eq!(err.field, Some("length"));
println!("overflowing u32 field -> {err}");
// The per-type form: the newtype carries the codec, the field carries nothing.
let f = Frame {
kind: 1,
length: Varint(300),
crc: 0xBEEF,
};
let bytes = f.to_bytes().unwrap();
assert_eq!(bytes, [0x01, 0xAC, 0x02, 0xBE, 0xEF]); // kind | varint(300) | crc
assert_eq!(Frame::decode_exact(&bytes).unwrap(), f);
assert_eq!(u64::from(Varint(300)), 300); // From both ways comes generated
println!("codec-newtype frame -> {bytes:02x?}");
println!("all checks passed");
}