wireshift-core 0.1.1

Core typed operations, buffers, and backend traits for wireshift
Documentation
//! Operation trait and erased descriptor types.

use std::any::Any;
use std::fs::File;
use std::net::{SocketAddr, TcpListener, TcpStream};
use std::os::fd::{AsRawFd, RawFd};
use std::path::PathBuf;
use std::time::Duration;

use crate::buffer::{Buffer, Completed, Submitted};
use crate::error::{Error, Result};

/// Result payload returned from backend execution before operation-specific mapping.
#[doc(hidden)]
#[derive(Debug)]
#[non_exhaustive]
pub enum CompletionPayload {
    /// Empty payload.
    Unit,
    /// Byte count payload.
    Bytes(usize),
    /// Single-buffer read payload.
    Read {
        /// Completed buffer.
        buffer: Buffer<Completed>,
        /// Number of bytes read.
        bytes: usize,
    },
    /// Stream receive payload returning ownership of the stream.
    Recv {
        /// The returned TCP stream.
        stream: std::net::TcpStream,
        /// Completed buffer.
        buffer: Buffer<Completed>,
        /// Number of bytes read.
        bytes: usize,
    },
    /// Multi-buffer read payload.
    ReadVectored {
        /// Completed buffers.
        buffers: Vec<Buffer<Completed>>,
        /// Total bytes read.
        bytes: usize,
    },
    /// GPU-staged read payload.
    GpuRead {
        /// Completed aligned buffer.
        buffer: crate::ops::AlignedBuffer,
        /// Number of bytes read.
        bytes: usize,
    },
    /// Linked chain payloads.
    LinkedChain(Vec<CompletionPayload>),
    /// File payload.
    File(File),
    /// TCP stream payload.
    Stream(TcpStream),
    /// Portable metadata payload.
    Metadata {
        /// File size.
        size: u64,
        /// Regular file flag.
        is_file: bool,
        /// Directory flag.
        is_dir: bool,
    },
}

