Skip to main content

monocoque_core/
buffer.rs

1use bytes::{Buf, Bytes, BytesMut};
2use std::collections::VecDeque;
3
4/// A segmented buffer optimized for zero-copy operations.
5///
6/// This buffer holds multiple `Bytes` segments and provides efficient
7/// extraction without unnecessary copies. When data is requested and
8/// fits within a single segment, it can be returned with zero copies
9/// (just a refcount increment on the underlying `Bytes`).
10///
11/// # Use Cases
12///
13/// - Protocol decoders reading frames from streaming data
14/// - Minimizing memcpy overhead for small messages (< 8KB)
15/// - Preserving zero-copy buffer segments through the pipeline
16///
17/// # Tradeoffs
18///
19/// - **Fast path**: Single-segment extraction is O(1) with no copy
20/// - **Slow path**: Multi-segment extraction requires copying into contiguous buffer
21/// - For large messages spanning many reads, the copy cost is unavoidable
22#[derive(Debug, Default)]
23pub struct SegmentedBuffer {
24    segs: VecDeque<Bytes>,
25    len: usize,
26}
27
28impl SegmentedBuffer {
29    #[must_use]
30    pub const fn new() -> Self {
31        Self {
32            segs: VecDeque::new(),
33            len: 0,
34        }
35    }
36
37    #[inline]
38    #[must_use]
39    pub const fn len(&self) -> usize {
40        self.len
41    }
42
43    #[inline]
44    #[must_use]
45    pub const fn is_empty(&self) -> bool {
46        self.len == 0
47    }
48
49    /// Return the first contiguous chunk without consuming it.
50    #[inline]
51    #[must_use]
52    pub fn front_chunk(&self) -> &[u8] {
53        self.segs.front().map_or(&[], Bytes::as_ref)
54    }
55
56    #[inline]
57    pub fn push(&mut self, bytes: Bytes) {
58        if bytes.is_empty() {
59            return;
60        }
61        self.len += bytes.len();
62        self.segs.push_back(bytes);
63    }
64
65    /// Copy the first `n` bytes into `dst`.
66    ///
67    /// Returns `false` if fewer than `n` bytes are available.
68    pub fn copy_prefix(&self, n: usize, dst: &mut [u8]) -> bool {
69        if n > self.len || dst.len() < n {
70            return false;
71        }
72
73        let mut remaining = n;
74        let mut out_off = 0;
75        for seg in &self.segs {
76            if remaining == 0 {
77                break;
78            }
79            let take = remaining.min(seg.len());
80            dst[out_off..out_off + take].copy_from_slice(&seg[..take]);
81            out_off += take;
82            remaining -= take;
83        }
84        true
85    }
86
87    /// Advance the queue by `n` bytes, dropping fully-consumed segments.
88    ///
89    /// # Panics
90    ///
91    /// Panics if `n > self.len`.
92    pub fn advance(&mut self, mut n: usize) {
93        assert!(n <= self.len);
94        self.len -= n;
95
96        while n > 0 {
97            let Some(front) = self.segs.front_mut() else {
98                break;
99            };
100            if n < front.len() {
101                front.advance(n);
102                break;
103            }
104
105            n -= front.len();
106            self.segs.pop_front();
107        }
108    }
109
110    /// Take exactly `n` bytes from the front of the queue.
111    ///
112    /// If the first segment contains all `n` bytes, this is zero-copy.
113    /// If the bytes span multiple segments, this copies into a contiguous buffer.
114    pub fn take_bytes(&mut self, n: usize) -> Option<Bytes> {
115        if n == 0 {
116            return Some(Bytes::new());
117        }
118        if n > self.len {
119            return None;
120        }
121
122        let Some(front) = self.segs.front_mut() else {
123            return None;
124        };
125
126        if front.len() >= n {
127            self.len -= n;
128            if front.len() == n {
129                let out = self.segs.pop_front();
130                return out;
131            }
132
133            let out = front.slice(..n);
134            front.advance(n);
135            if front.is_empty() {
136                self.segs.pop_front();
137            }
138            return Some(out);
139        }
140
141        // Spans segments: copy.
142        let mut out = BytesMut::with_capacity(n);
143        let mut remaining = n;
144        while remaining > 0 {
145            let seg = self
146                .segs
147                .pop_front()
148                .expect("len check ensures segments exist");
149            let take = remaining.min(seg.len());
150            out.extend_from_slice(&seg[..take]);
151            remaining -= take;
152            self.len -= take;
153            if take < seg.len() {
154                let mut rest = seg;
155                rest.advance(take);
156                self.segs.push_front(rest);
157            }
158        }
159
160        Some(out.freeze())
161    }
162
163    /// Skip `skip` bytes, then take `n` bytes from the front of the queue.
164    ///
165    /// When the whole range sits in the first segment, this avoids the
166    /// `advance(skip)` pop/push round trip before extracting the payload.
167    #[inline]
168    pub fn take_bytes_after(&mut self, skip: usize, n: usize) -> Option<Bytes> {
169        let total = skip.checked_add(n)?;
170        if total > self.len {
171            return None;
172        }
173        Some(self.take_bytes_after_available(skip, n))
174    }
175
176    /// Skip `skip` bytes, then take `n` bytes when the caller has checked availability.
177    ///
178    /// # Panics
179    ///
180    /// Panics if `skip + n` overflows or exceeds the buffered length.
181    #[inline]
182    pub fn take_bytes_after_available(&mut self, skip: usize, n: usize) -> Bytes {
183        let total = skip.checked_add(n).expect("skip + n must not overflow");
184        assert!(total <= self.len);
185        if n == 0 {
186            self.advance(skip);
187            return Bytes::new();
188        }
189
190        let front = self.segs.front_mut().expect("non-empty buffer");
191        if front.len() >= total {
192            self.len -= total;
193            front.advance(skip);
194            let out = front.split_to(n);
195            if front.is_empty() {
196                self.segs.pop_front();
197            }
198            return out;
199        }
200
201        self.advance(skip);
202        self.take_bytes(n)
203            .expect("len check ensures requested bytes are available")
204    }
205}
206
207#[cfg(test)]
208mod tests {
209    use super::*;
210    use bytes::Bytes;
211
212    #[test]
213    fn take_bytes_after_slices_within_front_segment() {
214        let mut buf = SegmentedBuffer::new();
215        buf.push(Bytes::from_static(b"hhpayloadtail"));
216
217        let payload = buf.take_bytes_after(2, 7).unwrap();
218
219        assert_eq!(&payload[..], b"payload");
220        assert_eq!(buf.len(), 4);
221        assert_eq!(&buf.take_bytes(4).unwrap()[..], b"tail");
222    }
223
224    #[test]
225    fn take_bytes_after_falls_back_across_segments() {
226        let mut buf = SegmentedBuffer::new();
227        buf.push(Bytes::from_static(b"hhpay"));
228        buf.push(Bytes::from_static(b"loadtail"));
229
230        let payload = buf.take_bytes_after(2, 7).unwrap();
231
232        assert_eq!(&payload[..], b"payload");
233        assert_eq!(buf.len(), 4);
234        assert_eq!(&buf.take_bytes(4).unwrap()[..], b"tail");
235    }
236
237    #[test]
238    fn take_bytes_slices_single_segment_without_losing_remainder() {
239        let mut buf = SegmentedBuffer::new();
240        buf.push(Bytes::from_static(b"abcdef"));
241
242        let out = buf.take_bytes(2).unwrap();
243        assert_eq!(&out[..], b"ab");
244        assert_eq!(buf.len(), 4);
245
246        let rest = buf.take_bytes(4).unwrap();
247        assert_eq!(&rest[..], b"cdef");
248        assert!(buf.is_empty());
249    }
250
251    #[test]
252    fn take_bytes_removes_exact_single_segment() {
253        let mut buf = SegmentedBuffer::new();
254        buf.push(Bytes::from_static(b"abc"));
255
256        let out = buf.take_bytes(3).unwrap();
257        assert_eq!(&out[..], b"abc");
258        assert!(buf.is_empty());
259    }
260
261    #[test]
262    fn take_bytes_copies_across_segments_and_preserves_tail() {
263        let mut buf = SegmentedBuffer::new();
264        buf.push(Bytes::from_static(b"abc"));
265        buf.push(Bytes::from_static(b"def"));
266
267        let out = buf.take_bytes(4).unwrap();
268        assert_eq!(&out[..], b"abcd");
269        assert_eq!(buf.len(), 2);
270
271        let tail = buf.take_bytes(2).unwrap();
272        assert_eq!(&tail[..], b"ef");
273        assert!(buf.is_empty());
274    }
275}