vox_types/conduit.rs
1#![allow(async_fn_in_trait)]
2
3use std::future::Future;
4
5use facet::Facet;
6use facet_core::Shape;
7
8use crate::{MaybeSend, SelfRef};
9
10/// Maps a lifetime to a concrete message type.
11///
12/// Rust doesn't have higher-kinded types, so this trait bridges the gap:
13/// `F::Msg<'a>` gives you the message type for any lifetime `'a`.
14///
15/// The send path uses `Msg<'a>` (borrowed data serialized in place).
16/// The recv path uses `Msg<'static>` (owned, via `SelfRef`).
17pub trait MsgFamily: 'static {
18 type Msg<'a>: Facet<'a> + 'a;
19
20 fn shape() -> &'static Shape {
21 <Self::Msg<'static> as Facet<'static>>::SHAPE
22 }
23}
24
25/// Bidirectional typed transport. Wraps a [`Link`](crate::Link) and owns serialization.
26///
27/// Uses a `MsgFamily` so that the same type family serves both sides:
28/// - Send: `MsgFamily::Msg<'a>` for any `'a` (borrowed data serialized in place)
29/// - Recv: `MsgFamily::Msg<'static>` (owned, via `SelfRef`)
30///
31/// Two implementations:
32/// - `BareConduit`: Link + postcard. If the link dies, it's dead.
33/// - `StableConduit`: Link + postcard + seq/ack/replay. Handles reconnect
34/// transparently. Replay buffer stores encoded bytes (no clone needed).
35// r[impl conduit]
36pub trait Conduit {
37 type Msg: MsgFamily;
38 type Tx: ConduitTx<Msg = Self::Msg>;
39 type Rx: ConduitRx<Msg = Self::Msg>;
40
41 // r[impl conduit.split]
42 fn split(self) -> (Self::Tx, Self::Rx);
43}
44
45/// Sending half of a [`Conduit`].
46///
47/// Permit-based: `reserve()` is the backpressure point, `permit.send()`
48/// serializes and writes.
49// r[impl conduit.permit]
50pub trait ConduitTx {
51 type Msg: MsgFamily;
52 type Permit<'a>: for<'m> ConduitTxPermit<Msg = Self::Msg> + MaybeSend
53 where
54 Self: 'a;
55
56 /// Reserve capacity for one outbound message.
57 ///
58 /// Backpressure lives here — this may block waiting for:
59 /// - StableConduit: replay buffer capacity (bounded outstanding)
60 /// - Flow control from the peer
61 ///
62 /// Dropping the permit without sending releases the reservation.
63 fn reserve(&self) -> impl Future<Output = std::io::Result<Self::Permit<'_>>> + MaybeSend + '_;
64
65 /// Graceful close of the outbound direction.
66 async fn close(self) -> std::io::Result<()>
67 where
68 Self: Sized;
69}
70
71/// Permit for sending exactly one message through a [`ConduitTx`].
72// r[impl conduit.permit.send]
73// r[impl zerocopy.framing.conduit]
74pub trait ConduitTxPermit {
75 type Msg: MsgFamily;
76 type Error: std::error::Error + MaybeSend + 'static;
77
78 fn send(self, item: <Self::Msg as MsgFamily>::Msg<'_>) -> Result<(), Self::Error>;
79}
80
81/// Receiving half of a [`Conduit`].
82///
83/// Yields decoded values as [`SelfRef<Msg<'static>>`](SelfRef) (value + backing storage).
84/// Uses a precomputed `TypePlanCore` for fast plan-driven deserialization.
85/// The result of receiving a message from a conduit.
86pub type RecvResult<M, E> = Result<Option<SelfRef<<M as MsgFamily>::Msg<'static>>>, E>;
87
88pub trait ConduitRx {
89 type Msg: MsgFamily;
90 type Error: std::error::Error + MaybeSend + 'static;
91
92 /// Receive and decode the next message.
93 ///
94 /// Returns `Ok(None)` when the peer has closed.
95 fn recv(&mut self)
96 -> impl Future<Output = RecvResult<Self::Msg, Self::Error>> + MaybeSend + '_;
97}
98
99/// Yields new conduits from inbound connections.
100pub trait ConduitAcceptor {
101 type Conduit: Conduit;
102
103 async fn accept(&mut self) -> std::io::Result<Self::Conduit>;
104}
105
106/// Whether the session is acting as initiator or acceptor.
107#[derive(Debug, Clone, Copy, PartialEq, Eq)]
108pub enum SessionRole {
109 Initiator,
110 Acceptor,
111}