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}