Skip to main content

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