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.
6
7use crate::buf::WriteBuf;
8use crate::error::{Error, Result};
9use crate::framing::{Frame, Framer};
10
11/// Width of the length prefix, in bytes.
12///
13/// Marked `#[non_exhaustive]` so wider prefix widths can be added in future
14/// minor releases without a major version bump.
15#[non_exhaustive]
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
17pub enum LengthWidth {
18    /// One-byte length prefix; payload up to 255 bytes.
19    U8,
20    /// Two-byte length prefix; payload up to 65 535 bytes.
21    U16,
22    /// Four-byte length prefix; payload up to 4 294 967 295 bytes.
23    U32,
24}
25
26impl LengthWidth {
27    /// Width of the prefix in bytes.
28    #[inline]
29    pub const fn header_size(self) -> usize {
30        match self {
31            LengthWidth::U8 => 1,
32            LengthWidth::U16 => 2,
33            LengthWidth::U32 => 4,
34        }
35    }
36
37    /// Largest payload size encodable by this width.
38    #[inline]
39    pub const fn max_payload(self) -> u64 {
40        match self {
41            LengthWidth::U8 => u8::MAX as u64,
42            LengthWidth::U16 => u16::MAX as u64,
43            LengthWidth::U32 => u32::MAX as u64,
44        }
45    }
46}
47
48/// Byte order for the length prefix.
49#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
50pub enum Endian {
51    /// Most significant byte first (network order).
52    Big,
53    /// Least significant byte first.
54    Little,
55}
56
57/// Length-prefixed framer.
58///
59/// # Example
60///
61/// ```
62/// use wire_codec::WriteBuf;
63/// use wire_codec::framing::{Endian, Framer, LengthPrefixed, LengthWidth};
64///
65/// let framer = LengthPrefixed::new(LengthWidth::U16, Endian::Big);
66///
67/// let mut out = [0u8; 16];
68/// let mut buf = WriteBuf::new(&mut out);
69/// framer.write_frame(b"hello", &mut buf).unwrap();
70/// let written = buf.position();
71/// assert_eq!(&out[..written], &[0x00, 0x05, b'h', b'e', b'l', b'l', b'o']);
72///
73/// let frame = framer.next_frame(&out[..written]).unwrap().unwrap();
74/// assert_eq!(frame.payload(), b"hello");
75/// assert_eq!(frame.consumed(), 7);
76/// ```
77#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
78pub struct LengthPrefixed {
79    width: LengthWidth,
80    endian: Endian,
81    max_payload: u64,
82}
83
84impl LengthPrefixed {
85    /// Build a framer using the given prefix width and endianness.
86    ///
87    /// The maximum payload size defaults to the largest value the prefix can
88    /// encode. Use [`LengthPrefixed::with_max_payload`] to tighten it.
89    #[inline]
90    pub const fn new(width: LengthWidth, endian: Endian) -> Self {
91        Self {
92            width,
93            endian,
94            max_payload: width.max_payload(),
95        }
96    }
97
98    /// Override the payload size limit enforced when reading and writing frames.
99    ///
100    /// The limit is clamped to [`LengthWidth::max_payload`] of the configured
101    /// width.
102    #[inline]
103    #[must_use]
104    pub const fn with_max_payload(mut self, max: u64) -> Self {
105        let cap = self.width.max_payload();
106        self.max_payload = if max < cap { max } else { cap };
107        self
108    }
109
110    /// Configured prefix width.
111    #[inline]
112    pub const fn width(&self) -> LengthWidth {
113        self.width
114    }
115
116    /// Configured endianness.
117    #[inline]
118    pub const fn endian(&self) -> Endian {
119        self.endian
120    }
121
122    /// Configured maximum payload size in bytes.
123    #[inline]
124    pub const fn max_payload(&self) -> u64 {
125        self.max_payload
126    }
127
128    fn read_prefix(&self, header: &[u8]) -> u64 {
129        match (self.width, self.endian) {
130            (LengthWidth::U8, _) => u64::from(header[0]),
131            (LengthWidth::U16, Endian::Big) => {
132                u64::from(u16::from_be_bytes([header[0], header[1]]))
133            }
134            (LengthWidth::U16, Endian::Little) => {
135                u64::from(u16::from_le_bytes([header[0], header[1]]))
136            }
137            (LengthWidth::U32, Endian::Big) => u64::from(u32::from_be_bytes([
138                header[0], header[1], header[2], header[3],
139            ])),
140            (LengthWidth::U32, Endian::Little) => u64::from(u32::from_le_bytes([
141                header[0], header[1], header[2], header[3],
142            ])),
143        }
144    }
145
146    fn write_prefix(&self, len: u64, out: &mut WriteBuf<'_>) -> Result<()> {
147        match (self.width, self.endian) {
148            (LengthWidth::U8, _) => out.write_u8(len as u8),
149            (LengthWidth::U16, Endian::Big) => out.write_u16_be(len as u16),
150            (LengthWidth::U16, Endian::Little) => out.write_u16_le(len as u16),
151            (LengthWidth::U32, Endian::Big) => out.write_u32_be(len as u32),
152            (LengthWidth::U32, Endian::Little) => out.write_u32_le(len as u32),
153        }
154    }
155}
156
157impl Framer for LengthPrefixed {
158    fn next_frame<'a>(&self, input: &'a [u8]) -> Result<Option<Frame<'a>>> {
159        let header_size = self.width.header_size();
160        if input.len() < header_size {
161            return Ok(None);
162        }
163        let len = self.read_prefix(&input[..header_size]);
164        if len > self.max_payload {
165            return Err(Error::FrameTooLarge {
166                len: len as usize,
167                limit: self.max_payload as usize,
168            });
169        }
170        let consumed = header_size + (len as usize);
171        if input.len() < consumed {
172            return Ok(None);
173        }
174        let payload = &input[header_size..consumed];
175        Ok(Some(Frame::new(payload, consumed)))
176    }
177
178    fn write_frame(&self, payload: &[u8], out: &mut WriteBuf<'_>) -> Result<()> {
179        let len = payload.len() as u64;
180        if len > self.max_payload {
181            return Err(Error::FrameTooLarge {
182                len: payload.len(),
183                limit: self.max_payload as usize,
184            });
185        }
186        self.write_prefix(len, out)?;
187        out.write_bytes(payload)
188    }
189}
190
191#[cfg(test)]
192mod tests {
193    use super::*;
194
195    #[test]
196    fn u8_big_round_trip() {
197        let framer = LengthPrefixed::new(LengthWidth::U8, Endian::Big);
198        let mut out = [0u8; 32];
199        let mut buf = WriteBuf::new(&mut out);
200        framer.write_frame(b"abc", &mut buf).unwrap();
201        let n = buf.position();
202        assert_eq!(&out[..n], &[0x03, b'a', b'b', b'c']);
203
204        let frame = framer.next_frame(&out[..n]).unwrap().unwrap();
205        assert_eq!(frame.payload(), b"abc");
206        assert_eq!(frame.consumed(), 4);
207    }
208
209    #[test]
210    fn partial_frame_returns_none() {
211        let framer = LengthPrefixed::new(LengthWidth::U16, Endian::Big);
212        // Header advertises 10 bytes but only 3 follow.
213        let input = &[0x00, 0x0A, 0x01, 0x02, 0x03];
214        assert_eq!(framer.next_frame(input).unwrap(), None);
215    }
216
217    #[test]
218    fn empty_input_returns_none() {
219        let framer = LengthPrefixed::new(LengthWidth::U16, Endian::Big);
220        assert_eq!(framer.next_frame(&[]).unwrap(), None);
221    }
222
223    #[test]
224    fn exceeds_max_payload() {
225        let framer = LengthPrefixed::new(LengthWidth::U16, Endian::Big).with_max_payload(4);
226        let input = &[0x00, 0x05, 0, 0, 0, 0, 0];
227        assert!(matches!(
228            framer.next_frame(input),
229            Err(Error::FrameTooLarge { len: 5, limit: 4 })
230        ));
231    }
232
233    #[test]
234    fn write_rejects_oversize_payload() {
235        let framer = LengthPrefixed::new(LengthWidth::U8, Endian::Big).with_max_payload(3);
236        let mut out = [0u8; 16];
237        let mut buf = WriteBuf::new(&mut out);
238        assert!(matches!(
239            framer.write_frame(&[0u8; 4], &mut buf),
240            Err(Error::FrameTooLarge { len: 4, limit: 3 })
241        ));
242    }
243}