rpi_cli/remote/
framing.rs1use std::fmt;
10
11pub const FRAME_HEADER_LENGTH: usize = 4;
13pub const DEFAULT_MAX_FRAME_LENGTH: usize = 16 * 1024 * 1024;
15pub const PAYLOAD_BLOCK_SIZE: usize = 64 * 1024;
17
18#[derive(Debug, Clone, PartialEq, Eq)]
20pub struct FrameError(pub String);
21
22impl fmt::Display for FrameError {
23 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
24 f.write_str(&self.0)
25 }
26}
27
28impl std::error::Error for FrameError {}
29
30pub fn encode_frame(payload: &[u8]) -> Result<Vec<u8>, FrameError> {
32 if payload.len() > u32::MAX as usize {
33 return Err(FrameError("Frame payload exceeds the unsigned 32-bit length limit".into()));
34 }
35 let mut frame = Vec::with_capacity(FRAME_HEADER_LENGTH + payload.len());
36 frame.extend_from_slice(&(payload.len() as u32).to_be_bytes());
37 frame.extend_from_slice(payload);
38 Ok(frame)
39}
40
41pub fn assert_complete_frame(frame: &[u8], max_frame_length: usize) -> Result<(), FrameError> {
43 if frame.len() < FRAME_HEADER_LENGTH {
44 return Err(FrameError("Frame does not contain a complete length prefix".into()));
45 }
46 let length = u32::from_be_bytes([frame[0], frame[1], frame[2], frame[3]]) as usize;
47 if length > max_frame_length {
48 return Err(FrameError(format!(
49 "Frame length {length} exceeds configured limit of {max_frame_length}"
50 )));
51 }
52 if frame.len() != FRAME_HEADER_LENGTH + length {
53 return Err(FrameError("Frame must contain exactly one complete payload".into()));
54 }
55 Ok(())
56}
57
58#[derive(Debug, Clone, Copy, PartialEq, Eq)]
59enum State {
60 Open,
61 Ended,
62 Failed,
63}
64
65pub struct FrameDecoder {
67 header: [u8; FRAME_HEADER_LENGTH],
68 header_len: usize,
69 max_frame_length: usize,
70 payload: Vec<u8>,
71 expected_payload_len: Option<usize>,
72 state: State,
73}
74
75impl FrameDecoder {
76 pub fn new(max_frame_length: usize) -> Self {
77 Self {
78 header: [0; FRAME_HEADER_LENGTH],
79 header_len: 0,
80 max_frame_length,
81 payload: Vec::new(),
82 expected_payload_len: None,
83 state: State::Open,
84 }
85 }
86
87 pub fn with_default_max() -> Self {
88 Self::new(DEFAULT_MAX_FRAME_LENGTH)
89 }
90
91 pub fn push(&mut self, chunk: &[u8]) -> Result<Vec<Vec<u8>>, FrameError> {
93 match self.state {
94 State::Ended => return Err(FrameError("Frame decoder has ended".into())),
95 State::Failed => return Err(FrameError("Frame decoder has failed".into())),
96 State::Open => {}
97 }
98
99 let mut frames = Vec::new();
100 let mut offset = 0usize;
101 while offset < chunk.len() {
102 if self.expected_payload_len.is_none() {
103 let take = (FRAME_HEADER_LENGTH - self.header_len).min(chunk.len() - offset);
104 self.header[self.header_len..self.header_len + take]
105 .copy_from_slice(&chunk[offset..offset + take]);
106 self.header_len += take;
107 offset += take;
108 if self.header_len < FRAME_HEADER_LENGTH {
109 continue;
110 }
111 let frame_length =
112 u32::from_be_bytes(self.header) as usize;
113 self.header_len = 0;
114 if frame_length > self.max_frame_length {
115 self.state = State::Failed;
116 return Err(FrameError(format!(
117 "Frame length {frame_length} exceeds configured limit of {}",
118 self.max_frame_length
119 )));
120 }
121 if frame_length == 0 {
122 frames.push(Vec::new());
123 continue;
124 }
125 self.expected_payload_len = Some(frame_length);
126 self.payload = Vec::with_capacity(frame_length.min(PAYLOAD_BLOCK_SIZE));
127 }
128
129 let expected = self.expected_payload_len.unwrap();
130 let want = expected - self.payload.len();
131 let take = want.min(chunk.len() - offset);
132 self.payload.extend_from_slice(&chunk[offset..offset + take]);
133 offset += take;
134
135 if self.payload.len() == expected {
136 frames.push(std::mem::take(&mut self.payload));
137 self.expected_payload_len = None;
138 }
139 }
140 Ok(frames)
141 }
142
143 pub fn end(&mut self) -> Result<(), FrameError> {
145 if self.header_len != 0 || self.expected_payload_len.is_some() {
146 self.state = State::Failed;
147 return Err(FrameError("Frame decoder ended mid-frame".into()));
148 }
149 self.state = State::Ended;
150 Ok(())
151 }
152}
153
154#[cfg(test)]
155mod tests {
156 use super::*;
157
158 #[test]
159 fn encode_round_trip() {
160 let frame = encode_frame(b"hello").unwrap();
161 assert_eq!(&frame[..4], &[0, 0, 0, 5]);
162 assert_complete_frame(&frame, DEFAULT_MAX_FRAME_LENGTH).unwrap();
163 }
164
165 #[test]
166 fn decoder_splits_multiple_and_partial_chunks() {
167 let mut decoder = FrameDecoder::with_default_max();
168 let a = encode_frame(b"one").unwrap();
169 let b = encode_frame(b"two").unwrap();
170 let mut stream = Vec::new();
171 stream.extend_from_slice(&a);
172 stream.extend_from_slice(&b);
173
174 let out1 = decoder.push(&stream[..2]).unwrap();
176 assert!(out1.is_empty());
177 let out2 = decoder.push(&stream[2..5]).unwrap();
178 assert!(out2.is_empty());
179 let out3 = decoder.push(&stream[5..7]).unwrap();
180 assert_eq!(out3, vec![b"one".to_vec()]);
181 let out4 = decoder.push(&stream[7..]).unwrap();
182 assert_eq!(out4, vec![b"two".to_vec()]);
183 decoder.end().unwrap();
184 }
185
186 #[test]
187 fn empty_frame() {
188 let frame = encode_frame(b"").unwrap();
189 let mut decoder = FrameDecoder::with_default_max();
190 assert_eq!(decoder.push(&frame).unwrap(), vec![Vec::<u8>::new()]);
191 }
192
193 #[test]
194 fn rejects_oversized() {
195 let frame = encode_frame(&vec![0u8; 32]).unwrap();
196 assert!(assert_complete_frame(&frame, 16).is_err());
197 let mut decoder = FrameDecoder::new(16);
198 assert!(decoder.push(&frame).is_err());
199 }
200
201 #[test]
202 fn rejects_trailing_bytes() {
203 let mut frame = encode_frame(b"x").unwrap();
204 frame.push(0);
205 assert!(assert_complete_frame(&frame, DEFAULT_MAX_FRAME_LENGTH).is_err());
206 }
207}