wire_codec/framing/length.rs
1//! Length-prefixed framing.
2//!
3//! Each frame is preceded by a fixed-width unsigned integer that gives the
4//! length of the payload in bytes. The width and endianness are chosen at
5//! construction time. An optional length adjustment covers protocols whose
6//! length field counts something other than exactly the payload (for example
7//! "length includes the header").
8//!
9//! # Limits
10//!
11//! [`LengthPrefixed::bounded`] caps payloads at
12//! [`LengthPrefixed::DEFAULT_MAX_PAYLOAD`] (8 MiB) and
13//! [`LengthPrefixed::bounded_to`] at a limit you choose: use one of them for
14//! input from untrusted peers. [`LengthPrefixed::unbounded`] sets the limit to
15//! the largest value the prefix can encode (4 GiB for [`LengthWidth::U32`]); a
16//! caller that buffers until a frame is complete would buffer that much for a
17//! hostile peer. `LengthPrefixed::new` keeps the 1.0 unbounded behaviour, is
18//! deprecated since 1.2.0, and becomes bounded in 2.0.
19
20use crate::buf::WriteBuf;
21use crate::error::{Error, Result};
22use crate::framing::{Frame, Framer};
23
24/// Width of the length prefix, in bytes.
25///
26/// Marked `#[non_exhaustive]` so wider prefix widths can be added in future
27/// minor releases without a major version bump.
28#[non_exhaustive]
29#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
30pub enum LengthWidth {
31 /// One-byte length prefix; payload up to 255 bytes.
32 U8,
33 /// Two-byte length prefix; payload up to 65 535 bytes.
34 U16,
35 /// Four-byte length prefix; payload up to 4 294 967 295 bytes.
36 U32,
37 /// Three-byte length prefix; payload up to 16 777 215 bytes.
38 ///
39 /// Used by HTTP/2 frame headers, TLS handshake messages, and MySQL
40 /// packets.
41 U24,
42 /// Eight-byte length prefix; payload up to `u64::MAX` bytes.
43 ///
44 /// On targets where `usize` is narrower than 64 bits, a length that does
45 /// not fit in `usize` is reported as [`Error::FrameTooLarge`].
46 U64,
47}
48
49impl LengthWidth {
50 /// Width of the prefix in bytes.
51 ///
52 /// # Example
53 ///
54 /// ```
55 /// use wire_codec::framing::LengthWidth;
56 /// assert_eq!(LengthWidth::U24.header_size(), 3);
57 /// ```
58 #[inline]
59 #[must_use]
60 pub const fn header_size(self) -> usize {
61 match self {
62 LengthWidth::U8 => 1,
63 LengthWidth::U16 => 2,
64 LengthWidth::U24 => 3,
65 LengthWidth::U32 => 4,
66 LengthWidth::U64 => 8,
67 }
68 }
69
70 /// Largest value the prefix can encode.
71 ///
72 /// # Example
73 ///
74 /// ```
75 /// use wire_codec::framing::LengthWidth;
76 /// assert_eq!(LengthWidth::U16.max_payload(), 65_535);
77 /// ```
78 #[inline]
79 #[must_use]
80 pub const fn max_payload(self) -> u64 {
81 match self {
82 LengthWidth::U8 => u8::MAX as u64,
83 LengthWidth::U16 => u16::MAX as u64,
84 LengthWidth::U24 => 0x00FF_FFFF,
85 LengthWidth::U32 => u32::MAX as u64,
86 LengthWidth::U64 => u64::MAX,
87 }
88 }
89}
90
91/// Byte order for the length prefix.
92///
93/// # Example
94///
95/// ```
96/// use wire_codec::framing::{Endian, Framer, LengthPrefixed, LengthWidth};
97///
98/// let framer = LengthPrefixed::unbounded(LengthWidth::U16, Endian::Little);
99/// let frame = framer.next_frame(&[0x02, 0x00, b'o', b'k']).unwrap().unwrap();
100/// assert_eq!(frame.payload(), b"ok");
101/// ```
102#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
103pub enum Endian {
104 /// Most significant byte first (network order).
105 Big,
106 /// Least significant byte first.
107 Little,
108}
109
110/// Length-prefixed framer.
111///
112/// # Example
113///
114/// ```
115/// use wire_codec::WriteBuf;
116/// use wire_codec::framing::{Endian, Framer, LengthPrefixed, LengthWidth};
117///
118/// let framer = LengthPrefixed::unbounded(LengthWidth::U16, Endian::Big);
119///
120/// let mut out = [0u8; 16];
121/// let mut buf = WriteBuf::new(&mut out);
122/// framer.write_frame(b"hello", &mut buf).unwrap();
123/// let written = buf.position();
124/// assert_eq!(&out[..written], &[0x00, 0x05, b'h', b'e', b'l', b'l', b'o']);
125///
126/// let frame = framer.next_frame(&out[..written]).unwrap().unwrap();
127/// assert_eq!(frame.payload(), b"hello");
128/// assert_eq!(frame.consumed(), 7);
129/// ```
130#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
131pub struct LengthPrefixed {
132 width: LengthWidth,
133 endian: Endian,
134 max_payload: u64,
135 adjustment: i64,
136}
137
138impl LengthPrefixed {
139 /// Payload limit applied by [`LengthPrefixed::bounded`]: 8 MiB.
140 ///
141 /// Matches the default of `tokio_util::codec::LengthDelimitedCodec`.
142 pub const DEFAULT_MAX_PAYLOAD: u64 = 8 * 1024 * 1024;
143
144 /// Build an **unbounded** framer using the given prefix width and
145 /// endianness.
146 ///
147 /// **Warning:** the payload limit is the largest value the prefix can
148 /// encode: 4 GiB for [`LengthWidth::U32`], and for [`LengthWidth::U64`]
149 /// everything addressable. A peer that sends a large prefix and then
150 /// trickles bytes makes a streaming reader (`tokio_util::codec::Framed`,
151 /// [`StreamDecoder`](crate::framing::StreamDecoder)) buffer toward that
152 /// size. Use [`LengthPrefixed::bounded`] (8 MiB) or
153 /// [`LengthPrefixed::bounded_to`] for input from untrusted peers, and
154 /// [`LengthPrefixed::unbounded`] where the full range is really wanted.
155 /// `new` becomes bounded in 2.0.
156 ///
157 /// # Example
158 ///
159 /// ```
160 /// # #![allow(deprecated)]
161 /// use wire_codec::framing::{Endian, LengthPrefixed, LengthWidth};
162 ///
163 /// let framer = LengthPrefixed::new(LengthWidth::U32, Endian::Big);
164 /// assert_eq!(framer.max_payload(), u64::from(u32::MAX));
165 /// ```
166 #[deprecated(
167 since = "1.2.0",
168 note = "unbounded: the payload limit is the prefix maximum (4 GiB for U32). \
169 Use `LengthPrefixed::bounded` or `LengthPrefixed::bounded_to` for peer \
170 input, or `LengthPrefixed::unbounded` to keep this behaviour"
171 )]
172 #[inline]
173 #[must_use]
174 pub const fn new(width: LengthWidth, endian: Endian) -> Self {
175 Self::unbounded(width, endian)
176 }
177
178 /// Build a framer whose payload limit is the largest value the prefix
179 /// can encode: the behaviour of `new` before 1.2.0, under a name that
180 /// says so.
181 ///
182 /// Only for input whose size is already bounded by other means (a
183 /// trusted local file, a transport that caps frames itself). For peer
184 /// input use [`LengthPrefixed::bounded`] or
185 /// [`LengthPrefixed::bounded_to`].
186 ///
187 /// # Example
188 ///
189 /// ```
190 /// use wire_codec::framing::{Endian, LengthPrefixed, LengthWidth};
191 ///
192 /// let framer = LengthPrefixed::unbounded(LengthWidth::U16, Endian::Big);
193 /// assert_eq!(framer.max_payload(), 65_535);
194 /// ```
195 #[inline]
196 #[must_use]
197 pub const fn unbounded(width: LengthWidth, endian: Endian) -> Self {
198 Self {
199 width,
200 endian,
201 max_payload: width.max_payload(),
202 adjustment: 0,
203 }
204 }
205
206 /// Build a framer capped at `max` payload bytes (or the width's own
207 /// maximum, if smaller). Equivalent to
208 /// `LengthPrefixed::unbounded(width, endian).with_max_payload(max)`.
209 ///
210 /// # Example
211 ///
212 /// ```
213 /// use wire_codec::framing::{Endian, Framer, LengthPrefixed, LengthWidth};
214 /// use wire_codec::Error;
215 ///
216 /// let framer = LengthPrefixed::bounded_to(LengthWidth::U32, Endian::Big, 1024);
217 /// assert_eq!(framer.max_payload(), 1024);
218 /// assert!(matches!(
219 /// framer.next_frame(&[0, 0, 0x10, 0]),
220 /// Err(Error::FrameTooLarge { .. })
221 /// ));
222 /// ```
223 #[inline]
224 #[must_use]
225 pub const fn bounded_to(width: LengthWidth, endian: Endian, max: u64) -> Self {
226 Self::unbounded(width, endian).with_max_payload(max)
227 }
228
229 /// Build a framer capped at [`LengthPrefixed::DEFAULT_MAX_PAYLOAD`]
230 /// payload bytes (or the width's own maximum, if smaller).
231 ///
232 /// This is the recommended constructor for input from untrusted peers.
233 ///
234 /// # Example
235 ///
236 /// ```
237 /// use wire_codec::framing::{Endian, LengthPrefixed, LengthWidth};
238 ///
239 /// let framer = LengthPrefixed::bounded(LengthWidth::U32, Endian::Big);
240 /// assert_eq!(framer.max_payload(), LengthPrefixed::DEFAULT_MAX_PAYLOAD);
241 ///
242 /// // A u16 prefix cannot describe more than 65 535 bytes anyway.
243 /// let small = LengthPrefixed::bounded(LengthWidth::U16, Endian::Big);
244 /// assert_eq!(small.max_payload(), 65_535);
245 /// ```
246 #[inline]
247 #[must_use]
248 pub const fn bounded(width: LengthWidth, endian: Endian) -> Self {
249 Self::bounded_to(width, endian, Self::DEFAULT_MAX_PAYLOAD)
250 }
251
252 /// Override the payload size limit enforced when reading and writing frames.
253 ///
254 /// The limit is clamped to [`LengthWidth::max_payload`] of the configured
255 /// width.
256 ///
257 /// # Example
258 ///
259 /// ```
260 /// use wire_codec::framing::{Endian, LengthPrefixed, LengthWidth};
261 ///
262 /// let framer = LengthPrefixed::unbounded(LengthWidth::U8, Endian::Big).with_max_payload(1_000);
263 /// assert_eq!(framer.max_payload(), 255);
264 /// ```
265 #[inline]
266 #[must_use]
267 pub const fn with_max_payload(mut self, max: u64) -> Self {
268 let cap = self.width.max_payload();
269 self.max_payload = if max < cap { max } else { cap };
270 self
271 }
272
273 /// Set the length adjustment: the payload length is the prefix value
274 /// plus `adjustment`.
275 ///
276 /// The default is `0` (the prefix counts exactly the payload bytes). When
277 /// the prefix counts its own bytes as well, use `-(header_size)`; when it
278 /// counts a fixed trailer that the caller handles separately, use the
279 /// trailer size. This mirrors `length_adjustment` in
280 /// `tokio_util::codec::LengthDelimitedCodec`.
281 ///
282 /// On decode, a prefix that adjusts to a negative length yields
283 /// [`Error::InvalidLengthPrefix`]. On encode, a payload whose adjusted
284 /// prefix value would be negative also yields
285 /// [`Error::InvalidLengthPrefix`], and one whose prefix value would not
286 /// fit in the width yields [`Error::FrameTooLarge`].
287 ///
288 /// The payload limit ([`LengthPrefixed::max_payload`]) is counted in
289 /// payload bytes and is capped at the width's largest prefix value,
290 /// independently of the adjustment. Two consequences:
291 ///
292 /// - A negative adjustment lowers the largest payload that can be
293 /// written to `width maximum + adjustment` (254 bytes for a
294 /// self-counting `U8` prefix). `write_frame` reports that effective
295 /// bound as the `limit` of [`Error::FrameTooLarge`].
296 /// - A positive adjustment does not raise the limit: payloads longer
297 /// than [`LengthWidth::max_payload`] are rejected even though the
298 /// adjusted prefix could describe them.
299 ///
300 /// # Example
301 ///
302 /// ```
303 /// use wire_codec::WriteBuf;
304 /// use wire_codec::framing::{Endian, Framer, LengthPrefixed, LengthWidth};
305 ///
306 /// // The 2-byte length field counts itself.
307 /// let framer = LengthPrefixed::unbounded(LengthWidth::U16, Endian::Big).with_length_adjustment(-2);
308 ///
309 /// let mut out = [0u8; 8];
310 /// let mut buf = WriteBuf::new(&mut out);
311 /// framer.write_frame(b"abc", &mut buf).unwrap();
312 /// assert_eq!(buf.written(), &[0x00, 0x05, b'a', b'b', b'c']);
313 ///
314 /// let frame = framer.next_frame(&out[..5]).unwrap().unwrap();
315 /// assert_eq!(frame.payload(), b"abc");
316 /// ```
317 #[inline]
318 #[must_use]
319 pub const fn with_length_adjustment(mut self, adjustment: i64) -> Self {
320 self.adjustment = adjustment;
321 self
322 }
323
324 /// Configured prefix width.
325 #[inline]
326 #[must_use]
327 pub const fn width(&self) -> LengthWidth {
328 self.width
329 }
330
331 /// Configured endianness.
332 #[inline]
333 #[must_use]
334 pub const fn endian(&self) -> Endian {
335 self.endian
336 }
337
338 /// Configured maximum payload size in bytes.
339 #[inline]
340 #[must_use]
341 pub const fn max_payload(&self) -> u64 {
342 self.max_payload
343 }
344
345 /// Configured length adjustment (see
346 /// [`LengthPrefixed::with_length_adjustment`]).
347 #[inline]
348 #[must_use]
349 pub const fn length_adjustment(&self) -> i64 {
350 self.adjustment
351 }
352
353 /// Decode the prefix from the first `header_size()` bytes of `input`,
354 /// or `None` if fewer bytes are available.
355 #[inline]
356 fn read_prefix(&self, input: &[u8]) -> Option<u64> {
357 let big = matches!(self.endian, Endian::Big);
358 Some(match self.width {
359 LengthWidth::U8 => u64::from(*input.first()?),
360 LengthWidth::U16 => {
361 let b: [u8; 2] = *input.first_chunk()?;
362 u64::from(if big {
363 u16::from_be_bytes(b)
364 } else {
365 u16::from_le_bytes(b)
366 })
367 }
368 LengthWidth::U24 => {
369 let [a, b, c] = *input.first_chunk::<3>()?;
370 let word = if big { [0, a, b, c] } else { [a, b, c, 0] };
371 u64::from(if big {
372 u32::from_be_bytes(word)
373 } else {
374 u32::from_le_bytes(word)
375 })
376 }
377 LengthWidth::U32 => {
378 let b: [u8; 4] = *input.first_chunk()?;
379 u64::from(if big {
380 u32::from_be_bytes(b)
381 } else {
382 u32::from_le_bytes(b)
383 })
384 }
385 LengthWidth::U64 => {
386 let b: [u8; 8] = *input.first_chunk()?;
387 if big {
388 u64::from_be_bytes(b)
389 } else {
390 u64::from_le_bytes(b)
391 }
392 }
393 })
394 }
395
396 /// Encode `value` (already checked to fit the width) as the prefix.
397 /// Returns the bytes and how many of them are used.
398 #[inline]
399 fn prefix_bytes(&self, value: u64) -> ([u8; 8], usize) {
400 let n = self.width.header_size();
401 // `value` fits in `n` bytes, so after the shift (big endian) or as is
402 // (little endian) the prefix occupies exactly the first `n` bytes.
403 let out = match self.endian {
404 Endian::Big => (value << (8 * (8 - n))).to_be_bytes(),
405 Endian::Little => value.to_le_bytes(),
406 };
407 (out, n)
408 }
409
410 /// Build a `FrameTooLarge` error, saturating both fields at `usize::MAX`
411 /// so the conversion is lossless-or-saturating on 32-bit targets.
412 fn too_large(&self, len: u64) -> Error {
413 Error::FrameTooLarge {
414 len: saturate_usize(len),
415 limit: saturate_usize(self.max_payload),
416 }
417 }
418}
419
420impl Framer for LengthPrefixed {
421 /// Extract one frame if the prefix and the full payload are present.
422 ///
423 /// # Errors
424 ///
425 /// - [`Error::FrameTooLarge`] if the (adjusted) prefix exceeds the
426 /// configured limit, or the payload cannot be addressed on this target.
427 /// - [`Error::InvalidLengthPrefix`] if the length adjustment turns the
428 /// prefix into a negative length.
429 fn next_frame<'a>(&self, input: &'a [u8]) -> Result<Option<Frame<'a>>> {
430 let header_size = self.width.header_size();
431 let Some(raw) = self.read_prefix(input) else {
432 return Ok(None);
433 };
434 // The prefix is attacker-controlled: the adjustment is applied with
435 // checked arithmetic, so neither the sum nor its sign can wrap.
436 let len = match raw.checked_add_signed(self.adjustment) {
437 Some(len) => len,
438 None if self.adjustment < 0 => return Err(Error::InvalidLengthPrefix),
439 None => return Err(self.too_large(u64::MAX)),
440 };
441 if len > self.max_payload {
442 return Err(self.too_large(len));
443 }
444 // On 32-bit targets a `u32` (or wider) length does not necessarily
445 // fit in `usize`, and adding the header size can overflow, so both
446 // steps are checked. A length that cannot be addressed can never be
447 // buffered, so it is reported as too large rather than "need more".
448 let consumed = usize::try_from(len)
449 .ok()
450 .and_then(|len| len.checked_add(header_size))
451 .ok_or_else(|| self.too_large(len))?;
452 let Some(frame) = input.get(header_size..consumed) else {
453 return Ok(None);
454 };
455 Ok(Some(Frame::new(frame, consumed)))
456 }
457
458 /// Write the prefix and the payload.
459 ///
460 /// # Errors
461 ///
462 /// - [`Error::FrameTooLarge`] if the payload exceeds the configured
463 /// limit, or its adjusted length does not fit in the prefix width.
464 /// - [`Error::InvalidLengthPrefix`] if the adjusted length is negative.
465 /// - [`Error::BufferFull`] if `out` cannot hold the whole frame.
466 ///
467 /// Nothing is written on error.
468 fn write_frame(&self, payload: &[u8], out: &mut WriteBuf<'_>) -> Result<()> {
469 let len = u64::try_from(payload.len()).unwrap_or(u64::MAX);
470 if len > self.max_payload {
471 return Err(self.too_large(len));
472 }
473 // prefix = len - adjustment, in i128 so `-i64::MIN` cannot overflow.
474 let prefix = if self.adjustment == 0 {
475 len
476 } else {
477 let prefix = i128::from(len) - i128::from(self.adjustment);
478 if prefix < 0 {
479 return Err(Error::InvalidLengthPrefix);
480 }
481 u64::try_from(prefix).unwrap_or(u64::MAX)
482 };
483 if prefix > self.width.max_payload() {
484 // Report the largest payload this width and adjustment can carry.
485 let effective = i128::from(self.width.max_payload()) + i128::from(self.adjustment);
486 let effective = u64::try_from(effective.max(0)).unwrap_or(u64::MAX);
487 return Err(Error::FrameTooLarge {
488 len: saturate_usize(len),
489 limit: saturate_usize(effective.min(self.max_payload)),
490 });
491 }
492 let (header, header_len) = self.prefix_bytes(prefix);
493 let total = payload
494 .len()
495 .checked_add(header_len)
496 .ok_or(Error::BufferFull)?;
497 if total > out.remaining() {
498 return Err(Error::BufferFull);
499 }
500 out.write_bytes(&header[..header_len])?;
501 out.write_bytes(payload)
502 }
503
504 #[inline]
505 fn max_frame_len(&self, payload_len: usize) -> Option<usize> {
506 payload_len.checked_add(self.width.header_size())
507 }
508}
509
510/// Convert `value` to `usize`, saturating at `usize::MAX` on targets where
511/// `usize` is narrower than 64 bits.
512#[inline]
513fn saturate_usize(value: u64) -> usize {
514 usize::try_from(value).unwrap_or(usize::MAX)
515}
516
517#[cfg(test)]
518mod tests {
519 use super::*;
520
521 const WIDTHS: [LengthWidth; 5] = [
522 LengthWidth::U8,
523 LengthWidth::U16,
524 LengthWidth::U24,
525 LengthWidth::U32,
526 LengthWidth::U64,
527 ];
528
529 #[test]
530 #[allow(deprecated)]
531 fn deprecated_new_is_unbounded_and_bounded_to_caps() {
532 let w = LengthWidth::U32;
533 assert_eq!(
534 LengthPrefixed::new(w, Endian::Big),
535 LengthPrefixed::unbounded(w, Endian::Big)
536 );
537 assert_eq!(
538 LengthPrefixed::unbounded(w, Endian::Big).max_payload(),
539 u64::from(u32::MAX)
540 );
541 let capped = LengthPrefixed::bounded_to(w, Endian::Little, 100);
542 assert_eq!(capped.max_payload(), 100);
543 assert_eq!(
544 LengthPrefixed::bounded_to(LengthWidth::U8, Endian::Big, 1000).max_payload(),
545 255
546 );
547 // A prefix above the cap is refused as soon as it arrives.
548 assert!(matches!(
549 capped.next_frame(&101u32.to_le_bytes()),
550 Err(Error::FrameTooLarge { .. })
551 ));
552 assert_eq!(
553 LengthPrefixed::bounded(w, Endian::Big),
554 LengthPrefixed::bounded_to(w, Endian::Big, LengthPrefixed::DEFAULT_MAX_PAYLOAD)
555 );
556 }
557
558 #[test]
559 fn huge_u32_prefix_never_panics() {
560 // Regression test for the 32-bit overflow in `header_size + len`.
561 // On 64-bit targets the frame is merely incomplete; on 32-bit targets
562 // the length cannot be addressed and is reported as too large.
563 let framer = LengthPrefixed::unbounded(LengthWidth::U32, Endian::Big);
564 for prefix in [0xFFFF_FFFFu32, 0xFFFF_FFFE, 0xFFFF_FFFD, 0xFFFF_FFFC] {
565 let mut input = [0u8; 6];
566 input[..4].copy_from_slice(&prefix.to_be_bytes());
567 let result = framer.next_frame(&input);
568 if usize::BITS > 32 {
569 assert_eq!(result, Ok(None));
570 } else {
571 assert!(matches!(result, Err(Error::FrameTooLarge { .. })));
572 }
573 }
574 }
575
576 #[test]
577 fn huge_u64_prefix_never_panics() {
578 let framer = LengthPrefixed::unbounded(LengthWidth::U64, Endian::Little);
579 let input = [0xFF; 12];
580 assert!(matches!(
581 framer.next_frame(&input),
582 Err(Error::FrameTooLarge { .. })
583 ));
584 let framer = framer.with_length_adjustment(i64::MAX);
585 assert!(matches!(
586 framer.next_frame(&input),
587 Err(Error::FrameTooLarge { .. })
588 ));
589 }
590
591 #[test]
592 fn every_width_and_endian_round_trips() {
593 for width in WIDTHS {
594 for endian in [Endian::Big, Endian::Little] {
595 let framer = LengthPrefixed::unbounded(width, endian);
596 let mut out = [0u8; 32];
597 let mut buf = WriteBuf::new(&mut out);
598 framer.write_frame(b"abc", &mut buf).unwrap();
599 let n = buf.position();
600 assert_eq!(n, width.header_size() + 3);
601 let frame = framer.next_frame(&out[..n]).unwrap().unwrap();
602 assert_eq!(frame.payload(), b"abc");
603 assert_eq!(frame.consumed(), n);
604 assert_eq!(framer.max_frame_len(3), Some(n));
605 }
606 }
607 }
608
609 #[test]
610 fn prefix_byte_order() {
611 let mut out = [0u8; 16];
612 let cases: [(LengthWidth, Endian, &[u8]); 4] = [
613 (LengthWidth::U24, Endian::Big, &[0x00, 0x01, 0x02]),
614 (LengthWidth::U24, Endian::Little, &[0x02, 0x01, 0x00]),
615 (
616 LengthWidth::U64,
617 Endian::Big,
618 &[0, 0, 0, 0, 0, 0, 0x01, 0x02],
619 ),
620 (
621 LengthWidth::U64,
622 Endian::Little,
623 &[0x02, 0x01, 0, 0, 0, 0, 0, 0],
624 ),
625 ];
626 for (width, endian, header) in cases {
627 let framer = LengthPrefixed::unbounded(width, endian);
628 assert_eq!(framer.read_prefix(header), Some(0x0102));
629 assert_eq!(framer.read_prefix(&header[1..]), None);
630 let (bytes, n) = framer.prefix_bytes(0x0102);
631 assert_eq!(&bytes[..n], header);
632 let mut buf = WriteBuf::new(&mut out);
633 assert_eq!(framer.write_frame(&[0u8; 2], &mut buf), Ok(()));
634 }
635 }
636
637 #[test]
638 fn u24_limits() {
639 assert_eq!(LengthWidth::U24.max_payload(), (1 << 24) - 1);
640 let framer = LengthPrefixed::unbounded(LengthWidth::U24, Endian::Big);
641 assert_eq!(framer.next_frame(&[0xFF, 0xFF, 0xFF, 0x00]), Ok(None));
642 }
643
644 #[test]
645 fn u8_big_round_trip() {
646 let framer = LengthPrefixed::unbounded(LengthWidth::U8, Endian::Big);
647 let mut out = [0u8; 32];
648 let mut buf = WriteBuf::new(&mut out);
649 framer.write_frame(b"abc", &mut buf).unwrap();
650 let n = buf.position();
651 assert_eq!(&out[..n], &[0x03, b'a', b'b', b'c']);
652
653 let frame = framer.next_frame(&out[..n]).unwrap().unwrap();
654 assert_eq!(frame.payload(), b"abc");
655 assert_eq!(frame.consumed(), 4);
656 }
657
658 #[test]
659 fn partial_frame_returns_none() {
660 let framer = LengthPrefixed::unbounded(LengthWidth::U16, Endian::Big);
661 // Header advertises 10 bytes but only 3 follow.
662 let input = &[0x00, 0x0A, 0x01, 0x02, 0x03];
663 assert_eq!(framer.next_frame(input).unwrap(), None);
664 }
665
666 #[test]
667 fn header_only_input() {
668 let framer = LengthPrefixed::unbounded(LengthWidth::U16, Endian::Big);
669 // Exactly the header, advertising an empty payload: a complete frame.
670 let frame = framer.next_frame(&[0x00, 0x00]).unwrap().unwrap();
671 assert_eq!(frame.payload(), b"");
672 assert_eq!(frame.consumed(), 2);
673 // Exactly the header, advertising one byte: incomplete.
674 assert_eq!(framer.next_frame(&[0x00, 0x01]), Ok(None));
675 // One byte short of the header.
676 assert_eq!(framer.next_frame(&[0x00]), Ok(None));
677 }
678
679 #[test]
680 fn empty_input_returns_none() {
681 let framer = LengthPrefixed::unbounded(LengthWidth::U16, Endian::Big);
682 assert_eq!(framer.next_frame(&[]).unwrap(), None);
683 }
684
685 #[test]
686 fn exceeds_max_payload() {
687 let framer = LengthPrefixed::unbounded(LengthWidth::U16, Endian::Big).with_max_payload(4);
688 let input = &[0x00, 0x05, 0, 0, 0, 0, 0];
689 assert_eq!(
690 framer.next_frame(input),
691 Err(Error::FrameTooLarge { len: 5, limit: 4 })
692 );
693 // Exactly at the limit is accepted.
694 let input = &[0x00, 0x04, 1, 2, 3, 4];
695 assert_eq!(
696 framer.next_frame(input).unwrap().unwrap().payload(),
697 &[1, 2, 3, 4]
698 );
699 }
700
701 #[test]
702 fn write_limit_boundaries() {
703 let framer = LengthPrefixed::unbounded(LengthWidth::U8, Endian::Big).with_max_payload(3);
704 let mut out = [0u8; 16];
705 let mut buf = WriteBuf::new(&mut out);
706 framer.write_frame(&[0u8; 3], &mut buf).unwrap();
707 assert_eq!(
708 framer.write_frame(&[0u8; 4], &mut buf),
709 Err(Error::FrameTooLarge { len: 4, limit: 3 })
710 );
711 assert_eq!(buf.position(), 4);
712 }
713
714 #[test]
715 fn write_is_atomic_on_buffer_full() {
716 let framer = LengthPrefixed::unbounded(LengthWidth::U32, Endian::Big);
717 let mut out = [0xEEu8; 6];
718 let mut buf = WriteBuf::new(&mut out);
719 assert_eq!(framer.write_frame(b"abc", &mut buf), Err(Error::BufferFull));
720 assert_eq!(buf.position(), 0);
721 assert_eq!(out, [0xEE; 6], "no partial header is written");
722 }
723
724 #[test]
725 fn bounded_constructor() {
726 let framer = LengthPrefixed::bounded(LengthWidth::U64, Endian::Big);
727 assert_eq!(framer.max_payload(), LengthPrefixed::DEFAULT_MAX_PAYLOAD);
728 assert_eq!(framer.width(), LengthWidth::U64);
729 assert_eq!(framer.endian(), Endian::Big);
730 assert_eq!(framer.length_adjustment(), 0);
731 let mut input = [0u8; 8];
732 input.copy_from_slice(&(LengthPrefixed::DEFAULT_MAX_PAYLOAD + 1).to_be_bytes());
733 assert!(matches!(
734 framer.next_frame(&input),
735 Err(Error::FrameTooLarge { .. })
736 ));
737 }
738
739 #[test]
740 fn length_adjustment_including_header() {
741 let framer =
742 LengthPrefixed::unbounded(LengthWidth::U32, Endian::Big).with_length_adjustment(-4);
743 let mut out = [0u8; 16];
744 let mut buf = WriteBuf::new(&mut out);
745 framer.write_frame(b"xyz", &mut buf).unwrap();
746 assert_eq!(buf.written(), &[0, 0, 0, 7, b'x', b'y', b'z']);
747 let frame = framer.next_frame(&out[..7]).unwrap().unwrap();
748 assert_eq!(frame.payload(), b"xyz");
749 assert_eq!(frame.consumed(), 7);
750 // A prefix smaller than the header adjusts to a negative length.
751 assert_eq!(
752 framer.next_frame(&[0, 0, 0, 3]),
753 Err(Error::InvalidLengthPrefix)
754 );
755 }
756
757 #[test]
758 fn length_adjustment_positive() {
759 // The prefix omits a 2-byte field that is part of the payload.
760 let framer =
761 LengthPrefixed::unbounded(LengthWidth::U8, Endian::Big).with_length_adjustment(2);
762 let frame = framer
763 .next_frame(&[0x01, b'a', b'b', b'c'])
764 .unwrap()
765 .unwrap();
766 assert_eq!(frame.payload(), b"abc");
767 let mut out = [0u8; 8];
768 let mut buf = WriteBuf::new(&mut out);
769 framer.write_frame(b"abc", &mut buf).unwrap();
770 assert_eq!(buf.written(), &[0x01, b'a', b'b', b'c']);
771 // A payload shorter than the adjustment cannot be represented.
772 assert_eq!(
773 framer.write_frame(b"a", &mut buf),
774 Err(Error::InvalidLengthPrefix)
775 );
776 }
777
778 #[test]
779 fn length_adjustment_overflowing_the_width() {
780 // 255 payload bytes plus a self-counting header does not fit in u8.
781 let framer =
782 LengthPrefixed::unbounded(LengthWidth::U8, Endian::Big).with_length_adjustment(-1);
783 let mut out = [0u8; 512];
784 let mut buf = WriteBuf::new(&mut out);
785 assert!(matches!(
786 framer.write_frame(&[0u8; 255], &mut buf),
787 Err(Error::FrameTooLarge { .. })
788 ));
789 // The error names the effective bound, not the configured 255.
790 assert_eq!(
791 framer.write_frame(&[0u8; 255], &mut buf),
792 Err(Error::FrameTooLarge {
793 len: 255,
794 limit: 254
795 })
796 );
797 framer.write_frame(&[0u8; 254], &mut buf).unwrap();
798 assert_eq!(buf.written()[0], 255);
799 // A positive adjustment does not raise the payload limit.
800 let framer =
801 LengthPrefixed::unbounded(LengthWidth::U8, Endian::Big).with_length_adjustment(2);
802 assert_eq!(framer.max_payload(), 255);
803 assert_eq!(
804 framer.next_frame(&[0xFF]),
805 Err(Error::FrameTooLarge {
806 len: 257,
807 limit: 255
808 })
809 );
810 }
811
812 #[test]
813 fn saturate_usize_is_identity_when_it_fits() {
814 assert_eq!(saturate_usize(42), 42);
815 if usize::BITS == 32 {
816 assert_eq!(saturate_usize(u64::MAX), usize::MAX);
817 }
818 }
819}