Skip to main content

ipfrs_transport/
arrow_deframer.rs

1//! Arrow IPC stream deframer for TensorSwap.
2//!
3//! When receiving Arrow IPC data over TensorSwap, chunks arrive in arbitrary
4//! sizes. This module reassembles them into complete Arrow IPC messages using
5//! the standard frame format:
6//!
7//! ```text
8//! [ 4 bytes continuation marker 0xFF_FF_FF_FF ]
9//! [ 4 bytes metadata length (LE u32) ]
10//! [ metadata_length bytes flatbuffer ]
11//! [ 8 bytes body length (LE u64) ]  ← only present when metadata_length > 0
12//! [ body_length bytes body data ]
13//! ```
14//!
15//! An EOS marker is signalled when metadata_length == 0x00_00_00_00.
16
17use std::sync::atomic::{AtomicU64, Ordering};
18use std::sync::Arc;
19use thiserror::Error;
20
21// ---------------------------------------------------------------------------
22// Public types
23// ---------------------------------------------------------------------------
24
25/// The type of an Arrow IPC frame.
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub enum ArrowFrameType {
28    /// Schema message (metadata only, body is empty).
29    Schema,
30    /// Record batch message.
31    RecordBatch,
32    /// Dictionary batch message.
33    DictionaryBatch,
34    /// End-of-stream marker (metadata_length == 0).
35    EosMarker,
36}
37
38/// A fully reassembled Arrow IPC frame.
39#[derive(Debug, Clone)]
40pub struct ArrowFrame {
41    /// Serialized Arrow flatbuffer metadata.
42    pub metadata: Vec<u8>,
43    /// Record batch body (may be empty for schema / EOS messages).
44    pub body: Vec<u8>,
45    /// Logical frame type.
46    pub frame_type: ArrowFrameType,
47    /// Monotonically increasing frame counter within the stream (starts at 0).
48    pub sequence: u64,
49}
50
51// ---------------------------------------------------------------------------
52// Errors
53// ---------------------------------------------------------------------------
54
55/// Errors returned by [`ArrowStreamDeframer::push`].
56#[derive(Debug, Error)]
57pub enum DeframerError {
58    /// The 4-byte continuation marker was not `0xFF_FF_FF_FF`.
59    #[error("invalid continuation marker: expected FF FF FF FF, got {got:02X?}")]
60    InvalidContinuationMarker { got: [u8; 4] },
61
62    /// The metadata length field exceeds the configured maximum.
63    #[error("metadata too large: {size} bytes exceeds maximum {max} bytes")]
64    MetadataTooLarge { size: u32, max: u32 },
65
66    /// The body length field exceeds the configured maximum.
67    #[error("body too large: {size} bytes exceeds maximum {max} bytes")]
68    BodyTooLarge { size: u64, max: u64 },
69
70    /// The input stream ended before a frame was complete.
71    #[error("unexpected end of input while assembling frame")]
72    UnexpectedEof,
73}
74
75// ---------------------------------------------------------------------------
76// Internal state machine
77// ---------------------------------------------------------------------------
78
79const CONTINUATION_MARKER: [u8; 4] = [0xFF, 0xFF, 0xFF, 0xFF];
80
81/// Internal state of the deframer.
82///
83/// Each variant represents the portion of the frame format that still needs
84/// to be consumed from the input stream.
85enum DeframerState {
86    /// Waiting for the 4-byte continuation marker `0xFF_FF_FF_FF`.
87    WaitingForContinuation,
88
89    /// Reading the 4-byte metadata length field (little-endian u32).
90    ReadingMetadataLen { buf: [u8; 4], read: usize },
91
92    /// Reading `len` bytes of flatbuffer metadata.
93    ReadingMetadata { len: u32, buf: Vec<u8>, read: usize },
94
95    /// Reading the 8-byte body-length header followed by the body.
96    ///
97    /// Phase 1 (`body_len == BODY_LEN_PENDING`): reading the 8-byte header.
98    ///   In this phase `buf` holds the header bytes read so far and `read`
99    ///   counts how many of those 8 bytes have been consumed.
100    ///
101    /// Phase 2 (`body_len` is the actual value): reading the body bytes.
102    ///   `buf` holds the body bytes read so far and `read` counts how many.
103    ReadingBody {
104        metadata: Vec<u8>,
105        body_len: u64,
106        buf: Vec<u8>,
107        read: usize,
108    },
109}
110
111/// Sentinel value used in `ReadingBody.body_len` to signal that we are still
112/// reading the 8-byte body-length header.
113const 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// ---------------------------------------------------------------------------
133// Statistics
134// ---------------------------------------------------------------------------
135
136/// Atomically-updated statistics for an [`ArrowStreamDeframer`].
137#[derive(Debug, Default)]
138pub struct DeframerStats {
139    /// Total bytes fed via [`ArrowStreamDeframer::push`].
140    pub total_bytes_pushed: AtomicU64,
141    /// Total fully-assembled frames returned.
142    pub total_frames_complete: AtomicU64,
143    /// Total errors encountered.
144    pub total_errors: AtomicU64,
145    /// Total times [`ArrowStreamDeframer::reset`] was called explicitly.
146    pub total_resets: AtomicU64,
147}
148
149/// A snapshot of [`DeframerStats`] with plain `u64` values.
150#[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    /// Take a consistent (relaxed) snapshot of all counters.
164    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
174// ---------------------------------------------------------------------------
175// Deframer
176// ---------------------------------------------------------------------------
177
178/// Default maximum metadata size: 64 MiB.
179const DEFAULT_MAX_METADATA_BYTES: u32 = 64 * 1024 * 1024;
180
181/// Default maximum body size: 2 GiB.
182const DEFAULT_MAX_BODY_BYTES: u64 = 2 * 1024 * 1024 * 1024;
183
184/// Reassembles Arrow IPC stream chunks into complete [`ArrowFrame`]s.
185///
186/// # Frame format (simplified)
187///
188/// ```text
189/// 4 bytes   continuation marker  (must be 0xFF_FF_FF_FF)
190/// 4 bytes   metadata length LE u32  (0 → EOS)
191/// N bytes   metadata flatbuffer
192/// 8 bytes   body length LE u64      (only when metadata_length > 0)
193/// M bytes   body
194/// ```
195#[derive(Debug)]
196pub struct ArrowStreamDeframer {
197    /// Current parser state.
198    state: DeframerState,
199    /// Continuation marker bytes accumulated so far (for partial reads).
200    cont_buf: [u8; 4],
201    /// How many continuation marker bytes have been read so far.
202    cont_read: usize,
203    /// Frame sequence counter.
204    sequence: u64,
205    /// Maximum metadata size in bytes.
206    max_metadata_bytes: u32,
207    /// Maximum body size in bytes.
208    max_body_bytes: u64,
209    /// Shared stats handle.
210    stats: Arc<DeframerStats>,
211}
212
213impl Default for ArrowStreamDeframer {
214    fn default() -> Self {
215        Self::new()
216    }
217}
218
219impl ArrowStreamDeframer {
220    /// Create a new deframer with default limits (64 MiB metadata, 2 GiB body).
221    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    /// Create a deframer with explicit limits.
234    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    /// Returns `true` when the deframer is idle (waiting for the next frame's
247    /// continuation marker), i.e., no partial frame is buffered.
248    pub fn is_idle(&self) -> bool {
249        matches!(self.state, DeframerState::WaitingForContinuation) && self.cont_read == 0
250    }
251
252    /// Reset the deframer to the idle state.  Increments the reset counter.
253    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    /// Return a clone of the shared statistics handle.
261    pub fn stats(&self) -> Arc<DeframerStats> {
262        Arc::clone(&self.stats)
263    }
264
265    /// Feed `data` into the deframer.  Returns all complete frames assembled
266    /// during this call, or a [`DeframerError`] if the stream is malformed.
267    ///
268    /// On error the deframer is automatically reset so the caller may attempt
269    /// to continue with later data (e.g., after re-synchronisation).
270    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    // -----------------------------------------------------------------------
291    // Internal helpers
292    // -----------------------------------------------------------------------
293
294    /// Advance the state machine by consuming bytes from `data[*cursor..]`.
295    /// Returns `Some(frame)` when a frame is complete, `None` when more data
296    /// is needed or when a state transition happened without producing output.
297    fn step(
298        &mut self,
299        data: &[u8],
300        cursor: &mut usize,
301    ) -> Result<Option<ArrowFrame>, DeframerError> {
302        match &self.state {
303            // ------------------------------------------------------------------
304            // 1. Waiting for the 4-byte continuation marker
305            // ------------------------------------------------------------------
306            DeframerState::WaitingForContinuation => {
307                // Accumulate bytes into cont_buf until we have 4.
308                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                    // Still waiting for more bytes.
316                    return Ok(None);
317                }
318
319                // Full marker received — validate.
320                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                // Transition: start reading the 4-byte metadata length.
331                self.state = DeframerState::ReadingMetadataLen {
332                    buf: [0u8; 4],
333                    read: 0,
334                };
335                Ok(None)
336            }
337
338            // ------------------------------------------------------------------
339            // 2. Reading the 4-byte metadata length (LE u32)
340            // ------------------------------------------------------------------
341            DeframerState::ReadingMetadataLen { .. } => {
342                // Extract fields by replacing state with sentinel then restoring.
343                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                // EOS: meta_len == 0.
365                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                // Enforce metadata size limit.
378                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            // ------------------------------------------------------------------
396            // 3. Reading `len` bytes of flatbuffer metadata
397            // ------------------------------------------------------------------
398            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                // Metadata complete — begin reading the 8-byte body-length header.
422                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            // ------------------------------------------------------------------
432            // 4. Reading 8-byte body-length header then body bytes
433            // ------------------------------------------------------------------
434            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                // Sub-phase 1: read the 8-byte body-length header.
449                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                    // Parse the 8 header bytes.
467                    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                    // Enforce body size limit.
472                    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                    // Prepare body buffer and reset read counter for body bytes.
482                    buf = vec![0u8; body_len as usize];
483                    read = 0;
484                }
485
486                // Sub-phase 2: read body_len bytes of body data.
487                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                // Body complete — emit frame.
518                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
532// ---------------------------------------------------------------------------
533// Test frame builders
534// ---------------------------------------------------------------------------
535
536/// Build a raw Arrow IPC frame for testing.
537///
538/// Format: `[FF FF FF FF][meta_len_le4][metadata][body_len_le8][body]`
539pub 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
549/// Build a raw Arrow IPC EOS frame for testing.
550pub 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// ---------------------------------------------------------------------------
558// Tests
559// ---------------------------------------------------------------------------
560
561#[cfg(test)]
562mod tests {
563    use super::*;
564
565    // -----------------------------------------------------------------------
566    // T-01: Single complete frame parsed correctly
567    // -----------------------------------------------------------------------
568    #[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    // -----------------------------------------------------------------------
585    // T-02: Frame split across two pushes
586    // -----------------------------------------------------------------------
587    #[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    // -----------------------------------------------------------------------
607    // T-03: Multiple frames in one push
608    // -----------------------------------------------------------------------
609    #[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    // -----------------------------------------------------------------------
629    // T-04: EOS marker produces EosMarker frame
630    // -----------------------------------------------------------------------
631    #[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    // -----------------------------------------------------------------------
643    // T-05: Invalid continuation marker returns error
644    // -----------------------------------------------------------------------
645    #[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        // After error the deframer should be idle again.
658        assert!(df.is_idle());
659    }
660
661    // -----------------------------------------------------------------------
662    // T-06: Frame with empty body (body_length == 0)
663    // -----------------------------------------------------------------------
664    #[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    // -----------------------------------------------------------------------
677    // T-07: is_idle state tracking
678    // -----------------------------------------------------------------------
679    #[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        // Feed partial: just the continuation marker
689        df.push(&raw[..4]).expect("ok");
690        assert!(!df.is_idle(), "not idle mid-frame");
691
692        // Feed the rest
693        df.push(&raw[4..]).expect("ok");
694        assert!(df.is_idle(), "idle after frame completed");
695    }
696
697    // -----------------------------------------------------------------------
698    // T-08: reset clears state
699    // -----------------------------------------------------------------------
700    #[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        // After reset, a fresh frame should be parseable.
714        let frames = df.push(&raw).expect("ok after reset");
715        assert_eq!(frames.len(), 1);
716    }
717
718    // -----------------------------------------------------------------------
719    // T-09: Stats accumulation
720    // -----------------------------------------------------------------------
721    #[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    // -----------------------------------------------------------------------
738    // T-10: Large frame within limits
739    // -----------------------------------------------------------------------
740    #[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    // -----------------------------------------------------------------------
754    // T-11: Sequence counter increments
755    // -----------------------------------------------------------------------
756    #[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    // -----------------------------------------------------------------------
772    // T-12: MetadataTooLarge error
773    // -----------------------------------------------------------------------
774    #[test]
775    fn test_metadata_too_large_error() {
776        // Craft a frame that claims a huge metadata length.
777        let mut raw = Vec::new();
778        raw.extend_from_slice(&CONTINUATION_MARKER);
779        // Claim metadata_length = 65 MB (above default 64 MB limit).
780        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    // -----------------------------------------------------------------------
799    // T-13: BodyTooLarge error
800    // -----------------------------------------------------------------------
801    #[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        // Claim body_length = 3 GB (above default 2 GB limit).
809        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    // -----------------------------------------------------------------------
828    // T-14: Frame followed immediately by EOS in one push
829    // -----------------------------------------------------------------------
830    #[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    // -----------------------------------------------------------------------
847    // T-15: Byte-at-a-time feeding (extreme fragmentation)
848    // -----------------------------------------------------------------------
849    #[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    // -----------------------------------------------------------------------
868    // T-16: Reset increments reset counter
869    // -----------------------------------------------------------------------
870    #[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    // -----------------------------------------------------------------------
881    // T-17: Stats error counter increments on bad marker
882    // -----------------------------------------------------------------------
883    #[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}