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}