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}