1use zerocopy::FromBytes;
9use zerocopy::byteorder::big_endian::U32 as BeU32;
10
11use crate::frame::{
12 CHECKSUM_LEN, Frame, FrameType, HEADER_CHECKSUM_OFFSET, HEADER_LEN, MAX_PAYLOAD_SIZE,
13 SYNC_WORD, SYNC_WORD_LEN, WireHeader, crc8, crc32,
14};
15
16pub(crate) const MAX_BUFFER_CAPACITY: usize = 64 * 1024;
18
19const COMPACTION_THRESHOLD_BYTES: usize = 4096;
21
22#[derive(Debug)]
24pub struct FrameParser {
25 buffer: Vec<u8>,
26 cursor: usize,
27 checksum_errors: u64,
28}
29
30impl Default for FrameParser {
31 fn default() -> Self {
32 Self::new()
33 }
34}
35
36impl FrameParser {
37 pub fn new() -> Self {
39 Self { buffer: Vec::new(), cursor: 0, checksum_errors: 0 }
40 }
41
42 pub fn reset(&mut self) {
44 self.buffer.clear();
45 self.cursor = 0;
46 self.checksum_errors = 0;
47 }
48
49 pub fn is_empty(&self) -> bool {
51 self.unconsumed_len() == 0
52 }
53
54 pub fn take_unconsumed(&mut self) -> Vec<u8> {
56 let remaining = self.buffer[self.cursor..].to_vec();
57 self.buffer.clear();
58 self.cursor = 0;
59 remaining
60 }
61
62 pub fn checksum_errors(&self) -> u64 {
64 self.checksum_errors
65 }
66
67 #[inline]
69 pub fn unconsumed(&self) -> &[u8] {
70 &self.buffer[self.cursor..]
71 }
72
73 #[inline]
75 pub fn unconsumed_len(&self) -> usize {
76 self.buffer.len() - self.cursor
77 }
78
79 #[inline]
80 fn consume(&mut self, count: usize) {
81 self.cursor += count;
82 if self.cursor >= self.buffer.len() {
83 self.buffer.clear();
84 self.cursor = 0;
85 }
86 }
87
88 pub fn feed(&mut self, data: &[u8]) {
93 let unconsumed_len = self.unconsumed_len();
94 let total_len = unconsumed_len.saturating_add(data.len());
95
96 if self.cursor > 0
98 && (self.cursor >= COMPACTION_THRESHOLD_BYTES
99 || self.cursor >= self.buffer.len() / 2
100 || self.buffer.len().saturating_add(data.len()) > MAX_BUFFER_CAPACITY)
101 {
102 self.buffer.drain(..self.cursor);
103 self.cursor = 0;
104 }
105
106 if total_len > MAX_BUFFER_CAPACITY {
107 let overflow = total_len - MAX_BUFFER_CAPACITY;
108 let current_len = self.buffer.len();
109 if overflow >= current_len {
110 self.buffer.clear();
111 let data_start = data.len().saturating_sub(MAX_BUFFER_CAPACITY);
112 self.buffer.extend_from_slice(&data[data_start..]);
113 return;
114 } else {
115 self.buffer.drain(..overflow);
116 }
117 }
118 self.buffer.extend_from_slice(data);
119 }
120
121 pub fn next_frame(&mut self) -> Option<Frame> {
125 loop {
126 let unconsumed = self.unconsumed();
127 if unconsumed.len() < SYNC_WORD_LEN {
128 return None;
129 }
130
131 let Some(sync_pos) = unconsumed.windows(SYNC_WORD_LEN).position(|w| w == SYNC_WORD)
132 else {
133 let to_consume = if unconsumed.last() == Some(&SYNC_WORD[0]) {
134 unconsumed.len() - 1
135 } else {
136 unconsumed.len()
137 };
138 self.consume(to_consume);
139 return None;
140 };
141 if sync_pos > 0 {
142 self.consume(sync_pos);
143 }
144
145 let unconsumed = self.unconsumed();
146 if unconsumed.len() < HEADER_LEN {
147 return None;
148 }
149
150 let header_fields = &unconsumed[SYNC_WORD_LEN..HEADER_CHECKSUM_OFFSET];
152 let received_hdr_chk = unconsumed[HEADER_CHECKSUM_OFFSET];
153 let calculated_hdr_chk = crc8(header_fields);
154
155 if received_hdr_chk != calculated_hdr_chk {
156 log::debug!(
157 "FrameParser: header checksum mismatch! Calculated: {calculated_hdr_chk:#04x}, received: {received_hdr_chk:#04x}"
158 );
159 self.checksum_errors += 1;
160 self.consume(SYNC_WORD_LEN);
161 continue;
162 }
163
164 let (wire_header, _) = WireHeader::read_from_prefix(unconsumed)
165 .expect("unconsumed length verified >= HEADER_LEN");
166 let session_id = wire_header.session_id.get();
167 let channel_id = wire_header.channel_id.get();
168 let seq = wire_header.seq;
169 let frame_type = FrameType::from(wire_header.frame_type);
170 let length = wire_header.payload_len.get() as usize;
171
172 if length > MAX_PAYLOAD_SIZE {
173 log::debug!("FrameParser: invalid payload length in header: {length}");
174 self.consume(SYNC_WORD_LEN);
175 continue;
176 }
177
178 let total_needed = HEADER_LEN + length + CHECKSUM_LEN;
179 if unconsumed.len() < total_needed {
180 return None;
181 }
182
183 let (received_cksum_be, _) =
184 BeU32::read_from_prefix(&unconsumed[HEADER_LEN + length..])
185 .expect("unconsumed length verified >= total_needed");
186 let received_cksum = received_cksum_be.get();
187
188 let calculated_cksum = crc32(&unconsumed[SYNC_WORD_LEN..HEADER_LEN + length]);
189
190 if received_cksum == calculated_cksum {
191 let is_negotiate =
192 frame_type == FrameType::NegotiateReq || frame_type == FrameType::NegotiateResp;
193 if is_negotiate && channel_id != 0 {
194 log::debug!(
195 "FrameParser: dropping handshake frame {frame_type:?} on non-zero channel {channel_id}"
196 );
197 self.consume(total_needed);
198 continue;
199 }
200
201 let payload = unconsumed[HEADER_LEN..HEADER_LEN + length].to_vec();
202 self.consume(total_needed);
203 return Some(Frame { session_id, channel_id, seq, frame_type, payload });
204 } else {
205 log::debug!(
206 "FrameParser: CRC-32 mismatch for frame type {frame_type:?}, length={length}! Calculated: {calculated_cksum:#010x}, received: {received_cksum:#010x}"
207 );
208 self.checksum_errors += 1;
209 self.consume(SYNC_WORD_LEN);
210 }
211 }
212 }
213}
214
215impl Iterator for FrameParser {
216 type Item = Frame;
217
218 fn next(&mut self) -> Option<Self::Item> {
219 self.next_frame()
220 }
221}
222
223#[cfg(test)]
224mod tests {
225 use super::*;
226 use crate::frame::{FrameType, PAYLOAD_LEN_OFFSET, encode_frame};
227
228 fn assert_frame_eq(
229 frame: &Frame,
230 session_id: u32,
231 channel_id: u16,
232 seq: u8,
233 frame_type: FrameType,
234 payload: &[u8],
235 ) {
236 assert_eq!(frame.session_id, session_id);
237 assert_eq!(frame.channel_id, channel_id);
238 assert_eq!(frame.seq, seq);
239 assert_eq!(frame.frame_type, frame_type);
240 assert_eq!(frame.payload, payload);
241 }
242
243 #[fuchsia::test]
244 fn test_frame_parser_happy_path() {
245 let payload = b"ping_message";
246 let frame = encode_frame(100, 2, 5, FrameType::Data, payload).unwrap();
247 let mut parser = FrameParser::new();
248 parser.feed(&frame);
249
250 let parsed = parser.next_frame().expect("expected parsed frame");
251 assert_frame_eq(&parsed, 100, 2, 5, FrameType::Data, payload);
252 assert_eq!(parser.next_frame(), None);
253 }
254
255 #[fuchsia::test]
256 fn test_frame_parser_partial_reads() {
257 let payload = b"fragmented";
258 let frame = encode_frame(100, 2, 5, FrameType::Data, payload).unwrap();
259 let mut parser = FrameParser::new();
260
261 for chunk in frame.chunks(3) {
262 parser.feed(chunk);
263 }
264
265 let parsed = parser.next_frame().expect("expected frame after fragments");
266 assert_frame_eq(&parsed, 100, 2, 5, FrameType::Data, payload);
267 assert_eq!(parser.next_frame(), None);
268 }
269
270 #[fuchsia::test]
271 fn test_frame_parser_sync_recovery() {
272 let payload = b"recovered";
273 let valid_frame = encode_frame(1, 1, 0, FrameType::Data, payload).unwrap();
274
275 let mut corrupted_stream = vec![0xFF, 0xAA, 0x00, 0x55, 0x12];
276 corrupted_stream.extend_from_slice(&valid_frame);
277
278 let mut parser = FrameParser::new();
279 parser.feed(&corrupted_stream);
280
281 let parsed = parser.next_frame().expect("parser must parse valid frame");
282 assert_eq!(parsed.session_id, 1);
283 assert_eq!(parsed.payload, b"recovered");
284 }
285
286 #[fuchsia::test]
287 fn test_frame_parser_bad_checksum() {
288 let payload = b"corrupted";
289 let mut frame = encode_frame(1, 1, 0, FrameType::Data, payload).unwrap();
290 let len = frame.len();
291 frame[len - 1] ^= 0xFF; let mut parser = FrameParser::new();
294 parser.feed(&frame);
295 assert_eq!(parser.next_frame(), None);
296 assert_eq!(parser.checksum_errors(), 1);
297 }
298
299 #[fuchsia::test]
300 fn test_frame_parser_bad_header_checksum() {
301 let payload = b"header_corrupt";
302 let mut frame = encode_frame(1, 1, 0, FrameType::Data, payload).unwrap();
303 frame[HEADER_CHECKSUM_OFFSET] ^= 0xFF; let mut parser = FrameParser::new();
306 parser.feed(&frame);
307 assert_eq!(parser.next_frame(), None);
308 assert_eq!(parser.checksum_errors(), 1);
309 }
310
311 #[fuchsia::test]
312 fn test_frame_parser_checksum_errors_counter() {
313 let mut parser = FrameParser::new();
314 assert_eq!(parser.checksum_errors(), 0);
315
316 let mut bad_frame = encode_frame(1, 1, 0, FrameType::Data, b"test_crc").unwrap();
317 let len = bad_frame.len();
318 bad_frame[len - 1] ^= 0xFF;
319
320 parser.feed(&bad_frame);
321 assert_eq!(parser.next_frame(), None);
322 assert_eq!(parser.checksum_errors(), 1);
323 }
324
325 #[fuchsia::test]
326 fn test_frame_parser_invalid_length() {
327 let mut frame = vec![0xAA, 0x55];
328 let mut header_fields = [0u8; 10];
329 header_fields[0..4].copy_from_slice(&1u32.to_be_bytes());
330 header_fields[4..6].copy_from_slice(&1u16.to_be_bytes());
331 header_fields[6] = 0;
332 header_fields[7] = u8::from(FrameType::Data);
333 header_fields[8..10].copy_from_slice(&2000u16.to_be_bytes()); let hdr_chk = crc8(&header_fields);
336 frame.extend_from_slice(&header_fields);
337 frame.push(hdr_chk);
338 frame.extend_from_slice(&[0u8; 100]);
339
340 let mut parser = FrameParser::new();
341 parser.feed(&frame);
342 assert_eq!(parser.next_frame(), None);
343 }
344
345 #[fuchsia::test]
346 fn test_frame_parser_bounded_buffer_capacity() {
347 let mut parser = FrameParser::new();
348 let massive_chunk = vec![0x42u8; MAX_BUFFER_CAPACITY + 1024];
349 parser.feed(&massive_chunk);
350 assert!(parser.unconsumed_len() <= MAX_BUFFER_CAPACITY);
351
352 let payload = b"recovered";
353 let valid_frame = encode_frame(1, 1, 0, FrameType::Data, payload).unwrap();
354 parser.feed(&valid_frame);
355
356 let parsed = parser.next_frame().expect("parser must parse valid frame");
357 assert_eq!(parsed.payload, b"recovered");
358 }
359
360 #[fuchsia::test]
361 fn test_frame_parser_recovers_from_corrupted_length_via_header_checksum() {
362 let mut parser = FrameParser::new();
363
364 let mut raw_hdr = [0u8; 10];
365 raw_hdr[0..4].copy_from_slice(&1u32.to_be_bytes());
366 raw_hdr[4..6].copy_from_slice(&1u16.to_be_bytes());
367 raw_hdr[6] = 0;
368 raw_hdr[7] = u8::from(FrameType::Data);
369 raw_hdr[8..10].copy_from_slice(&10u16.to_be_bytes());
370 let hdr_chk = crc8(&raw_hdr);
371
372 let mut corrupted = vec![0xAA, 0x55];
373 corrupted.extend_from_slice(&raw_hdr);
374 corrupted.push(hdr_chk);
375 corrupted.extend_from_slice(b"0123456789");
376
377 corrupted[PAYLOAD_LEN_OFFSET + 1] ^= 0x01;
379
380 parser.feed(&corrupted);
381 assert_eq!(parser.next_frame(), None);
382 assert_eq!(parser.checksum_errors(), 1);
383
384 let valid_frame = encode_frame(1, 1, 1, FrameType::Data, b"hello world payload").unwrap();
386 parser.feed(&valid_frame);
387
388 let parsed = parser.next_frame().expect("Parser must recover after corrupted header");
389 assert_eq!(parsed.session_id, 1);
390 assert_eq!(parsed.payload, b"hello world payload");
391 assert_eq!(parsed.seq, 1);
392
393 let ack_frame = encode_frame(1, 1, 1, FrameType::Ack, &[]).unwrap();
395 parser.feed(&ack_frame);
396 let parsed_ack = parser.next_frame().expect("Subsequent ACK must parse");
397 assert_eq!(parsed_ack.frame_type, FrameType::Ack);
398
399 let close_frame = encode_frame(1, 1, 2, FrameType::Close, &[]).unwrap();
401 parser.feed(&close_frame);
402 let parsed_close = parser.next_frame().expect("Subsequent CLOSE must parse");
403 assert_eq!(parsed_close.frame_type, FrameType::Close);
404 }
405
406 #[fuchsia::test]
407 fn test_negotiate_req_dropped_on_non_zero_channel() {
408 let mut parser = FrameParser::new();
409 let invalid_req = encode_frame(1, 1, 0, FrameType::NegotiateReq, b"invalid").unwrap();
410 parser.feed(&invalid_req);
411 assert_eq!(parser.next_frame(), None);
412
413 let valid_req = encode_frame(1, 0, 0, FrameType::NegotiateReq, b"valid").unwrap();
414 parser.feed(&valid_req);
415 let parsed = parser.next_frame().expect("NegotiateReq on channel 0 accepted");
416 assert_eq!(parsed.channel_id, 0);
417 assert_eq!(parsed.frame_type, FrameType::NegotiateReq);
418 assert_eq!(parsed.payload, b"valid");
419 }
420
421 #[fuchsia::test]
422 fn test_frame_parser_state_management() {
423 let mut parser = FrameParser::new();
424 assert!(parser.is_empty());
425 assert_eq!(parser.unconsumed_len(), 0);
426
427 parser.feed(b"partial_stream_data");
428 assert!(!parser.is_empty());
429 assert_eq!(parser.unconsumed_len(), 19);
430
431 let unconsumed = parser.take_unconsumed();
432 assert_eq!(unconsumed, b"partial_stream_data");
433 assert!(parser.is_empty());
434 assert_eq!(parser.unconsumed_len(), 0);
435
436 parser.feed(b"new_data");
437 assert!(!parser.is_empty());
438 parser.reset();
439 assert!(parser.is_empty());
440 assert_eq!(parser.unconsumed_len(), 0);
441 }
442
443 #[fuchsia::test]
444 fn test_frame_parser_iterator() {
445 let frame1 = encode_frame(1, 1, 0, FrameType::Data, b"first").unwrap();
446 let frame2 = encode_frame(1, 1, 1, FrameType::Data, b"second").unwrap();
447
448 let mut parser = FrameParser::new();
449 parser.feed(&frame1);
450 parser.feed(&frame2);
451
452 let frames: Vec<Frame> = parser.collect();
453 assert_eq!(frames.len(), 2);
454 assert_eq!(frames[0].payload, b"first");
455 assert_eq!(frames[1].payload, b"second");
456 }
457
458 #[fuchsia::test]
459 fn test_differential_chunking() {
460 let mut stream = Vec::new();
461 for i in 0..50u8 {
462 let payload = format!("chunk-msg-{}", i).into_bytes();
463 let frame = encode_frame(42, 1, i, FrameType::Data, &payload).unwrap();
464 stream.extend_from_slice(&frame);
465 if i % 5 == 0 {
466 stream.extend_from_slice(&[0x11, 0x22, 0x33, 0x44]);
467 }
468 }
469
470 let mut parser_mono = FrameParser::new();
471 parser_mono.feed(&stream);
472 let mono_frames: Vec<Frame> = parser_mono.collect();
473
474 let mut parser_frag = FrameParser::new();
475 let mut offset = 0;
476 let chunk_sizes = [1, 3, 7, 2, 5, 4, 11, 1];
477 let mut size_idx = 0;
478 while offset < stream.len() {
479 let sz = chunk_sizes[size_idx % chunk_sizes.len()];
480 size_idx += 1;
481 let end = std::cmp::min(offset + sz, stream.len());
482 parser_frag.feed(&stream[offset..end]);
483 offset = end;
484 }
485 let frag_frames: Vec<Frame> = parser_frag.collect();
486
487 assert_eq!(mono_frames.len(), 50);
488 assert_eq!(mono_frames, frag_frames);
489 }
490}