//! Transport shape. //! //! The IPC channel is a Unix domain socket. Each connection //! exchanges length-prefixed CBOR frames: a 4-byte big-endian //! length followed by that many bytes of CBOR. Clients send //! `ClientRequest` frames; the daemon emits `ServerEvent` frames //! tagged by `RequestId`. Framing details intentionally not exposed //! at this layer — a future Cap'n Proto or QUIC transport should be //! drop-in. use std::path::{Path, PathBuf}; use crate::event::ServerEvent; use crate::request::ClientRequest; /// Default Unix socket the daemon binds and clients dial. Override /// via `EVERYCYCLE_SOCKET` in either process. #[must_use] pub fn default_socket_path() -> PathBuf { std::env::var_os("EVERYCYCLE_SOCKET").map_or_else( || Path::new("/run/everycycle/api.sock").to_path_buf(), PathBuf::from, ) } /// Maximum frame size in bytes. Larger payloads must be split by /// the producer; the daemon refuses oversized frames. Constant /// across pre-v1; raise carefully — bumping it is semver-minor but /// shrinking it is semver-major. pub const MAX_FRAME_BYTES: u32 = 16 * 1024 * 1024; /// Length-prefix size in bytes. Big-endian u32. pub const FRAME_LENGTH_BYTES: usize = 4; /// Anything that can submit a request and receive a stream of events. /// /// Synchronous in shape because the daemon is local and the C-ABI /// boundary cannot express Rust futures. An async wrapper around /// this trait is a separate crate concern. pub trait ClientTransport: Send + Sync { /// Submit a request. Returns a stream of events for that /// request. The stream terminates after the first event whose /// `finished` discriminator (or equivalent terminal shape) is /// set, or after an `Error` event. /// /// # Errors /// /// Transport-layer failures — connection lost, frame too /// large, encoding error. Application-level failures arrive as /// `ServerEvent::Error` events on the stream. fn submit(&self, request: ClientRequest) -> Result; } /// Opaque event-stream handle. Implementations choose the /// concrete iterator type; this newtype keeps the trait /// object-safe and the C-ABI translation tractable. pub struct EventStream(pub Box> + Send>); impl Iterator for EventStream { type Item = Result; fn next(&mut self) -> Option { self.0.next() } } /// Errors raised by the transport layer itself (not by the daemon /// rejecting a request — those arrive as `ServerEvent::Error`). #[derive(Debug)] pub enum TransportError { /// Could not connect to the daemon socket. Connect(std::io::Error), /// Read or write failed mid-conversation. Io(std::io::Error), /// A frame exceeded `MAX_FRAME_BYTES`. FrameTooLarge { size: u32 }, /// Encoding or decoding failure. String is the codec's report; /// we do not bind to a specific codec at this layer. Codec(String), } impl core::fmt::Display for TransportError { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self { Self::Connect(e) => write!(f, "connect failed: {e}"), Self::Io(e) => write!(f, "transport io: {e}"), Self::FrameTooLarge { size } => { write!(f, "frame size {size} exceeds {MAX_FRAME_BYTES}") } Self::Codec(s) => write!(f, "codec error: {s}"), } } } impl std::error::Error for TransportError {}