Skip to main content

pim_protocol/
length_delimited.rs

1use bytes::{Buf, BufMut, BytesMut};
2
3use pim_core::PimError;
4
5/// Length-delimited framing for stream transports (TCP).
6///
7/// Each message is prefixed with a 4-byte big-endian length.
8/// This codec handles framing only — it doesn't interpret the payload.
9pub struct LengthDelimitedCodec;
10
11/// Maximum frame size: 1 MB.
12const MAX_FRAME_SIZE: u32 = 1_048_576;
13
14impl LengthDelimitedCodec {
15    /// Wrap a message with a 4-byte length prefix.
16    pub fn encode(payload: &[u8], buf: &mut BytesMut) {
17        buf.put_u32(payload.len() as u32);
18        buf.put_slice(payload);
19    }
20
21    /// Try to extract a complete framed message from the buffer.
22    ///
23    /// Returns:
24    /// - `Ok(Some(bytes))` if a complete message was extracted
25    /// - `Ok(None)` if more data is needed
26    /// - `Err(...)` if the frame is invalid
27    pub fn decode(buf: &mut BytesMut) -> Result<Option<BytesMut>, PimError> {
28        if buf.len() < 4 {
29            return Ok(None);
30        }
31
32        let length = (&buf[0..4]).get_u32();
33
34        if length > MAX_FRAME_SIZE {
35            return Err(PimError::Protocol(format!(
36                "frame too large: {length} bytes, max {MAX_FRAME_SIZE}"
37            )));
38        }
39
40        let total = 4 + length as usize;
41        if buf.len() < total {
42            return Ok(None); // need more data
43        }
44
45        buf.advance(4); // skip length prefix
46        let payload = buf.split_to(length as usize);
47        Ok(Some(payload))
48    }
49}
50
51#[cfg(test)]
52mod tests;