Skip to main content

wire_codec/framing/
mod.rs

1//! Frame extraction strategies.
2//!
3//! A [`Framer`] is a stateless protocol that locates and emits one frame at a
4//! time from a borrowed byte slice. Frames are described by [`Frame`], which
5//! carries a zero-copy reference to the payload alongside the count of bytes
6//! the framer consumed from the input.
7//!
8//! Concrete framers shipped with the crate:
9//!
10//! - [`length::LengthPrefixed`] for fixed-width length-prefixed protocols
11//!   (1, 2, 3, 4, or 8 byte prefixes, either byte order, optional length
12//!   adjustment).
13//! - [`varint_prefixed::VarintPrefixed`] for LEB128 length-prefixed
14//!   messages, the "length-delimited" framing used by Protocol Buffers and
15//!   gRPC-style transports.
16//! - [`delimiter::Delimited`] for byte-delimited protocols such as newline-
17//!   terminated text.
18//! - [`checksummed::Checksummed`], a decorator that appends and verifies a
19//!   CRC (or any [`Checksum`][`crate::checksum::Checksum`]) after the frames
20//!   of any other framer.
21//!
22//! Custom framers are implemented by writing a `Framer` impl on a user-defined
23//! type.
24//!
25//! # Untrusted input
26//!
27//! A framer only guards memory if it has a limit. Use the `bounded`
28//! constructors ([`LengthPrefixed::bounded`], [`Delimited::bounded`]) or
29//! `with_max_payload` for any stream that comes from a peer you do not
30//! control. [`VarintPrefixed::new`] is bounded by default.
31//!
32//! When bytes arrive in pieces, drive the framer through a [`StreamDecoder`]
33//! (or [`Framer::next_frame_from`]) so that a delimiter search resumes where
34//! the previous call stopped instead of rescanning the whole buffer. That
35//! keeps the total work linear in the input size even when a peer drips one
36//! byte at a time.
37
38pub mod checksummed;
39pub mod delimiter;
40pub mod length;
41mod search;
42pub mod varint_prefixed;
43
44pub use checksummed::{ChecksumScope, Checksummed};
45pub use delimiter::Delimited;
46pub use length::{Endian, LengthPrefixed, LengthWidth};
47pub use varint_prefixed::VarintPrefixed;
48
49use crate::buf::WriteBuf;
50use crate::error::Result;
51
52/// A frame extracted from an input byte slice.
53///
54/// The `payload` borrow lives as long as the input slice, so framers never
55/// copy data. `consumed` tells the caller how many bytes from the input the
56/// frame occupied in total, including any header or delimiter bytes.
57///
58/// # Example
59///
60/// ```
61/// use wire_codec::framing::Frame;
62///
63/// let input = b"\x03abcrest";
64/// let frame = Frame::new(&input[1..4], 4);
65/// assert_eq!(frame.payload(), b"abc");
66/// assert_eq!(frame.consumed(), 4);
67/// ```
68#[derive(Debug, Clone, Copy, PartialEq, Eq)]
69pub struct Frame<'a> {
70    payload: &'a [u8],
71    consumed: usize,
72}
73
74impl<'a> Frame<'a> {
75    /// Construct a frame from its payload and total consumed-byte count.
76    ///
77    /// `consumed` must be at least `payload.len()` and, for any framer used
78    /// in a `while let Some(frame)` loop, greater than zero: a framer that
79    /// reports zero consumed bytes would hand back the same frame forever.
80    /// Framers in this crate uphold both invariants. The first one is checked
81    /// with a `debug_assert!`.
82    #[inline]
83    #[must_use]
84    pub const fn new(payload: &'a [u8], consumed: usize) -> Self {
85        debug_assert!(
86            consumed >= payload.len(),
87            "Frame::consumed must cover the payload"
88        );
89        Self { payload, consumed }
90    }
91
92    /// Zero-copy borrow of the frame payload.
93    #[inline]
94    #[must_use]
95    pub const fn payload(&self) -> &'a [u8] {
96        self.payload
97    }
98
99    /// Number of bytes the framer consumed from the input to produce this frame.
100    #[inline]
101    #[must_use]
102    pub const fn consumed(&self) -> usize {
103        self.consumed
104    }
105}
106
107/// Strategy that extracts and emits one frame at a time.
108///
109/// Implementations are stateless: each call to [`Framer::next_frame`] is
110/// independent. To process a stream, the caller advances their own read
111/// position by [`Frame::consumed`] after each successful call.
112///
113/// # Example
114///
115/// ```
116/// use wire_codec::framing::{Framer, LengthPrefixed, LengthWidth, Endian};
117///
118/// let framer = LengthPrefixed::unbounded(LengthWidth::U16, Endian::Big);
119/// let input = &[0x00, 0x03, b'h', b'i', b'!'];
120/// let frame = framer.next_frame(input).unwrap().unwrap();
121/// assert_eq!(frame.payload(), b"hi!");
122/// assert_eq!(frame.consumed(), 5);
123/// ```
124pub trait Framer {
125    /// Try to extract a single frame from the start of `input`.
126    ///
127    /// Returns `Ok(Some(frame))` when a complete frame is available,
128    /// `Ok(None)` when more bytes are needed, and `Err(_)` when the input
129    /// violates the protocol.
130    ///
131    /// # Errors
132    ///
133    /// Implementation-defined; see each concrete framer for its error contract.
134    fn next_frame<'a>(&self, input: &'a [u8]) -> Result<Option<Frame<'a>>>;
135
136    /// Serialize `payload` as a single frame into `out`.
137    ///
138    /// The built-in framers are atomic: on error, `out` is left at its
139    /// original position.
140    ///
141    /// # Errors
142    ///
143    /// Implementation-defined; see each concrete framer.
144    fn write_frame(&self, payload: &[u8], out: &mut WriteBuf<'_>) -> Result<()>;
145
146    /// Resumable variant of [`Framer::next_frame`] for streaming input.
147    ///
148    /// `scanned` is a progress hint owned by the caller. Start it at `0`.
149    /// Between calls the caller may only append bytes to `input`; when a call
150    /// returns `Ok(Some(frame))`, the framer resets `scanned` to `0` and the
151    /// caller drops `frame.consumed()` bytes from the front before the next
152    /// call. A framer that searches its input (such as [`Delimited`]) records
153    /// how far it got on `Ok(None)` and continues from there next time, which
154    /// turns a byte-at-a-time stream from quadratic into linear work.
155    ///
156    /// If the buffered input is discarded or replaced (for example after an
157    /// error), set `scanned` back to `0`; a stale hint would make the framer
158    /// skip bytes it has never examined.
159    ///
160    /// The default implementation ignores the hint and calls
161    /// [`Framer::next_frame`], which is optimal for framers that read a
162    /// fixed-position header. [`StreamDecoder`] manages the hint for you.
163    ///
164    /// # Errors
165    ///
166    /// Same contract as [`Framer::next_frame`].
167    ///
168    /// # Example
169    ///
170    /// ```
171    /// use wire_codec::framing::{Delimited, Framer};
172    ///
173    /// let framer = Delimited::bounded(b"\n").unwrap();
174    /// let mut scanned = 0;
175    /// assert_eq!(framer.next_frame_from(b"hel", &mut scanned).unwrap(), None);
176    /// assert_eq!(scanned, 3);
177    /// let frame = framer.next_frame_from(b"hello\n", &mut scanned).unwrap().unwrap();
178    /// assert_eq!(frame.payload(), b"hello");
179    /// assert_eq!(scanned, 0);
180    /// ```
181    #[inline]
182    fn next_frame_from<'a>(
183        &self,
184        input: &'a [u8],
185        scanned: &mut usize,
186    ) -> Result<Option<Frame<'a>>> {
187        let result = self.next_frame(input);
188        if let Ok(Some(_)) = result {
189            *scanned = 0;
190        }
191        result
192    }
193
194    /// Upper bound on the number of bytes [`Framer::write_frame`] writes for
195    /// a payload of `payload_len` bytes.
196    ///
197    /// Returns `None` when the framer cannot say, or when the bound would
198    /// overflow `usize`. Buffer-growing adapters (such as the optional
199    /// `bytes` codec) use it to reserve space in one step. The default
200    /// implementation returns `None`.
201    ///
202    /// # Example
203    ///
204    /// ```
205    /// use wire_codec::framing::{Endian, Framer, LengthPrefixed, LengthWidth};
206    ///
207    /// let framer = LengthPrefixed::unbounded(LengthWidth::U32, Endian::Big);
208    /// assert_eq!(framer.max_frame_len(10), Some(14));
209    /// ```
210    #[inline]
211    fn max_frame_len(&self, payload_len: usize) -> Option<usize> {
212        let _ = payload_len;
213        None
214    }
215}
216
217/// Stateful wrapper that drives a [`Framer`] over a growing input buffer.
218///
219/// It owns the [`Framer::next_frame_from`] progress hint, so a delimiter
220/// search resumes where the previous call stopped. Use it whenever input
221/// arrives in pieces (a socket read loop, a serial port, a message queue).
222///
223/// The input contract is the one described on [`Framer::next_frame_from`]:
224/// between calls, only append to the buffer; after a frame is returned, drop
225/// its `consumed()` bytes from the front.
226///
227/// # Example
228///
229/// ```
230/// use wire_codec::framing::{Delimited, StreamDecoder};
231///
232/// let mut decoder = StreamDecoder::new(Delimited::bounded(b"\n").unwrap());
233/// let mut buffer: Vec<u8> = Vec::new();
234/// let mut lines: Vec<Vec<u8>> = Vec::new();
235///
236/// for chunk in [&b"PI"[..], b"NG\nQU", b"IT\n"] {
237///     buffer.extend_from_slice(chunk);
238///     let mut start = 0;
239///     while let Some(frame) = decoder.decode(&buffer[start..]).unwrap() {
240///         lines.push(frame.payload().to_vec());
241///         start += frame.consumed();
242///     }
243///     buffer.drain(..start);
244/// }
245/// assert_eq!(lines, [b"PING".to_vec(), b"QUIT".to_vec()]);
246/// ```
247#[derive(Debug, Clone, Default, PartialEq, Eq, Hash)]
248pub struct StreamDecoder<F> {
249    framer: F,
250    scanned: usize,
251}
252
253impl<F: Framer> StreamDecoder<F> {
254    /// Wrap `framer` with fresh streaming state.
255    #[inline]
256    #[must_use]
257    pub const fn new(framer: F) -> Self {
258        Self { framer, scanned: 0 }
259    }
260
261    /// Try to extract the next frame from the start of `input`.
262    ///
263    /// # Errors
264    ///
265    /// Propagates the wrapped framer's errors. The scan progress is cleared
266    /// on error, because the usual response (discarding the buffered input
267    /// and resynchronising) breaks the append-only contract the progress
268    /// hint relies on. Call [`StreamDecoder::reset`] yourself whenever you
269    /// discard or replace buffered input without an error.
270    #[inline]
271    pub fn decode<'a>(&mut self, input: &'a [u8]) -> Result<Option<Frame<'a>>> {
272        let result = self.framer.next_frame_from(input, &mut self.scanned);
273        if result.is_err() {
274            self.scanned = 0;
275        }
276        result
277    }
278
279    /// Forget any partial-scan progress, for example after discarding the
280    /// buffered input or switching to a new connection.
281    #[inline]
282    pub fn reset(&mut self) {
283        self.scanned = 0;
284    }
285
286    /// Borrow the wrapped framer, for example to call
287    /// [`Framer::write_frame`] on the outbound path.
288    #[inline]
289    #[must_use]
290    pub const fn framer(&self) -> &F {
291        &self.framer
292    }
293
294    /// Consume the decoder and return the wrapped framer.
295    #[inline]
296    #[must_use]
297    pub fn into_inner(self) -> F {
298        self.framer
299    }
300}
301
302#[cfg(test)]
303mod tests {
304    use super::*;
305    use crate::error::Error;
306
307    /// A framer that only implements the two required methods, so the
308    /// trait's provided methods are exercised with their defaults.
309    struct FixedFour;
310
311    impl Framer for FixedFour {
312        fn next_frame<'a>(&self, input: &'a [u8]) -> Result<Option<Frame<'a>>> {
313            Ok(input.get(..4).map(|p| Frame::new(p, 4)))
314        }
315        fn write_frame(&self, payload: &[u8], out: &mut WriteBuf<'_>) -> Result<()> {
316            if payload.len() != 4 {
317                return Err(Error::InvalidEncoding);
318            }
319            out.write_bytes(payload)
320        }
321    }
322
323    #[test]
324    fn default_next_frame_from_resets_hint_on_frame() {
325        let mut scanned = 7;
326        assert_eq!(FixedFour.next_frame_from(b"ab", &mut scanned), Ok(None));
327        assert_eq!(scanned, 7, "hint is left alone when no frame is produced");
328        let frame = FixedFour
329            .next_frame_from(b"abcdef", &mut scanned)
330            .unwrap()
331            .unwrap();
332        assert_eq!(frame.payload(), b"abcd");
333        assert_eq!(scanned, 0);
334    }
335
336    #[test]
337    fn default_max_frame_len_is_unknown() {
338        assert_eq!(FixedFour.max_frame_len(4), None);
339        let mut out = [0u8; 4];
340        let mut buf = WriteBuf::new(&mut out);
341        FixedFour.write_frame(b"wxyz", &mut buf).unwrap();
342        assert_eq!(
343            FixedFour.write_frame(b"w", &mut buf),
344            Err(Error::InvalidEncoding)
345        );
346    }
347
348    #[test]
349    fn stream_decoder_clears_progress_on_error() {
350        // Regression test (WC2-001): a stale hint after an error made the
351        // decoder skip the delimiter of the next, unrelated input.
352        let framer = Delimited::unbounded(b"\n").unwrap().with_max_payload(8);
353        let mut decoder = StreamDecoder::new(framer);
354        assert_eq!(decoder.decode(b"aaaaaaa").unwrap(), None);
355        assert_eq!(decoder.scanned, 7);
356        assert!(decoder.decode(b"aaaaaaaaaaaa").is_err());
357        assert_eq!(decoder.scanned, 0);
358        let frame = decoder.decode(b"hi\nthere\n").unwrap().unwrap();
359        assert_eq!(frame.payload(), b"hi");
360    }
361
362    #[test]
363    fn stream_decoder_accessors_and_reset() {
364        let mut decoder = StreamDecoder::new(Delimited::unbounded(b"\n").unwrap());
365        assert_eq!(decoder.decode(b"abc").unwrap(), None);
366        assert_eq!(decoder.scanned, 3);
367        decoder.reset();
368        assert_eq!(decoder.scanned, 0);
369        assert_eq!(decoder.framer().delimiter(), b"\n");
370        let framer = decoder.into_inner();
371        assert_eq!(framer.delimiter(), b"\n");
372    }
373}