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}