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