Skip to main content

phantom_protocol/transport/
session_transport.rs

1//! The `SessionTransport` byte-pipe abstraction — the boundary between
2//! `PhantomSession`'s background data pump and a concrete physical transport
3//! (`TcpSessionTransport`, `WebSocketLeg`, `EmbeddedLeg`, ...).
4//!
5//! Defined with **native async fn in trait** (AFIT, stable since Rust 1.75)
6//! rather than the `#[async_trait]` macro: method calls do not allocate a
7//! boxed future, and `Send`-ness of the returned futures is checked at the
8//! *use* site (the concrete impl type), not at the trait-impl site. The
9//! latter is what allows `EmbeddedLeg<R, W, ...>` to compose with
10//! `embedded-io-async`, whose async-fn-in-trait futures are not `Send`-
11//! bounded — `#[async_trait]` here would have failed to prove `Send`-ness
12//! at the generic impl site.
13//!
14//! Dependency-light (only `bytes` + `CoreError`) so it can compile in a
15//! future `no_std + alloc` build ahead of the rest of the crate.
16//! Re-exported from [`crate::api::session`] so the historical import path
17//! stays stable.
18//!
19//! Phase 3.6 (no-std foundation): module compiles without `std` because the
20//! implementation uses only `core::future::Future` and pulls nothing from
21//! `std::*`. The crate-level `#![cfg_attr(not(feature = "std"), no_std)]` in
22//! `lib.rs` drives the no_std switch; this module needs no extra attribute.
23
24use crate::errors::CoreError;
25use bytes::Bytes;
26
27/// Lifecycle phase of a [`SessionTransport`], used to bound the receive frame
28/// size differently before vs. after the handshake (WIRE-001). During the
29/// unauthenticated handshake a peer can open a connection and declare a large
30/// frame; capping the receive size tightly there bounds the memory a single
31/// unauthenticated peer can make the server buffer. After establishment the cap
32/// rises to the steady-state application limit.
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34pub enum FramePhase {
35    /// Pre-establishment: only small handshake messages are expected.
36    Handshake,
37    /// Post-establishment: full-size application frames are allowed.
38    Established,
39}
40
41/// Async transport trait for PhantomSession.
42///
43/// Abstractions over UDP, TCP, FakeTLS, etc.
44/// Used by the background handshake task for I/O.
45///
46/// `recv_bytes` returns `Bytes` (Phase 2.8) so the recv pipeline can
47/// fan out the same buffer to multiple consumers via cheap refcount
48/// clones — no `Vec → Bytes` conversion at the trait boundary.
49/// `send_bytes` keeps `&[u8]` because the caller routinely sends a
50/// borrowed slice of an already-allocated send buffer.
51pub trait SessionTransport: Send + Sync + 'static {
52    /// Send raw bytes to the peer.
53    ///
54    /// Desugared form (`fn -> impl Future + Send`) rather than `async fn`
55    /// so the `+ Send` bound on the returned future is explicit. This is
56    /// what lets the data pump spawn its task generically over any
57    /// `T: SessionTransport` without an AFIT `return_type_notation` hack.
58    fn send_bytes(
59        &self,
60        data: &[u8],
61    ) -> impl core::future::Future<Output = Result<(), CoreError>> + Send;
62    /// Receive the next message from the peer. The returned `Bytes` is
63    /// a refcounted view over an opaque buffer; subsequent `clone()`s
64    /// are cheap.
65    fn recv_bytes(&self) -> impl core::future::Future<Output = Result<Bytes, CoreError>> + Send;
66
67    /// Move the transport to a new [`FramePhase`], adjusting the receive
68    /// frame-size cap (WIRE-001). Default no-op — transports that do not
69    /// length-prefix / buffer, or are inherently bounded, need not implement it.
70    /// Called once by the session machinery at the handshake → data-pump
71    /// boundary. Adding this defaulted method is source-compatible for every
72    /// existing impl.
73    fn set_frame_phase(&self, _phase: FramePhase) {}
74}