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