1use crate::error::{DecodeError, DecodeResult};
4
5pub const DEFAULT_MAX_MESSAGE_BYTES: usize = 268_435_456; #[derive(Debug, Clone, PartialEq, Eq)]
11pub enum SegmentOutcome {
12 Complete(Vec<u8>),
16 Pending,
18 Control(Vec<u8>),
21}
22
23struct Pending {
24 header: [u8; 8],
25 payloads: Vec<Vec<u8>>,
26 total: usize,
27}
28
29pub struct SegmentReassembler {
35 max_bytes: usize,
36 pending: Option<Pending>,
37}
38
39impl SegmentReassembler {
40 pub fn new() -> Self {
42 Self::with_max_bytes(DEFAULT_MAX_MESSAGE_BYTES)
43 }
44
45 pub fn with_max_bytes(max_bytes: usize) -> Self {
47 Self { max_bytes, pending: None }
48 }
49
50 pub fn pending_bytes(&self) -> usize {
52 self.pending.as_ref().map_or(0, |p| p.total)
53 }
54
55 pub fn reset(&mut self) {
57 self.pending = None;
58 }
59
60 pub fn push(&mut self, header: [u8; 8], payload: Vec<u8>) -> DecodeResult<SegmentOutcome> {
66 let flags = header[2];
67
68 if (flags & 0x01) != 0 {
69 let mut out = Vec::with_capacity(8 + payload.len());
70 out.extend_from_slice(&header);
71 out.extend_from_slice(&payload);
72 return Ok(SegmentOutcome::Control(out));
73 }
74
75 let command = header[3];
76 match (flags & 0x30) >> 4 {
77 0 => {
79 if let Some(p) = self.pending.take() {
80 return Err(DecodeError::SegmentInterrupted {
81 expected: p.header[3],
82 got: command,
83 });
84 }
85 let mut out = Vec::with_capacity(8 + payload.len());
86 out.extend_from_slice(&header);
87 out.extend_from_slice(&payload);
88 Ok(SegmentOutcome::Complete(out))
89 }
90 1 => {
92 if self.pending.take().is_some() {
93 return Err(DecodeError::UnexpectedSegment { flags });
94 }
95 if payload.len() > self.max_bytes {
96 return Err(DecodeError::MessageTooLarge {
97 total: payload.len(),
98 limit: self.max_bytes,
99 });
100 }
101 self.pending = Some(Pending {
102 header,
103 total: payload.len(),
104 payloads: vec![payload],
105 });
106 Ok(SegmentOutcome::Pending)
107 }
108 code => {
110 let mut p = match self.pending.take() {
111 Some(p) => p,
112 None => return Err(DecodeError::UnexpectedSegment { flags }),
113 };
114 if p.header[3] != command || (p.header[2] & 0x40) != (flags & 0x40) {
115 return Err(DecodeError::SegmentCommandMismatch {
116 expected: p.header[3],
117 got: command,
118 });
119 }
120 let total = p.total + payload.len();
121 if total > self.max_bytes {
122 return Err(DecodeError::MessageTooLarge { total, limit: self.max_bytes });
123 }
124 p.total = total;
125 p.payloads.push(payload);
126 if code == 2 {
127 Ok(SegmentOutcome::Complete(finish(p)))
128 } else {
129 self.pending = Some(p);
130 Ok(SegmentOutcome::Pending)
131 }
132 }
133 }
134 }
135}
136
137impl Default for SegmentReassembler {
138 fn default() -> Self {
139 Self::new()
140 }
141}
142
143fn finish(p: Pending) -> Vec<u8> {
145 let mut header = p.header;
146 let is_be = (header[2] & 0x80) != 0;
147 header[2] &= !0x30;
148 let total = p.total as u32;
149 let len = if is_be { total.to_be_bytes() } else { total.to_le_bytes() };
150 header[4..8].copy_from_slice(&len);
151
152 let mut out = Vec::with_capacity(8 + p.total);
153 out.extend_from_slice(&header);
154 for payload in p.payloads {
155 out.extend_from_slice(&payload);
156 }
157 out
158}
159
160#[cfg(test)]
161mod tests {
162 use super::*;
163 use crate::error::DecodeError;
164
165 fn hdr(command: u8, seg: u8, payload_len: u32, is_be: bool) -> [u8; 8] {
168 let mut flags = 0x40u8; if is_be {
170 flags |= 0x80;
171 }
172 flags |= seg << 4;
173 let mut h = [0u8; 8];
174 h[0] = 0xCA;
175 h[1] = 2;
176 h[2] = flags;
177 h[3] = command;
178 let l = if is_be { payload_len.to_be_bytes() } else { payload_len.to_le_bytes() };
179 h[4..8].copy_from_slice(&l);
180 h
181 }
182
183 fn control_hdr() -> [u8; 8] {
184 let mut h = hdr(3, 0, 0, false);
185 h[2] |= 0x01;
186 h
187 }
188
189 #[test]
190 fn three_segments_reassemble_into_one_message() {
191 let mut r = SegmentReassembler::new();
192 assert_eq!(r.push(hdr(13, 1, 2, false), vec![1, 2]).unwrap(), SegmentOutcome::Pending);
193 assert_eq!(r.push(hdr(13, 3, 2, false), vec![3, 4]).unwrap(), SegmentOutcome::Pending);
194 let out = match r.push(hdr(13, 2, 2, false), vec![5, 6]).unwrap() {
195 SegmentOutcome::Complete(b) => b,
196 other => panic!("expected Complete, got {other:?}"),
197 };
198 assert_eq!(&out[8..], &[1, 2, 3, 4, 5, 6]);
199 assert_eq!(out[0], 0xCA, "magic byte preserved");
200 assert_eq!(out[1], 2, "version byte preserved");
201 assert_eq!(out[2] & 0x30, 0, "segment bits must be cleared");
202 assert_eq!(u32::from_le_bytes(out[4..8].try_into().unwrap()), 6);
203 assert_eq!(out[3], 13, "command byte preserved");
204 assert_eq!(r.pending_bytes(), 0);
205 }
206
207 #[test]
208 fn big_endian_length_is_rewritten_big_endian() {
209 let mut r = SegmentReassembler::new();
210 r.push(hdr(13, 1, 2, true), vec![1, 2]).unwrap();
211 let out = match r.push(hdr(13, 2, 1, true), vec![3]).unwrap() {
212 SegmentOutcome::Complete(b) => b,
213 other => panic!("expected Complete, got {other:?}"),
214 };
215 assert_eq!(u32::from_be_bytes(out[4..8].try_into().unwrap()), 3);
216 }
217
218 #[test]
219 fn unsegmented_message_passes_straight_through() {
220 let mut r = SegmentReassembler::new();
221 let out = match r.push(hdr(11, 0, 3, false), vec![7, 8, 9]).unwrap() {
222 SegmentOutcome::Complete(b) => b,
223 other => panic!("expected Complete, got {other:?}"),
224 };
225 assert_eq!(&out[8..], &[7, 8, 9]);
226 }
227
228 #[test]
229 fn control_frame_between_segments_does_not_disturb_reassembly() {
230 let mut r = SegmentReassembler::new();
231 r.push(hdr(13, 1, 2, false), vec![1, 2]).unwrap();
232 match r.push(control_hdr(), vec![]).unwrap() {
233 SegmentOutcome::Control(b) => assert_eq!(b.len(), 8),
234 other => panic!("expected Control, got {other:?}"),
235 }
236 assert_eq!(r.pending_bytes(), 2, "pending state survives the control frame");
237 let out = match r.push(hdr(13, 2, 2, false), vec![3, 4]).unwrap() {
238 SegmentOutcome::Complete(b) => b,
239 other => panic!("expected Complete, got {other:?}"),
240 };
241 assert_eq!(&out[8..], &[1, 2, 3, 4]);
242 }
243
244 #[test]
245 fn unsegmented_message_mid_reassembly_is_an_error() {
246 let mut r = SegmentReassembler::new();
247 r.push(hdr(13, 1, 2, false), vec![1, 2]).unwrap();
248 assert_eq!(
249 r.push(hdr(11, 0, 1, false), vec![9]).unwrap_err(),
250 DecodeError::SegmentInterrupted { expected: 13, got: 11 }
251 );
252 assert_eq!(r.pending_bytes(), 0, "state is reset after the error");
253 }
254
255 #[test]
256 fn orphan_middle_and_last_segments_are_errors() {
257 let mut r = SegmentReassembler::new();
258 assert!(matches!(
259 r.push(hdr(13, 3, 1, false), vec![1]).unwrap_err(),
260 DecodeError::UnexpectedSegment { .. }
261 ));
262 assert!(matches!(
263 r.push(hdr(13, 2, 1, false), vec![1]).unwrap_err(),
264 DecodeError::UnexpectedSegment { .. }
265 ));
266 }
267
268 #[test]
269 fn second_first_segment_while_pending_is_an_error() {
270 let mut r = SegmentReassembler::new();
271 r.push(hdr(13, 1, 1, false), vec![1]).unwrap();
272 assert!(matches!(
273 r.push(hdr(13, 1, 1, false), vec![2]).unwrap_err(),
274 DecodeError::UnexpectedSegment { .. }
275 ));
276 assert_eq!(r.pending_bytes(), 0);
277 }
278
279 #[test]
280 fn command_mismatch_across_segments_is_an_error() {
281 let mut r = SegmentReassembler::new();
282 r.push(hdr(13, 1, 1, false), vec![1]).unwrap();
283 assert_eq!(
284 r.push(hdr(11, 3, 1, false), vec![2]).unwrap_err(),
285 DecodeError::SegmentCommandMismatch { expected: 13, got: 11 }
286 );
287 assert_eq!(r.pending_bytes(), 0);
288 }
289
290 #[test]
291 fn exceeding_the_cap_errors_and_leaves_the_reassembler_reusable() {
292 let mut r = SegmentReassembler::with_max_bytes(4);
293 r.push(hdr(13, 1, 3, false), vec![1, 2, 3]).unwrap();
294 assert_eq!(
295 r.push(hdr(13, 3, 3, false), vec![4, 5, 6]).unwrap_err(),
296 DecodeError::MessageTooLarge { total: 6, limit: 4 }
297 );
298 assert_eq!(r.pending_bytes(), 0);
299 let out = match r.push(hdr(11, 0, 1, false), vec![9]).unwrap() {
301 SegmentOutcome::Complete(b) => b,
302 other => panic!("expected Complete, got {other:?}"),
303 };
304 assert_eq!(&out[8..], &[9]);
305 }
306
307 #[test]
308 fn reset_discards_pending_state() {
309 let mut r = SegmentReassembler::new();
310 r.push(hdr(13, 1, 2, false), vec![1, 2]).unwrap();
311 assert_eq!(r.pending_bytes(), 2);
312 r.reset();
313 assert_eq!(r.pending_bytes(), 0);
314 }
315}