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::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}