bnb/lib.rs
1/*!
2`bnb` — an owned, bit-aware binary codec: ergonomic, fast bit/byte field types and
3the unified `#[bin]` whole-message codec.
4
5It provides, designed to *compose*:
6
7- **Arbitrary-width integers** ([`u1`]..[`u127`], via [`UInt`]) for sub-byte
8 fields — a dependency-free replacement for `arbitrary-int`.
9- **`#[bitfield]`** — an attribute macro that packs typed fields into a single
10 backing integer with **explicit, independent control of bit order**
11 (MSB/LSB-first) **and byte order** (big/little). It generates getters,
12 immutable `with_*` setters, raw access, and allocation-free `*_bytes` conversions.
13- **`#[derive(BitEnum)]`** — enum ⇄ integer at a chosen width, with an optional
14 `#[catch_all]` variant that preserves unknown values (the dual-use convention).
15- **`#[bin]`** — the unified whole-message codec (see below): reads/writes a struct
16 at arbitrary bit offsets, with a rich, `binrw`-inspired directive surface.
17
18The aim is to collapse a whole stack of overlapping helpers —
19`modular-bitfield`(`-msb`), `bitfield-struct`, `bitbybit`, `arbitrary-int`,
20`num_enum`, and a `binrw`-style codec — into one fast, integer-backed
21(shift/mask, no `bitvec`) crate. See [Inspiration](#inspiration).
22
23# Example — a DNS-style 16-bit header field
24
25```
26use bnb::{bitfield, BitEnum, u4, u5, u7};
27
28# #[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
29# struct Flags(bnb::u7);
30# bnb::impl_bits! {
31# impl Bits for Flags {
32# const BITS: u32 = 7;
33# const fn into_bits(self) -> u128 { self.0.value() as u128 }
34# const fn from_bits(raw: u128) -> Self { Flags(u7::from_raw(raw as u8)) }
35# }
36# }
37#[derive(BitEnum, Clone, Copy, Debug, PartialEq, Eq)]
38#[bit_enum(u4)]
39enum RCode {
40 NoError, // 0
41 FormErr, // 1
42 ServFail, // 2
43 #[catch_all]
44 Other(u4), // any other 4-bit value
45}
46
47// MSB-first packing (network/RFC order), big-endian on the wire.
48#[bitfield(u16, bits = msb, bytes = big)]
49#[derive(Clone, Copy, Debug, PartialEq, Eq)]
50struct State {
51 opcode: u5, // first field -> high bits
52 flags: Flags,
53 rcode: RCode, // last field -> low bits
54}
55
56let s = State::new()
57 .with_opcode(u5::new(2))
58 .with_rcode(RCode::ServFail);
59assert_eq!(s.rcode(), RCode::ServFail);
60// opcode occupies bits 11..=15 (the high 5 of the u16).
61assert_eq!(s.to_be_bytes()[0] >> 3, 2);
62```
63
64# Bit order vs. byte order
65
66These are independent knobs, which is the whole point:
67
68- `bits = msb | lsb` — does the **first** declared field land in the high or low
69 bits of the backing integer. Default: `msb` (matches RFC ASCII-art layouts).
70- `bytes = big | le` — endianness of the backing integer when serialized.
71 Default: `be`.
72
73# The `#[bin]` codec
74
75Whole-message bit-aware codec: `#[bin]` (magic/count/ctx/map/if/calc·temp/reserved/
76positioning/validate) over a `Source`/`SeekSource`/`BufSource`/`SeekReader` I/O
77ladder, with an opt-in `bytes` feature for async framing. Common field codecs —
78LEB128 varints, NUL-terminated and length-prefixed strings — ship ready-made in
79[`codecs`] (referenced via `parse_with`/`write_with`).
80
81# Feature flags & `no_std`
82
83`bnb` is `no_std`-compatible — it always needs `alloc` (the codec produces
84`Vec<u8>` and owns variable-length payloads), but not `std`. Build with
85`default-features = false` for an embedded target.
86
87- **`std`** *(default)* — the `std::io` ladder ([`StreamBitReader`], [`BufSource`],
88 [`SeekReader`], [`Source::as_read`]/[`Sink::as_write`]), the `From<std::io::Error>`
89 bridge, and the `encode(writer)` convenience ([`EncodeExt`]). The `#[br(dbg)]`
90 directive (which emits a `tracing` event) is also `std`-only.
91- **`bytes`** — the zero-copy `bytes`-crate adapters; implies `std` (async/tokio framing).
92- **`tokio`** — [`BinCodec`], a `tokio_util::codec` `Decoder`/`Encoder` for any `#[bin]`
93 message: `Framed::new(tcp, BinCodec::<T>::new())` (a stream) or `UdpFramed::new(udp, …)` (a
94 datagram `Stream + Sink` of `(T, addr)`) — one codec, both async transports. Implies `bytes`.
95 This **is** bnb's async support: a native async `Source`/`Sink` family is deliberately out of
96 scope (the codec is in-memory and fast; framing is the async boundary, and `BinCodec` covers it).
97- **`net`** — ergonomic `std` socket helpers: [`MessageStream`] (whole-message read/write over
98 any `Read + Write`, e.g. a `TcpStream`, no `try_clone`) and [`MessageDatagram`] (`send_message`/
99 `recv_message` over a sealed [`DatagramSocket`] — `UdpSocket` or `UnixDatagram`). Implies `std`.
100- **`mock`** — test-only in-memory transports for exercising `net` code without a real socket:
101 [`MockDatagramSocket`] (a [`DatagramSocket`]) and [`MockStream`] (a `Read + Write`, with chunked
102 delivery to drive the read-more path). Put it in your `[dev-dependencies]`. Implies `net`.
103
104Without `std` you still get the full macro surface plus: decode from a `&[u8]`
105([`BitReader`], `Type::decode`/`decode_all`/`decode_iter`/`decode_exact`/`peek`), encode to a
106`Vec<u8>` (`Type::to_bytes`/`to_canonical_bytes`, or [`BitEncode::bit_encode`] over a [`Sink`]),
107and **incremental framing with [`BitBuf`]** — push bytes from any transport (a UART ISR, a
108radio, a channel), pull whole messages; [`BitBuf::bounded`] gives an alloc-once fixed
109footprint. You lose only the streaming `std::io` adapters and `encode(&mut impl Write)`; on
110`no_std`, encode with `to_bytes()` and write the bytes to your transport yourself. That is the
111deliberate `no_std` boundary: streaming reader adapters without `std` are out of scope for 1.0
112(if demanded later, they'd arrive as additive adapters over the ecosystem's `embedded-io`
113traits — see `ROADMAP.md`).
114
115# Inspiration
116
117`bnb` stands on the shoulders of several excellent crates, collapsing their
118capabilities into one: the arbitrary-width integers of `arbitrary-int`; the
119bitfield packing of `modular-bitfield`, `bitfield-struct`, and `bitbybit`; the
120enum ⇄ integer mapping of `num_enum`; and — most of all — the declarative,
121bidirectional codec design of [`binrw`](https://github.com/jam1garner/binrw),
122whose `#[br]`/`#[bw]` attribute vocabulary `#[bin]` deliberately echoes so the two
123feel like one toolkit. `bnb` shares no code with these crates; it is a from-scratch
124implementation, extended to do the one thing a byte-oriented `Read + Seek` codec
125cannot: read and write fields at arbitrary **bit** offsets. See `ACKNOWLEDGMENTS.md`
126for the full credit.
127
128# Guide
129
130The [`guide`] module is a set of worked, runnable walkthroughs — start there for a
131tour of the crate and the rationale behind each piece. Reading order:
132
1331. [`guide::quick_start`] — a five-minute tour of every macro.
1342. [`guide::numbers`] — the arbitrary-width integers (`u1`..`u127`) and the [`Bits`]
135 trait that lets everything compose.
1363. [`guide::bitfields`] — `#[bitfield]`: bit order, byte order, widths and ranges,
137 nesting.
1384. [`guide::enums`] — `#[derive(BitEnum)]`: catch-all vs. closed, `num_enum` parity.
1395. [`guide::flags`] — `#[bitflags]`: single-bit flag sets with set algebra.
1406. [`guide::builders`] — `#[derive(BitsBuilder)]`: the required-by-default builder.
1417. [`guide::bin_codec`] — `#[bin]`: a whole protocol header, end to end.
1428. [`guide::directives`] — the field-directive reference, one example each.
1439. [`guide::mapping`] — `#[bin(map/bw_map = …)]`: a whole struct mapped to/from a wire type.
14410. [`guide::dispatch`] — `#[bin]` on an enum: tagged-union dispatch by wire `magic` or off-wire `tag`.
14511. [`guide::io`] — the `Source`/`Sink` I/O ladder (slice, stream, socket, file, `bytes`).
14612. [`guide::errors`] — position-aware errors and the streaming `Incomplete` signal.
14713. [`guide::dual_use`] — the compliant-by-default-but-violatable philosophy.
14814. [`guide::composition`] — how the pieces nest and size each other.
14915. [`guide::migrating`] — coming from `binrw` / `modular-bitfield` / `num_enum`.
150*/
151
152// Every public item must be documented (the `uN` aliases are the one self-
153// evident exception, allowed at their module).
154#![forbid(unsafe_code)]
155#![deny(missing_docs)]
156// On docs.rs (which sets `--cfg docsrs` and builds on nightly), annotate every
157// feature-gated item with an "Available on crate feature …" badge. A no-op on
158// stable, where `docsrs` is never set.
159#![cfg_attr(docsrs, feature(doc_cfg))]
160// `bnb` is `no_std` when built without the (default) `std` feature; `alloc` is
161// always required — the codec produces `Vec<u8>` and owns variable-length
162// payloads/error messages. The `std` feature re-enables the `std::io` ladder
163// (`StreamBitReader`/`BufSource`/`SeekReader`, `encode(writer)`).
164#![cfg_attr(not(feature = "std"), no_std)]
165
166extern crate alloc;
167// Macro-generated code references the runtime by the path `proc-macro-crate`
168// resolves — `::bnb` for the crate itself. This self-alias makes that path
169// resolve inside the crate's own modules (the lib name is `bnb`).
170extern crate self as bnb;
171
172pub mod bitstream;
173pub mod builder;
174pub mod codecs;
175pub mod error;
176mod field;
177/// Async framing — a [`tokio_util::codec`] adapter (the `tokio` feature).
178#[cfg(feature = "tokio")]
179pub mod framing;
180pub mod guide;
181pub mod int;
182/// Ergonomic `std` socket helpers — [`MessageStream`] + [`MessageDatagram`] (the `net` feature).
183#[cfg(feature = "net")]
184pub mod net;
185mod wirelen;
186
187pub use bitstream::{
188 BitAmount, BitBuf, BitDecode, BitEncode, BitError, BitReader, BitWriter, CapacityError,
189 DecodeWith, EncodeWith, ErrorKind, FixedBitLen, Layout, SeekSource, Sink, Source,
190};
191pub use wirelen::WireLen;
192
193/// The `std::io` I/O ladder and writer conveniences — only with the (default)
194/// `std` feature. Without it, `bnb` is `no_std + alloc`: decode from a `&[u8]`
195/// (`BitReader`), encode to a `Vec<u8>` (`to_bytes`/`to_canonical_bytes`).
196#[cfg(feature = "std")]
197pub use bitstream::{BufSource, EncodeExt, SeekReader, SinkWriter, SourceReader, StreamBitReader};
198
199/// Zero-copy `bytes`-crate adapters (the `bytes` feature).
200#[cfg(feature = "bytes")]
201pub use bitstream::{BytesReader, BytesWriter};
202
203/// The async `tokio_util` codec adapter (the `tokio` feature).
204#[cfg(feature = "tokio")]
205pub use framing::BinCodec;
206
207/// Ergonomic `std` socket helpers (the `net` feature).
208#[cfg(feature = "net")]
209pub use net::{DatagramSocket, MessageDatagram, MessageStream};
210#[cfg(feature = "mock")]
211pub use net::{MockDatagramSocket, MockStream};
212
213/// Common imports for the codec — the typed positioning amounts (`4.bits()`,
214/// `3.bytes()`) used by `#[br(pad_before = …)]` etc., plus the [`EncodeExt`] trait that
215/// carries `encode(writer)` (the `std` feature).
216pub mod prelude {
217 pub use crate::BitAmount;
218 #[cfg(feature = "std")]
219 pub use crate::EncodeExt;
220}
221pub use builder::BuilderError;
222pub use error::{UnknownDiscriminant, WidthError};
223pub use field::{BitOrder, Bitfield, Bits, ByteOrder};
224pub use int::{UInt, *};
225
226// Re-export the macros so users depend only on `bnb`. A derive macro and a
227// trait may share a name (like `Debug`) — they live in different namespaces —
228// so `BitEnum`/`BitDecode`/`BitEncode` are each both a derive and a trait.
229pub use bnb_macros::{BitDecode, BitEncode, BitEnum, BitsBuilder, bin, bitfield, bitflags};
230
231/// Marker trait implemented by `#[derive(BitEnum)]` enums: a [`Bits`] value
232/// whose representation is an integer discriminant of a fixed width.
233pub trait BitEnum: Bits {}
234
235/// Implementation details referenced by macro-generated code. Not a stable API.
236#[doc(hidden)]
237pub mod __private {
238 /// The `std::io::Write`-based encode helpers, behind the `std` feature.
239 #[cfg(feature = "std")]
240 pub use crate::bitstream::encode_to_writer_with;
241 pub use crate::bitstream::{
242 BitDecode, BitEncode, BitError, BitReader, BitWriter, CountPrefix, FixedBitLen, Layout,
243 SeekSource, Sink, Source, align_read, align_write, bits_of, decode_all, decode_exact,
244 decode_exact_with, decode_iter, decode_mapped_msg, decode_peek, decode_peek_with,
245 decode_try_mapped_msg, encode_mapped_msg, encode_to_vec, encode_to_vec_with, peek_bytes,
246 read_byte_array, read_mapped, read_try_mapped, skip_read, skip_write, verify_magic,
247 write_byte_array, write_mapped,
248 };
249 pub use crate::error::UnknownDiscriminant;
250 pub use crate::field::{BitOrder, Bitfield, Bits, ByteOrder};
251 pub use crate::wirelen::WireLen;
252 /// Owned-collection re-exports so macro-generated code names neither `std` nor
253 /// `alloc` directly (the user crate need not declare `extern crate alloc`).
254 pub use ::alloc::{format, string::String, vec, vec::Vec};
255 /// Re-exported for the `#[br(dbg)]` directive's generated `trace!` call, so a user
256 /// of the directive needs no direct `tracing` dependency. `#[br(dbg)]` is a
257 /// `std`-only debugging aid (see the `tracing` dependency note in `Cargo.toml`).
258 #[cfg(feature = "std")]
259 pub use ::tracing;
260
261 /// Adaptive byte-buffer formatter for the `#[bin]` `#[try_str]` field hint: renders a
262 /// buffer that is **valid UTF-8** as a quoted, escaped string (`"hello"`, `"a\nb"`) and any
263 /// buffer with a non-UTF-8 byte as **hex bytes** (`[de, ad, be, ef]`). All-or-nothing — it
264 /// never replaces bytes (no lossy `�`), so the rendering can't misrepresent the wire.
265 pub struct TryStr<'a, T: ?Sized>(pub &'a T);
266
267 impl<T: AsRef<[u8]> + ?Sized> ::core::fmt::Debug for TryStr<'_, T> {
268 fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
269 let bytes = self.0.as_ref();
270 match ::core::str::from_utf8(bytes) {
271 ::core::result::Result::Ok(s) => ::core::fmt::Debug::fmt(s, f),
272 ::core::result::Result::Err(_) => write!(f, "{bytes:02x?}"),
273 }
274 }
275 }
276}