wireshift-core 0.1.1

Core typed operations, buffers, and backend traits for wireshift
Documentation
//! Backend abstractions used by the generic ring.
//!
//! This module defines the core trait contracts that all backends must implement,
//! including the [`Backend`] trait for pluggable backends and [`AnyBackend`] for
//! enum-based zero-cost dispatch.

use crate::error::{Error, Result};
use crate::op::{CompletionPayload, OpDescriptor};
use crate::RingConfig;
use std::sync::mpsc::Sender;

/// The active backend implementation.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum BackendKind {
    /// Linux `io_uring`.
    IoUring,
    /// Fallback worker backend.
    Fallback,
}

impl BackendKind {
    /// Returns a stable string name.
    #[must_use]
    pub fn as_str(self) -> &'static str {
        match self {
            Self::IoUring => "io_uring",
            Self::Fallback => "fallback",
        }
    }
}

/// Boxed backend trait object (legacy, kept for backwards compatibility).
///
/// This type alias exists for backwards compatibility with code that uses
/// `Box<dyn Backend>` directly. New code should prefer [`AnyBackend`] for
/// better performance through enum dispatch.
pub type BoxedBackend = Box<dyn Backend>;

/// Backend submission payload.
///
/// Carries an operation descriptor from the ring to the backend along with
/// a stable request identifier used for completion routing and cancellation.
#[derive(Debug)]
pub struct BackendSubmission {
    /// Stable request id.
    pub id: u64,
    /// Operation descriptor.
    pub descriptor: OpDescriptor,
    /// Optional timeout for this operation.
    pub timeout: Option<std::time::Duration>,
}

impl BackendSubmission {
    /// Constructs a submission value.
    #[must_use]
    pub fn new(id: u64, descriptor: OpDescriptor) -> Self {
        Self {
            id,
            descriptor,
            timeout: None,
        }
    }

    /// Sets the timeout for this submission.
    #[must_use]
    pub fn with_timeout(mut self, timeout: Option<std::time::Duration>) -> Self {
        self.timeout = timeout;
        self
    }
}

/// Backend completion payload.
///
/// Delivered from the backend to the ring when an operation completes,
/// successfully or with an error.
#[derive(Debug)]
pub struct BackendCompletion {
    /// Stable request id matching the original submission.
    pub id: u64,
    /// Completion result containing the payload or an error.
    pub result: Result<CompletionPayload>,
}

/// Cancellation request handle.
///
/// Passed to [`Backend::cancel`] to request cancellation of an in-flight operation.
#[derive(Clone, Copy, Debug)]
pub struct CancellationHandle {
    /// Request id to cancel.
    pub target: u64,
}

/// Pluggable backend interface.
///
/// # Contract
///
/// All implementations must satisfy these behavioral contracts:
///
/// ## Thread Safety
/// - [`Backend`] extends [`Send`] + [`Sync`]: implementations must be safe to
///   share between threads and call concurrently from multiple threads.
///
/// ## Submission (`submit`)
/// - Must be **non-blocking**: the method should return immediately after
///   queueing the work, not wait for completion.
/// - Must accept submissions until [`Backend::shutdown`] is called.
/// - Must return an error if the backend is unable to accept new work.
/// - The backend is responsible for eventually calling the completion callback
///   for every successfully submitted operation.
///
/// ## Cancellation (`cancel`)
/// - Best-effort cancellation of in-flight work identified by request id.
/// - Cancellation may race with completion: both outcomes are valid.
/// - Returns an error if the target request id is unknown or already complete.
///
/// ## Shutdown (`shutdown`)
/// - Must be **idempotent**: calling multiple times should not panic or error.
/// - Must gracefully wait for all in-flight operations to complete or cancel.
/// - After shutdown returns, no new completions should be delivered.
/// - Should signal any worker threads to exit and wait for them to join.
pub trait Backend: Send + Sync {
    /// Returns the backend kind.
    fn kind(&self) -> BackendKind;

    /// Submits work to the backend.
    ///
    /// # Contract
    /// - This method must be non-blocking.
    /// - Returns `Ok(())` if the submission was accepted.
    /// - Returns an error if the backend cannot accept the submission.
    fn submit(&self, submission: BackendSubmission) -> Result<()>;

    /// Requests cancellation for in-flight work.
    ///
    /// # Contract
    /// - Best-effort: the operation may complete before cancellation takes effect.
    /// - Returns an error if the target request is not found.
    fn cancel(&self, cancellation: CancellationHandle) -> Result<()>;

    /// Shuts the backend down.
    ///
    /// # Contract
    /// - Idempotent: safe to call multiple times.
    /// - Waits for all in-flight operations to complete.
    /// - No new completions are delivered after this returns.
    fn shutdown(&self) -> Result<()>;
}

