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::{
6    IoBuf, IoBufMut, Slice,
7    bytes::{Buf, BufMut},
8};
9
10/// An extracted frame
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub struct Frame {
13    /// Offset where the frame payload begins
14    prefix: usize,
15
16    /// Length of the frame payload
17    payload: usize,
18
19    /// Suffix length of the frame
20    suffix: usize,
21}
22
23impl Frame {
24    /// Create a new [`Frame`] with the specified prefix, payload, and suffix
25    /// lengths.
26    pub fn new(prefix: usize, payload: usize, suffix: usize) -> Self {
27        Self {
28            prefix,
29            payload,
30            suffix,
31        }
32    }
33
34    /// Length of the entire frame
35    pub fn len(&self) -> usize {
36        self.prefix + self.payload + self.suffix
37    }
38
39    /// If the frame is empty
40    pub fn is_empty(&self) -> bool {
41        self.len() == 0
42    }
43
44    /// Slice payload out of the buffer
45    pub fn slice<B: IoBuf>(&self, buf: B) -> Slice<B> {
46        buf.slice(self.prefix..self.prefix + self.payload)
47    }
48}
49
50/// Enclosing and extracting frames in a buffer.
51pub trait Framer<B: IoBufMut> {
52    /// Enclose a frame in the given buffer.
53    ///
54    /// All initialized bytes in `buf` (`buf[0..buf.buf_len()]`) are valid and
55    /// required to be enclosed. All modifications should happen in-place; one
56    /// can use [`IoBufMut::reserve`], [`IoBufMut::copy_within`] or a temporary
57    /// buffer if prepending data is necessary.
58    ///
59    /// [`slice::copy_within`]: https://doc.rust-lang.org/std/primitive.slice.html#method.copy_within
60    fn enclose(&mut self, buf: &mut B);
61
62    /// Extract a frame from the given buffer.
63    ///
64    /// # Returns
65    /// - `Ok(Some(frame))` if a complete frame is found.
66    /// - `Ok(None)` if no complete frame is found.
67    /// - `Err(io::Error)` if an error occurs during extraction.
68    fn extract(&mut self, buf: &Slice<B>) -> io::Result<Option<Frame>>;
69}
70
71/// A simple extractor that frames data by its length.
72///
73/// It uses 8 bytes to represent the length of the data at the beginning.
74#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
75pub struct LengthDelimited {
76    length_field_len: usize,
77    length_field_is_big_endian: bool,
78}
79
80impl Default for LengthDelimited {
81    fn default() -> Self {
82        Self {
83            length_field_len: 4,
84            length_field_is_big_endian: true,
85        }
86    }
87}
88
89impl LengthDelimited {
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    pub fn set_length_field_len(mut self, len_field_len: usize) -> Self {
102        self.length_field_len = len_field_len;
103        self
104    }
105
106    /// Returns whether the length field is big-endian.
107    pub fn length_field_is_big_endian(&self) -> bool {
108        self.length_field_is_big_endian
109    }
110
111    /// Sets whether the length field is big-endian.
112    pub fn set_length_field_is_big_endian(mut self, big_endian: bool) -> Self {
113        self.length_field_is_big_endian = big_endian;
114        self
115    }
116}
117
118impl<B: IoBufMut> Framer<B> for LengthDelimited {
119    fn enclose(&mut self, buf: &mut B) {
120        let len = (*buf).buf_len();
121
122        buf.reserve(self.length_field_len).expect("Reserve failed");
123        buf.copy_within(0..len, self.length_field_len); // Shift existing data
124        unsafe { buf.advance_to(len + self.length_field_len) };
125
126        let slice = buf.as_mut_slice();
127
128        // Write the length at the beginning
129        if self.length_field_is_big_endian {
130            (&mut slice[0..self.length_field_len]).put_uint(len as _, self.length_field_len);
131        } else {
132            (&mut slice[0..self.length_field_len]).put_uint_le(len as _, self.length_field_len);
133        }
134    }
135
136    fn extract(&mut self, buf: &Slice<B>) -> io::Result<Option<Frame>> {
137        if buf.len() < self.length_field_len {
138            return Ok(None);
139        }
140
141        let mut buf = buf.as_init();
142
143        let len = if self.length_field_is_big_endian {
144            buf.get_uint(self.length_field_len)
145        } else {
146            buf.get_uint_le(self.length_field_len)
147        } as usize;
148
149        if buf.len() < len {
150            return Ok(None);
151        }
152
153        Ok(Some(Frame::new(self.length_field_len, len, 0)))
154    }
155}
156
157/// A generic delimiter that uses a single character encoded as UTF-8.
158///
159/// If you need to use a multi-byte delimiter or other encodings, consider using
160/// [`AnyDelimited`].
161#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
162pub struct CharDelimited<const C: char> {
163    char_buf: [u8; 4],
164}
165
166impl<const C: char> CharDelimited<C> {
167    /// Creates a new `CharDelimited`
168    pub fn new() -> Self {
169        Self { char_buf: [0; 4] }
170    }
171
172    fn as_any_delimited(&mut self) -> AnyDelimited<'_> {
173        let bytes = C.encode_utf8(&mut self.char_buf).as_bytes();
174
175        AnyDelimited::new(bytes)
176    }
177}
178
179impl<B: IoBufMut, const C: char> Framer<B> for CharDelimited<C> {
180    fn enclose(&mut self, buf: &mut B) {
181        self.as_any_delimited().enclose(buf);
182    }
183
184    fn extract(&mut self, buf: &Slice<B>) -> io::Result<Option<Frame>> {
185        self.as_any_delimited().extract(buf)
186    }
187}
188
189/// A generic delimiter that uses any sequence of bytes as a delimiter.
190#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
191pub struct AnyDelimited<'a> {
192    bytes: &'a [u8],
193}
194
195impl<'a> AnyDelimited<'a> {
196    /// Creates a new `AnyDelimited` with the specified delimiter bytes.
197    pub fn new(bytes: &'a [u8]) -> Self {
198        Self { bytes }
199    }
200}
201
202impl<B: IoBufMut> Framer<B> for AnyDelimited<'_> {
203    fn extract(&mut self, buf: &Slice<B>) -> io::Result<Option<Frame>> {
204        if buf.is_empty() {
205            return Ok(None);
206        }
207
208        // Search for the first occurrence of any byte in `self.bytes`
209        // TODO(George-Miao): Optimize with memchr if performance is a concern
210        if let Some(pos) = buf
211            .windows(self.bytes.len())
212            .position(|window| window == self.bytes)
213        {
214            Ok(Some(Frame::new(0, pos, self.bytes.len())))
215        } else {
216            Ok(None)
217        }
218    }
219
220    fn enclose(&mut self, buf: &mut B) {
221        buf.extend_from_slice(self.bytes)
222            .expect("Failed to append delimiter");
223    }
224}
225
226/// Delimiter that uses newline characters (`\n`) as delimiters.
227pub type LineDelimited = CharDelimited<'\n'>;
228
229#[cfg(test)]
230mod tests {
231    use compio_buf::{IntoInner, IoBufMut};
232
233    use super::*;
234
235    #[test]
236    fn test_length_delimited() {
237        let mut framer = LengthDelimited::new();
238
239        let mut buf = Vec::from(b"hello");
240        framer.enclose(&mut buf);
241        assert_eq!(&buf.as_slice()[..9], b"\x00\x00\x00\x05hello");
242
243        let buf = buf.slice(..);
244        let frame = framer.extract(&buf).unwrap().unwrap();
245        let buf = buf.into_inner();
246        assert_eq!(frame, Frame::new(4, 5, 0));
247        let payload = frame.slice(buf);
248        assert_eq!(payload.as_init(), b"hello");
249    }
250
251    #[test]
252    fn test_char_delimited() {
253        let mut framer = CharDelimited::<'ℝ'>::new();
254
255        let mut buf = Vec::new();
256        IoBufMut::extend_from_slice(&mut buf, b"hello").unwrap();
257        framer.enclose(&mut buf);
258        assert_eq!(buf.as_slice(), "helloℝ".as_init());
259
260        let buf = buf.slice(..);
261        let frame = framer.extract(&buf).unwrap().unwrap();
262        assert_eq!(frame, Frame::new(0, 5, 3));
263        let payload = frame.slice(buf);
264        assert_eq!(payload.as_init(), b"hello");
265    }
266}