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