Skip to main content

ormdb_proto/
framing.rs

1//! Message framing utilities for transport layer.
2//!
3//! This module provides length-prefix framing for messages sent over the wire.
4//! The format is simple: 4-byte big-endian length prefix followed by the payload.
5
6use crate::Error;
7
8/// Maximum message size (64 MB).
9pub const MAX_MESSAGE_SIZE: usize = 64 * 1024 * 1024;
10
11/// Size of the length prefix in bytes.
12pub const LENGTH_PREFIX_SIZE: usize = 4;
13
14/// Encode a payload with a length prefix.
15///
16/// Returns a new buffer containing `[length (4 bytes BE)][payload]`.
17pub fn encode_frame(payload: &[u8]) -> Result<Vec<u8>, Error> {
18    if payload.len() > MAX_MESSAGE_SIZE {
19        return Err(Error::InvalidMessage(format!(
20            "payload size {} exceeds maximum {}",
21            payload.len(),
22            MAX_MESSAGE_SIZE
23        )));
24    }
25
26    let len = payload.len() as u32;
27    let mut frame = Vec::with_capacity(LENGTH_PREFIX_SIZE + payload.len());
28    frame.extend_from_slice(&len.to_be_bytes());
29    frame.extend_from_slice(payload);
30    Ok(frame)
31}
32
33/// Decode the length from a 4-byte header.
34///
35/// Returns the payload length as a usize.
36pub fn decode_frame_length(header: &[u8; LENGTH_PREFIX_SIZE]) -> Result<usize, Error> {
37    let len = u32::from_be_bytes(*header) as usize;
38    if len > MAX_MESSAGE_SIZE {
39        return Err(Error::InvalidMessage(format!(
40            "frame length {} exceeds maximum {}",
41            len, MAX_MESSAGE_SIZE
42        )));
43    }
44    Ok(len)
45}
46
47/// Read a length prefix from a byte slice.
48///
49/// Returns the length and a slice starting after the prefix.
50pub fn read_length_prefix(data: &[u8]) -> Result<(usize, &[u8]), Error> {
51    if data.len() < LENGTH_PREFIX_SIZE {
52        return Err(Error::InvalidMessage(format!(
53            "buffer too short for length prefix: {} < {}",
54            data.len(),
55            LENGTH_PREFIX_SIZE
56        )));
57    }
58
59    let mut header = [0u8; LENGTH_PREFIX_SIZE];
60    header.copy_from_slice(&data[..LENGTH_PREFIX_SIZE]);
61    let len = decode_frame_length(&header)?;
62
63    Ok((len, &data[LENGTH_PREFIX_SIZE..]))
64}
65
66/// Validate that a buffer contains a complete frame.
67///
68/// Returns the total frame size (including prefix) if complete, or None if more data is needed.
69pub fn frame_complete(data: &[u8]) -> Option<usize> {
70    if data.len() < LENGTH_PREFIX_SIZE {
71        return None;
72    }
73
74    let mut header = [0u8; LENGTH_PREFIX_SIZE];
75    header.copy_from_slice(&data[..LENGTH_PREFIX_SIZE]);
76    let len = u32::from_be_bytes(header) as usize;
77
78    let total = LENGTH_PREFIX_SIZE + len;
79    if data.len() >= total {
80        Some(total)
81    } else {
82        None
83    }
84}
85
86/// Extract the payload from a complete frame.
87///
88/// Assumes the frame is complete (use `frame_complete` to check first).
89pub fn extract_payload(frame: &[u8]) -> Result<&[u8], Error> {
90    if frame.len() < LENGTH_PREFIX_SIZE {
91        return Err(Error::InvalidMessage("frame too short".to_string()));
92    }
93
94    let mut header = [0u8; LENGTH_PREFIX_SIZE];
95    header.copy_from_slice(&frame[..LENGTH_PREFIX_SIZE]);
96    let len = decode_frame_length(&header)?;
97
98    if frame.len() < LENGTH_PREFIX_SIZE + len {
99        return Err(Error::InvalidMessage(format!(
100            "frame incomplete: have {}, need {}",
101            frame.len(),
102            LENGTH_PREFIX_SIZE + len
103        )));
104    }
105
106    Ok(&frame[LENGTH_PREFIX_SIZE..LENGTH_PREFIX_SIZE + len])
107}
108
109#[cfg(test)]
110mod tests {
111    use super::*;
112
113    #[test]
114    fn test_encode_frame_empty() {
115        let frame = encode_frame(&[]).unwrap();
116        assert_eq!(frame.len(), LENGTH_PREFIX_SIZE);
117        assert_eq!(&frame[..4], &[0, 0, 0, 0]);
118    }
119
120    #[test]
121    fn test_encode_frame_small() {
122        let payload = b"hello";
123        let frame = encode_frame(payload).unwrap();
124
125        assert_eq!(frame.len(), LENGTH_PREFIX_SIZE + payload.len());
126        // Length should be 5 in big-endian
127        assert_eq!(&frame[..4], &[0, 0, 0, 5]);
128        assert_eq!(&frame[4..], payload);
129    }
130
131    #[test]
132    fn test_encode_frame_large() {
133        let payload = vec![0u8; 1000];
134        let frame = encode_frame(&payload).unwrap();
135
136        assert_eq!(frame.len(), LENGTH_PREFIX_SIZE + 1000);
137        // Length 1000 = 0x3E8 in big-endian
138        assert_eq!(&frame[..4], &[0, 0, 0x03, 0xE8]);
139    }
140
141    #[test]
142    fn test_encode_frame_too_large() {
143        let payload = vec![0u8; MAX_MESSAGE_SIZE + 1];
144        let result = encode_frame(&payload);
145        assert!(result.is_err());
146    }
147
148    #[test]
149    fn test_decode_frame_length() {
150        // Zero length
151        let header = [0, 0, 0, 0];
152        assert_eq!(decode_frame_length(&header).unwrap(), 0);
153
154        // Small length (5)
155        let header = [0, 0, 0, 5];
156        assert_eq!(decode_frame_length(&header).unwrap(), 5);
157
158        // Larger length (1000)
159        let header = [0, 0, 0x03, 0xE8];
160        assert_eq!(decode_frame_length(&header).unwrap(), 1000);
161
162        // Max valid length
163        let max_len = MAX_MESSAGE_SIZE as u32;
164        let header = max_len.to_be_bytes();
165        assert_eq!(decode_frame_length(&header).unwrap(), MAX_MESSAGE_SIZE);
166    }
167
168    #[test]
169    fn test_decode_frame_length_too_large() {
170        let too_large = (MAX_MESSAGE_SIZE as u32) + 1;
171        let header = too_large.to_be_bytes();
172        let result = decode_frame_length(&header);
173        assert!(result.is_err());
174    }
175
176    #[test]
177    fn test_read_length_prefix() {
178        let data = [0, 0, 0, 5, 1, 2, 3, 4, 5, 6, 7];
179        let (len, rest) = read_length_prefix(&data).unwrap();
180
181        assert_eq!(len, 5);
182        assert_eq!(rest, &[1, 2, 3, 4, 5, 6, 7]);
183    }
184
185    #[test]
186    fn test_read_length_prefix_too_short() {
187        let data = [0, 0, 0];
188        let result = read_length_prefix(&data);
189        assert!(result.is_err());
190    }
191
192    #[test]
193    fn test_frame_complete() {
194        // Incomplete - not enough for header
195        assert_eq!(frame_complete(&[0, 0, 0]), None);
196
197        // Incomplete - header present but no payload
198        assert_eq!(frame_complete(&[0, 0, 0, 5]), None);
199
200        // Incomplete - partial payload
201        assert_eq!(frame_complete(&[0, 0, 0, 5, 1, 2, 3]), None);
202
203        // Complete - empty payload
204        assert_eq!(frame_complete(&[0, 0, 0, 0]), Some(4));
205
206        // Complete - with payload
207        assert_eq!(frame_complete(&[0, 0, 0, 3, 1, 2, 3]), Some(7));
208
209        // Complete - with extra data
210        assert_eq!(frame_complete(&[0, 0, 0, 2, 1, 2, 3, 4, 5]), Some(6));
211    }
212
213    #[test]
214    fn test_extract_payload() {
215        // Empty payload
216        let frame = [0, 0, 0, 0];
217        let payload = extract_payload(&frame).unwrap();
218        assert!(payload.is_empty());
219
220        // Non-empty payload
221        let frame = [0, 0, 0, 3, 1, 2, 3];
222        let payload = extract_payload(&frame).unwrap();
223        assert_eq!(payload, &[1, 2, 3]);
224
225        // With extra data (should only extract declared length)
226        let frame = [0, 0, 0, 2, 1, 2, 3, 4, 5];
227        let payload = extract_payload(&frame).unwrap();
228        assert_eq!(payload, &[1, 2]);
229    }
230
231    #[test]
232    fn test_roundtrip() {
233        let original = b"The quick brown fox jumps over the lazy dog";
234        let frame = encode_frame(original).unwrap();
235        let payload = extract_payload(&frame).unwrap();
236        assert_eq!(payload, original);
237    }
238
239    #[test]
240    fn test_roundtrip_binary() {
241        let original: Vec<u8> = (0..=255).collect();
242        let frame = encode_frame(&original).unwrap();
243        let payload = extract_payload(&frame).unwrap();
244        assert_eq!(payload, original.as_slice());
245    }
246}