wireshift-core 0.1.1

Core typed operations, buffers, and backend traits for wireshift
Documentation
//! Zero-copy splice(2) operation: transfer data between file descriptors via
//! the kernel pipe buffer without userspace copies.

use std::fs::File;

use crate::error::Result;
use crate::op::{CompletionPayload, Op, OpDescriptor};

/// Result of a successful splice operation.
#[derive(Debug, Clone, Copy)]
pub struct SpliceResult {
    /// Number of bytes actually transferred.
    pub bytes_transferred: usize,
}

/// Zero-copy data transfer between two file descriptors.
///
/// Uses the kernel's `splice(2)` syscall to move data directly from one
/// file descriptor to another through the kernel pipe buffer. No data
/// crosses the kernel-userspace boundary.
///
/// # Requirements
///
/// At least one of `fd_in` or `fd_out` must be a pipe. For file-to-pipe
/// transfers (the warpscan output path), open the source file and splice
/// directly to stdout.
///
/// # Example
///
/// ```no_run
/// use wireshift_core::ops::Splice;
/// use std::fs::File;
///
/// let src = File::open("data.bin").unwrap();
/// let dst = File::create("/dev/stdout").unwrap();
/// let op = Splice::new(src, Some(0), dst, None, 4096).unwrap();
/// ```
pub struct Splice {
    fd_in: File,
    off_in: Option<u64>,
    fd_out: File,
    off_out: Option<u64>,
    len: usize,
}

impl Splice {
    /// Create a new splice operation.
    ///
    /// # Errors
    ///
    /// Returns an error if `len` is zero.
    pub fn new(
        fd_in: File,
        off_in: Option<u64>,
        fd_out: File,
        off_out: Option<u64>,
        len: usize,
    ) -> Result<Self> {
        if len == 0 {
            return Err(crate::Error::invalid_config("splice length must be > 0"));
        }
        Ok(Self {
            fd_in,
            off_in,
            fd_out,
            off_out,
            len,
        })
    }
}

impl Op for Splice {
    type Output = SpliceResult;

    fn name(&self) -> &'static str {
        "splice"
    }

    fn into_descriptor(self) -> Result<OpDescriptor> {
        Ok(OpDescriptor::Splice {
            fd_in: self.fd_in,
            off_in: self.off_in,
            fd_out: self.fd_out,
            off_out: self.off_out,
            len: self.len,
        })
    }

    fn map_completion(payload: CompletionPayload) -> Result<Self::Output> {
        match payload {
            CompletionPayload::Bytes(n) => Ok(SpliceResult {
                bytes_transferred: n,
            }),
            // Any other payload means the completion was misrouted. Silently
            // returning 0 bytes (the old `Unit`/`_` arms) told the caller the
            // splice succeeded transferring nothing, masking the routing bug
            // (Law 10). Fail loud, matching the sibling ops (nop/cancel/read).
            other => Err(crate::error::Error::completion(
                format!("splice received unexpected completion payload: {other:?}"),
                "ensure the backend routes splice completions as byte-count payloads",
            )),
        }
    }
}