blob: c4ccbbe0c5039f614672f4edd28694464d97a01f [file]
// Licensed under the Apache-2.0 license
// SPDX-License-Identifier: Apache-2.0
//! Transport and dispatch traits for IPC services.
//!
//! Three traits: `Transport` (blocking), `AsyncTransport` (split-phase),
//! and `Dispatch` (server). A service defines its wire format in its own
//! crate and plugs into these.
#![no_std]
mod delayed;
mod loopback;
pub use delayed::Delayed;
pub use loopback::Loopback;
/// Why a transport round-trip failed.
///
/// Service-level errors travel inside the response payload, not here.
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TransportError {
/// The channel, syscall, or loopback call failed.
Failed,
/// `start` while in flight, or `poll`/`cancel` with nothing in flight.
WrongState,
/// Request or response does not fit its buffer.
TooLarge,
}
impl core::fmt::Display for TransportError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::Failed => f.write_str("transport round-trip failed"),
Self::WrongState => f.write_str("transport is in the wrong state"),
Self::TooLarge => f.write_str("message does not fit the buffer"),
}
}
}
impl core::error::Error for TransportError {}
/// One round-trip, caller waits for the response.
pub trait Transport {
fn transact(&mut self, req: &[u8], resp: &mut [u8]) -> Result<usize, TransportError>;
}
/// One round-trip at a time, split so the caller never blocks.
///
/// `start` copies the request and returns immediately. `poll` returns
/// `Ok(None)` while the response is outstanding, `Ok(Some(len))` when
/// `resp[..len]` holds the reply. `poll` never waits.
///
/// One round-trip at a time: `start` while in flight or `poll`/`cancel`
/// with nothing in flight is `WrongState`. Any error from `poll` ends
/// the round-trip and discards the response: a later `poll` cannot
/// retrieve it, and the next call is `start`.
///
/// A request that is too large for the transport is caught at different
/// points depending on the implementation. A channel transport checks at
/// `start` and returns `TooLarge` before anything goes out. A loopback
/// has no send buffer to overflow, so it hands the request straight to
/// the server, which answers with a protocol error frame at `poll`.
/// Callers need to handle both cases. A response too large for the
/// caller's buffer is always `TooLarge` from `poll`.
///
/// Registering for wake-up signals needs the concrete transport (a
/// kernel channel has a handle, a loopback does not), so that happens
/// at wiring time, not through this trait.
pub trait AsyncTransport {
fn start(&mut self, req: &[u8]) -> Result<(), TransportError>;
fn poll(&mut self, resp: &mut [u8]) -> Result<Option<usize>, TransportError>;
/// Abandon the in-flight round-trip.
fn cancel(&mut self) -> Result<(), TransportError>;
}
/// Why a dispatch produced no response frame at all.
///
/// Service errors go in the response frame (still `Ok`). This covers
/// the case where even an error frame does not fit.
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DispatchError {
/// The response does not fit the buffer the caller gave, not even
/// as an error frame.
ResponseTooLarge,
}
impl core::fmt::Display for DispatchError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::ResponseTooLarge => f.write_str("response does not fit the buffer"),
}
}
}
impl core::error::Error for DispatchError {}
impl From<DispatchError> for TransportError {
fn from(e: DispatchError) -> Self {
match e {
DispatchError::ResponseTooLarge => Self::TooLarge,
}
}
}
/// Server end: one request frame in, one response frame out.
///
/// Returns bytes written to `response`. No state between calls beyond
/// what the service itself owns.
pub trait Dispatch {
fn dispatch(&mut self, request: &[u8], response: &mut [u8]) -> Result<usize, DispatchError>;
}