Skip to main content

wire_codec/framing/
delimiter.rs

1//! Byte-delimited framing.
2//!
3//! Frames are separated by a configured delimiter byte sequence (typically
4//! `\n` for newline-terminated text, or `\r\n` for HTTP-style protocols). The
5//! delimiter is stripped from the emitted payload but counted toward
6//! [`Frame::consumed`].
7//!
8//! # Limits and streaming
9//!
10//! [`Delimited::new`] keeps the 1.0 behaviour of an unbounded payload size,
11//! which lets a peer that never sends the delimiter grow the caller's buffer
12//! without limit. For untrusted input, use [`Delimited::bounded`] (a
13//! [`Delimited::DEFAULT_MAX_PAYLOAD`] cap) or [`Delimited::with_max_payload`].
14//!
15//! When input arrives in pieces, call [`Framer::next_frame_from`] or use a
16//! [`StreamDecoder`][`crate::framing::StreamDecoder`] so the search resumes
17//! where it stopped instead of rescanning the buffered prefix on every call.
18
19use crate::buf::WriteBuf;
20use crate::error::{Error, Result};
21use crate::framing::search;
22use crate::framing::{Frame, Framer};
23
24/// Byte-delimited framer.
25///
26/// # Example
27///
28/// ```
29/// use wire_codec::WriteBuf;
30/// use wire_codec::framing::{Delimited, Framer};
31///
32/// let framer = Delimited::bounded(b"\n").unwrap();
33///
34/// let mut out = [0u8; 32];
35/// let mut buf = WriteBuf::new(&mut out);
36/// framer.write_frame(b"line one", &mut buf).unwrap();
37/// framer.write_frame(b"line two", &mut buf).unwrap();
38/// let n = buf.position();
39/// assert_eq!(&out[..n], b"line one\nline two\n");
40///
41/// let first = framer.next_frame(&out[..n]).unwrap().unwrap();
42/// assert_eq!(first.payload(), b"line one");
43/// assert_eq!(first.consumed(), 9);
44/// ```
45#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
46pub struct Delimited<'d> {
47    delimiter: &'d [u8],
48    max_payload: usize,
49}
50
51impl<'d> Delimited<'d> {
52    /// Payload limit applied by [`Delimited::bounded`]: 64 KiB.
53    ///
54    /// Generous for line-oriented text protocols (SMTP caps a line at 1000
55    /// bytes, HTTP servers commonly cap a header line at 8 KiB) while
56    /// bounding the memory a single peer can pin.
57    pub const DEFAULT_MAX_PAYLOAD: usize = 64 * 1024;
58
59    /// Build a framer using `delimiter` to separate frames, with no payload
60    /// size limit.
61    ///
62    /// Without a limit the framer keeps asking for more input until it sees
63    /// the delimiter, however long that takes. Prefer [`Delimited::bounded`]
64    /// for input from untrusted peers.
65    ///
66    /// # Errors
67    ///
68    /// Returns [`Error::EmptyDelimiter`] if `delimiter` is empty. An empty
69    /// delimiter cannot uniquely separate frames.
70    ///
71    /// # Example
72    ///
73    /// ```
74    /// use wire_codec::framing::Delimited;
75    ///
76    /// let framer = Delimited::new(b"\r\n").unwrap();
77    /// assert_eq!(framer.max_payload(), usize::MAX);
78    /// ```
79    #[inline]
80    pub const fn new(delimiter: &'d [u8]) -> Result<Self> {
81        if delimiter.is_empty() {
82            return Err(Error::EmptyDelimiter);
83        }
84        Ok(Self {
85            delimiter,
86            max_payload: usize::MAX,
87        })
88    }
89
90    /// Build a framer using `delimiter`, capped at
91    /// [`Delimited::DEFAULT_MAX_PAYLOAD`] payload bytes per frame.
92    ///
93    /// This is the recommended constructor for input from untrusted peers.
94    /// Chain [`Delimited::with_max_payload`] to pick a different cap.
95    ///
96    /// # Errors
97    ///
98    /// Returns [`Error::EmptyDelimiter`] if `delimiter` is empty.
99    ///
100    /// # Example
101    ///
102    /// ```
103    /// use wire_codec::framing::Delimited;
104    ///
105    /// let framer = Delimited::bounded(b"\n").unwrap();
106    /// assert_eq!(framer.max_payload(), Delimited::DEFAULT_MAX_PAYLOAD);
107    /// ```
108    #[inline]
109    pub const fn bounded(delimiter: &'d [u8]) -> Result<Self> {
110        match Self::new(delimiter) {
111            Ok(framer) => Ok(framer.with_max_payload(Self::DEFAULT_MAX_PAYLOAD)),
112            Err(e) => Err(e),
113        }
114    }
115
116    /// Set an upper bound on payload size. Frames whose payload would exceed
117    /// this size cause [`Error::FrameTooLarge`].
118    ///
119    /// The delimiter search never looks further than `max + delimiter.len()`
120    /// bytes into the input, so the limit also bounds the work per call.
121    ///
122    /// # Example
123    ///
124    /// ```
125    /// use wire_codec::framing::{Delimited, Framer};
126    /// use wire_codec::Error;
127    ///
128    /// let framer = Delimited::new(b"\n").unwrap().with_max_payload(4);
129    /// assert!(framer.next_frame(b"1234\n").unwrap().is_some());
130    /// assert!(matches!(
131    ///     framer.next_frame(b"123456"),
132    ///     Err(Error::FrameTooLarge { limit: 4, .. })
133    /// ));
134    /// ```
135    #[inline]
136    #[must_use]
137    pub const fn with_max_payload(mut self, max: usize) -> Self {
138        self.max_payload = max;
139        self
140    }
141
142    /// Configured delimiter sequence.
143    #[inline]
144    #[must_use]
145    pub const fn delimiter(&self) -> &'d [u8] {
146        self.delimiter
147    }
148
149    /// Configured maximum payload size.
150    #[inline]
151    #[must_use]
152    pub const fn max_payload(&self) -> usize {
153        self.max_payload
154    }
155
156    /// Whether `payload ++ delimiter` contains a delimiter that starts before
157    /// `payload.len()`, which would make the receiver cut the frame short.
158    fn would_split(&self, payload: &[u8]) -> bool {
159        let delim = self.delimiter;
160        if search::find(payload, delim, 0).is_some() {
161            return true;
162        }
163        // A match straddling the boundary has its first `k` bytes in the
164        // payload and the rest in the appended delimiter. That needs the
165        // payload to end with `delim[..k]` and the delimiter to have a border
166        // of length `m - k` (`delim[k..] == delim[..m - k]`). Delimiters
167        // without such a border, like `\n` or `\r\n`, never match here.
168        let m = delim.len();
169        (1..m.min(payload.len() + 1))
170            .any(|k| payload[payload.len() - k..] == delim[..k] && delim[k..] == delim[..m - k])
171    }
172}
173
174impl Framer for Delimited<'_> {
175    /// Extract the bytes up to the first delimiter.
176    ///
177    /// # Errors
178    ///
179    /// Returns [`Error::FrameTooLarge`] once more than
180    /// `max_payload + delimiter.len()` bytes are buffered without a
181    /// delimiter starting at or before offset `max_payload`. The `len` field
182    /// is then a lower bound (`max_payload + 1`): the true length is unknown
183    /// because the frame has not ended yet.
184    #[inline]
185    fn next_frame<'a>(&self, input: &'a [u8]) -> Result<Option<Frame<'a>>> {
186        self.next_frame_from(input, &mut 0)
187    }
188
189    /// Serialize `payload` followed by the delimiter.
190    ///
191    /// # Errors
192    ///
193    /// - [`Error::FrameTooLarge`] if `payload` exceeds the configured limit.
194    /// - [`Error::DelimiterInPayload`] if the receiver would not read the
195    ///   payload back as a single frame: either `payload` contains the
196    ///   delimiter, or (for a self-overlapping delimiter such as `b"aa"`) the
197    ///   payload ends in a way that makes a delimiter appear earlier than
198    ///   intended (`b"a"` followed by `b"aa"` reads back as an empty frame).
199    /// - [`Error::BufferFull`] if `out` cannot hold the payload and the
200    ///   delimiter.
201    ///
202    /// Nothing is written on error.
203    fn write_frame(&self, payload: &[u8], out: &mut WriteBuf<'_>) -> Result<()> {
204        if payload.len() > self.max_payload {
205            return Err(Error::FrameTooLarge {
206                len: payload.len(),
207                limit: self.max_payload,
208            });
209        }
210        if self.would_split(payload) {
211            return Err(Error::DelimiterInPayload);
212        }
213        let total = payload
214            .len()
215            .checked_add(self.delimiter.len())
216            .ok_or(Error::BufferFull)?;
217        if total > out.remaining() {
218            return Err(Error::BufferFull);
219        }
220        out.write_bytes(payload)?;
221        out.write_bytes(self.delimiter)
222    }
223
224    /// Resumable delimiter search; see [`Framer::next_frame_from`].
225    ///
226    /// On `Ok(None)`, `scanned` is set to the number of leading bytes that
227    /// can no longer start a delimiter, so the next call only examines new
228    /// input (plus `delimiter.len() - 1` bytes of overlap for a delimiter
229    /// split across reads). Values larger than the input are clamped.
230    ///
231    /// # Errors
232    ///
233    /// Same contract as [`Framer::next_frame`] for this type.
234    fn next_frame_from<'a>(
235        &self,
236        input: &'a [u8],
237        scanned: &mut usize,
238    ) -> Result<Option<Frame<'a>>> {
239        let delim_len = self.delimiter.len();
240        // A valid delimiter starts at offset <= max_payload, so it ends within
241        // `max_payload + delim_len` bytes. Saturating because the unbounded
242        // default is usize::MAX.
243        let window_cap = self.max_payload.saturating_add(delim_len);
244        let window = if input.len() > window_cap {
245            &input[..window_cap]
246        } else {
247            input
248        };
249        let start = (*scanned).min(window.len());
250        if let Some(pos) = search::find(window, self.delimiter, start) {
251            *scanned = 0;
252            return Ok(Some(Frame::new(&input[..pos], pos + delim_len)));
253        }
254        if input.len() > window_cap {
255            // No delimiter inside the window and the input has already run
256            // past it: the payload is at least max_payload + 1 bytes long.
257            return Err(Error::FrameTooLarge {
258                len: self.max_payload.saturating_add(1),
259                limit: self.max_payload,
260            });
261        }
262        // Every offset before this one has been ruled out as a delimiter
263        // start. The last `delim_len - 1` bytes might begin a delimiter that
264        // completes once more input arrives.
265        *scanned = window.len().saturating_sub(delim_len - 1);
266        Ok(None)
267    }
268
269    #[inline]
270    fn max_frame_len(&self, payload_len: usize) -> Option<usize> {
271        payload_len.checked_add(self.delimiter.len())
272    }
273}
274
275#[cfg(test)]
276mod tests {
277    use super::*;
278    use crate::framing::StreamDecoder;
279
280    #[test]
281    fn newline_round_trip() {
282        let framer = Delimited::new(b"\n").unwrap();
283        let mut storage = [0u8; 32];
284        let mut buf = WriteBuf::new(&mut storage);
285        framer.write_frame(b"hello", &mut buf).unwrap();
286        let n = buf.position();
287        assert_eq!(&storage[..n], b"hello\n");
288
289        let frame = framer.next_frame(&storage[..n]).unwrap().unwrap();
290        assert_eq!(frame.payload(), b"hello");
291        assert_eq!(frame.consumed(), 6);
292    }
293
294    #[test]
295    fn multi_byte_delimiter() {
296        let framer = Delimited::new(b"\r\n").unwrap();
297        let frame = framer.next_frame(b"GET /\r\nrest").unwrap().unwrap();
298        assert_eq!(frame.payload(), b"GET /");
299        assert_eq!(frame.consumed(), 7);
300    }
301
302    #[test]
303    fn overlapping_delimiter_prefix() {
304        let framer = Delimited::new(b"\r\n").unwrap();
305        let frame = framer.next_frame(b"ab\r\r\ncd").unwrap().unwrap();
306        assert_eq!(frame.payload(), b"ab\r");
307        assert_eq!(frame.consumed(), 5);
308    }
309
310    #[test]
311    fn no_delimiter_returns_none() {
312        let framer = Delimited::new(b"\n").unwrap();
313        assert_eq!(framer.next_frame(b"no terminator here").unwrap(), None);
314    }
315
316    #[test]
317    fn exceeds_max_payload_short_circuits_search() {
318        // Input is 6 bytes; max_payload=3 limits the scan window to 4 bytes
319        // (3 + delimiter length). The delimiter is beyond the window, so the
320        // framer bails with FrameTooLarge instead of scanning to the end.
321        let framer = Delimited::new(b"\n").unwrap().with_max_payload(3);
322        let result = framer.next_frame(b"abcde\n");
323        assert_eq!(result, Err(Error::FrameTooLarge { len: 4, limit: 3 }));
324    }
325
326    #[test]
327    fn read_limit_boundaries() {
328        let framer = Delimited::new(b"\r\n").unwrap().with_max_payload(3);
329        // Exactly max_payload bytes of payload is accepted.
330        let frame = framer.next_frame(b"abc\r\n").unwrap().unwrap();
331        assert_eq!(frame.payload(), b"abc");
332        // max_payload + delimiter bytes without a delimiter: still waiting,
333        // because the delimiter could start at offset 3.
334        assert_eq!(framer.next_frame(b"abc\r"), Ok(None));
335        assert_eq!(framer.next_frame(b"abcd\r"), Ok(None));
336        // One byte past the window without a delimiter: too large.
337        assert_eq!(
338            framer.next_frame(b"abcd\r\n"),
339            Err(Error::FrameTooLarge { len: 4, limit: 3 })
340        );
341    }
342
343    #[test]
344    fn write_limit_boundaries() {
345        let framer = Delimited::new(b"\n").unwrap().with_max_payload(3);
346        let mut storage = [0u8; 16];
347        let mut buf = WriteBuf::new(&mut storage);
348        framer.write_frame(b"abc", &mut buf).unwrap();
349        assert_eq!(
350            framer.write_frame(b"abcd", &mut buf),
351            Err(Error::FrameTooLarge { len: 4, limit: 3 })
352        );
353        assert_eq!(buf.written(), b"abc\n");
354    }
355
356    #[test]
357    fn write_rejects_delimiter_inside_payload() {
358        let framer = Delimited::new(b"\r\n").unwrap();
359        let mut storage = [0u8; 16];
360        let mut buf = WriteBuf::new(&mut storage);
361        assert_eq!(
362            framer.write_frame(b"a\r\nb", &mut buf),
363            Err(Error::DelimiterInPayload)
364        );
365        assert_eq!(buf.position(), 0, "nothing is written on error");
366        // A lone '\r' or '\n' is not the delimiter.
367        framer.write_frame(b"a\rb\nc", &mut buf).unwrap();
368        let n = buf.position();
369        let frame = framer.next_frame(&storage[..n]).unwrap().unwrap();
370        assert_eq!(frame.payload(), b"a\rb\nc");
371        assert_eq!(frame.consumed(), n);
372    }
373
374    #[test]
375    fn write_rejects_payloads_that_merge_with_a_self_overlapping_delimiter() {
376        let mut storage = [0u8; 16];
377        let cases: [(&[u8], &[u8], bool); 7] = [
378            (b"aa", b"a", true),   // "a" + "aa" reads back as ""
379            (b"aa", b"ba", true),  // "ba" + "aa" reads back as "b"
380            (b"aa", b"ab", false), // "ab" + "aa" reads back as "ab"
381            (b"aba", b"xab", true),
382            (b"aba", b"xa", false), // "xa" + "aba": first match at 2
383            (b"\r\n", b"x\r", false),
384            (b"abab", b"xab", true),
385        ];
386        for (delim, payload, rejected) in cases {
387            let framer = Delimited::new(delim).unwrap();
388            let mut buf = WriteBuf::new(&mut storage);
389            let result = framer.write_frame(payload, &mut buf);
390            if rejected {
391                assert_eq!(
392                    result,
393                    Err(Error::DelimiterInPayload),
394                    "{delim:?} {payload:?}"
395                );
396            } else {
397                result.unwrap();
398                let n = buf.position();
399                let frame = framer.next_frame(&storage[..n]).unwrap().unwrap();
400                assert_eq!(frame.payload(), payload, "{delim:?}");
401                assert_eq!(frame.consumed(), n);
402            }
403        }
404    }
405
406    #[test]
407    fn write_is_atomic_on_buffer_full() {
408        let framer = Delimited::new(b"\r\n").unwrap();
409        let mut storage = [0u8; 4];
410        let mut buf = WriteBuf::new(&mut storage);
411        assert_eq!(framer.write_frame(b"abc", &mut buf), Err(Error::BufferFull));
412        assert_eq!(buf.position(), 0);
413        framer.write_frame(b"ab", &mut buf).unwrap();
414        assert_eq!(buf.position(), 4);
415    }
416
417    #[test]
418    fn empty_payload_is_valid() {
419        let framer = Delimited::new(b"\n").unwrap();
420        let frame = framer.next_frame(b"\nrest").unwrap().unwrap();
421        assert_eq!(frame.payload(), b"");
422        assert_eq!(frame.consumed(), 1);
423    }
424
425    #[test]
426    fn empty_delimiter_rejected() {
427        assert!(matches!(Delimited::new(b""), Err(Error::EmptyDelimiter)));
428        assert!(matches!(
429            Delimited::bounded(b""),
430            Err(Error::EmptyDelimiter)
431        ));
432    }
433
434    #[test]
435    fn bounded_constructor_and_max_frame_len() {
436        let framer = Delimited::bounded(b"\r\n").unwrap();
437        assert_eq!(framer.max_payload(), Delimited::DEFAULT_MAX_PAYLOAD);
438        assert_eq!(framer.max_frame_len(10), Some(12));
439        assert_eq!(framer.max_frame_len(usize::MAX), None);
440    }
441
442    #[test]
443    fn resumable_scan_handles_split_delimiter() {
444        let framer = Delimited::new(b"\r\n").unwrap();
445        let mut scanned = 0;
446        assert_eq!(framer.next_frame_from(b"abc\r", &mut scanned), Ok(None));
447        // The trailing '\r' may begin the delimiter, so it is not skipped.
448        assert_eq!(scanned, 3);
449        let frame = framer
450            .next_frame_from(b"abc\r\n", &mut scanned)
451            .unwrap()
452            .unwrap();
453        assert_eq!(frame.payload(), b"abc");
454        assert_eq!(scanned, 0);
455    }
456
457    #[test]
458    fn resumable_scan_clamps_oversized_hint() {
459        let framer = Delimited::new(b"\n").unwrap();
460        let mut scanned = 1_000;
461        // The hint is a promise about the input; an out-of-range value is
462        // clamped rather than trusted.
463        assert_eq!(framer.next_frame_from(b"ab", &mut scanned), Ok(None));
464        assert_eq!(scanned, 2);
465    }
466
467    #[test]
468    fn byte_at_a_time_stream_does_linear_work() {
469        // A 256 KiB line delivered one byte at a time. With the resumable
470        // scan each call only looks at the new byte, so the total number of
471        // bytes examined is bounded by the input length plus overlap.
472        let framer = Delimited::new(b"\r\n").unwrap();
473        let mut decoder = StreamDecoder::new(framer);
474        let mut line = vec![b'x'; 256 * 1024];
475        line.extend_from_slice(b"\r\n");
476        let mut examined = 0usize;
477        let mut frames = 0;
478        for end in 1..=line.len() {
479            let before = decoder.scanned;
480            match decoder.decode(&line[..end]).unwrap() {
481                Some(frame) => {
482                    assert_eq!(frame.payload().len(), 256 * 1024);
483                    frames += 1;
484                }
485                None => examined += end - before,
486            }
487        }
488        assert_eq!(frames, 1);
489        assert!(examined <= 2 * line.len(), "examined {examined} bytes");
490    }
491}