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