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}