From 843ddae6faeab97560b936d13bd6120613946eab Mon Sep 17 00:00:00 2001 From: "K. Isom" Date: Sat, 17 Oct 2026 13:05:25 -0700 Subject: [PATCH] feat(core): Transport trait --- tines-core/src/lib.rs | 2 + tines-core/src/transport.rs | 84 +++++++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+) create mode 100644 tines-core/src/transport.rs diff --git a/tines-core/src/lib.rs b/tines-core/src/lib.rs index fadc3b1..455cf3a 100644 --- a/tines-core/src/lib.rs +++ b/tines-core/src/lib.rs @@ -10,11 +10,13 @@ pub mod observation; pub mod store; #[cfg(feature = "alloc")] pub mod mem_store; +pub mod transport; pub mod wire; pub use bloom::Bloom; pub use observation::{Observation, NodeId, ObservationId, Payload, Tick, Topic}; pub use store::{Store, TrimPolicy, StoreError}; +pub use transport::{Event, FrameBuf, SendError, Transport}; pub use wire::{Frame, WireError}; #[cfg(feature = "alloc")] diff --git a/tines-core/src/transport.rs b/tines-core/src/transport.rs new file mode 100644 index 0000000..ab93f39 --- /dev/null +++ b/tines-core/src/transport.rs @@ -0,0 +1,84 @@ +//! Transport trait and wire event types for tines-core. +//! +//! The `Transport` trait abstracts the byte pipe that carries `Frame`s between +//! peers. Implementations are `#![no_std]`-friendly: the only buffer type, +//! `FrameBuf`, is a fixed-capacity `heapless::Vec` that never touches the heap. + +use crate::consts; + +/// Inbound/outbound frame buffer: a `MAX_FRAME`-capacity stack buffer. +pub type FrameBuf = heapless::Vec; + +/// An event observed on a `Transport` from a single peer. +/// +/// `Frame` carries a full `MAX_FRAME` buffer inline, making it far larger than +/// `Contact`/`Lost`; the `large_enum_variant` lint is intentionally suppressed +/// to keep `FrameBuf` off the heap in the `no_std` core. +#[derive(Clone, Debug, PartialEq, Eq)] +#[allow(clippy::large_enum_variant)] +pub enum Event { + /// A peer became reachable. + Contact(A), + /// A previously reachable peer became unreachable. + Lost(A), + /// A frame arrived from `A`. + Frame(A, FrameBuf), +} + +/// Reasons a `Transport::send` call can fail. +#[derive(Debug, PartialEq, Eq)] +pub enum SendError { + /// The transport is busy with an in-flight send and cannot accept another. + Busy, + /// The destination peer is not currently reachable. + Unreachable, + /// The frame exceeds the transport's MTU and cannot be sent. + TooLarge, +} + +/// A byte pipe between peers, polled for inbound events and driven with `send`. +pub trait Transport { + /// Address of a peer on this transport. + type Addr: Clone + PartialEq + core::fmt::Debug; + + /// Poll for the next inbound event (contact, loss, or frame). + fn poll(&mut self) -> Option>; + + /// Attempt to deliver `frame` to `to`. + fn send(&mut self, to: &Self::Addr, frame: &[u8]) -> Result<(), SendError>; + + /// Maximum frame size in bytes this transport can send (must be `<= MAX_FRAME`). + fn mtu(&self) -> usize; +} + +#[cfg(test)] +mod tests { + use super::{Event, SendError, Transport}; + use crate::consts; + + /// A no-op transport used to pin the trait's shape at compile time. + struct NullTransport; + + impl Transport for NullTransport { + type Addr = u8; + + fn poll(&mut self) -> Option> { + None + } + + fn send(&mut self, _to: &Self::Addr, _frame: &[u8]) -> Result<(), SendError> { + Ok(()) + } + + fn mtu(&self) -> usize { + consts::MAX_FRAME + } + } + + #[test] + fn null_transport_poll_is_none() { + let mut t = NullTransport; + assert!(t.poll().is_none()); + assert_eq!(t.mtu(), consts::MAX_FRAME); + } +}