Skip to main content

bnb/guide/
io.rs

1//! The I/O ladder — where the codec reads from and writes to.
2//!
3//! The everyday entry points (`decode_exact`/`decode_all`/`peek`/`to_bytes`) work on byte
4//! slices and `Vec`s. When you need to read from a socket or a file, `decode` takes any
5//! [`Source`](crate::Source) cursor; to write into an explicit [`Sink`](crate::Sink),
6//! [`BitEncode::bit_encode`](crate::BitEncode::bit_encode) does the dual.
7//! Pick the source by what your input can do:
8//!
9//! | Source | Backing | Can seek? | Use for |
10//! |---|---|---|---|
11//! | [`BitReader`](crate::BitReader) | `&[u8]` slice | yes (free cursor math) | in-memory bytes |
12//! | [`StreamBitReader`](crate::StreamBitReader) | any `Read` | no (forward only) | a stream you read once |
13//! | [`BufSource`](crate::BufSource) | any `Read` | yes (within a bounded buffer) | a socket that also needs to seek |
14//! | [`BitBuf`](crate::BitBuf) | owned `Vec<u8>` (pushable) | yes (cursor math) | incremental framing: push bytes, pull messages |
15//! | [`SeekReader`](crate::SeekReader) | `Read + Seek` | yes (via `io::Seek`) | a large file / container |
16//! | `BytesReader` (`bytes` feature) | owned `Bytes` | yes | zero-copy async framing |
17//!
18//! Seeking is only needed by messages that use `#[br(restore_position)]`; everything
19//! else runs over the forward-only [`StreamBitReader`](crate::StreamBitReader) too.
20//!
21//! # The low-level cursor
22//!
23//! [`BitReader`](crate::BitReader)/[`BitWriter`](crate::BitWriter) are the bit cursors
24//! under everything — read or write any [`Bits`](crate::Bits) value at the current bit
25//! offset:
26//!
27//! ```
28//! use bnb::{BitReader, BitWriter, u4, u12};
29//!
30//! let mut w = BitWriter::new();
31//! w.write(u4::new(0xA)).unwrap();
32//! w.write(u12::new(0xBCD)).unwrap();
33//! assert_eq!(w.into_bytes(), [0xAB, 0xCD]);
34//!
35//! let mut r = BitReader::new(&[0xAB, 0xCD]);
36//! assert_eq!(r.read::<u4>().unwrap(), u4::new(0xA));
37//! assert_eq!(r.read::<u12>().unwrap(), u12::new(0xBCD));
38//! ```
39//!
40//! # `decode` over each source
41//!
42//! The same message decodes from a slice cursor, a forward stream, a buffered socket,
43//! or a seekable file — only the source type changes:
44//!
45//! ```
46//! use bnb::{bin, BitReader, StreamBitReader, BufSource, SeekReader};
47//! use std::io::Cursor;
48//!
49//! #[bin(big)]
50//! #[derive(Debug, PartialEq)]
51//! struct Word { value: u32 }
52//!
53//! let bytes = [0x12, 0x34, 0x56, 0x78];
54//!
55//! // in-memory slice cursor
56//! let mut r = BitReader::new(&bytes);
57//! assert_eq!(Word::decode(&mut r).unwrap(), Word { value: 0x1234_5678 });
58//!
59//! // a forward-only `Read` (a `&[u8]` is `Read` but not `Seek`)
60//! let mut s = StreamBitReader::new(&bytes[..]);
61//! assert_eq!(Word::decode(&mut s).unwrap(), Word { value: 0x1234_5678 });
62//!
63//! // a `Read` with a bounded retain-and-seek buffer (the socket case)
64//! let mut b = BufSource::new(&bytes[..]);
65//! assert_eq!(Word::decode(&mut b).unwrap(), Word { value: 0x1234_5678 });
66//!
67//! // a `Read + Seek` (a file; here a Cursor over a Vec)
68//! let mut f = SeekReader::new(Cursor::new(bytes.to_vec()));
69//! assert_eq!(Word::decode(&mut f).unwrap(), Word { value: 0x1234_5678 });
70//! ```
71//!
72//! # Encoding
73//!
74//! `to_bytes()` (the common case) returns a `Vec`; `encode(&mut impl Write)` writes straight
75//! to a socket or file, and [`bit_encode(&mut impl Sink)`](crate::BitEncode::bit_encode) targets
76//! an explicit bit sink (for composing into a cursor you already hold). `encode` follows the
77//! value's [`encode_mode`](crate::EncodeMode) — **verbatim** by default, or **canonical** if set
78//! — see [Two encode forms](super::bin_codec#two-encode-forms-verbatim-vs-canonical).
79//!
80//! ```
81//! use bnb::bin;
82//! use bnb::EncodeExt; // brings `.encode(&mut impl Write)` into scope (the `std` feature)
83//! # #[bin(big)] #[derive(Debug, PartialEq)] struct Word { value: u32 }
84//! let w = Word { value: 0x1234_5678 };
85//! assert_eq!(w.to_bytes().unwrap(), [0x12, 0x34, 0x56, 0x78]);
86//!
87//! let mut out: Vec<u8> = Vec::new();   // any std::io::Write
88//! w.encode(&mut out).unwrap();         // Word has no canonical form → always verbatim
89//! assert_eq!(out, [0x12, 0x34, 0x56, 0x78]);
90//! ```
91//!
92//! # The `bytes` feature
93//!
94//! With `--features bytes`, `BytesReader`/`BytesWriter` decode from / encode to the
95//! `bytes` crate's `Bytes`/`BytesMut` for zero-copy async framing:
96//!
97//! ```ignore
98//! // requires `bnb = { features = ["bytes"] }`
99//! use bnb::{BytesReader, BytesWriter, Sink};
100//! let mut w = BytesWriter::new();
101//! w.write(0x1234u16).unwrap();
102//! let frame = w.freeze();              // a zero-copy `bytes::Bytes`
103//! let mut r = BytesReader::new(frame); // owns the frame, no copy
104//! ```
105//!
106//! # Bridging to `std::io`
107//!
108//! The ladder above adapts a `std::io::Read` *into* a [`Source`](crate::Source)
109//! ([`BufSource`](crate::BufSource)/[`SeekReader`](crate::SeekReader)). The reverse —
110//! handing a bnb cursor to `std::io`-based code from a `parse_with`/`write_with` — is
111//! [`Source::as_read`](crate::Source::as_read) and [`Sink::as_write`](crate::Sink::as_write),
112//! byte views over the cursor. With `From<io::Error>`, `std::io` results `?` straight
113//! into a [`BitError`](crate::BitError):
114//!
115//! ```
116//! use bnb::{BitError, BitReader, Source};
117//! use std::io::Read;
118//!
119//! fn read_three<S: Source>(r: &mut S) -> Result<[u8; 3], BitError> {
120//!     let mut buf = [0u8; 3];
121//!     r.as_read().read_exact(&mut buf)?; // a `std::io::Read` view over the cursor
122//!     Ok(buf)
123//! }
124//!
125//! let mut r = BitReader::new(&[0xAA, 0xBB, 0xCC]);
126//! assert_eq!(read_three(&mut r).unwrap(), [0xAA, 0xBB, 0xCC]);
127//! ```
128//!
129//! # Streaming and partial input
130//!
131//! A [`StreamBitReader`](crate::StreamBitReader) or [`BufSource`](crate::BufSource)
132//! that runs out mid-message reports [`ErrorKind::Incomplete`](crate::ErrorKind), the
133//! "read more bytes and retry" signal — distinct from a definitive parse failure. See
134//! [`errors`](super::errors).
135//!
136//! When bytes arrive in pieces from something that *isn't* a `Read` (a channel, a callback, an
137//! async chunk), [`BitBuf`](crate::BitBuf) is the **push/pull** counterpart: `push(&bytes)` as
138//! they come, `pull::<T>()` to take whole messages off the front (it returns `None` until a full
139//! message is buffered). `BitBuf` is itself a [`SeekSource`](crate::SeekSource), so it also reads
140//! through plain [`decode`](crate::BitDecode) (`Type::decode(&mut bitbuf)`); `pull` adds the
141//! reclaim + layout-baking + `None`-on-incomplete on top. Reclaim is deferred and in place, so a
142//! push/pull loop reuses one allocation; for a guaranteed-fixed footprint use
143//! [`BitBuf::bounded(cap)`](crate::BitBuf::bounded) with [`try_push`](crate::BitBuf::try_push)
144//! (which refuses to grow) and [`grow`](crate::BitBuf::grow) for explicit resizing.
145//!
146//! # Reading *and* writing one connection (without `try_clone`)
147//!
148//! To run a request/response loop on a single TCP connection you need to read and write the
149//! same socket. You don't need `try_clone()` (which dups the fd): **`std`'s `&TcpStream`
150//! implements both [`Read`](std::io::Read) and [`Write`](std::io::Write)**, so wrap the read
151//! half in a [`BufSource`](crate::BufSource) and write through `&TcpStream` — two shared borrows
152//! of the *same* socket:
153//!
154//! ```no_run
155//! use bnb::{bin, BufSource};
156//! use std::io::Write;
157//! use std::net::TcpStream;
158//! # #[bin(big)] #[derive(Debug, PartialEq)] struct Msg { seq: u32 }
159//! let stream = TcpStream::connect("127.0.0.1:9000")?;
160//! let mut reader = BufSource::new(&stream); // &TcpStream: Read
161//! let mut writer = &stream;                 // &TcpStream: Write — the same socket
162//!
163//! writer.write_all(&Msg { seq: 1 }.to_bytes()?)?;
164//! let reply = Msg::decode(&mut reader)?; // one framed message off the stream
165//! # Ok::<(), Box<dyn std::error::Error>>(())
166//! ```
167//!
168//! For halves you need to **move across threads** (a dedicated reader thread and writer thread),
169//! the equivalent of tokio's `into_split` is `Arc<TcpStream>` — clone the `Arc` per side and use
170//! `&*arc` (still `Read + Write`), no `try_clone`. The runnable `examples/tcp.rs` shows a full
171//! client/server.
172//!
173//! For an ergonomic wrapper, the **`net` feature** adds `MessageStream` — it owns a `Read +
174//! Write` stream and exposes `read_message`/`write_message` (so you exchange `#[bin]` values,
175//! not bytes) — and `MessageDatagram`, the datagram counterpart over a sealed `DatagramSocket`
176//! (`UdpSocket` or `UnixDatagram`) with `send_message`/`recv_message`; both are unit-testable
177//! without a real socket via the **`mock`** feature. With **`tokio`**, `BinCodec` does the same
178//! for an async `Framed` stream.