Skip to main content

compio_io/framed/
frame.rs

1//! Traits and implementations for frame extraction and enclosing
2
3use std::io;
4
5use compio_buf::{IoBuf, IoBufExt, IoBufMut, IoBufMutExt, SetLenExt, Slice};
6
7/// An extracted frame
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub struct Frame {
10    /// Offset where the frame payload begins
11    prefix: usize,
12
13    /// Length of the frame payload
14    payload: usize,
15
16    /// Suffix length of the frame
17    suffix: usize,
18}
19
20impl Frame {
21    /// Create a new [`Frame`] with the specified prefix, payload, and suffix
22    /// lengths.
23    pub fn new(prefix: usize, payload: usize, suffix: usize) -> Self {
24        Self {
25            prefix,
26            payload,
27            suffix,
28        }
29    }
30
31    /// Length of the entire frame
32    pub fn len(&self) -> usize {
33        self.prefix + self.payload + self.suffix
34    }
35
36    /// If the frame is empty
37    pub fn is_empty(&self) -> bool {
38        self.len() == 0
39    }
40
41    /// Slice payload out of the buffer
42    pub fn slice<B: IoBuf>(&self, buf: B) -> Slice<B> {
43        buf.slice(self.prefix..self.prefix + self.payload)
44    }
45}
46
47/// Enclosing and extracting frames in a buffer.
48pub trait Framer<B: IoBufMut> {
49    /// Enclose a frame in the given buffer.
50    ///
51    /// All initialized bytes in `buf` (`buf[0..buf.buf_len()]`) are valid and
52    /// required to be enclosed. All modifications should happen in-place; one
53    /// can use [`IoBufMut::reserve`], [`IoBufMutExt::copy_within`] or a
54    /// temporary buffer if prepending data is necessary.
55    ///
56    /// [`slice::copy_within`]: https://doc.rust-lang.org/std/primitive.slice.html#method.copy_within
57    fn enclose(&mut self, buf: &mut B);
58
59    /// Extract a frame from the given buffer.
60    ///
61    /// # Returns
62    /// - `Ok(Some(frame))` if a complete frame is found.
63    /// - `Ok(None)` if no complete frame is found.
64    /// - `Err(io::Error)` if an error occurs during extraction.
65    fn extract(&mut self, buf: &Slice<B>) -> io::Result<Option<Frame>>;
66}
67
68/// A simple extractor that frames data by its length.
69///
70/// It uses 8 bytes to represent the length of the data at the beginning.
71#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
72pub struct LengthDelimited {
73    length_field_len: usize,
74    length_field_is_big_endian: bool,
75}
76
77impl Default for LengthDelimited {
78    fn default() -> Self {
79        Self {
80            length_field_len: 4,
81            length_field_is_big_endian: true,
82        }
83    }
84}
85
86impl LengthDelimited {
87    /// Max allowed length of `len` field
88    const MAX_LFL: usize = 8;
89
90    /// Creates a new `LengthDelimited` framer.
91    pub fn new() -> Self {
92        Self::default()
93    }
94
95    /// Returns the length of the length field in bytes.
96    pub fn length_field_len(&self) -> usize {
97        self.length_field_len
98    }
99
100    /// Sets the length of the length field in bytes.
101    ///
102    /// # Panics
103    ///
104    /// This will panic if `len_field_len` is too long (8 bytes is the maximum
105    /// allowed for now).
106    pub fn set_length_field_len(mut self, len_field_len: usize) -> Self {
107        assert!(
108            len_field_len <= Self::MAX_LFL,
109            "Length field cannot take over 8 bytes"
110        );
111        self.length_field_len = len_field_len;
112        self
113    }
114
115    /// Returns whether the length field is big-endian.
116    pub fn length_field_is_big_endian(&self) -> bool {
117        self.length_field_is_big_endian
118    }
119
120    /// Sets whether the length field is big-endian.
121    pub fn set_length_field_is_big_endian(mut self, big_endian: bool) -> Self {
122        self.length_field_is_big_endian = big_endian;
123        self
124    }
125}
126
127impl<B: IoBufMut> Framer<B> for LengthDelimited {
128    fn enclose(&mut self, buf: &mut B) {
129        let len = (*buf).buf_len();
130        let lfl = self.length_field_len;
131
132        buf.reserve(lfl).expect("Reserve failed");
133        // SAFETY: The source range `0..len` is fully initialized.
134        unsafe { buf.copy_within(0..len, lfl) }; // Shift existing data
135
136        // Write the length at the beginning
137        let len_u64 = len as u64;
138        let len_bytes = if self.length_field_is_big_endian {
139            &len_u64.to_be_bytes()[Self::MAX_LFL - lfl..]
140        } else {
141            &len_u64.to_le_bytes()[..lfl]
142        };
143        // SAFETY: Only the reserved head `0..lfl` is written.
144        (unsafe { buf.as_uninit() })[..lfl].write_copy_of_slice(len_bytes);
145
146        // SAFETY: Both the header and the shifted data are initialized now.
147        unsafe { buf.advance_to(len + lfl) };
148    }
149
150    fn extract(&mut self, buf: &Slice<B>) -> io::Result<Option<Frame>> {
151        if buf.len() < self.length_field_len {
152            return Ok(None);
153        }
154
155        let buf = buf.as_init();
156        let lfl = self.length_field_len;
157        let mut len_bytes = [0; Self::MAX_LFL];
158
159        let len = if self.length_field_is_big_endian {
160            len_bytes[Self::MAX_LFL - lfl..].copy_from_slice(&buf[..lfl]);
161            u64::from_be_bytes(len_bytes)
162        } else {
163            len_bytes[..lfl].copy_from_slice(&buf[..lfl]);
164            u64::from_le_bytes(len_bytes)
165        } as usize;
166
167        if buf.len() < self.length_field_len + len {
168            return Ok(None);
169        }
170
171        Ok(Some(Frame::new(self.length_field_len, len, 0)))
172    }
173}
174
175/// A generic delimiter that uses a single character encoded as UTF-8.
176///
177/// If you need to use a multi-byte delimiter or other encodings, consider using
178/// [`AnyDelimited`].
179#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
180pub struct CharDelimited<const C: char> {
181    char_buf: [u8; 4],
182}
183
184impl<const C: char> CharDelimited<C> {
185    /// Creates a new `CharDelimited`
186    pub fn new() -> Self {
187        Self { char_buf: [0; 4] }
188    }
189
190    fn as_any_delimited(&mut self) -> AnyDelimited<'_> {
191        let bytes = C.encode_utf8(&mut self.char_buf).as_bytes();
192
193        AnyDelimited::new(bytes)
194    }
195}
196
197impl<B: IoBufMut, const C: char> Framer<B> for CharDelimited<C> {
198    fn enclose(&mut self, buf: &mut B) {
199        self.as_any_delimited().enclose(buf);
200    }
201
202    fn extract(&mut self, buf: &Slice<B>) -> io::Result<Option<Frame>> {
203        self.as_any_delimited().extract(buf)
204    }
205}
206
207/// A generic delimiter that uses any sequence of bytes as a delimiter.
208#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
209pub struct AnyDelimited<'a> {
210    bytes: &'a [u8],
211}
212
213impl<'a> AnyDelimited<'a> {
214    /// Creates a new `AnyDelimited` with the specified delimiter bytes.
215    pub fn new(bytes: &'a [u8]) -> Self {
216        Self { bytes }
217    }
218}
219
220impl<B: IoBufMut> Framer<B> for AnyDelimited<'_> {
221    fn extract(&mut self, buf: &Slice<B>) -> io::Result<Option<Frame>> {
222        if buf.is_empty() {
223            return Ok(None);
224        }
225
226        // Search for the first occurrence of any byte in `self.bytes`
227        // TODO(George-Miao): Optimize with memchr if performance is a concern
228        if let Some(pos) = buf
229            .windows(self.bytes.len())
230            .position(|window| window == self.bytes)
231        {
232            Ok(Some(Frame::new(0, pos, self.bytes.len())))
233        } else {
234            Ok(None)
235        }
236    }
237
238    fn enclose(&mut self, buf: &mut B) {
239        buf.extend_from_slice(self.bytes)
240            .expect("Failed to append delimiter");
241    }
242}
243
244/// Delimiter that uses newline characters (`\n`) as delimiters.
245pub type LineDelimited = CharDelimited<'\n'>;
246
247/// A framer that does nothing.
248///
249/// It simply reserves space in the buffer without adding any framing.
250#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
251pub struct NoopFramer {
252    max_size: usize,
253}
254
255impl Default for NoopFramer {
256    fn default() -> Self {
257        Self { max_size: 4096 }
258    }
259}
260
261impl NoopFramer {
262    /// Creates a new `NoopFramer` framer.
263    pub fn new() -> Self {
264        Self::default()
265    }
266
267    /// Returns the size of the capacity.
268    pub fn max_size(&self) -> usize {
269        self.max_size
270    }
271}
272
273impl<B: IoBufMut> Framer<B> for NoopFramer {
274    fn enclose(&mut self, _: &mut B) {}
275
276    fn extract(&mut self, buf: &Slice<B>) -> io::Result<Option<Frame>> {
277        if buf.is_empty() {
278            return Ok(None);
279        }
280
281        let len = if buf.len() < self.max_size {
282            buf.len()
283        } else {
284            self.max_size
285        };
286
287        Ok(Some(Frame::new(0, len, 0)))
288    }
289}
290
291#[cfg(test)]
292mod tests {
293    use compio_buf::IntoInner;
294
295    use super::*;
296
297    #[test]
298    fn test_length_delimited() {
299        let mut framer = LengthDelimited::new();
300
301        let mut buf = Vec::from(b"hello");
302        framer.enclose(&mut buf);
303        assert_eq!(&buf.as_slice()[..9], b"\x00\x00\x00\x05hello");
304
305        let buf = buf.slice(..);
306        let frame = framer.extract(&buf).unwrap().unwrap();
307        let buf = buf.into_inner();
308        assert_eq!(frame, Frame::new(4, 5, 0));
309        let payload = frame.slice(buf);
310        assert_eq!(payload.as_init(), b"hello");
311    }
312
313    #[test]
314    fn test_noop_framer() {
315        let mut framer = NoopFramer::new();
316
317        let mut buf = Vec::from(b"hello");
318        framer.enclose(&mut buf);
319        assert_eq!(&buf.as_slice()[..5], b"hello");
320
321        let buf = buf.slice(..);
322        let frame = framer.extract(&buf).unwrap().unwrap();
323        let buf = buf.into_inner();
324        assert_eq!(frame, Frame::new(0, 5, 0));
325        let payload = frame.slice(buf);
326        assert_eq!(payload.as_init(), b"hello");
327    }
328
329    #[test]
330    fn test_char_delimited() {
331        let mut framer = CharDelimited::<'ℝ'>::new();
332
333        let mut buf = Vec::new();
334        IoBufMutExt::extend_from_slice(&mut buf, b"hello").unwrap();
335        framer.enclose(&mut buf);
336        assert_eq!(buf.as_slice(), "helloℝ".as_init());
337
338        let buf = buf.slice(..);
339        let frame = framer.extract(&buf).unwrap().unwrap();
340        assert_eq!(frame, Frame::new(0, 5, 3));
341        let payload = frame.slice(buf);
342        assert_eq!(payload.as_init(), b"hello");
343    }
344}