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}