1use std::sync::atomic::{AtomicU64, Ordering};
18use std::sync::Arc;
19use thiserror::Error;
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub enum ArrowFrameType {
28 Schema,
30 RecordBatch,
32 DictionaryBatch,
34 EosMarker,
36}
37
38#[derive(Debug, Clone)]
40pub struct ArrowFrame {
41 pub metadata: Vec<u8>,
43 pub body: Vec<u8>,
45 pub frame_type: ArrowFrameType,
47 pub sequence: u64,
49}
50
51#[derive(Debug, Error)]
57pub enum DeframerError {
58 #[error("invalid continuation marker: expected FF FF FF FF, got {got:02X?}")]
60 InvalidContinuationMarker { got: [u8; 4] },
61
62 #[error("metadata too large: {size} bytes exceeds maximum {max} bytes")]
64 MetadataTooLarge { size: u32, max: u32 },
65
66 #[error("body too large: {size} bytes exceeds maximum {max} bytes")]
68 BodyTooLarge { size: u64, max: u64 },
69
70 #[error("unexpected end of input while assembling frame")]
72 UnexpectedEof,
73}
74
75const CONTINUATION_MARKER: [u8; 4] = [0xFF, 0xFF, 0xFF, 0xFF];
80
81enum DeframerState {
86 WaitingForContinuation,
88
89 ReadingMetadataLen { buf: [u8; 4], read: usize },
91
92 ReadingMetadata { len: u32, buf: Vec<u8>, read: usize },
94
95 ReadingBody {
104 metadata: Vec<u8>,
105 body_len: u64,
106 buf: Vec<u8>,
107 read: usize,
108 },
109}
110
111const BODY_LEN_PENDING: u64 = u64::MAX;
114
115impl std::fmt::Debug for DeframerState {
116 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
117 match self {
118 DeframerState::WaitingForContinuation => write!(f, "WaitingForContinuation"),
119 DeframerState::ReadingMetadataLen { read, .. } => {
120 write!(f, "ReadingMetadataLen(read={read})")
121 }
122 DeframerState::ReadingMetadata { len, read, .. } => {
123 write!(f, "ReadingMetadata(len={len}, read={read})")
124 }
125 DeframerState::ReadingBody { body_len, read, .. } => {
126 write!(f, "ReadingBody(body_len={body_len}, read={read})")
127 }
128 }
129 }
130}
131
132#[derive(Debug, Default)]
138pub struct DeframerStats {
139 pub total_bytes_pushed: AtomicU64,
141 pub total_frames_complete: AtomicU64,
143 pub total_errors: AtomicU64,
145 pub total_resets: AtomicU64,
147}
148
149#[derive(Debug, Clone, Copy, PartialEq, Eq)]
151pub struct DeframerStatsSnapshot {
152 pub total_bytes_pushed: u64,
153 pub total_frames_complete: u64,
154 pub total_errors: u64,
155 pub total_resets: u64,
156}
157
158impl DeframerStats {
159 fn new() -> Arc<Self> {
160 Arc::new(Self::default())
161 }
162
163 pub fn snapshot(&self) -> DeframerStatsSnapshot {
165 DeframerStatsSnapshot {
166 total_bytes_pushed: self.total_bytes_pushed.load(Ordering::Relaxed),
167 total_frames_complete: self.total_frames_complete.load(Ordering::Relaxed),
168 total_errors: self.total_errors.load(Ordering::Relaxed),
169 total_resets: self.total_resets.load(Ordering::Relaxed),
170 }
171 }
172}
173
174const DEFAULT_MAX_METADATA_BYTES: u32 = 64 * 1024 * 1024;
180
181const DEFAULT_MAX_BODY_BYTES: u64 = 2 * 1024 * 1024 * 1024;
183
184#[derive(Debug)]
196pub struct ArrowStreamDeframer {
197 state: DeframerState,
199 cont_buf: [u8; 4],
201 cont_read: usize,
203 sequence: u64,
205 max_metadata_bytes: u32,
207 max_body_bytes: u64,
209 stats: Arc<DeframerStats>,
211}
212
213impl Default for ArrowStreamDeframer {
214 fn default() -> Self {
215 Self::new()
216 }
217}
218
219impl ArrowStreamDeframer {
220 pub fn new() -> Self {
222 Self {
223 state: DeframerState::WaitingForContinuation,
224 cont_buf: [0u8; 4],
225 cont_read: 0,
226 sequence: 0,
227 max_metadata_bytes: DEFAULT_MAX_METADATA_BYTES,
228 max_body_bytes: DEFAULT_MAX_BODY_BYTES,
229 stats: DeframerStats::new(),
230 }
231 }
232
233 pub fn with_limits(max_metadata_bytes: u32, max_body_bytes: u64) -> Self {
235 Self {
236 state: DeframerState::WaitingForContinuation,
237 cont_buf: [0u8; 4],
238 cont_read: 0,
239 sequence: 0,
240 max_metadata_bytes,
241 max_body_bytes,
242 stats: DeframerStats::new(),
243 }
244 }
245
246 pub fn is_idle(&self) -> bool {
249 matches!(self.state, DeframerState::WaitingForContinuation) && self.cont_read == 0
250 }
251
252 pub fn reset(&mut self) {
254 self.state = DeframerState::WaitingForContinuation;
255 self.cont_buf = [0u8; 4];
256 self.cont_read = 0;
257 self.stats.total_resets.fetch_add(1, Ordering::Relaxed);
258 }
259
260 pub fn stats(&self) -> Arc<DeframerStats> {
262 Arc::clone(&self.stats)
263 }
264
265 pub fn push(&mut self, data: &[u8]) -> Result<Vec<ArrowFrame>, DeframerError> {
271 self.stats
272 .total_bytes_pushed
273 .fetch_add(data.len() as u64, Ordering::Relaxed);
274
275 let mut cursor = 0usize;
276 let mut frames = Vec::new();
277
278 while cursor < data.len() {
279 if let Some(frame) = self.step(data, &mut cursor)? {
280 self.stats
281 .total_frames_complete
282 .fetch_add(1, Ordering::Relaxed);
283 frames.push(frame);
284 }
285 }
286
287 Ok(frames)
288 }
289
290 fn step(
298 &mut self,
299 data: &[u8],
300 cursor: &mut usize,
301 ) -> Result<Option<ArrowFrame>, DeframerError> {
302 match &self.state {
303 DeframerState::WaitingForContinuation => {
307 while self.cont_read < 4 && *cursor < data.len() {
309 self.cont_buf[self.cont_read] = data[*cursor];
310 *cursor += 1;
311 self.cont_read += 1;
312 }
313
314 if self.cont_read < 4 {
315 return Ok(None);
317 }
318
319 let got = self.cont_buf;
321 self.cont_read = 0;
322 self.cont_buf = [0u8; 4];
323
324 if got != CONTINUATION_MARKER {
325 self.stats.total_errors.fetch_add(1, Ordering::Relaxed);
326 self.state = DeframerState::WaitingForContinuation;
327 return Err(DeframerError::InvalidContinuationMarker { got });
328 }
329
330 self.state = DeframerState::ReadingMetadataLen {
332 buf: [0u8; 4],
333 read: 0,
334 };
335 Ok(None)
336 }
337
338 DeframerState::ReadingMetadataLen { .. } => {
342 let (mut buf, mut read) = if let DeframerState::ReadingMetadataLen { buf, read } =
344 std::mem::replace(&mut self.state, DeframerState::WaitingForContinuation)
345 {
346 (buf, read)
347 } else {
348 unreachable!()
349 };
350
351 while read < 4 && *cursor < data.len() {
352 buf[read] = data[*cursor];
353 *cursor += 1;
354 read += 1;
355 }
356
357 if read < 4 {
358 self.state = DeframerState::ReadingMetadataLen { buf, read };
359 return Ok(None);
360 }
361
362 let meta_len = u32::from_le_bytes(buf);
363
364 if meta_len == 0 {
366 let seq = self.sequence;
367 self.sequence += 1;
368 self.state = DeframerState::WaitingForContinuation;
369 return Ok(Some(ArrowFrame {
370 metadata: Vec::new(),
371 body: Vec::new(),
372 frame_type: ArrowFrameType::EosMarker,
373 sequence: seq,
374 }));
375 }
376
377 if meta_len > self.max_metadata_bytes {
379 self.stats.total_errors.fetch_add(1, Ordering::Relaxed);
380 self.state = DeframerState::WaitingForContinuation;
381 return Err(DeframerError::MetadataTooLarge {
382 size: meta_len,
383 max: self.max_metadata_bytes,
384 });
385 }
386
387 self.state = DeframerState::ReadingMetadata {
388 len: meta_len,
389 buf: vec![0u8; meta_len as usize],
390 read: 0,
391 };
392 Ok(None)
393 }
394
395 DeframerState::ReadingMetadata { .. } => {
399 let (len, mut buf, mut read) =
400 if let DeframerState::ReadingMetadata { len, buf, read } =
401 std::mem::replace(&mut self.state, DeframerState::WaitingForContinuation)
402 {
403 (len, buf, read)
404 } else {
405 unreachable!()
406 };
407
408 let needed = len as usize - read;
409 let available = data.len() - *cursor;
410 let take = needed.min(available);
411
412 buf[read..read + take].copy_from_slice(&data[*cursor..*cursor + take]);
413 *cursor += take;
414 read += take;
415
416 if read < len as usize {
417 self.state = DeframerState::ReadingMetadata { len, buf, read };
418 return Ok(None);
419 }
420
421 self.state = DeframerState::ReadingBody {
423 metadata: buf,
424 body_len: BODY_LEN_PENDING,
425 buf: Vec::with_capacity(8),
426 read: 0,
427 };
428 Ok(None)
429 }
430
431 DeframerState::ReadingBody { .. } => {
435 let (metadata, mut body_len, mut buf, mut read) =
436 if let DeframerState::ReadingBody {
437 metadata,
438 body_len,
439 buf,
440 read,
441 } = std::mem::replace(&mut self.state, DeframerState::WaitingForContinuation)
442 {
443 (metadata, body_len, buf, read)
444 } else {
445 unreachable!()
446 };
447
448 if body_len == BODY_LEN_PENDING {
450 while read < 8 && *cursor < data.len() {
451 buf.push(data[*cursor]);
452 *cursor += 1;
453 read += 1;
454 }
455
456 if read < 8 {
457 self.state = DeframerState::ReadingBody {
458 metadata,
459 body_len: BODY_LEN_PENDING,
460 buf,
461 read,
462 };
463 return Ok(None);
464 }
465
466 let len_arr: [u8; 8] =
468 buf[..8].try_into().expect("slice is known to be 8 bytes");
469 body_len = u64::from_le_bytes(len_arr);
470
471 if body_len > self.max_body_bytes {
473 self.stats.total_errors.fetch_add(1, Ordering::Relaxed);
474 self.state = DeframerState::WaitingForContinuation;
475 return Err(DeframerError::BodyTooLarge {
476 size: body_len,
477 max: self.max_body_bytes,
478 });
479 }
480
481 buf = vec![0u8; body_len as usize];
483 read = 0;
484 }
485
486 if body_len == 0 {
488 let seq = self.sequence;
489 self.sequence += 1;
490 self.state = DeframerState::WaitingForContinuation;
491 return Ok(Some(ArrowFrame {
492 metadata,
493 body: Vec::new(),
494 frame_type: ArrowFrameType::RecordBatch,
495 sequence: seq,
496 }));
497 }
498
499 let needed = body_len as usize - read;
500 let available = data.len() - *cursor;
501 let take = needed.min(available);
502
503 buf[read..read + take].copy_from_slice(&data[*cursor..*cursor + take]);
504 *cursor += take;
505 read += take;
506
507 if (read as u64) < body_len {
508 self.state = DeframerState::ReadingBody {
509 metadata,
510 body_len,
511 buf,
512 read,
513 };
514 return Ok(None);
515 }
516
517 let seq = self.sequence;
519 self.sequence += 1;
520 self.state = DeframerState::WaitingForContinuation;
521 Ok(Some(ArrowFrame {
522 metadata,
523 body: buf,
524 frame_type: ArrowFrameType::RecordBatch,
525 sequence: seq,
526 }))
527 }
528 }
529 }
530}
531
532pub fn build_test_frame(metadata: &[u8], body: &[u8]) -> Vec<u8> {
540 let mut out = Vec::new();
541 out.extend_from_slice(&CONTINUATION_MARKER);
542 out.extend_from_slice(&(metadata.len() as u32).to_le_bytes());
543 out.extend_from_slice(metadata);
544 out.extend_from_slice(&(body.len() as u64).to_le_bytes());
545 out.extend_from_slice(body);
546 out
547}
548
549pub fn build_test_eos() -> Vec<u8> {
551 let mut out = Vec::new();
552 out.extend_from_slice(&CONTINUATION_MARKER);
553 out.extend_from_slice(&0u32.to_le_bytes());
554 out
555}
556
557#[cfg(test)]
562mod tests {
563 use super::*;
564
565 #[test]
569 fn test_single_complete_frame() {
570 let meta = b"flatbuf-meta";
571 let body = b"body-data-here";
572 let raw = build_test_frame(meta, body);
573
574 let mut df = ArrowStreamDeframer::new();
575 let frames = df.push(&raw).expect("should succeed");
576
577 assert_eq!(frames.len(), 1);
578 assert_eq!(frames[0].metadata, meta);
579 assert_eq!(frames[0].body, body);
580 assert_eq!(frames[0].sequence, 0);
581 assert!(df.is_idle());
582 }
583
584 #[test]
588 fn test_frame_split_across_two_pushes() {
589 let meta = b"schema-meta-bytes";
590 let body = b"record-body";
591 let raw = build_test_frame(meta, body);
592
593 let split = raw.len() / 2;
594 let mut df = ArrowStreamDeframer::new();
595
596 let frames_first = df.push(&raw[..split]).expect("first push ok");
597 assert!(frames_first.is_empty(), "no complete frames yet");
598 assert!(!df.is_idle());
599
600 let frames_second = df.push(&raw[split..]).expect("second push ok");
601 assert_eq!(frames_second.len(), 1);
602 assert_eq!(frames_second[0].metadata, meta);
603 assert_eq!(frames_second[0].body, body);
604 }
605
606 #[test]
610 fn test_multiple_frames_in_one_push() {
611 let mut raw = Vec::new();
612 for i in 0u8..3 {
613 let meta = vec![i; 8];
614 let body = vec![i + 10; 16];
615 raw.extend_from_slice(&build_test_frame(&meta, &body));
616 }
617
618 let mut df = ArrowStreamDeframer::new();
619 let frames = df.push(&raw).expect("ok");
620 assert_eq!(frames.len(), 3);
621 for (i, frame) in frames.iter().enumerate() {
622 assert_eq!(frame.sequence, i as u64);
623 assert_eq!(frame.metadata, vec![i as u8; 8]);
624 assert_eq!(frame.body, vec![i as u8 + 10; 16]);
625 }
626 }
627
628 #[test]
632 fn test_eos_marker_produces_eos_frame() {
633 let eos = build_test_eos();
634 let mut df = ArrowStreamDeframer::new();
635 let frames = df.push(&eos).expect("ok");
636 assert_eq!(frames.len(), 1);
637 assert_eq!(frames[0].frame_type, ArrowFrameType::EosMarker);
638 assert!(frames[0].metadata.is_empty());
639 assert!(frames[0].body.is_empty());
640 }
641
642 #[test]
646 fn test_invalid_continuation_marker() {
647 let bad: Vec<u8> = vec![0x00, 0x01, 0x02, 0x03, 0xFF, 0xFF, 0xFF, 0xFF];
648 let mut df = ArrowStreamDeframer::new();
649 let result = df.push(&bad);
650 assert!(result.is_err());
651 match result.unwrap_err() {
652 DeframerError::InvalidContinuationMarker { got } => {
653 assert_eq!(got, [0x00, 0x01, 0x02, 0x03]);
654 }
655 other => panic!("unexpected error: {other}"),
656 }
657 assert!(df.is_idle());
659 }
660
661 #[test]
665 fn test_empty_body_frame() {
666 let meta = b"schema-only";
667 let raw = build_test_frame(meta, b"");
668
669 let mut df = ArrowStreamDeframer::new();
670 let frames = df.push(&raw).expect("ok");
671 assert_eq!(frames.len(), 1);
672 assert_eq!(frames[0].metadata, meta);
673 assert!(frames[0].body.is_empty());
674 }
675
676 #[test]
680 fn test_is_idle_state_tracking() {
681 let meta = b"m";
682 let body = b"b";
683 let raw = build_test_frame(meta, body);
684
685 let mut df = ArrowStreamDeframer::new();
686 assert!(df.is_idle(), "idle before any data");
687
688 df.push(&raw[..4]).expect("ok");
690 assert!(!df.is_idle(), "not idle mid-frame");
691
692 df.push(&raw[4..]).expect("ok");
694 assert!(df.is_idle(), "idle after frame completed");
695 }
696
697 #[test]
701 fn test_reset_clears_state() {
702 let meta = b"partial";
703 let body = b"data";
704 let raw = build_test_frame(meta, body);
705
706 let mut df = ArrowStreamDeframer::new();
707 df.push(&raw[..4]).expect("ok");
708 assert!(!df.is_idle());
709
710 df.reset();
711 assert!(df.is_idle());
712
713 let frames = df.push(&raw).expect("ok after reset");
715 assert_eq!(frames.len(), 1);
716 }
717
718 #[test]
722 fn test_stats_accumulation() {
723 let raw1 = build_test_frame(b"meta1", b"body1");
724 let raw2 = build_test_frame(b"meta2", b"body2");
725 let combined = [raw1.clone(), raw2.clone()].concat();
726
727 let mut df = ArrowStreamDeframer::new();
728 df.push(&combined).expect("ok");
729
730 let snap = df.stats().snapshot();
731 assert_eq!(snap.total_bytes_pushed, combined.len() as u64);
732 assert_eq!(snap.total_frames_complete, 2);
733 assert_eq!(snap.total_errors, 0);
734 assert_eq!(snap.total_resets, 0);
735 }
736
737 #[test]
741 fn test_large_frame_within_limits() {
742 let meta = vec![0xABu8; 1024];
743 let body = vec![0xCDu8; 65536];
744 let raw = build_test_frame(&meta, &body);
745
746 let mut df = ArrowStreamDeframer::new();
747 let frames = df.push(&raw).expect("ok");
748 assert_eq!(frames.len(), 1);
749 assert_eq!(frames[0].metadata.len(), 1024);
750 assert_eq!(frames[0].body.len(), 65536);
751 }
752
753 #[test]
757 fn test_sequence_counter_increments() {
758 let mut raw = Vec::new();
759 for _ in 0..5 {
760 raw.extend_from_slice(&build_test_frame(b"meta", b"body"));
761 }
762
763 let mut df = ArrowStreamDeframer::new();
764 let frames = df.push(&raw).expect("ok");
765 assert_eq!(frames.len(), 5);
766 for (expected_seq, frame) in frames.iter().enumerate() {
767 assert_eq!(frame.sequence, expected_seq as u64);
768 }
769 }
770
771 #[test]
775 fn test_metadata_too_large_error() {
776 let mut raw = Vec::new();
778 raw.extend_from_slice(&CONTINUATION_MARKER);
779 let huge: u32 = 65 * 1024 * 1024;
781 raw.extend_from_slice(&huge.to_le_bytes());
782
783 let mut df = ArrowStreamDeframer::new();
784 let result = df.push(&raw);
785 assert!(result.is_err());
786 match result.unwrap_err() {
787 DeframerError::MetadataTooLarge { size, max } => {
788 assert_eq!(size, huge);
789 assert_eq!(max, DEFAULT_MAX_METADATA_BYTES);
790 }
791 other => panic!("unexpected: {other}"),
792 }
793
794 let snap = df.stats().snapshot();
795 assert_eq!(snap.total_errors, 1);
796 }
797
798 #[test]
802 fn test_body_too_large_error() {
803 let meta = b"valid-meta";
804 let mut raw = Vec::new();
805 raw.extend_from_slice(&CONTINUATION_MARKER);
806 raw.extend_from_slice(&(meta.len() as u32).to_le_bytes());
807 raw.extend_from_slice(meta);
808 let huge_body: u64 = 3 * 1024 * 1024 * 1024;
810 raw.extend_from_slice(&huge_body.to_le_bytes());
811
812 let mut df = ArrowStreamDeframer::new();
813 let result = df.push(&raw);
814 assert!(result.is_err());
815 match result.unwrap_err() {
816 DeframerError::BodyTooLarge { size, max } => {
817 assert_eq!(size, huge_body);
818 assert_eq!(max, DEFAULT_MAX_BODY_BYTES);
819 }
820 other => panic!("unexpected: {other}"),
821 }
822
823 let snap = df.stats().snapshot();
824 assert_eq!(snap.total_errors, 1);
825 }
826
827 #[test]
831 fn test_frame_followed_by_eos() {
832 let mut raw = Vec::new();
833 raw.extend_from_slice(&build_test_frame(b"schema", b""));
834 raw.extend_from_slice(&build_test_eos());
835
836 let mut df = ArrowStreamDeframer::new();
837 let frames = df.push(&raw).expect("ok");
838 assert_eq!(frames.len(), 2);
839 assert_eq!(frames[0].frame_type, ArrowFrameType::RecordBatch);
840 assert_eq!(frames[1].frame_type, ArrowFrameType::EosMarker);
841 assert_eq!(frames[0].sequence, 0);
842 assert_eq!(frames[1].sequence, 1);
843 assert!(df.is_idle());
844 }
845
846 #[test]
850 fn test_byte_at_a_time_feeding() {
851 let meta = b"granular-meta";
852 let body = b"granular-body";
853 let raw = build_test_frame(meta, body);
854
855 let mut df = ArrowStreamDeframer::new();
856 let mut all_frames = Vec::new();
857 for byte in &raw {
858 let mut frames = df.push(std::slice::from_ref(byte)).expect("ok");
859 all_frames.append(&mut frames);
860 }
861 assert_eq!(all_frames.len(), 1);
862 assert_eq!(all_frames[0].metadata, meta);
863 assert_eq!(all_frames[0].body, body);
864 assert!(df.is_idle());
865 }
866
867 #[test]
871 fn test_reset_increments_counter() {
872 let mut df = ArrowStreamDeframer::new();
873 df.reset();
874 df.reset();
875 df.reset();
876 let snap = df.stats().snapshot();
877 assert_eq!(snap.total_resets, 3);
878 }
879
880 #[test]
884 fn test_error_counter_on_bad_marker() {
885 let bad = [0xDE, 0xAD, 0xBE, 0xEF];
886 let mut df = ArrowStreamDeframer::new();
887 let _ = df.push(&bad);
888 let snap = df.stats().snapshot();
889 assert_eq!(snap.total_errors, 1);
890 }
891}