/// Erased operation descriptor submitted to a backend.
#[doc(hidden)]
#[derive(Debug)]
#[non_exhaustive]
pub enum OpDescriptor {
    /// File read.
    Read {
        /// File handle.
        file: File,
        /// Offset to read from.
        offset: u64,
        /// Destination buffer.
        buffer: Buffer<Submitted>,
        /// Requested read length, overriding buffer capacity if needed.
        len: Option<usize>,
    },
    /// File write.
    Write {
        /// File handle.
        file: File,
        /// Offset to write to.
        offset: u64,
        /// Source buffer.
        buffer: Buffer<Submitted>,
    },
    /// File vectored read.
    ReadVectored {
        /// File handle.
        file: File,
        /// Offset to read from.
        offset: u64,
        /// Destination buffers.
        buffers: Vec<Buffer<Submitted>>,
    },
    /// File read directly into an aligned GPU staging buffer.
    ReadGpu {
        /// File handle.
        file: File,
        /// Offset to read from.
        offset: u64,
        /// Destination aligned buffer.
        buffer: crate::ops::AlignedBuffer,
    },
    /// File vectored write.
    WriteVectored {
        /// File handle.
        file: File,
        /// Offset to write to.
        offset: u64,
        /// Source buffers.
        buffers: Vec<Buffer<Submitted>>,
    },
    /// TCP connect.
    Connect {
        /// Remote socket address.
        addr: SocketAddr,
        /// Optional connect timeout.
        ///
        /// *Note:* Timeouts are currently only respected by the `FallbackBackend`.
        /// The native `io_uring` backend will ignore this field and rely on the OS
        /// default connection timeout. Use `Cancel` to manually abort a stalled connect.
        timeout: Option<Duration>,
    },
    /// TCP accept.
    Accept {
        /// Listening socket.
        listener: TcpListener,
    },
    /// TCP send.
    Send {
        /// Connected stream.
        stream: TcpStream,
        /// Source buffer.
        buffer: Buffer<Submitted>,
    },
    /// TCP recv.
    Recv {
        /// Connected stream.
        stream: TcpStream,
        /// Destination buffer.
        buffer: Buffer<Submitted>,
    },
    /// Open path relative to a directory.
    OpenAt {
        /// Optional directory handle.
        dir: Option<File>,
        /// Target path.
        path: PathBuf,
        /// Platform-specific open flag bits.
        flags: u32,
        /// Read access flag.
        read: bool,
        /// Write access flag.
        write: bool,
        /// Create flag.
        create: bool,
        /// Truncate flag.
        truncate: bool,
    },
    /// Open path into an `io_uring` direct-descriptor slot.
    OpenAtDirect {
        /// Optional directory handle.
        dir: Option<File>,
        /// Target path.
        path: PathBuf,
        /// Platform-specific open flag bits.
        flags: u32,
        /// Read access flag.
        read: bool,
        /// Write access flag.
        write: bool,
        /// Create flag.
        create: bool,
        /// Truncate flag.
        truncate: bool,
        /// Fixed descriptor slot to populate.
        slot: u32,
    },
    /// Read from an `io_uring` direct-descriptor slot.
    ReadFixed {
        /// Fixed descriptor slot.
        slot: u32,
        /// Offset to read from.
        offset: u64,
        /// Destination buffer.
        buffer: Buffer<Submitted>,
    },
    /// Close an `io_uring` direct descriptor.
    CloseFixed {
        /// Fixed descriptor slot.
        slot: u32,
    },
    /// Metadata lookup.
    Statx {
        /// Target path.
        path: PathBuf,
    },
    /// File sync.
    Fsync {
        /// File handle.
        file: File,
    },
    /// Internal cancel request.
    Cancel {
        /// Target request id.
        target: u64,
    },
    /// Linked operation chain.
    Linked {
        /// Child descriptors in submission order.
        descriptors: Vec<OpDescriptor>,
    },
    /// splice(2): zero-copy data transfer between two file descriptors via the kernel.
    /// Moves data from `fd_in` to `fd_out` without copying to userspace.
    Splice {
        /// Source file.
        fd_in: File,
        /// Offset in source (None = current position).
        off_in: Option<u64>,
        /// Destination file.
        fd_out: File,
        /// Offset in destination (None = current position).
        off_out: Option<u64>,
        /// Number of bytes to transfer.
        len: usize,
    },
    /// madvise(2): hint the kernel about expected access pattern.
    Madvise {
        /// File to advise on (used to determine the memory region).
        file: File,
        /// Advice constant (platform-specific).
        advice: MadviseAdvice,
    },
    /// No-op.
    Nop,
}

/// Kernel memory advice for sequential or random access patterns.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum MadviseAdvice {
    /// Expect sequential access (aggressive readahead).
    Sequential,
    /// Expect random access (disable readahead).
    Random,
    /// Data will not be needed soon (release pages).
    DontNeed,
    /// Normal access pattern (default).
    Normal,
}

/// A composable I/O operation.
pub trait Op: Send + 'static {
    /// Successful output type.
    type Output: Send + 'static;

    /// Stable operation name for tracing and diagnostics.
    fn name(&self) -> &'static str;

    /// Converts the operation into a backend descriptor.
    fn into_descriptor(self) -> Result<OpDescriptor>;

    /// Maps a backend payload into the public output type.
    fn map_completion(payload: CompletionPayload) -> Result<Self::Output>;
}

pub(crate) type BoxedMapper =
    Box<dyn FnOnce(CompletionPayload) -> Result<Box<dyn Any + Send>> + Send>;

pub(crate) fn boxed_mapper<O: Op>() -> BoxedMapper {
    Box::new(|payload| {
        let mapped = O::map_completion(payload)?;
        Ok(Box::new(mapped))
    })
}

#[doc(hidden)]
pub fn validate_fd<T: AsRawFd>(value: &T, context: &str) -> Result<RawFd> {
    let fd = value.as_raw_fd();
    if fd < 0 {
        return Err(Error::validation(
            format!("{context} has an invalid file descriptor"),
            "ensure the file or socket is open before building the operation",
        ));
    }
    Ok(fd)
}