Skip to main content

wireshift_core/ops/
splice.rs

1//! Zero-copy splice(2) operation: transfer data between file descriptors via
2//! the kernel pipe buffer without userspace copies.
3
4use std::fs::File;
5
6use crate::error::Result;
7use crate::op::{CompletionPayload, Op, OpDescriptor};
8
9/// Result of a successful splice operation.
10#[derive(Debug, Clone, Copy)]
11pub struct SpliceResult {
12    /// Number of bytes actually transferred.
13    pub bytes_transferred: usize,
14}
15
16/// Zero-copy data transfer between two file descriptors.
17///
18/// Uses the kernel's `splice(2)` syscall to move data directly from one
19/// file descriptor to another through the kernel pipe buffer. No data
20/// crosses the kernel-userspace boundary.
21///
22/// # Requirements
23///
24/// At least one of `fd_in` or `fd_out` must be a pipe. For file-to-pipe
25/// transfers (the warpscan output path), open the source file and splice
26/// directly to stdout.
27///
28/// # Example
29///
30/// ```no_run
31/// use wireshift_core::ops::Splice;
32/// use std::fs::File;
33///
34/// let src = File::open("data.bin").unwrap();
35/// let dst = File::create("/dev/stdout").unwrap();
36/// let op = Splice::new(src, Some(0), dst, None, 4096).unwrap();
37/// ```
38pub struct Splice {
39    fd_in: File,
40    off_in: Option<u64>,
41    fd_out: File,
42    off_out: Option<u64>,
43    len: usize,
44}
45
46impl Splice {
47    /// Create a new splice operation.
48    ///
49    /// # Errors
50    ///
51    /// Returns an error if `len` is zero.
52    pub fn new(
53        fd_in: File,
54        off_in: Option<u64>,
55        fd_out: File,
56        off_out: Option<u64>,
57        len: usize,
58    ) -> Result<Self> {
59        if len == 0 {
60            return Err(crate::Error::invalid_config("splice length must be > 0"));
61        }
62        Ok(Self {
63            fd_in,
64            off_in,
65            fd_out,
66            off_out,
67            len,
68        })
69    }
70}
71
72impl Op for Splice {
73    type Output = SpliceResult;
74
75    fn name(&self) -> &'static str {
76        "splice"
77    }
78
79    fn into_descriptor(self) -> Result<OpDescriptor> {
80        Ok(OpDescriptor::Splice {
81            fd_in: self.fd_in,
82            off_in: self.off_in,
83            fd_out: self.fd_out,
84            off_out: self.off_out,
85            len: self.len,
86        })
87    }
88
89    fn map_completion(payload: CompletionPayload) -> Result<Self::Output> {
90        match payload {
91            CompletionPayload::Bytes(n) => Ok(SpliceResult {
92                bytes_transferred: n,
93            }),
94            // Any other payload means the completion was misrouted. Silently
95            // returning 0 bytes (the old `Unit`/`_` arms) told the caller the
96            // splice succeeded transferring nothing, masking the routing bug
97            // (Law 10). Fail loud, matching the sibling ops (nop/cancel/read).
98            other => Err(crate::error::Error::completion(
99                format!("splice received unexpected completion payload: {other:?}"),
100                "ensure the backend routes splice completions as byte-count payloads",
101            )),
102        }
103    }
104}