Skip to main content

wireshift_core/buffer/
mod.rs

1//! Type-state buffer pool.
2
3use std::marker::PhantomData;
4
5use crate::error::{Error, Result};
6
7pub(crate) mod pool;
8pub use pool::BufferPool;
9
10pub(crate) const REGISTERED_BUFFER_CAPACITY: usize = 256 * 1024;
11pub(crate) const REGISTERED_BUFFER_COUNT: usize = 32;
12
13/// Marker type for buffers owned by user code.
14#[derive(Debug)]
15pub struct Owned;
16
17/// Marker type for buffers currently owned by the backend.
18#[derive(Debug)]
19pub struct Submitted;
20
21/// Marker type for buffers returned from the backend with data available.
22#[derive(Debug)]
23pub struct Completed;
24
25/// Marker type for buffers part of a special pool.
26#[derive(Debug)]
27pub struct Pool;
28
29#[derive(Debug)]
30pub(crate) struct PooledBuffer {
31    pub(crate) data: Vec<u8>,
32    pub(crate) reusable: bool,
33}
34
35impl PooledBuffer {
36    pub(crate) fn new(data: Vec<u8>, reusable: bool) -> Self {
37        Self { data, reusable }
38    }
39
40    pub(crate) fn capacity(&self) -> usize {
41        self.data.capacity()
42    }
43
44    pub(crate) fn zero_contents(&mut self) {
45        self.data.fill(0);
46    }
47
48    pub(crate) fn deallocate(self) {
49        drop(self.data);
50    }
51}
52
53#[cfg(test)]
54mod tests;
55
56pub(crate) fn allocate_reusable_buffer(capacity: usize) -> Result<PooledBuffer> {
57    if capacity > 1024 * 1024 * 1024 {
58        return Err(Error::validation(
59            "requested buffer capacity is too large",
60            "request a buffer size less than 1 GiB",
61        ));
62    }
63    // For simplicity and safety, we use a standard Vec.
64    // If strict 4096 alignment is required for O_DIRECT,
65    // we would need a safe aligned allocation library.
66    // For now, we prioritize the "No Unsafe" law.
67    let mut data = vec![0; capacity];
68    data.shrink_to_fit();
69    Ok(PooledBuffer::new(data, true))
70}
71
72pub(crate) fn allocate_overflow_buffer(capacity: usize) -> PooledBuffer {
73    let mut data = vec![0; capacity];
74    data.shrink_to_fit();
75    PooledBuffer::new(data, false)
76}
77
78/// A buffer with type-state tracking ownership.
79#[derive(Debug)]
80pub struct Buffer<State> {
81    pool: std::sync::Arc<pool::BufferPoolInner>,
82    data: Option<PooledBuffer>,
83    filled: usize,
84    state: PhantomData<State>,
85}
86
87impl<State> Buffer<State> {
88    pub(crate) fn new(
89        pool: std::sync::Arc<pool::BufferPoolInner>,
90        data: PooledBuffer,
91        filled: usize,
92    ) -> Self {
93        Self {
94            pool,
95            data: Some(data),
96            filled,
97            state: PhantomData,
98        }
99    }
100
101    fn data_ref(&self) -> &[u8] {
102        self.data.as_ref().map_or(&[], |data| data.data.as_slice())
103    }
104
105    fn data_mut(&mut self) -> &mut [u8] {
106        self.data
107            .as_mut()
108            .map_or(&mut [], |data| data.data.as_mut_slice())
109    }
110
111    /// Returns the total capacity.
112    #[must_use]
113    pub fn capacity(&self) -> usize {
114        self.data.as_ref().map_or(0, PooledBuffer::capacity)
115    }
116
117    /// Returns the number of initialized bytes.
118    #[must_use]
119    pub fn filled_len(&self) -> usize {
120        self.filled
121    }
122}
123
124impl Buffer<Owned> {
125    /// Returns the full immutable slice.
126    #[must_use]
127    pub fn as_slice(&self) -> &[u8] {
128        self.data_ref()
129    }
130
131    /// Returns the full mutable slice for writing.
132    pub fn as_mut_slice(&mut self) -> &mut [u8] {
133        self.data_mut()
134    }
135
136    /// Marks bytes as initialized.
137    pub fn set_filled_len(mut self, len: usize) -> Result<Self> {
138        if len > self.capacity() {
139            return Err(Error::validation(
140                "filled length exceeds buffer capacity",
141                "ensure data written does not exceed buffer size",
142            ));
143        }
144        self.filled = len;
145        Ok(self)
146    }
147
148    /// Transitions the buffer into submitted state.
149    #[must_use]
150    pub fn into_submitted(mut self) -> Buffer<Submitted> {
151        Buffer {
152            pool: self.pool.clone(),
153            data: self.data.take(),
154            filled: self.filled,
155            state: PhantomData,
156        }
157    }
158}
159
160impl Buffer<Submitted> {
161    /// Creates a submitted buffer from caller-owned memory.
162    ///
163    /// This bypasses the buffer pool entirely, useful for operations like `Write`
164    /// where the user provides an arbitrary vector.
165    #[allow(clippy::needless_pass_by_value)]
166    pub fn from_vec(data: Vec<u8>) -> Result<Self> {
167        if data.is_empty() {
168            return Err(Error::validation(
169                "submitted buffer must be non-empty",
170                "allocate at least one byte before submitting a read buffer",
171            ));
172        }
173        let capacity = data.len();
174        let pool = std::sync::Arc::new(pool::BufferPoolInner {
175            capacity,
176            max_buffers: 1,
177            overflow_on_exhaustion: false,
178            free: crossbeam_queue::ArrayQueue::new(1),
179        });
180        Ok(Buffer::new(pool, PooledBuffer::new(data, false), 0))
181    }
182
183    /// Provides a mutable view for backend code.
184    #[doc(hidden)]
185    pub fn backend_mut(&mut self) -> &mut [u8] {
186        self.data_mut()
187    }
188
189    /// Provides an immutable view for backend code.
190    #[doc(hidden)]
191    #[must_use]
192    pub fn backend_ref(&self) -> &[u8] {
193        self.data_ref()
194    }
195
196    /// Transitions the buffer into completed state.
197    pub fn into_completed(mut self, filled: usize) -> Result<Buffer<Completed>> {
198        if filled > self.capacity() {
199            return Err(Error::validation(
200                "completed length exceeds buffer capacity",
201                "return at most the number of bytes the buffer can hold",
202            ));
203        }
204        Ok(Buffer {
205            pool: self.pool.clone(),
206            data: self.data.take(),
207            filled,
208            state: PhantomData,
209        })
210    }
211}
212
213impl Buffer<Pool> {
214    /// Provides a mutable view for backend code.
215    #[doc(hidden)]
216    pub fn as_mut_slice(&mut self) -> &mut [u8] {
217        self.data_mut()
218    }
219
220    /// Transitions the buffer into completed state.
221    pub fn into_completed(mut self, filled: usize) -> Result<Buffer<Completed>> {
222        if filled > self.capacity() {
223            return Err(Error::validation(
224                "completed length exceeds buffer capacity",
225                "return at most the number of bytes the buffer can hold",
226            ));
227        }
228        Ok(Buffer {
229            pool: self.pool.clone(),
230            data: self.data.take(),
231            filled,
232            state: PhantomData,
233        })
234    }
235}
236
237impl Buffer<Completed> {
238    /// Returns the slice of initialized data.
239    #[must_use]
240    pub fn filled(&self) -> &[u8] {
241        &self.data_ref()[..self.filled]
242    }
243
244    /// Truncates the filled portion to `len` bytes.
245    pub fn truncate(mut self, len: usize) -> Result<Self> {
246        if len > self.filled {
247            return Err(Error::validation(
248                "truncate length exceeds currently filled bytes",
249                "truncate to a length less than or equal to filled_len()",
250            ));
251        }
252        self.filled = len;
253        Ok(self)
254    }
255
256    /// Releases the buffer back to the user code explicitly resetting the filled length.
257    #[must_use]
258    pub fn release(mut self) -> Buffer<Owned> {
259        let data = self
260            .data
261            .take()
262            .unwrap_or_else(|| allocate_overflow_buffer(0));
263        Buffer::new(self.pool.clone(), data, 0)
264    }
265}
266
267impl<State> Drop for Buffer<State> {
268    fn drop(&mut self) {
269        if let Some(mut data) = self.data.take() {
270            data.zero_contents();
271            if data.reusable {
272                if let Err(data) = self.pool.free.push(data) {
273                    data.deallocate();
274                }
275            } else {
276                data.deallocate();
277            }
278        }
279    }
280}