/// Enum dispatch backend for eliminating vtable indirection on the hot path.
///
/// # Enum Dispatch vs Vtable
///
/// This enum uses **enum dispatch** (also known as "inline dispatch") to avoid
/// vtable lookups for the built-in backends:
///
/// | Approach | Pros | Cons |
/// |----------|------|------|
/// | **Enum dispatch** (`AnyBackend`) | Zero-cost: no vtable indirection, better inlining, cache-friendly | Closed set of variants |
/// | **Vtable** (`Box<dyn Backend>`) | Open for extension, dynamic plugin loading | Virtual call overhead, harder to inline |
///
/// ## Migration Path: Adding a New Backend (e.g., kqueue)
///
/// 1. **Create a new crate**: `kqueue/Cargo.toml`
/// 2. **Implement the trait**: `impl Backend for KqueueBackend { ... }`
/// 3. **Add the variant** (optional, for zero-cost):
///    ```rust
///    // In core/src/backend.rs
///    pub enum AnyBackend {
///        Boxed(BoxedBackend),
///        Kqueue(wireshift_kqueue::KqueueBackend), // Zero-cost variant
///    }
///    ```
/// 4. **Feature-flag it** in the workspace Cargo.toml:
///    ```toml
///    [features]
///    kqueue = ["dep:wireshift-kqueue"]
///    ```
///
/// ## Send + Sync Guarantees
///
/// `AnyBackend` is `Send + Sync` because every variant contains only `Send + Sync`
/// types. The compiler automatically derives these traits  -  no `unsafe impl` needed.
/// If a future variant breaks this contract, the compiler will reject it.
#[non_exhaustive]
pub enum AnyBackend {
    /// A boxed backend (legacy or third-party).
    ///
    /// Use this for foreign backends that aren't part of the core wireshift
    /// workspace. For built-in backends, prefer dedicated variants for
    /// zero-cost dispatch.
    Boxed(BoxedBackend),
}

impl AnyBackend {
    /// Wrap a boxed backend into the enum dispatch.
    #[must_use]
    pub fn from_boxed(backend: BoxedBackend) -> Self {
        Self::Boxed(backend)
    }
}

impl Backend for AnyBackend {
    fn kind(&self) -> BackendKind {
        match self {
            Self::Boxed(b) => b.kind(),
        }
    }

    fn submit(&self, submission: BackendSubmission) -> Result<()> {
        match self {
            Self::Boxed(b) => b.submit(submission),
        }
    }

    fn cancel(&self, cancellation: CancellationHandle) -> Result<()> {
        match self {
            Self::Boxed(b) => b.cancel(cancellation),
        }
    }

    fn shutdown(&self) -> Result<()> {
        match self {
            Self::Boxed(b) => b.shutdown(),
        }
    }
}

/// Backend construction hook used by [`crate::Ring`].
///
/// Implementations provide a way to create backends with specific configurations.
/// The factory pattern allows rings to be generic over backend selection while
/// still supporting configuration-specific backend creation.
///
/// # Example
///
/// ```rust,ignore
/// #[derive(Default)]
/// struct MyBackendFactory;
///
/// impl BackendFactory for MyBackendFactory {
///     fn create(
///         &self,
///         config: &RingConfig,
///         completion_tx: Sender<BackendCompletion>,
///     ) -> Result<BoxedBackend> {
///         Ok(Box::new(MyBackend::new(config, completion_tx)?))
///     }
/// }
/// ```
pub trait BackendFactory: Send + Sync + Default + 'static {
    /// Creates the backend for the given ring configuration (legacy API).
    ///
    /// # Errors
    ///
    /// Returns an error if the backend cannot be created (e.g., unsupported
    /// platform, resource exhaustion, invalid configuration).
    fn create(
        &self,
        config: &RingConfig,
        completion_tx: Sender<BackendCompletion>,
    ) -> Result<BoxedBackend>;

    /// Creates the backend as an enum-dispatched value.
    ///
    /// The default implementation wraps the result of [`create`](Self::create)
    /// into [`AnyBackend::Boxed`]. Override this in concrete factories to
    /// return specific variants for zero-cost dispatch.
    ///
    /// # Errors
    ///
    /// Returns an error if the backend cannot be created.
    fn create_enum(
        &self,
        config: &RingConfig,
        completion_tx: Sender<BackendCompletion>,
    ) -> Result<AnyBackend> {
        self.create(config, completion_tx)
            .map(AnyBackend::from_boxed)
    }
}

/// Returns a shared error when a backend worker channel disconnects.
#[must_use]
pub fn disconnected_error() -> Error {
    Error::completion(
        "backend worker channel disconnected",
        "keep backend worker threads alive until the ring is dropped",
    )
}