Skip to main content

aimdb_core/session/
io.rs

1//! Runtime-neutral byte-stream layer: the seam where an adapter owns sockets,
2//! clocks and name resolution, and a connector owns framing and protocol.
3//!
4//! These traits sit one layer **below** [`Connection`](super::Connection): a
5//! [`ByteStream`] is unframed, a [`Connection`](super::Connection) is framed. A
6//! connector generic over them needs no per-runtime module and no `cfg` on its
7//! code path.
8//!
9//! Every async method returns `impl Future<…> + Send`. The bound is on the
10//! return type because generic code must produce `Send` futures at the boxing
11//! boundary and cannot otherwise prove it — return-type notation, which would
12//! say exactly that, is still experimental on the pinned toolchain. An
13//! implementor whose runtime futures are `!Send` wraps them in a force-`Send`
14//! newtype; that `unsafe` belongs to the adapter, and core has none.
15//!
16//! The traits are deliberately not `dyn`-compatible. Connectors are generic
17//! over them; the `dyn` boundary stays at `Box<dyn Connection>` per frame.
18
19use alloc::boxed::Box;
20use alloc::string::String;
21use alloc::vec::Vec;
22use core::future::Future;
23
24use super::{BoxFut, Connection, Dialer, Listener, PeerInfo, TransportError, TransportResult};
25
26/// Failure of a byte-level I/O operation.
27///
28/// An alias, not a new type: these traits sit directly beneath
29/// [`Connection`](super::Connection), so nothing converts at that boundary.
30pub type IoError = TransportError;
31
32// ===========================================================================
33// Byte streams — the one real fork between runtimes.
34// ===========================================================================
35
36/// The read half of a [`split`](ByteStream::split) stream.
37pub trait ByteRead {
38    /// Read into `buf`, returning the byte count; `Ok(0)` is EOF.
39    fn read<'a>(
40        &'a mut self,
41        buf: &'a mut [u8],
42    ) -> impl Future<Output = TransportResult<usize>> + Send + 'a;
43}
44
45/// The write half of a [`split`](ByteStream::split) stream.
46pub trait ByteWrite {
47    /// Write every byte of `buf`, or fail.
48    fn write_all<'a>(
49        &'a mut self,
50        buf: &'a [u8],
51    ) -> impl Future<Output = TransportResult<()>> + Send + 'a;
52
53    /// Flush any buffered bytes toward the peer.
54    fn flush(&mut self) -> impl Future<Output = TransportResult<()>> + Send + '_;
55}
56
57/// An unframed, bidirectional byte stream — one TCP connection, one UART, one
58/// TLS session. The adapter owns it; the connector never names its type.
59///
60/// `read` returning `Ok(0)` is end of stream, matching both
61/// `embedded_io_async::Read` and `tokio::io::AsyncRead`. The stream is **one
62/// value** — `&mut self` on both directions — so it can wrap a socket that
63/// lends out only borrowed halves; a caller needing both directions at once
64/// borrows them apart with [`split`](ByteStream::split).
65///
66/// # Cancellation
67///
68/// [`read`](ByteStream::read) is cancel-safe on both adapters AimDB ships, but
69/// that is a property of those transports, not a promise of this trait.
70/// [`write_all`](ByteStream::write_all) is **not** cancel-safe anywhere and
71/// must never sit in a `select` arm: a partial write desynchronises the framing
72/// above it with nothing to resync on.
73pub trait ByteStream {
74    /// Read into `buf`, returning the byte count; `Ok(0)` is EOF.
75    fn read<'a>(
76        &'a mut self,
77        buf: &'a mut [u8],
78    ) -> impl Future<Output = TransportResult<usize>> + Send + 'a;
79
80    /// Write every byte of `buf`, or fail.
81    fn write_all<'a>(
82        &'a mut self,
83        buf: &'a [u8],
84    ) -> impl Future<Output = TransportResult<()>> + Send + 'a;
85
86    /// Flush any buffered bytes toward the peer.
87    fn flush(&mut self) -> impl Future<Output = TransportResult<()>> + Send + '_;
88
89    /// Borrow this stream as independent read and write halves, pollable
90    /// concurrently without either waiting on the other.
91    ///
92    /// The halves borrow rather than own, so a caller wanting owned or
93    /// `'static` halves needs a different seam.
94    fn split(&mut self) -> (impl ByteRead + Send + '_, impl ByteWrite + Send + '_);
95}
96
97/// Produces streams: the client side.
98///
99/// `host` is unresolved — name resolution belongs to the adapter, so a
100/// connector carries no resolver.
101pub trait StreamDialer {
102    /// The stream this dialer produces.
103    type Stream: ByteStream + Send;
104
105    /// Open a stream to `host:port`.
106    /// `host` is either a hostname or an unbracketed IP literal.
107    fn connect<'a>(
108        &'a self,
109        host: &'a str,
110        port: u16,
111    ) -> impl Future<Output = TransportResult<Self::Stream>> + Send + 'a;
112}
113
114/// Produces streams: the server side, and the dual of [`StreamDialer`].
115///
116/// One `accept` at a time, matching
117/// [`Listener::accept`](super::Listener::accept) and the `serve` loop that
118/// drives it. The single-shot signature costs no concurrency: an adapter
119/// backing it with a pool of sockets keeps every one of them listening across
120/// calls and consumes only the one that completes.
121pub trait StreamListener {
122    /// The stream this listener produces.
123    type Stream: ByteStream + Send;
124
125    /// Accept the next inbound stream, with whatever peer metadata the
126    /// transport exposes.
127    fn accept(
128        &mut self,
129    ) -> impl Future<Output = TransportResult<(Self::Stream, PeerInfo)>> + Send + '_;
130}
131
132// ===========================================================================
133// Datagrams.
134// ===========================================================================
135
136/// Connectionless I/O.
137pub trait Datagram {
138    /// Send `buf` to `to`.
139    fn send_to<'a>(
140        &'a mut self,
141        buf: &'a [u8],
142        to: core::net::SocketAddr,
143    ) -> impl Future<Output = TransportResult<()>> + Send + 'a;
144
145    /// Receive one datagram into `buf`, with its source address.
146    fn recv_from<'a>(
147        &'a mut self,
148        buf: &'a mut [u8],
149    ) -> impl Future<Output = TransportResult<(usize, core::net::SocketAddr)>> + Send + 'a;
150
151    /// The address this socket is bound to, or `None` on stacks that expose
152    /// none. A protocol that advertises its own endpoint in-band needs the real
153    /// address rather than the NAT-style `0.0.0.0:0`.
154    fn local_addr(&self) -> Option<core::net::SocketAddr>;
155}
156
157/// Binds [`Datagram`] sockets on demand.
158///
159/// A protocol task takes this rather than a socket, because a moved-in socket
160/// cannot be rebound after a reset. A std adapter binds a fresh socket; an
161/// Embassy one closes and re-binds the same socket, whose buffers live as long
162/// as it does.
163pub trait DatagramBinder {
164    /// The socket this binder produces.
165    type Socket: Datagram + Send;
166
167    /// Bind a socket to `port` (`0` for any).
168    fn bind(&self, port: u16) -> impl Future<Output = TransportResult<Self::Socket>> + Send + '_;
169}
170
171// ===========================================================================
172// Time.
173// ===========================================================================
174
175/// A non-allocating sleep.
176///
177/// [`RuntimeOps::sleep`](crate::executor::RuntimeOps::sleep) is `dyn` and must
178/// box; this one is generic and returns the adapter's own timer type with
179/// nothing on the heap. Only sleeping lives here — `RuntimeOps::now_nanos` is a
180/// plain call and stays the clock.
181///
182/// The returned future borrows nothing, so a task holding a `D: Delay` still
183/// produces `'static` futures from it.
184pub trait Delay {
185    /// Complete after at least `d` has elapsed.
186    fn sleep(&self, d: core::time::Duration) -> impl Future<Output = ()> + Send;
187}
188
189// ===========================================================================
190// Framing — a transport crate contributes one of these and inherits the rest.
191// ===========================================================================
192
193/// How badly a framing step failed.
194///
195/// A self-delimiting format resyncs on its next delimiter; a length prefix has
196/// none, so nothing tells payload bytes from the next header. Only the framer
197/// knows which case it is in, so it says, rather than the connection guessing.
198#[derive(Debug, Clone, Copy, PartialEq, Eq)]
199pub enum FrameFault {
200    /// Bad frame, good link: skip it and keep reading.
201    Recoverable,
202    /// The link can no longer be interpreted and must close.
203    Fatal,
204}
205
206/// Frames a byte stream: COBS, length-prefix, NDJSON.
207pub trait Framer {
208    /// Encode one logical frame, appending its wire bytes to `out`.
209    ///
210    /// On `Err` nothing is appended, so a rejected frame never reaches the wire
211    /// half-written.
212    fn encode(&self, frame: &[u8], out: &mut Vec<u8>) -> Result<(), FrameFault>;
213    /// Feed received bytes into the accumulator.
214    fn push_bytes(&mut self, bytes: &[u8]);
215    /// Pull the next complete frame: `Some(Ok(frame))`, `Some(Err(fault))` (see
216    /// [`FrameFault`]), or `None` when more bytes are needed.
217    fn next_frame(&mut self) -> Option<Result<Vec<u8>, FrameFault>>;
218}
219
220/// Builds a fresh [`Framer`] per connection.
221///
222/// A blanket impl covers closures, so a caller writes `|| CobsFramer::new()`.
223pub trait FramerFactory {
224    /// The framer produced.
225    type Framer: Framer + Send;
226    /// Build one, for one connection.
227    fn framer(&self) -> Self::Framer;
228}
229
230impl<F, T> FramerFactory for F
231where
232    F: Fn() -> T,
233    T: Framer + Send,
234{
235    type Framer = T;
236    fn framer(&self) -> T {
237        self()
238    }
239}
240
241// ===========================================================================
242// Framed connections — a [`ByteStream`] plus a [`Framer`] is a [`Connection`].
243// ===========================================================================
244
245/// A framed [`Connection`] over any [`ByteStream`] and [`Framer`].
246///
247/// `RC` caps the per-`read` chunk and `WC` caps a single `write_all`; both are
248/// stack buffers, and some HAL `BufferedUart::write` rejects a write larger
249/// than its TX ring, which is what `WC` exists for.
250pub struct FramedConnection<S, F, const RC: usize = 256, const WC: usize = 256> {
251    stream: S,
252    framer: F,
253    peer: PeerInfo,
254}
255
256impl<S, F, const RC: usize, const WC: usize> FramedConnection<S, F, RC, WC> {
257    /// Frame `stream` with `framer`.
258    pub fn new(stream: S, framer: F) -> Self {
259        Self {
260            stream,
261            framer,
262            peer: PeerInfo::default(),
263        }
264    }
265
266    /// Frame `stream` with `framer`, carrying `peer` metadata from the accept.
267    pub fn with_peer(stream: S, framer: F, peer: PeerInfo) -> Self {
268        Self {
269            stream,
270            framer,
271            peer,
272        }
273    }
274}
275
276impl<S, F, const RC: usize, const WC: usize> Connection for FramedConnection<S, F, RC, WC>
277where
278    S: ByteStream + Send,
279    F: Framer + Send,
280{
281    fn recv(&mut self) -> BoxFut<'_, TransportResult<Option<Vec<u8>>>> {
282        Box::pin(async move {
283            loop {
284                match self.framer.next_frame() {
285                    Some(Ok(frame)) => return Ok(Some(frame)),
286                    // Line noise or a mid-stream join: skip it and resync on
287                    // the next frame.
288                    Some(Err(FrameFault::Recoverable)) => continue,
289                    // No boundary left to resync on: reading on would reinterpret
290                    // payload bytes as headers for the life of the connection.
291                    Some(Err(FrameFault::Fatal)) => {
292                        log_warn!("framed recv: unrecoverable framing error, closing connection");
293                        return Err(TransportError::Framing);
294                    }
295                    None => {}
296                }
297                let mut chunk = [0u8; RC];
298                match self.stream.read(&mut chunk).await {
299                    Ok(0) => return Ok(None), // EOF — peer closed
300                    Ok(n) => self.framer.push_bytes(&chunk[..n]),
301                    Err(e) => return Err(e),
302                }
303            }
304        })
305    }
306
307    fn send<'a>(&'a mut self, frame: &'a [u8]) -> BoxFut<'a, TransportResult<()>> {
308        Box::pin(async move {
309            let mut out = Vec::new();
310            if let Err(_fault) = self.framer.encode(frame, &mut out) {
311                log_warn!(
312                    "framed send: framer rejected a {}-byte frame ({:?}), closing connection",
313                    frame.len(),
314                    _fault
315                );
316                return Err(TransportError::Framing);
317            }
318            for chunk in out.chunks(WC) {
319                self.stream.write_all(chunk).await?;
320            }
321            self.stream.flush().await
322        })
323    }
324
325    fn peer(&self) -> &PeerInfo {
326        &self.peer
327    }
328}
329
330/// Lifts a [`StreamDialer`] and a [`FramerFactory`] into a [`Dialer`], so
331/// `run_client` drives an adapter transport unchanged.
332///
333/// `Clone` because `SessionClientConnector` clones its dialer per build.
334#[derive(Clone)]
335pub struct FramingDialer<D, FF, const RC: usize = 256, const WC: usize = 256> {
336    dialer: D,
337    framers: FF,
338    host: String,
339    port: u16,
340}
341
342impl<D, FF, const RC: usize, const WC: usize> FramingDialer<D, FF, RC, WC> {
343    /// Dial `host:port` through `dialer`, framing each stream with a framer
344    /// from `framers`.
345    pub fn new(dialer: D, framers: FF, host: impl Into<String>, port: u16) -> Self {
346        Self {
347            dialer,
348            framers,
349            host: host.into(),
350            port,
351        }
352    }
353}
354
355impl<D, FF, const RC: usize, const WC: usize> Dialer for FramingDialer<D, FF, RC, WC>
356where
357    D: StreamDialer + Send + Sync,
358    FF: FramerFactory + Send + Sync,
359    D::Stream: 'static,
360    FF::Framer: 'static,
361{
362    fn connect(&self) -> BoxFut<'_, TransportResult<Box<dyn Connection>>> {
363        Box::pin(async move {
364            let stream = self.dialer.connect(&self.host, self.port).await?;
365            let conn: FramedConnection<D::Stream, FF::Framer, RC, WC> =
366                FramedConnection::new(stream, self.framers.framer());
367            Ok(Box::new(conn) as Box<dyn Connection>)
368        })
369    }
370}
371
372/// Lifts a [`StreamListener`] and a [`FramerFactory`] into a [`Listener`], so
373/// `serve` drives an adapter transport unchanged.
374pub struct FramingListener<L, FF, const RC: usize = 256, const WC: usize = 256> {
375    listener: L,
376    framers: FF,
377}
378
379impl<L, FF, const RC: usize, const WC: usize> FramingListener<L, FF, RC, WC> {
380    /// Accept through `listener`, framing each stream with a framer from
381    /// `framers`.
382    pub fn new(listener: L, framers: FF) -> Self {
383        Self { listener, framers }
384    }
385}
386
387impl<L, FF, const RC: usize, const WC: usize> Listener for FramingListener<L, FF, RC, WC>
388where
389    L: StreamListener + Send,
390    FF: FramerFactory + Send,
391    L::Stream: 'static,
392    FF::Framer: 'static,
393{
394    fn accept(&mut self) -> BoxFut<'_, TransportResult<Box<dyn Connection>>> {
395        Box::pin(async move {
396            let (stream, peer) = self.listener.accept().await?;
397            let conn: FramedConnection<L::Stream, FF::Framer, RC, WC> =
398                FramedConnection::with_peer(stream, self.framers.framer(), peer);
399            Ok(Box::new(conn) as Box<dyn Connection>)
400        })
401    }
402}
403
404// ===========================================================================
405// Moved-in resources.
406// ===========================================================================
407
408/// A cell holding a resource until something takes it, once.
409///
410/// `ConnectorBuilder` is `Send + Sync` and its `build` takes `&self`, so a
411/// connector holding a moved-in listener, peripheral or credential set must
412/// take it through interior mutability. `spin::Mutex<Option<T>>` is
413/// `Send + Sync` whenever `T: Send`, with no `unsafe`.
414///
415/// The `T: Send` bound is the contract: a type that is not `Send` cannot be
416/// held here, and that refusal is the signal to fix the type rather than to
417/// reach for `unsafe impl`.
418pub struct OneShot<T> {
419    inner: spin::Mutex<Option<T>>,
420}
421
422impl<T> OneShot<T> {
423    /// Hold `value` for a single [`take`](Self::take).
424    pub fn new(value: T) -> Self {
425        Self {
426            inner: spin::Mutex::new(Some(value)),
427        }
428    }
429
430    /// Take the value, or `None` if it was already taken.
431    pub fn take(&self) -> Option<T> {
432        self.inner.lock().take()
433    }
434}
435
436impl<T> Default for OneShot<T> {
437    /// An already-empty cell, for a resource that may never be supplied.
438    fn default() -> Self {
439        Self {
440            inner: spin::Mutex::new(None),
441        }
442    }
443}
444
445impl<T> From<T> for OneShot<T> {
446    fn from(value: T) -> Self {
447        Self::new(value)
448    }
449}
450
451/// Hands out one pre-built connection, then refuses.
452///
453/// The [`Dialer`] a point-to-point transport wants: a moved-in UART, pipe or
454/// socket has nothing to redial, so the second attempt is
455/// [`TransportError::Closed`] — which [`run_client`](super::run_client) treats
456/// as terminal — rather than a silent reconnect loop.
457pub struct OneShotDialer<C> {
458    conn: OneShot<C>,
459}
460
461impl<C> OneShotDialer<C> {
462    /// Hold `conn` for the first [`connect`](Dialer::connect).
463    pub fn new(conn: C) -> Self {
464        Self {
465            conn: OneShot::new(conn),
466        }
467    }
468}
469
470impl<C: Connection + 'static> Dialer for OneShotDialer<C> {
471    fn connect(&self) -> BoxFut<'_, TransportResult<Box<dyn Connection>>> {
472        Box::pin(async move {
473            self.conn
474                .take()
475                .map(|c| Box::new(c) as Box<dyn Connection>)
476                .ok_or(TransportError::Closed)
477        })
478    }
479}
480
481/// Hands out one pre-built connection, then parks forever.
482///
483/// The [`Listener`] dual of [`OneShotDialer`]: [`serve`](super::serve) loops on
484/// `accept`, and a point-to-point link has no second peer, so parking is the
485/// correct end state rather than an error the loop would spin on.
486pub struct OneShotListener<C> {
487    conn: OneShot<C>,
488}
489
490impl<C> OneShotListener<C> {
491    /// Hold `conn` for the first [`accept`](Listener::accept).
492    pub fn new(conn: C) -> Self {
493        Self {
494            conn: OneShot::new(conn),
495        }
496    }
497}
498
499impl<C: Connection + 'static> Listener for OneShotListener<C> {
500    fn accept(&mut self) -> BoxFut<'_, TransportResult<Box<dyn Connection>>> {
501        Box::pin(async move {
502            match self.conn.take() {
503                Some(c) => Ok(Box::new(c) as Box<dyn Connection>),
504                None => core::future::pending().await,
505            }
506        })
507    }
508}
509
510// ===========================================================================
511// Compile-time assertions.
512// ===========================================================================
513
514/// Generic code over these traits produces `Send` futures, so a connector task
515/// built from them boxes as the runner requires. Drop a `+ Send` from any
516/// return type above and this stops compiling.
517#[allow(dead_code)]
518fn _generic_code_over_the_traits_is_send<D, L, B, T>(
519    dialer: D,
520    mut listener: L,
521    binder: B,
522    delay: T,
523) -> BoxFut<'static, ()>
524where
525    D: StreamDialer + Send + 'static,
526    L: StreamListener + Send + 'static,
527    B: DatagramBinder + Send + 'static,
528    T: Delay + Send + 'static,
529{
530    Box::pin(async move {
531        let mut buf = [0u8; 16];
532
533        if let Ok(mut stream) = dialer.connect("example.test", 1883).await {
534            let _ = stream.read(&mut buf).await;
535            let _ = stream.write_all(&buf).await;
536            let _ = stream.flush().await;
537        }
538        if let Ok((mut stream, _peer)) = listener.accept().await {
539            let _ = stream.read(&mut buf).await;
540        }
541        if let Ok(mut socket) = binder.bind(0).await {
542            let _ = socket.recv_from(&mut buf).await;
543            let _ = socket.local_addr();
544        }
545        delay.sleep(core::time::Duration::from_millis(1)).await;
546    })
547}
548
549/// `OneShot<T>` is `Send + Sync` for any `T: Send`, with no `unsafe`.
550#[allow(dead_code)]
551fn _one_shot_is_send_sync_without_unsafe() {
552    fn assert_send_sync<T: Send + Sync>() {}
553    assert_send_sync::<OneShot<Vec<u8>>>();
554}
555
556#[cfg(test)]
557mod tests {
558    use super::*;
559    use alloc::sync::Arc;
560    use alloc::vec;
561
562    // --- Test doubles -----------------------------------------------------
563
564    /// Length-prefixed framer: one length byte, then that many payload bytes.
565    /// A `0xFF` length marks a corrupt run, so resync has something to skip;
566    /// `0xFE` marks an unrecoverable one. A frame too long for the one-byte
567    /// length is rejected by `encode`.
568    #[derive(Default)]
569    struct LenFramer {
570        buf: Vec<u8>,
571    }
572
573    impl Framer for LenFramer {
574        fn encode(&self, frame: &[u8], out: &mut Vec<u8>) -> Result<(), FrameFault> {
575            if frame.len() >= 0xFE {
576                return Err(FrameFault::Recoverable);
577            }
578            out.push(frame.len() as u8);
579            out.extend_from_slice(frame);
580            Ok(())
581        }
582        fn push_bytes(&mut self, bytes: &[u8]) {
583            self.buf.extend_from_slice(bytes);
584        }
585        fn next_frame(&mut self) -> Option<Result<Vec<u8>, FrameFault>> {
586            let len = *self.buf.first()? as usize;
587            if len == 0xFF {
588                self.buf.remove(0);
589                return Some(Err(FrameFault::Recoverable));
590            }
591            if len == 0xFE {
592                self.buf.clear();
593                return Some(Err(FrameFault::Fatal));
594            }
595            if self.buf.len() < len + 1 {
596                return None;
597            }
598            let frame = self.buf[1..len + 1].to_vec();
599            self.buf.drain(..len + 1);
600            Some(Ok(frame))
601        }
602    }
603
604    /// Shared so a test can inspect what the connection wrote after moving the
605    /// stream into it.
606    #[derive(Default)]
607    struct StreamState {
608        /// Chunks `read` hands out, in order; empty means EOF.
609        reads: Vec<Vec<u8>>,
610        /// Bytes written, concatenated.
611        written: Vec<u8>,
612        /// Size of each individual `write_all` call.
613        write_sizes: Vec<usize>,
614        flushes: usize,
615    }
616
617    #[derive(Clone, Default)]
618    struct MockStream(Arc<spin::Mutex<StreamState>>);
619
620    impl MockStream {
621        fn with_reads(reads: Vec<Vec<u8>>) -> Self {
622            Self(Arc::new(spin::Mutex::new(StreamState {
623                reads,
624                ..Default::default()
625            })))
626        }
627    }
628
629    // Written as plain `async fn`s: an impl on a runtime whose futures are
630    // already `Send` needs nothing more, and the compiler checks the bound the
631    // trait declares.
632    impl ByteStream for MockStream {
633        async fn read<'a>(&'a mut self, buf: &'a mut [u8]) -> TransportResult<usize> {
634            let mut st = self.0.lock();
635            if st.reads.is_empty() {
636                return Ok(0);
637            }
638            let chunk = st.reads.remove(0);
639            let n = chunk.len().min(buf.len());
640            buf[..n].copy_from_slice(&chunk[..n]);
641            Ok(n)
642        }
643
644        async fn write_all<'a>(&'a mut self, buf: &'a [u8]) -> TransportResult<()> {
645            let mut st = self.0.lock();
646            st.written.extend_from_slice(buf);
647            st.write_sizes.push(buf.len());
648            Ok(())
649        }
650
651        async fn flush(&mut self) -> TransportResult<()> {
652            self.0.lock().flushes += 1;
653            Ok(())
654        }
655
656        fn split(&mut self) -> (impl ByteRead + Send + '_, impl ByteWrite + Send + '_) {
657            (MockHalf(self.clone()), MockHalf(self.clone()))
658        }
659    }
660
661    /// Either half of a split [`MockStream`]. The mock is already a shared
662    /// handle, so a half is just a clone of it.
663    struct MockHalf(MockStream);
664
665    impl ByteRead for MockHalf {
666        async fn read<'a>(&'a mut self, buf: &'a mut [u8]) -> TransportResult<usize> {
667            ByteStream::read(&mut self.0, buf).await
668        }
669    }
670
671    impl ByteWrite for MockHalf {
672        async fn write_all<'a>(&'a mut self, buf: &'a [u8]) -> TransportResult<()> {
673            ByteStream::write_all(&mut self.0, buf).await
674        }
675
676        async fn flush(&mut self) -> TransportResult<()> {
677            ByteStream::flush(&mut self.0).await
678        }
679    }
680
681    /// A stream whose first read fails, to check the error is propagated as-is
682    /// rather than flattened.
683    struct FailingStream;
684
685    impl ByteStream for FailingStream {
686        async fn read<'a>(&'a mut self, _buf: &'a mut [u8]) -> TransportResult<usize> {
687            Err(TransportError::Closed)
688        }
689        async fn write_all<'a>(&'a mut self, _buf: &'a [u8]) -> TransportResult<()> {
690            Err(TransportError::Closed)
691        }
692        async fn flush(&mut self) -> TransportResult<()> {
693            Ok(())
694        }
695        fn split(&mut self) -> (impl ByteRead + Send + '_, impl ByteWrite + Send + '_) {
696            (FailingHalf, FailingHalf)
697        }
698    }
699
700    /// Either half of a split [`FailingStream`].
701    struct FailingHalf;
702
703    impl ByteRead for FailingHalf {
704        async fn read<'a>(&'a mut self, _buf: &'a mut [u8]) -> TransportResult<usize> {
705            Err(TransportError::Closed)
706        }
707    }
708
709    impl ByteWrite for FailingHalf {
710        async fn write_all<'a>(&'a mut self, _buf: &'a [u8]) -> TransportResult<()> {
711            Err(TransportError::Closed)
712        }
713
714        async fn flush(&mut self) -> TransportResult<()> {
715            Ok(())
716        }
717    }
718
719    /// A stream whose read cannot finish until its write half has run, so a
720    /// blocked reader that blocked the writer would deadlock the test.
721    #[derive(Clone, Default)]
722    struct DuplexMock {
723        written: Arc<spin::Mutex<Vec<u8>>>,
724        wrote: Arc<tokio::sync::Notify>,
725    }
726
727    impl ByteStream for DuplexMock {
728        async fn read<'a>(&'a mut self, buf: &'a mut [u8]) -> TransportResult<usize> {
729            self.wrote.notified().await;
730            let written = self.written.lock();
731            let n = written.len().min(buf.len());
732            buf[..n].copy_from_slice(&written[..n]);
733            Ok(n)
734        }
735
736        async fn write_all<'a>(&'a mut self, buf: &'a [u8]) -> TransportResult<()> {
737            self.written.lock().extend_from_slice(buf);
738            self.wrote.notify_one();
739            Ok(())
740        }
741
742        async fn flush(&mut self) -> TransportResult<()> {
743            Ok(())
744        }
745
746        fn split(&mut self) -> (impl ByteRead + Send + '_, impl ByteWrite + Send + '_) {
747            (DuplexHalf(self.clone()), DuplexHalf(self.clone()))
748        }
749    }
750
751    /// Either half of a split [`DuplexMock`].
752    struct DuplexHalf(DuplexMock);
753
754    impl ByteRead for DuplexHalf {
755        async fn read<'a>(&'a mut self, buf: &'a mut [u8]) -> TransportResult<usize> {
756            ByteStream::read(&mut self.0, buf).await
757        }
758    }
759
760    impl ByteWrite for DuplexHalf {
761        async fn write_all<'a>(&'a mut self, buf: &'a [u8]) -> TransportResult<()> {
762            ByteStream::write_all(&mut self.0, buf).await
763        }
764
765        async fn flush(&mut self) -> TransportResult<()> {
766            ByteStream::flush(&mut self.0).await
767        }
768    }
769
770    struct MockDialer(MockStream);
771
772    impl StreamDialer for MockDialer {
773        type Stream = MockStream;
774        async fn connect<'a>(&'a self, _host: &'a str, _port: u16) -> TransportResult<MockStream> {
775            Ok(self.0.clone())
776        }
777    }
778
779    struct MockListener(Option<MockStream>);
780
781    impl StreamListener for MockListener {
782        type Stream = MockStream;
783        async fn accept(&mut self) -> TransportResult<(MockStream, PeerInfo)> {
784            let s = self.0.take().ok_or(TransportError::Closed)?;
785            let peer = PeerInfo {
786                peer_addr: Some("10.0.0.1:5555".into()),
787                ..Default::default()
788            };
789            Ok((s, peer))
790        }
791    }
792
793    fn framed(stream: MockStream) -> FramedConnection<MockStream, LenFramer, 256, 256> {
794        FramedConnection::new(stream, LenFramer::default())
795    }
796
797    // --- FramedConnection -------------------------------------------------
798
799    #[tokio::test]
800    async fn recv_yields_each_frame_then_eof() {
801        let mut conn = framed(MockStream::with_reads(vec![vec![
802            2, b'h', b'i', 3, b'y', b'e', b's',
803        ]]));
804        assert_eq!(conn.recv().await.unwrap(), Some(b"hi".to_vec()));
805        assert_eq!(conn.recv().await.unwrap(), Some(b"yes".to_vec()));
806        assert_eq!(conn.recv().await.unwrap(), None, "closed peer is Ok(None)");
807    }
808
809    #[tokio::test]
810    async fn recv_reassembles_a_frame_split_across_reads() {
811        let mut conn = framed(MockStream::with_reads(vec![
812            vec![3, b'a'],
813            vec![b'b'],
814            vec![b'c'],
815        ]));
816        assert_eq!(conn.recv().await.unwrap(), Some(b"abc".to_vec()));
817    }
818
819    #[tokio::test]
820    async fn recv_skips_a_corrupt_run_and_resyncs() {
821        let mut conn = framed(MockStream::with_reads(vec![vec![
822            0xFF, 0xFF, 2, b'o', b'k',
823        ]]));
824        assert_eq!(
825            conn.recv().await.unwrap(),
826            Some(b"ok".to_vec()),
827            "a run that fails to decode is skipped, not fatal"
828        );
829    }
830
831    #[tokio::test]
832    async fn recv_closes_on_an_unrecoverable_framing_error() {
833        // The bytes after the bad header are unreadable, so the connection ends
834        // rather than reinterpreting them as the next header forever.
835        let mut conn = framed(MockStream::with_reads(vec![vec![0xFE, 2, b'o', b'k']]));
836        assert_eq!(
837            conn.recv().await,
838            Err(TransportError::Framing),
839            "a fatal fault ends the connection, it is not skipped"
840        );
841    }
842
843    #[tokio::test]
844    async fn send_reports_a_frame_the_framer_rejects() {
845        let stream = MockStream::default();
846        let mut conn = framed(stream.clone());
847        let oversized = vec![b'x'; 0xFE];
848
849        assert_eq!(
850            conn.send(&oversized).await,
851            Err(TransportError::Framing),
852            "a dropped frame is an error, never a silent Ok"
853        );
854        let st = stream.0.lock();
855        assert!(
856            st.written.is_empty(),
857            "nothing half-encoded reaches the wire"
858        );
859        assert_eq!(st.flushes, 0);
860    }
861
862    #[tokio::test]
863    async fn recv_propagates_the_streams_own_error() {
864        let mut conn = FramedConnection::<_, _, 256, 256>::new(FailingStream, LenFramer::default());
865        assert_eq!(
866            conn.recv().await,
867            Err(TransportError::Closed),
868            "the stream classifies the failure; framing must not flatten it"
869        );
870    }
871
872    #[tokio::test]
873    async fn send_encodes_then_flushes() {
874        let stream = MockStream::default();
875        let mut conn = framed(stream.clone());
876        conn.send(b"hi").await.unwrap();
877
878        let st = stream.0.lock();
879        assert_eq!(st.written, vec![2, b'h', b'i']);
880        assert_eq!(st.flushes, 1);
881    }
882
883    #[tokio::test]
884    async fn send_splits_a_frame_larger_than_the_write_chunk() {
885        let stream = MockStream::default();
886        let mut conn: FramedConnection<MockStream, LenFramer, 256, 4> =
887            FramedConnection::new(stream.clone(), LenFramer::default());
888        conn.send(b"0123456789").await.unwrap();
889
890        let st = stream.0.lock();
891        assert_eq!(st.write_sizes, vec![4, 4, 3], "11 encoded bytes at WC = 4");
892        assert_eq!(st.written.len(), 11);
893    }
894
895    // --- FramingDialer / FramingListener ----------------------------------
896
897    #[tokio::test]
898    async fn framing_dialer_produces_a_working_connection() {
899        let stream = MockStream::with_reads(vec![vec![2, b'h', b'i']]);
900        let dialer: FramingDialer<_, _, 256, 256> =
901            FramingDialer::new(MockDialer(stream), LenFramer::default, "host.test", 1883);
902
903        let mut conn = dialer.connect().await.unwrap();
904        assert_eq!(conn.recv().await.unwrap(), Some(b"hi".to_vec()));
905    }
906
907    #[tokio::test]
908    async fn framing_listener_carries_peer_metadata_through() {
909        let stream = MockStream::with_reads(vec![vec![2, b'h', b'i']]);
910        let mut listener: FramingListener<_, _, 256, 256> =
911            FramingListener::new(MockListener(Some(stream)), LenFramer::default);
912
913        let mut conn = listener.accept().await.unwrap();
914        assert_eq!(conn.peer().peer_addr.as_deref(), Some("10.0.0.1:5555"));
915        assert_eq!(conn.recv().await.unwrap(), Some(b"hi".to_vec()));
916    }
917
918    #[test]
919    fn a_framed_connection_is_boxable_as_dyn_connection() {
920        let conn = framed(MockStream::default());
921        let _boxed: Box<dyn Connection> = Box::new(conn);
922    }
923
924    // --- ByteStream::split ------------------------------------------------
925
926    #[tokio::test]
927    async fn split_halves_run_concurrently() {
928        let mut stream = DuplexMock::default();
929        let (mut rx, mut tx) = stream.split();
930
931        // The read cannot complete until the write has run, so this joining at
932        // all is the assertion: both halves were live at the same time.
933        let mut buf = [0u8; 4];
934        let (read, write) = tokio::join!(rx.read(&mut buf), tx.write_all(b"ping"));
935
936        write.expect("write half");
937        assert_eq!(read.expect("read half"), 4);
938        assert_eq!(&buf, b"ping");
939    }
940
941    #[tokio::test]
942    async fn split_halves_address_the_same_stream() {
943        let stream = MockStream::with_reads(vec![b"hi".to_vec()]);
944        let mut split_me = stream.clone();
945        let (mut rx, mut tx) = split_me.split();
946
947        tx.write_all(b"out").await.expect("write half");
948        let mut buf = [0u8; 8];
949        let n = rx.read(&mut buf).await.expect("read half");
950
951        assert_eq!(&buf[..n], b"hi", "the read half drains the stream's reads");
952        assert_eq!(
953            stream.0.lock().written,
954            b"out",
955            "the write half reaches the stream the halves came from"
956        );
957    }
958
959    // --- OneShot / FramerFactory ------------------------------------------
960
961    #[test]
962    fn one_shot_yields_its_value_once() {
963        let cell = OneShot::new(7u32);
964        assert_eq!(cell.take(), Some(7));
965        assert_eq!(cell.take(), None, "a second take must not get the resource");
966    }
967
968    #[test]
969    fn default_one_shot_is_empty() {
970        assert_eq!(OneShot::<u32>::default().take(), None);
971    }
972
973    #[test]
974    fn framer_factory_is_implemented_for_closures() {
975        struct Noop;
976        impl Framer for Noop {
977            fn encode(&self, _frame: &[u8], _out: &mut Vec<u8>) -> Result<(), FrameFault> {
978                Ok(())
979            }
980            fn push_bytes(&mut self, _bytes: &[u8]) {}
981            fn next_frame(&mut self) -> Option<Result<Vec<u8>, FrameFault>> {
982                None
983            }
984        }
985
986        fn takes_factory<FF: FramerFactory>(ff: FF) -> FF::Framer {
987            ff.framer()
988        }
989        let _framer = takes_factory(|| Noop);
990    }
991}