Skip to main content

bnb/
net.rs

1//! Ergonomic `std` socket helpers — the `net` feature.
2//!
3//! Two wrappers so you exchange `#[bin]` messages instead of bytes + buffers, one per transport
4//! shape:
5//!   * [`MessageStream`] — whole-message read/write over any `Read + Write` (a *byte stream*,
6//!     e.g. a `TcpStream`). It owns the stream and buffers reads, so one value does both
7//!     directions (no `try_clone`); messages must be **self-delimiting** (their `#[bin]`
8//!     structure, a `magic`, or a length prefix bounds them).
9//!   * [`MessageDatagram`] — whole-message send/recv over any [`DatagramSocket`] (a
10//!     *message-oriented* socket where one recv is one whole message, e.g. a `UdpSocket` or a
11//!     `UnixDatagram`). It owns the socket and reuses one receive buffer.
12//!
13//! [`DatagramSocket`] is the datagram counterpart to `Read + Write` that std doesn't ship — so
14//! `MessageDatagram` is generic across UDP and Unix datagram sockets (and, under the `mock`
15//! feature, [`MockDatagramSocket`]; the trait is *sealed*, so those are the only impls). Both
16//! wrappers bridge `std::io::Error` into [`BitError`] (the `std` feature), so a single `?` covers
17//! I/O *and* codec errors.
18
19use crate::{BitBuf, BitDecode, BitEncode, BitError, BitReader, BitWriter, ErrorKind};
20use alloc::vec;
21use alloc::vec::Vec;
22use std::io::{self, Read, Write};
23use std::net::{SocketAddr, UdpSocket};
24#[cfg(feature = "mock")]
25use std::{
26    cell::{Cell, RefCell},
27    collections::VecDeque,
28};
29
30/// Encode any message to a fresh `Vec` (generic over [`BitEncode`], unlike the inherent
31/// `to_bytes`).
32fn encode<T: BitEncode>(msg: &T) -> Result<Vec<u8>, BitError> {
33    let mut w = BitWriter::with_layout(<T as BitEncode>::LAYOUT);
34    msg.bit_encode(&mut w)?;
35    Ok(w.into_bytes())
36}
37
38/// A whole-message reader/writer over a byte stream (anything `Read + Write`, e.g. a
39/// `TcpStream`). It owns the stream and keeps a read buffer, so [`read_message`] and
40/// [`write_message`] exchange `#[bin]` values — and one `MessageStream` serves *both*
41/// directions on a single connection, no `try_clone` needed.
42///
43/// [`read_message`]: MessageStream::read_message
44/// [`write_message`]: MessageStream::write_message
45#[derive(Debug)]
46pub struct MessageStream<S> {
47    inner: S,
48    buf: BitBuf,
49}
50
51impl<S> MessageStream<S> {
52    /// Wrap a stream with an **unbounded** read buffer. On an untrusted peer prefer
53    /// [`bounded`](Self::bounded) — a stream that never completes a frame would otherwise
54    /// grow the buffer without bound.
55    pub fn new(inner: S) -> Self {
56        Self {
57            inner,
58            buf: BitBuf::new(),
59        }
60    }
61
62    /// Wrap a stream with a **bounded** read buffer: a peer that streams bytes which never
63    /// complete a message can only grow the buffer to `cap` bytes before
64    /// [`read_message`](Self::read_message) fails with
65    /// [`ErrorKind::BufferFull`](crate::ErrorKind::BufferFull), rather than consuming memory
66    /// without bound. The bounded counterpart to [`new`](Self::new) for untrusted streams.
67    pub fn bounded(inner: S, cap: usize) -> Self {
68        Self {
69            inner,
70            buf: BitBuf::bounded(cap),
71        }
72    }
73
74    /// Borrow the underlying stream.
75    pub fn get_ref(&self) -> &S {
76        &self.inner
77    }
78
79    /// Mutably borrow the underlying stream (e.g. to set a timeout).
80    pub fn get_mut(&mut self) -> &mut S {
81        &mut self.inner
82    }
83
84    /// Recover the underlying stream (any buffered-but-unparsed bytes are dropped).
85    pub fn into_inner(self) -> S {
86        self.inner
87    }
88}
89
90impl<S: Read> MessageStream<S> {
91    /// Read exactly one `#[bin]` message, pulling more bytes from the stream as needed and
92    /// keeping any trailing bytes for the next call. The message's own byte/bit order is honored
93    /// (via [`BitBuf::pull`]).
94    ///
95    /// # Errors
96    /// A codec [`BitError`] for a malformed message, or an I/O error — an EOF mid-stream (a
97    /// closed connection) surfaces as an `Io(UnexpectedEof)` error, so a read loop ends on `Err`.
98    pub fn read_message<T: BitDecode + BitEncode>(&mut self) -> Result<T, BitError> {
99        loop {
100            // `pull` decodes in `T`'s own layout, returns `None` until a whole message is
101            // buffered, and reclaims consumed bytes — the framing logic lives in `BitBuf`.
102            if let Some(msg) = self.buf.pull::<T>()? {
103                return Ok(msg);
104            }
105            let mut chunk = [0u8; 4096];
106            let n = self.inner.read(&mut chunk)?;
107            if n == 0 {
108                return Err(
109                    io::Error::new(io::ErrorKind::UnexpectedEof, "connection closed").into(),
110                );
111            }
112            // `try_push` is a no-op bound for an unbounded buffer (`new`) and enforces the
113            // `cap` for a bounded one (`bounded`) — a never-completing frame is `BufferFull`,
114            // not unbounded growth.
115            self.buf.try_push(&chunk[..n]).map_err(|e| {
116                BitError::new(ErrorKind::BufferFull { cap: e.cap }, self.buf.bit_len())
117            })?;
118        }
119    }
120}
121
122impl<S: Write> MessageStream<S> {
123    /// Encode one `#[bin]` message and write it to the stream.
124    ///
125    /// # Errors
126    /// A codec [`BitError`] or an I/O write error.
127    pub fn write_message<T: BitEncode>(&mut self, msg: &T) -> Result<(), BitError> {
128        self.inner.write_all(&encode(msg)?)?;
129        Ok(())
130    }
131}
132
133/// A message-oriented (datagram) socket: each `recv_from` yields exactly one whole message with
134/// its sender, and each `send_to` writes one message to a peer. This is the datagram counterpart
135/// to `Read + Write` (which std *does* ship but has no datagram analog of) — it makes a transport
136/// usable with [`MessageDatagram`].
137///
138/// **Sealed:** `bnb` implements it for [`UdpSocket`], (on Unix) `std::os::unix::net::UnixDatagram`,
139/// and — under the `mock` feature — `MockDatagramSocket`. Downstream crates can't add their own
140/// impls, so `bnb` keeps the freedom to evolve the trait; to test datagram code, use
141/// `MockDatagramSocket` (the `mock` feature) or a loopback `UdpSocket`.
142pub trait DatagramSocket: sealed::Sealed {
143    /// The peer-address type (`SocketAddr` for UDP; `std::os::unix::net::SocketAddr` for Unix).
144    type Addr;
145
146    /// Receive one datagram into `buf`, returning how many bytes it held and who sent it.
147    ///
148    /// # Errors
149    /// An I/O receive error.
150    fn recv_from(&self, buf: &mut [u8]) -> io::Result<(usize, Self::Addr)>;
151
152    /// Send `buf` as one datagram to `addr`, returning the bytes sent.
153    ///
154    /// # Errors
155    /// An I/O send error.
156    fn send_to(&self, buf: &[u8], addr: &Self::Addr) -> io::Result<usize>;
157}
158
159/// Seals [`DatagramSocket`] — only `bnb`'s own types can implement it (the module is private).
160mod sealed {
161    pub trait Sealed {}
162}
163
164impl sealed::Sealed for UdpSocket {}
165impl DatagramSocket for UdpSocket {
166    type Addr = SocketAddr;
167
168    fn recv_from(&self, buf: &mut [u8]) -> io::Result<(usize, SocketAddr)> {
169        UdpSocket::recv_from(self, buf)
170    }
171
172    fn send_to(&self, buf: &[u8], addr: &SocketAddr) -> io::Result<usize> {
173        UdpSocket::send_to(self, buf, *addr)
174    }
175}
176
177#[cfg(unix)]
178impl sealed::Sealed for std::os::unix::net::UnixDatagram {}
179#[cfg(unix)]
180impl DatagramSocket for std::os::unix::net::UnixDatagram {
181    type Addr = std::os::unix::net::SocketAddr;
182
183    fn recv_from(&self, buf: &mut [u8]) -> io::Result<(usize, Self::Addr)> {
184        std::os::unix::net::UnixDatagram::recv_from(self, buf)
185    }
186
187    fn send_to(&self, buf: &[u8], addr: &Self::Addr) -> io::Result<usize> {
188        self.send_to_addr(buf, addr)
189    }
190}
191
192/// A whole-message sender/receiver over a [`DatagramSocket`] (a `UdpSocket`, a `UnixDatagram`, or
193/// — under the `mock` feature — a [`MockDatagramSocket`]). It owns the socket and reuses one
194/// receive buffer, so each datagram is exchanged
195/// as a `#[bin]` value — the datagram counterpart to [`MessageStream`]. Unlike a stream, a
196/// datagram socket talks to *many* peers, so every call carries the peer address.
197#[derive(Debug)]
198pub struct MessageDatagram<D> {
199    sock: D,
200    buf: Vec<u8>,
201}
202
203impl<D> MessageDatagram<D> {
204    /// Wrap a datagram socket, with a receive buffer sized for the largest datagram (64 KiB).
205    pub fn new(sock: D) -> Self {
206        Self::with_capacity(sock, 65_536)
207    }
208
209    /// Wrap a socket with a receive buffer of `capacity` bytes. A datagram larger than this is
210    /// truncated (as the OS itself would truncate an oversized `recv`).
211    pub fn with_capacity(sock: D, capacity: usize) -> Self {
212        Self {
213            sock,
214            buf: vec![0u8; capacity],
215        }
216    }
217
218    /// Borrow the underlying socket (e.g. for `connect`, multicast, or a read timeout).
219    pub fn get_ref(&self) -> &D {
220        &self.sock
221    }
222
223    /// Mutably borrow the underlying socket.
224    pub fn get_mut(&mut self) -> &mut D {
225        &mut self.sock
226    }
227
228    /// Recover the underlying socket.
229    pub fn into_inner(self) -> D {
230        self.sock
231    }
232}
233
234impl<D: DatagramSocket> MessageDatagram<D> {
235    /// Encode `msg` and send it as one datagram to `addr`. Returns the bytes sent.
236    ///
237    /// # Errors
238    /// A codec [`BitError`] or an I/O send error.
239    pub fn send_message<T: BitEncode>(&self, msg: &T, addr: &D::Addr) -> Result<usize, BitError> {
240        Ok(self.sock.send_to(&encode(msg)?, addr)?)
241    }
242
243    /// Receive one datagram and decode it as a `T`, with the sender's address.
244    ///
245    /// # Errors
246    /// A codec [`BitError`] (the datagram wasn't a valid `T`) or an I/O receive error.
247    pub fn recv_message<T: BitDecode + BitEncode>(&mut self) -> Result<(T, D::Addr), BitError> {
248        let (n, from) = self.sock.recv_from(&mut self.buf)?;
249        // Decode in `T`'s own byte/bit order (not the reader's default).
250        let mut r = BitReader::with_layout(&self.buf[..n], <T as BitEncode>::LAYOUT);
251        let msg = <T as BitDecode>::bit_decode(&mut r)?;
252        Ok((msg, from))
253    }
254}
255
256/// A test-only [`DatagramSocket`] backed by in-memory queues — exchange datagrams with a
257/// [`MessageDatagram`] in unit tests, no real socket bound. Enabled by the **`mock`** feature
258/// (put it in your `[dev-dependencies]`). Queue inbound datagrams with
259/// [`push_inbound`](Self::push_inbound) (each is one `recv_from`) and inspect what was sent with
260/// [`sent`](Self::sent).
261///
262/// ```
263/// use bnb::{bin, MessageDatagram, MockDatagramSocket};
264/// #[bin(big)]
265/// #[derive(Debug, PartialEq, Eq)]
266/// struct Ping {
267///     seq: u16,
268/// }
269///
270/// let mut peer = MessageDatagram::new(MockDatagramSocket::new());
271/// let from = "127.0.0.1:5000".parse().unwrap();
272/// peer.get_ref().push_inbound(&Ping { seq: 7 }.to_bytes().unwrap(), from); // as if it arrived
273///
274/// let (ping, who): (Ping, _) = peer.recv_message().unwrap();
275/// assert_eq!(ping, Ping { seq: 7 });
276/// peer.send_message(&Ping { seq: 8 }, &who).unwrap(); // reply to the sender
277/// assert_eq!(peer.get_ref().sent()[0].0, Ping { seq: 8 }.to_bytes().unwrap());
278/// ```
279#[cfg(feature = "mock")]
280#[derive(Debug, Default)]
281pub struct MockDatagramSocket {
282    inbound: RefCell<VecDeque<(Vec<u8>, SocketAddr)>>,
283    sent: RefCell<Vec<(Vec<u8>, SocketAddr)>>,
284    fail_recv: Cell<bool>,
285}
286
287#[cfg(feature = "mock")]
288impl MockDatagramSocket {
289    /// An empty mock with no queued datagrams.
290    #[must_use]
291    pub fn new() -> Self {
292        Self::default()
293    }
294
295    /// Queue one datagram (`bytes`, from `from`) to be returned by the next `recv_from`.
296    pub fn push_inbound(&self, bytes: &[u8], from: SocketAddr) {
297        self.inbound.borrow_mut().push_back((bytes.to_vec(), from));
298    }
299
300    /// Every datagram sent so far, as `(bytes, destination)`, in send order.
301    #[must_use]
302    pub fn sent(&self) -> Vec<(Vec<u8>, SocketAddr)> {
303        self.sent.borrow().clone()
304    }
305
306    /// Make the next `recv_from` fail with `ConnectionReset` instead of returning a datagram — to
307    /// test recv-error handling. One-shot: later recvs behave normally.
308    #[must_use]
309    pub fn fail_next_recv(self) -> Self {
310        self.fail_recv.set(true);
311        self
312    }
313}
314
315#[cfg(feature = "mock")]
316impl sealed::Sealed for MockDatagramSocket {}
317
318#[cfg(feature = "mock")]
319impl DatagramSocket for MockDatagramSocket {
320    type Addr = SocketAddr;
321
322    fn recv_from(&self, buf: &mut [u8]) -> io::Result<(usize, SocketAddr)> {
323        if self.fail_recv.replace(false) {
324            return Err(io::Error::new(
325                io::ErrorKind::ConnectionReset,
326                "mock: recv failed",
327            ));
328        }
329        let (data, from) = self
330            .inbound
331            .borrow_mut()
332            .pop_front()
333            .ok_or_else(|| io::Error::new(io::ErrorKind::WouldBlock, "no queued datagram"))?;
334        let n = data.len().min(buf.len());
335        buf[..n].copy_from_slice(&data[..n]);
336        Ok((n, from))
337    }
338
339    fn send_to(&self, buf: &[u8], addr: &SocketAddr) -> io::Result<usize> {
340        self.sent.borrow_mut().push((buf.to_vec(), *addr));
341        Ok(buf.len())
342    }
343}
344
345/// A test-only `Read + Write` byte stream backed by in-memory buffers — exercise [`MessageStream`]
346/// code in unit tests with no real socket. Enabled by the **`mock`** feature (put it in your
347/// `[dev-dependencies]`). Queue inbound bytes with [`push_inbound`](Self::push_inbound) and inspect
348/// what was written with [`written`](Self::written).
349///
350/// Unlike `std::io::Cursor` it keeps **separate** read and write buffers (so it handles duplex
351/// request/reply cleanly), and it can deliver inbound bytes a few at a time
352/// ([`with_chunk_size`](Self::with_chunk_size)) — to exercise `read_message`'s buffer-more-and-retry
353/// loop, i.e. a message split across reads, which `Cursor` (one read = everything) cannot.
354///
355/// ```
356/// use bnb::{bin, MessageStream, MockStream};
357/// #[bin(big)]
358/// #[derive(Debug, PartialEq, Eq)]
359/// struct Ping {
360///     seq: u16,
361/// }
362///
363/// // deliver the 2-byte Ping one byte per read — forces the read-more loop
364/// let mut conn = MessageStream::new(MockStream::with_chunk_size(1));
365/// conn.get_mut().push_inbound(&Ping { seq: 7 }.to_bytes().unwrap());
366///
367/// let ping: Ping = conn.read_message().unwrap();
368/// assert_eq!(ping, Ping { seq: 7 });
369/// conn.write_message(&Ping { seq: 8 }).unwrap();
370/// assert_eq!(conn.get_mut().written(), &Ping { seq: 8 }.to_bytes().unwrap()[..]);
371/// ```
372#[cfg(feature = "mock")]
373#[derive(Debug, Default, Clone)]
374pub struct MockStream {
375    inbound: VecDeque<u8>,
376    outbound: Vec<u8>,
377    chunk: usize,
378    fail_after: Option<usize>,
379    read_total: usize,
380}
381
382#[cfg(feature = "mock")]
383impl MockStream {
384    /// An empty stream that delivers all available inbound bytes per `read`.
385    #[must_use]
386    pub fn new() -> Self {
387        Self::default()
388    }
389
390    /// Like [`new`](Self::new), but each `read` returns at most `n` bytes (`n > 0`) — to simulate a
391    /// stream that dribbles one message across several reads (the `Incomplete` / read-more path).
392    #[must_use]
393    pub fn with_chunk_size(n: usize) -> Self {
394        Self {
395            chunk: n,
396            ..Self::default()
397        }
398    }
399
400    /// Queue bytes to be returned by future `read`s.
401    pub fn push_inbound(&mut self, bytes: &[u8]) {
402        self.inbound.extend(bytes.iter().copied());
403    }
404
405    /// All bytes written to the stream so far.
406    #[must_use]
407    pub fn written(&self) -> &[u8] {
408        &self.outbound
409    }
410
411    /// After `n` inbound bytes have been read, every further `read` fails with `ConnectionReset`
412    /// — to test a connection that drops mid-message (the error surfaces through `read_message`).
413    #[must_use]
414    pub fn fail_after(mut self, n: usize) -> Self {
415        self.fail_after = Some(n);
416        self
417    }
418}
419
420#[cfg(feature = "mock")]
421impl Read for MockStream {
422    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
423        if let Some(at) = self.fail_after {
424            if self.read_total >= at {
425                return Err(io::Error::new(
426                    io::ErrorKind::ConnectionReset,
427                    "mock: connection reset",
428                ));
429            }
430        }
431        if self.inbound.is_empty() || buf.is_empty() {
432            return Ok(0); // EOF: no more inbound (as a closed connection would read)
433        }
434        let mut cap = if self.chunk == 0 {
435            buf.len()
436        } else {
437            buf.len().min(self.chunk)
438        };
439        cap = cap.min(self.inbound.len());
440        if let Some(at) = self.fail_after {
441            cap = cap.min(at - self.read_total); // stop exactly at the failure point
442        }
443        for slot in buf.iter_mut().take(cap) {
444            *slot = self.inbound.pop_front().unwrap();
445        }
446        self.read_total += cap;
447        Ok(cap)
448    }
449}
450
451#[cfg(feature = "mock")]
452impl Write for MockStream {
453    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
454        self.outbound.extend_from_slice(buf);
455        Ok(buf.len())
456    }
457
458    fn flush(&mut self) -> io::Result<()> {
459        Ok(())
460    }
461}
462
463#[cfg(all(test, feature = "mock"))]
464mod component {
465    //! Component tests: the `net` wrappers driven by the in-memory mocks, one call at a time
466    //! (a queued read, a captured write, chunked reassembly, error injection, the accessors).
467    use bnb::{MessageDatagram, MessageStream, MockDatagramSocket, MockStream, bin};
468
469    #[bin(big)]
470    #[derive(Debug, Clone, PartialEq, Eq)]
471    struct Msg {
472        seq: u16,
473    }
474
475    // --- MessageStream over MockStream -------------------------------------------------
476
477    #[test]
478    fn stream_write_message_is_captured() {
479        let mut conn = MessageStream::new(MockStream::new());
480        conn.write_message(&Msg { seq: 7 }).unwrap();
481        assert_eq!(
482            conn.get_mut().written(),
483            &Msg { seq: 7 }.to_bytes().unwrap()[..]
484        );
485    }
486
487    #[test]
488    fn stream_reads_a_queued_message() {
489        let mut conn = MessageStream::new(MockStream::new());
490        conn.get_mut()
491            .push_inbound(&Msg { seq: 0xABCD }.to_bytes().unwrap());
492        assert_eq!(conn.read_message::<Msg>().unwrap(), Msg { seq: 0xABCD });
493    }
494
495    #[test]
496    fn stream_reassembles_a_message_split_across_reads() {
497        // One byte per read forces the buffer-more-and-retry loop in read_message.
498        let mut conn = MessageStream::new(MockStream::with_chunk_size(1));
499        conn.get_mut()
500            .push_inbound(&Msg { seq: 0x1234 }.to_bytes().unwrap());
501        assert_eq!(conn.read_message::<Msg>().unwrap(), Msg { seq: 0x1234 });
502    }
503
504    #[test]
505    fn stream_eof_mid_message_is_an_error() {
506        // Only one of the two needed bytes is available, then the connection closes.
507        let mut conn = MessageStream::new(MockStream::new());
508        conn.get_mut().push_inbound(&[0x12]);
509        assert!(conn.read_message::<Msg>().is_err());
510    }
511
512    #[test]
513    fn stream_connection_reset_surfaces_as_an_error() {
514        let mut conn = MessageStream::new(MockStream::new().fail_after(0));
515        conn.get_mut()
516            .push_inbound(&Msg { seq: 1 }.to_bytes().unwrap());
517        assert!(conn.read_message::<Msg>().is_err());
518    }
519
520    #[test]
521    fn stream_into_inner_recovers_the_transport() {
522        let conn = MessageStream::new(MockStream::new());
523        let _inner: MockStream = conn.into_inner();
524    }
525
526    // --- MessageDatagram over MockDatagramSocket ---------------------------------------
527
528    #[test]
529    fn datagram_recv_then_send_to_the_sender() {
530        let mut peer = MessageDatagram::new(MockDatagramSocket::new());
531        let from = "127.0.0.1:5000".parse().unwrap();
532        peer.get_ref()
533            .push_inbound(&Msg { seq: 7 }.to_bytes().unwrap(), from);
534
535        let (msg, who) = peer.recv_message::<Msg>().unwrap();
536        assert_eq!(msg, Msg { seq: 7 });
537        assert_eq!(who, from);
538
539        let n = peer.send_message(&Msg { seq: 8 }, &who).unwrap();
540        assert_eq!(n, 2, "send_message returns the byte count");
541        assert_eq!(
542            peer.get_ref().sent()[0].0,
543            Msg { seq: 8 }.to_bytes().unwrap()
544        );
545        assert_eq!(
546            peer.get_ref().sent()[0].1,
547            who,
548            "sent to the original sender"
549        );
550    }
551
552    #[test]
553    fn datagram_recv_error_is_injected() {
554        let mut peer = MessageDatagram::new(MockDatagramSocket::new().fail_next_recv());
555        assert!(peer.recv_message::<Msg>().is_err());
556    }
557
558    #[test]
559    fn datagram_recv_malformed_is_a_codec_error() {
560        #[bin(big, magic = 0xCAFEu16)]
561        #[derive(Debug, PartialEq, Eq)]
562        struct M {
563            v: u8,
564        }
565        let mut peer = MessageDatagram::new(MockDatagramSocket::new());
566        let from = "127.0.0.1:1".parse().unwrap();
567        peer.get_ref().push_inbound(&[0x00, 0x00, 0x09], from); // wrong magic
568        assert!(peer.recv_message::<M>().is_err());
569    }
570
571    #[test]
572    fn datagram_with_capacity_truncates_an_oversized_datagram() {
573        // Capacity 2 → only the first two bytes are delivered (OS-style truncation).
574        let mut peer = MessageDatagram::with_capacity(MockDatagramSocket::new(), 2);
575        let from = "127.0.0.1:2".parse().unwrap();
576        peer.get_ref().push_inbound(&[0x00, 0x05, 0xFF, 0xFF], from);
577        let (msg, _) = peer.recv_message::<Msg>().unwrap();
578        assert_eq!(msg, Msg { seq: 5 });
579    }
580
581    #[test]
582    fn datagram_get_mut_and_into_inner() {
583        let mut peer = MessageDatagram::new(MockDatagramSocket::new());
584        let _m: &mut MockDatagramSocket = peer.get_mut();
585        let _inner: MockDatagramSocket = peer.into_inner();
586    }
587}