compio_io/framed/
frame.rs

1//! Traits and implementations for frame extraction and enclosing
2
3use compio_buf::{
4    IoBufMut,
5    bytes::{Buf, BufMut},
6};
7
8/// An extracted frame
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub struct Frame {
11    /// Offset where the frame payload begins
12    prefix: usize,
13
14    /// Length of the frame payload
15    payload: usize,
16
17    /// Suffix length of the frame
18    suffix: usize,
19}
20
21impl Frame {
22    /// Create a new [`Frame`] with the specified prefix, payload, and suffix
23    /// lengths.
24    pub fn new(prefix: usize, payload: usize, suffix: usize) -> Self {
25        Self {
26            prefix,
27            payload,
28            suffix,
29        }
30    }
31
32    /// Length of the entire frame
33    pub fn len(&self) -> usize {
34        self.prefix + self.payload + self.suffix
35    }
36
37    /// If the frame is empty
38    pub fn is_empty(&self) -> bool {
39        self.len() == 0
40    }
41
42    /// Slice payload out of the buffer
43    pub fn payload<'a>(&self, buf: &'a [u8]) -> &'a [u8] {
44        &buf[self.prefix..self.prefix + self.payload]
45    }
46}
47
48/// Enclosing and extracting frames in a buffer.
49pub trait Framer {
50    /// Enclose a frame in the given buffer.
51    ///
52    /// All initialized bytes in `buf` (`buf[0..buf.len()]`) are valid and
53    /// required to be enclosed. All modifications should happen in-place; one
54    /// can use [`slice::copy_within`] or a temporary buffer if prepending data
55    /// is necessary.
56    ///
57    /// [`slice::copy_within`]: https://doc.rust-lang.org/std/primitive.slice.html#method.copy_within
58    fn enclose(&mut self, buf: &mut Vec<u8>);
59
60    /// Extract a frame from the given buffer.
61    ///
62    /// # Returns
63    /// - `Ok(Some(frame))` if a complete frame is found.
64    /// - `Ok(None)` if no complete frame is found.
65    /// - `Err(io::Error)` if an error occurs during extraction.
66    fn extract(&mut self, buf: &[u8]) -> Option<Frame>;
67}
68
69/// A simple extractor that frames data by its length.
70///
71/// It uses 8 bytes to represent the length of the data at the beginning.
72#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
73pub struct LengthDelimited {
74    length_field_len: usize,
75    length_field_is_big_endian: bool,
76}
77
78impl Default for LengthDelimited {
79    fn default() -> Self {
80        Self {
81            length_field_len: 4,
82            length_field_is_big_endian: true,
83        }
84    }
85}
86
87impl LengthDelimited {
88    /// Creates a new `LengthDelimited` framer.
89    pub fn new() -> Self {
90        Self::default()
91    }
92
93    /// Returns the length of the length field in bytes.
94    pub fn length_field_len(&self) -> usize {
95        self.length_field_len
96    }
97
98    /// Sets the length of the length field in bytes.
99    pub fn set_length_field_len(mut self, len_field_len: usize) -> Self {
100        self.length_field_len = len_field_len;
101        self
102    }
103
104    /// Returns whether the length field is big-endian.
105    pub fn length_field_is_big_endian(&self) -> bool {
106        self.length_field_is_big_endian
107    }
108
109    /// Sets whether the length field is big-endian.
110    pub fn set_length_field_is_big_endian(mut self, big_endian: bool) -> Self {
111        self.length_field_is_big_endian = big_endian;
112        self
113    }
114}
115
116impl Framer for LengthDelimited {
117    fn enclose(&mut self, buf: &mut Vec<u8>) {
118        let len = buf.len();
119
120        buf.reserve(self.length_field_len);
121        IoBufMut::as_mut_slice(buf).copy_within(0..len, self.length_field_len); // Shift existing data
122        unsafe { buf.set_len(len + self.length_field_len) };
123
124        // Write the length at the beginning
125        if self.length_field_is_big_endian {
126            (&mut buf[0..self.length_field_len]).put_uint(len as _, self.length_field_len);
127        } else {
128            (&mut buf[0..self.length_field_len]).put_uint_le(len as _, self.length_field_len);
129        }
130    }
131
132    fn extract(&mut self, mut buf: &[u8]) -> Option<Frame> {
133        if buf.len() < self.length_field_len {
134            return None;
135        }
136
137        let len = if self.length_field_is_big_endian {
138            buf.get_uint(self.length_field_len)
139        } else {
140            buf.get_uint_le(self.length_field_len)
141        } as usize;
142
143        if buf.len() < len {
144            return None;
145        }
146
147        Some(Frame::new(self.length_field_len, len, 0))
148    }
149}
150
151/// A generic delimiter that uses a single character.
152#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
153pub struct CharDelimited<const C: char> {}
154
155impl<const C: char> CharDelimited<C> {
156    /// Creates a new `CharDelimited`
157    pub fn new() -> Self {
158        Self {}
159    }
160}
161
162impl<const C: char> Framer for CharDelimited<C> {
163    fn enclose(&mut self, buf: &mut Vec<u8>) {
164        buf.push(C as u8);
165    }
166
167    fn extract(&mut self, buf: &[u8]) -> Option<Frame> {
168        if buf.is_empty() {
169            return None;
170        }
171
172        buf.iter()
173            .position(|&b| b == C as u8)
174            .map(|pos| Frame::new(0, pos, 1))
175    }
176}
177
178/// A generic delimiter that uses any sequence of bytes as a delimiter.
179#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
180pub struct AnyDelimited<'a> {
181    bytes: &'a [u8],
182}
183
184impl<'a> AnyDelimited<'a> {
185    /// Creates a new `AnyDelimited` with the specified delimiter bytes.
186    pub fn new(bytes: &'a [u8]) -> Self {
187        Self { bytes }
188    }
189}
190
191impl Framer for AnyDelimited<'_> {
192    fn extract(&mut self, buf: &[u8]) -> Option<Frame> {
193        if buf.is_empty() {
194            return None;
195        }
196
197        // Search for the first occurrence of any byte in `self.bytes`
198        // TODO(George-Miao): Optimize if performance is a concern
199        if let Some(pos) = buf
200            .windows(self.bytes.len())
201            .position(|window| window == self.bytes)
202        {
203            Some(Frame::new(0, pos, self.bytes.len()))
204        } else {
205            None
206        }
207    }
208
209    fn enclose(&mut self, buf: &mut Vec<u8>) {
210        buf.extend_from_slice(self.bytes);
211    }
212}
213
214/// Delimiter that uses newline characters (`\n`) as delimiters.
215pub type LineDelimited = CharDelimited<'\n'>;