Skip to main content

dial9_trace_format/
decoder.rs

1//! Streaming decoder for reading trace files.
2//!
3//! [`Decoder`] reads the file header, processes schema and string-pool frames,
4//! and yields events as [`DecodedFrame`] (owned) or [`DecodedFrameRef`]
5//! (zero-copy). It also implements [`Iterator`] and provides a
6//! [`for_each_event`](Decoder::for_each_event) callback API for
7//! allocation-free processing.
8
9use crate::codec::{
10    self, Frame, FrameRef, HEADER_SIZE, PoolEntry, PoolEntryRef, SchemaInfo, StackPoolEntry,
11    StackPoolEntryRef, WireTypeId,
12};
13use crate::schema::{SchemaEntry, SchemaRegistry};
14use crate::types::{FieldType, FieldValueRef, InternedStackFrames, InternedString, StackFrames};
15use std::collections::HashMap;
16use std::fmt;
17
18/// Error returned when the decoder cannot continue reading the stream.
19/// Because frames are not length-prefixed, a decode error is unrecoverable —
20/// the decoder cannot skip the malformed frame to find the next one.
21#[derive(Debug, Clone)]
22pub struct DecodeError {
23    pub pos: usize,
24    pub message: String,
25}
26
27impl fmt::Display for DecodeError {
28    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
29        write!(f, "decode error at byte {}: {}", self.pos, self.message)
30    }
31}
32
33impl std::error::Error for DecodeError {}
34
35/// Error returned by [`Decoder::try_for_each_event`].
36#[derive(Debug)]
37pub enum TryForEachError<E> {
38    Decode(DecodeError),
39    User(E),
40}
41
42impl<E: fmt::Display> fmt::Display for TryForEachError<E> {
43    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
44        match self {
45            TryForEachError::Decode(e) => write!(f, "{e}"),
46            TryForEachError::User(e) => write!(f, "{e}"),
47        }
48    }
49}
50
51impl<E: fmt::Display + fmt::Debug> std::error::Error for TryForEachError<E> {}
52
53/// A decoded event passed to [`Decoder::for_each_event`].
54///
55/// `'a` is the lifetime of the input data buffer (strings, stack frames borrow from it).
56/// `'f` is the lifetime of the `fields` slice and schema name (reused across calls).
57#[non_exhaustive]
58pub struct RawEvent<'a, 'f> {
59    pub type_id: WireTypeId,
60    pub name: &'f str,
61    pub timestamp_ns: u64,
62    pub fields: &'f [FieldValueRef<'a>],
63    pub schema: &'f SchemaEntry,
64    pub string_pool: &'f StringPool,
65    pub stack_pool: &'f StackPool,
66}
67
68impl<'a, 'f> RawEvent<'a, 'f> {
69    /// Field names from the schema, parallel to `fields`.
70    pub fn field_names(&self) -> impl Iterator<Item = &'f str> {
71        self.schema.fields.iter().map(|f| f.name.as_str())
72    }
73
74    /// Deserialize this event into a typed value `E` via serde.
75    ///
76    /// The deserializer presents the event as a flat map containing:
77    ///
78    /// 1. `"event"` → the schema name (the discriminant for
79    ///    `#[serde(tag = "event")]`).
80    /// 2. `"timestamp_ns"` → the absolute frame-header timestamp.
81    /// 3. One entry per schema field, keyed by field name.
82    ///
83    /// Pool-resolved values appear as their resolved form: `PooledString`
84    /// presents as a string, `PooledStackFrames` presents as a sequence of
85    /// `u64`. See [`crate::de`] for details.
86    ///
87    /// Available only when the `serde-deserialize` feature is enabled.
88    #[cfg(feature = "serde-deserialize")]
89    pub fn deserialize<E: serde::de::DeserializeOwned>(&self) -> Result<E, crate::de::DeserError> {
90        crate::de::from_raw_event(self)
91    }
92}
93
94/// A map from interned string IDs to their resolved string values.
95///
96/// Populated automatically by the [`Decoder`] as it processes `StringPool` frames.
97/// Each [`RawEvent`] carries a reference to it, so `InternedString` fields
98/// resolve to `&str` when the event is deserialized.
99#[derive(Debug, Default)]
100pub struct StringPool(pub(crate) HashMap<InternedString, String>);
101
102impl StringPool {
103    pub(crate) fn new() -> Self {
104        Self(HashMap::default())
105    }
106
107    pub(crate) fn insert(&mut self, id: InternedString, value: String) {
108        self.0.insert(id, value);
109    }
110
111    pub fn get(&self, id: InternedString) -> Option<&str> {
112        self.0.get(&id).map(|s| s.as_str())
113    }
114
115    pub fn len(&self) -> usize {
116        self.0.len()
117    }
118
119    pub fn is_empty(&self) -> bool {
120        self.0.is_empty()
121    }
122
123    /// Iterate over all interned strings as `(id, value)` pairs.
124    pub fn iter(&self) -> impl Iterator<Item = (InternedString, &str)> {
125        self.0.iter().map(|(&id, v)| (id, v.as_str()))
126    }
127}
128
129/// A map from interned stack-frame IDs to their resolved address vectors.
130///
131/// Populated automatically by the [`Decoder`] as it processes `StackPool` frames.
132#[derive(Debug, Default)]
133pub struct StackPool(pub(crate) HashMap<InternedStackFrames, Vec<u64>>);
134
135impl StackPool {
136    pub(crate) fn new() -> Self {
137        Self(HashMap::default())
138    }
139
140    pub(crate) fn insert(&mut self, id: InternedStackFrames, frames: StackFrames) {
141        self.0.insert(id, frames.0);
142    }
143
144    pub fn get(&self, id: InternedStackFrames) -> Option<&[u64]> {
145        self.0.get(&id).map(|v| v.as_slice())
146    }
147
148    pub fn len(&self) -> usize {
149        self.0.len()
150    }
151
152    pub fn is_empty(&self) -> bool {
153        self.0.is_empty()
154    }
155
156    /// Iterate over all interned stack frames as `(id, frames)` pairs.
157    pub fn iter(&self) -> impl Iterator<Item = (InternedStackFrames, &[u64])> {
158        self.0.iter().map(|(&id, v)| (id, v.as_slice()))
159    }
160}
161
162/// Decoded events yielded by the decoder.
163#[derive(Debug, Clone, PartialEq)]
164pub enum DecodedFrame {
165    Schema(SchemaEntry),
166    Event {
167        type_id: WireTypeId,
168        /// Absolute timestamp in nanoseconds.
169        timestamp_ns: u64,
170        values: Vec<crate::types::FieldValue>,
171    },
172    StringPool(Vec<PoolEntry>),
173    StackPool(Vec<StackPoolEntry>),
174    SchemaAnnotations {
175        type_id: WireTypeId,
176        annotations: Vec<crate::schema::FieldAnnotation>,
177    },
178}
179
180/// Zero-copy decoded frame that borrows from the input buffer.
181#[derive(Debug, Clone, PartialEq)]
182pub enum DecodedFrameRef<'a> {
183    Schema(SchemaEntry),
184    Event {
185        type_id: WireTypeId,
186        timestamp_ns: u64,
187        values: Vec<FieldValueRef<'a>>,
188    },
189    StringPool(Vec<PoolEntryRef<'a>>),
190    StackPool(Vec<StackPoolEntryRef<'a>>),
191    SchemaAnnotations {
192        type_id: WireTypeId,
193        annotations: Vec<crate::schema::FieldAnnotation>,
194    },
195}
196
197struct SchemaCache {
198    entry: SchemaEntry,
199    /// Raw field type tags for fast decode (avoids re-extracting from entry.fields).
200    field_tags: Vec<u8>,
201    /// Whether the on-wire schema byte indicated a packed timestamp.
202    has_timestamp_on_wire: bool,
203}
204
205/// Streaming trace file decoder.
206///
207/// Reads from a byte slice, processing schema, string-pool, and event frames.
208/// Implements [`Iterator`] over [`DecodedFrameRef`] for convenient consumption.
209pub struct Decoder<'a> {
210    data: &'a [u8],
211    pos: usize,
212    registry: SchemaRegistry,
213    schema_cache: Vec<Option<SchemaCache>>,
214    string_pool: StringPool,
215    stack_pool: StackPool,
216    version: u8,
217    timestamp_base_ns: u64,
218}
219
220impl<'a> Decoder<'a> {
221    pub fn new(data: &'a [u8]) -> Option<Self> {
222        let version = codec::decode_header(data)?;
223        Some(Self {
224            data,
225            pos: HEADER_SIZE,
226            registry: SchemaRegistry::new(),
227            schema_cache: Vec::new(),
228            string_pool: StringPool::new(),
229            stack_pool: StackPool::new(),
230            version,
231            timestamp_base_ns: 0,
232        })
233    }
234
235    pub fn registry(&self) -> &SchemaRegistry {
236        &self.registry
237    }
238
239    pub fn version(&self) -> u8 {
240        self.version
241    }
242
243    /// Returns the current byte offset within the input data.
244    ///
245    /// After `next_frame()` returns `Ok(None)`, this should equal `data_len()`
246    /// for a well-formed, non-truncated trace. A mismatch indicates trailing
247    /// bytes that could not be decoded.
248    pub fn position(&self) -> usize {
249        self.pos
250    }
251
252    /// Returns the total length of the input data slice.
253    pub fn data_len(&self) -> usize {
254        self.data.len()
255    }
256
257    pub fn string_pool(&self) -> &StringPool {
258        &self.string_pool
259    }
260
261    pub fn stack_pool(&self) -> &StackPool {
262        &self.stack_pool
263    }
264
265    /// Reset decoder state (schemas, string pool, timestamp base) as if
266    /// starting a fresh stream. Used when a mid-stream header is encountered
267    /// (the "reset frame" pattern for concatenated thread-local batches).
268    fn reset_state(&mut self) {
269        self.registry = SchemaRegistry::new();
270        self.schema_cache.clear();
271        self.string_pool = StringPool::new();
272        self.stack_pool = StackPool::new();
273        self.timestamp_base_ns = 0;
274    }
275
276    /// If the current position starts with a valid header, reset state and
277    /// skip past it, returning true.
278    fn try_consume_reset_header(&mut self) -> bool {
279        if self.pos + HEADER_SIZE <= self.data.len()
280            && codec::decode_header(&self.data[self.pos..]).is_some()
281        {
282            self.reset_state();
283            self.pos += HEADER_SIZE;
284            true
285        } else {
286            false
287        }
288    }
289
290    /// Consume this decoder and create an [`Encoder`](crate::encoder::Encoder) that appends to the
291    /// decoded trace. The encoder inherits the string pool, schema registry,
292    /// and timestamp base so new frames are compatible with the existing data.
293    ///
294    /// No file header is written — the caller is responsible for concatenating
295    /// the encoder's output after the original trace bytes.
296    pub fn into_encoder<W: std::io::Write>(self, writer: W) -> crate::encoder::Encoder<W> {
297        crate::encoder::Encoder::from_decoder(
298            self.registry,
299            self.string_pool,
300            self.stack_pool,
301            self.timestamp_base_ns,
302            writer,
303        )
304    }
305
306    pub(crate) fn schema_info(&self, type_id: WireTypeId) -> Option<SchemaInfo<'_>> {
307        self.schema_cache
308            .get(type_id.0 as usize)
309            .and_then(|s| s.as_ref())
310            .map(|c| SchemaInfo {
311                field_tags: &c.field_tags,
312                has_timestamp_on_wire: c.has_timestamp_on_wire,
313            })
314    }
315
316    fn register_schema(
317        &mut self,
318        type_id: WireTypeId,
319        entry: SchemaEntry,
320        has_timestamp_on_wire: bool,
321    ) -> Result<(), String> {
322        let idx = type_id.0 as usize;
323        if idx >= self.schema_cache.len() {
324            self.schema_cache.resize_with(idx + 1, || None);
325        }
326        self.schema_cache[idx] = Some(SchemaCache {
327            field_tags: entry.fields.iter().map(|f| f.field_type as u8).collect(),
328            entry: entry.clone(),
329            has_timestamp_on_wire,
330        });
331        self.registry.register(type_id, entry)
332    }
333
334    /// Decode the next frame. Returns `Ok(None)` when stream is exhausted.
335    /// Returns `Err` if the stream is malformed (e.g. duplicate type_id with
336    /// a different schema).
337    pub fn next_frame(&mut self) -> Result<Option<DecodedFrame>, DecodeError> {
338        if self.pos >= self.data.len() {
339            return Ok(None);
340        }
341        if self.try_consume_reset_header() {
342            return self.next_frame();
343        }
344        let remaining = &self.data[self.pos..];
345        let base = self.timestamp_base_ns;
346        let (frame, consumed) =
347            match codec::decode_frame(remaining, |type_id| self.schema_info(type_id), base) {
348                Some(r) => r,
349                None => return Ok(None),
350            };
351        self.pos += consumed;
352        match frame {
353            Frame::Schema {
354                type_id,
355                entry,
356                has_timestamp_on_wire,
357            } => {
358                let result = DecodedFrame::Schema(entry.clone());
359                self.register_schema(type_id, entry, has_timestamp_on_wire)
360                    .map_err(|msg| DecodeError {
361                        pos: self.pos,
362                        message: msg,
363                    })?;
364                Ok(Some(result))
365            }
366            Frame::Event {
367                type_id,
368                timestamp_ns,
369                values,
370            } => {
371                self.timestamp_base_ns = timestamp_ns;
372                Ok(Some(DecodedFrame::Event {
373                    type_id,
374                    timestamp_ns,
375                    values,
376                }))
377            }
378            Frame::StringPool(entries) => {
379                for e in &entries {
380                    if let Ok(s) = String::from_utf8(e.data.clone()) {
381                        self.string_pool.insert(InternedString(e.pool_id), s);
382                    }
383                }
384                Ok(Some(DecodedFrame::StringPool(entries)))
385            }
386            Frame::StackPool(entries) => {
387                for e in &entries {
388                    self.stack_pool
389                        .insert(InternedStackFrames(e.pool_id), e.frames.clone().into());
390                }
391                Ok(Some(DecodedFrame::StackPool(entries)))
392            }
393            Frame::TimestampReset(ts) => {
394                self.timestamp_base_ns = ts;
395                self.next_frame() // consume silently, return next real frame
396            }
397            Frame::SchemaAnnotations {
398                type_id,
399                annotations,
400            } => {
401                // Merge annotations into the cached schema (lenient: skip if unknown type_id)
402                if let Some(cache) = self
403                    .schema_cache
404                    .get_mut(type_id.0 as usize)
405                    .and_then(|s| s.as_mut())
406                {
407                    cache.entry.annotations.extend_from_slice(&annotations);
408                }
409                if let Some(entry) = self.registry.schemas.get_mut(&type_id) {
410                    entry.annotations.extend_from_slice(&annotations);
411                }
412                Ok(Some(DecodedFrame::SchemaAnnotations {
413                    type_id,
414                    annotations,
415                }))
416            }
417        }
418    }
419
420    /// Collect all remaining frames. Stops on error or end of stream.
421    pub fn decode_all(&mut self) -> Vec<DecodedFrame> {
422        let mut frames = Vec::new();
423        while let Ok(Some(f)) = self.next_frame() {
424            frames.push(f);
425        }
426        frames
427    }
428
429    /// Decode the next frame without copying field data. Returns `Ok(None)` when
430    /// stream is exhausted. Returns `Err` on malformed data.
431    pub fn next_frame_ref(&mut self) -> Result<Option<DecodedFrameRef<'a>>, DecodeError> {
432        if self.pos >= self.data.len() {
433            return Ok(None);
434        }
435        if self.try_consume_reset_header() {
436            return self.next_frame_ref();
437        }
438        let remaining = &self.data[self.pos..];
439        let base = self.timestamp_base_ns;
440        let (frame, consumed) =
441            match codec::decode_frame_ref(remaining, |type_id| self.schema_info(type_id), base) {
442                Some(r) => r,
443                None => return Ok(None),
444            };
445        self.pos += consumed;
446        match frame {
447            FrameRef::Schema {
448                type_id,
449                entry,
450                has_timestamp_on_wire,
451            } => {
452                let result = DecodedFrameRef::Schema(entry.clone());
453                self.register_schema(type_id, entry, has_timestamp_on_wire)
454                    .map_err(|msg| DecodeError {
455                        pos: self.pos,
456                        message: msg,
457                    })?;
458                Ok(Some(result))
459            }
460            FrameRef::Event {
461                type_id,
462                timestamp_ns,
463                values,
464            } => {
465                self.timestamp_base_ns = timestamp_ns;
466                Ok(Some(DecodedFrameRef::Event {
467                    type_id,
468                    timestamp_ns,
469                    values,
470                }))
471            }
472            FrameRef::StringPool(entries) => {
473                for e in &entries {
474                    if let Ok(s) = std::str::from_utf8(e.data) {
475                        self.string_pool
476                            .insert(InternedString(e.pool_id), s.to_string());
477                    }
478                }
479                Ok(Some(DecodedFrameRef::StringPool(entries)))
480            }
481            FrameRef::StackPool(entries) => {
482                for e in &entries {
483                    self.stack_pool
484                        .insert(InternedStackFrames(e.pool_id), e.to_stack_frames());
485                }
486                Ok(Some(DecodedFrameRef::StackPool(entries)))
487            }
488            FrameRef::TimestampReset(ts) => {
489                self.timestamp_base_ns = ts;
490                self.next_frame_ref()
491            }
492            FrameRef::SchemaAnnotations {
493                type_id,
494                annotations,
495            } => {
496                if let Some(cache) = self
497                    .schema_cache
498                    .get_mut(type_id.0 as usize)
499                    .and_then(|s| s.as_mut())
500                {
501                    cache.entry.annotations.extend_from_slice(&annotations);
502                }
503                if let Some(entry) = self.registry.schemas.get_mut(&type_id) {
504                    entry.annotations.extend_from_slice(&annotations);
505                }
506                Ok(Some(DecodedFrameRef::SchemaAnnotations {
507                    type_id,
508                    annotations,
509                }))
510            }
511        }
512    }
513
514    /// Collect all remaining frames using zero-copy decoding. Stops on error or end of stream.
515    pub fn decode_all_ref(&mut self) -> Vec<DecodedFrameRef<'a>> {
516        let mut frames = Vec::new();
517        while let Ok(Some(f)) = self.next_frame_ref() {
518            frames.push(f);
519        }
520        frames
521    }
522
523    /// Process all events with a callback, avoiding per-event Vec allocations.
524    /// Schemas and string pools are registered automatically.
525    ///
526    /// The [`RawEvent`] passed to the callback borrows from the decoder's input
527    /// buffer. The `fields` slice is reused across calls, so values cannot be
528    /// stored across iterations without copying.
529    ///
530    /// Returns `Err` if the stream is malformed.
531    pub fn for_each_event(
532        &mut self,
533        mut f: impl for<'f> FnMut(RawEvent<'a, 'f>),
534    ) -> Result<(), DecodeError> {
535        self.try_for_each_event(|ev| {
536            f(ev);
537            Ok::<(), std::convert::Infallible>(())
538        })
539        .map_err(|e| match e {
540            TryForEachError::Decode(d) => d,
541            TryForEachError::User(inf) => match inf {},
542        })
543    }
544
545    /// Like [`for_each_event`](Self::for_each_event), but the callback may
546    /// return an error to stop iteration early.
547    pub fn try_for_each_event<E>(
548        &mut self,
549        mut f: impl for<'f> FnMut(RawEvent<'a, 'f>) -> Result<(), E>,
550    ) -> Result<(), TryForEachError<E>> {
551        let mut values_buf: Vec<FieldValueRef<'a>> = Vec::new();
552        while self.pos < self.data.len() {
553            let remaining = &self.data[self.pos..];
554            let tag = match remaining.first() {
555                Some(t) => *t,
556                None => break,
557            };
558            match tag {
559                codec::TAG_EVENT => {
560                    let mut pos = 1;
561                    let type_id = match remaining.get(pos..pos + 2) {
562                        Some(b) => {
563                            pos += 2;
564                            WireTypeId(u16::from_le_bytes(b.try_into().unwrap()))
565                        }
566                        None => {
567                            return Err(TryForEachError::Decode(DecodeError {
568                                pos: self.pos,
569                                message: "truncated event frame".into(),
570                            }));
571                        }
572                    };
573                    let cache = match self
574                        .schema_cache
575                        .get(type_id.0 as usize)
576                        .and_then(|s| s.as_ref())
577                    {
578                        Some(c) => c,
579                        None => {
580                            return Err(TryForEachError::Decode(DecodeError {
581                                pos: self.pos,
582                                message: format!("unknown type_id {type_id:?}"),
583                            }));
584                        }
585                    };
586
587                    let timestamp_ns = if cache.has_timestamp_on_wire {
588                        match codec::decode_u24_le(&remaining[pos..]) {
589                            Some(delta) => {
590                                pos += 3;
591                                self.timestamp_base_ns + delta as u64
592                            }
593                            None => {
594                                return Err(TryForEachError::Decode(DecodeError {
595                                    pos: self.pos + pos,
596                                    message: "truncated timestamp delta".into(),
597                                }));
598                            }
599                        }
600                    } else {
601                        // Legacy schema without packed timestamp: use the current base.
602                        self.timestamp_base_ns
603                    };
604
605                    values_buf.clear();
606                    for &ftag in &cache.field_tags {
607                        let inner_type = match FieldType::from_tag(ftag) {
608                            Some(ft) => ft,
609                            None => {
610                                return Err(TryForEachError::Decode(DecodeError {
611                                    pos: self.pos + pos,
612                                    message: format!("unknown field type tag {ftag:#x}"),
613                                }));
614                            }
615                        };
616                        if inner_type.is_optional() {
617                            match remaining.get(pos) {
618                                Some(0x00) => {
619                                    values_buf.push(FieldValueRef::None);
620                                    pos += 1;
621                                }
622                                Some(_) => {
623                                    pos += 1;
624                                    match FieldValueRef::decode(inner_type.inner(), remaining, pos)
625                                    {
626                                        Some((val, consumed)) => {
627                                            values_buf.push(val);
628                                            pos += consumed;
629                                        }
630                                        None => {
631                                            return Err(TryForEachError::Decode(DecodeError {
632                                                pos: self.pos + pos,
633                                                message: "truncated optional field value".into(),
634                                            }));
635                                        }
636                                    }
637                                }
638                                None => {
639                                    return Err(TryForEachError::Decode(DecodeError {
640                                        pos: self.pos + pos,
641                                        message: "truncated optional field prefix".into(),
642                                    }));
643                                }
644                            }
645                        } else {
646                            match FieldValueRef::decode(inner_type, remaining, pos) {
647                                Some((val, consumed)) => {
648                                    values_buf.push(val);
649                                    pos += consumed;
650                                }
651                                None => {
652                                    return Err(TryForEachError::Decode(DecodeError {
653                                        pos: self.pos + pos,
654                                        message: "truncated field value".into(),
655                                    }));
656                                }
657                            }
658                        }
659                    }
660                    // Update mutable state. The borrow checker allows this
661                    // because `cache` borrows `self.schema_cache` while we
662                    // mutate `self.pos` and `self.timestamp_base_ns`, which
663                    // are disjoint fields. We use a block with destructured
664                    // refs to make this explicit.
665                    {
666                        let Self {
667                            pos: self_pos,
668                            timestamp_base_ns,
669                            ..
670                        } = self;
671                        *self_pos += pos;
672                        *timestamp_base_ns = timestamp_ns;
673                    }
674                    f(RawEvent {
675                        type_id,
676                        name: &cache.entry.name,
677                        timestamp_ns,
678                        fields: &values_buf,
679                        schema: &cache.entry,
680                        string_pool: &self.string_pool,
681                        stack_pool: &self.stack_pool,
682                    })
683                    .map_err(TryForEachError::User)?;
684                }
685                codec::TAG_TIMESTAMP_RESET => {
686                    let ts = match self.data.get(self.pos + 1..self.pos + 9) {
687                        Some(b) => u64::from_le_bytes(b.try_into().unwrap()),
688                        None => {
689                            return Err(TryForEachError::Decode(DecodeError {
690                                pos: self.pos,
691                                message: "truncated timestamp reset".into(),
692                            }));
693                        }
694                    };
695                    self.timestamp_base_ns = ts;
696                    self.pos += 9;
697                }
698                _ => {
699                    // Mid-stream header = reset frame (tag 0x54 = 'T' from TRC\0)
700                    if tag == codec::MAGIC[0] && self.try_consume_reset_header() {
701                        continue;
702                    }
703                    match self.next_frame_ref() {
704                        Ok(Some(_)) => {}
705                        Ok(None) => {
706                            return Err(TryForEachError::Decode(DecodeError {
707                                pos: self.pos,
708                                message: format!("failed to decode frame with tag 0x{tag:02x}"),
709                            }));
710                        }
711                        Err(e) => return Err(TryForEachError::Decode(e)),
712                    }
713                }
714            }
715        }
716        Ok(())
717    }
718
719    /// Returns an iterator that yields only [`DecodedFrameRef::Event`] variants,
720    /// silently consuming schema, string-pool, and symbol-table frames
721    /// (while still updating internal decoder state).
722    pub fn events(&mut self) -> EventIter<'_, 'a> {
723        EventIter { decoder: self }
724    }
725}
726
727impl<'a> Iterator for Decoder<'a> {
728    type Item = Result<DecodedFrameRef<'a>, DecodeError>;
729
730    fn next(&mut self) -> Option<Self::Item> {
731        self.next_frame_ref().transpose()
732    }
733}
734
735/// Iterator that yields only [`DecodedFrameRef::Event`] frames,
736/// consuming non-event frames to keep decoder state up to date.
737pub struct EventIter<'d, 'a> {
738    decoder: &'d mut Decoder<'a>,
739}
740
741impl<'d, 'a> Iterator for EventIter<'d, 'a> {
742    type Item = Result<DecodedFrameRef<'a>, DecodeError>;
743
744    fn next(&mut self) -> Option<Self::Item> {
745        loop {
746            match self.decoder.next()? {
747                Ok(frame @ DecodedFrameRef::Event { .. }) => return Some(Ok(frame)),
748                Ok(_) => continue, // schema, string pool, symbol table — skip
749                Err(e) => return Some(Err(e)),
750            }
751        }
752    }
753}
754
755#[cfg(test)]
756mod tests {
757    use super::*;
758    use crate::encoder::Encoder;
759    use crate::schema::FieldDef;
760    use crate::types::{FieldType, FieldValue};
761
762    #[test]
763    fn decode_empty_stream() {
764        let enc = Encoder::new();
765        let data = enc.finish();
766        let mut dec = Decoder::new(&data).unwrap();
767        assert_eq!(dec.version(), 1);
768        assert!(dec.next_frame().unwrap().is_none());
769    }
770
771    #[test]
772    fn decode_schema_frame() {
773        let mut enc = Encoder::new();
774        enc.register_schema(
775            "Ev",
776            vec![FieldDef {
777                name: "v".into(),
778                field_type: FieldType::Varint,
779            }],
780        )
781        .unwrap();
782        let data = enc.finish();
783        let mut dec = Decoder::new(&data).unwrap();
784        let frame = dec.next_frame().unwrap().unwrap();
785        assert!(matches!(frame, DecodedFrame::Schema(s) if s.name == "Ev"));
786    }
787
788    #[test]
789    fn decode_event_after_schema() {
790        let mut enc = Encoder::new();
791        let schema = enc
792            .register_schema(
793                "Ev",
794                vec![FieldDef {
795                    name: "v".into(),
796                    field_type: FieldType::Varint,
797                }],
798            )
799            .unwrap();
800        enc.write_event(&schema, 1_000, &[FieldValue::Varint(42)])
801            .unwrap();
802        let data = enc.finish();
803
804        let mut dec = Decoder::new(&data).unwrap();
805        let frames = dec.decode_all();
806        assert_eq!(frames.len(), 2);
807        if let DecodedFrame::Event { values, .. } = &frames[1] {
808            assert_eq!(*values, vec![FieldValue::Varint(42)]);
809        } else {
810            panic!("expected event");
811        }
812    }
813
814    #[test]
815    fn decode_string_pool_builds_map() {
816        let mut enc = Encoder::new();
817        let id = enc.intern_string("hello").unwrap();
818        let data = enc.finish();
819
820        let mut dec = Decoder::new(&data).unwrap();
821        dec.decode_all();
822        assert_eq!(dec.string_pool().get(id), Some("hello"));
823    }
824
825    #[test]
826    fn decode_multiple_events() {
827        let mut enc = Encoder::new();
828        let schema = enc
829            .register_schema(
830                "Ev",
831                vec![FieldDef {
832                    name: "v".into(),
833                    field_type: FieldType::Varint,
834                }],
835            )
836            .unwrap();
837        for i in 0..10u64 {
838            enc.write_event(&schema, i * 1000, &[FieldValue::Varint(i)])
839                .unwrap();
840        }
841        let data = enc.finish();
842
843        let mut dec = Decoder::new(&data).unwrap();
844        let frames = dec.decode_all();
845        assert_eq!(frames.len(), 11);
846    }
847
848    #[test]
849    fn bad_header_returns_none() {
850        assert!(Decoder::new(&[0x00, 0x00, 0x00, 0x00, 1]).is_none());
851    }
852
853    #[test]
854    fn iterator_yields_all_frames() {
855        let mut enc = Encoder::new();
856        let schema = enc
857            .register_schema(
858                "Ev",
859                vec![FieldDef {
860                    name: "v".into(),
861                    field_type: FieldType::Varint,
862                }],
863            )
864            .unwrap();
865        for i in 0..3u64 {
866            enc.write_event(&schema, i * 1000, &[FieldValue::Varint(i)])
867                .unwrap();
868        }
869        let data = enc.finish();
870
871        let dec = Decoder::new(&data).unwrap();
872        let frames: Vec<_> = dec.collect::<Result<Vec<_>, _>>().unwrap();
873        // 1 schema + 3 events
874        assert_eq!(frames.len(), 4);
875        assert!(matches!(frames[0], DecodedFrameRef::Schema(_)));
876        assert!(matches!(frames[1], DecodedFrameRef::Event { .. }));
877    }
878
879    #[test]
880    fn iterator_early_termination() {
881        let mut enc = Encoder::new();
882        let schema = enc
883            .register_schema(
884                "Ev",
885                vec![FieldDef {
886                    name: "v".into(),
887                    field_type: FieldType::Varint,
888                }],
889            )
890            .unwrap();
891        for i in 0..10u64 {
892            enc.write_event(&schema, i * 1000, &[FieldValue::Varint(i)])
893                .unwrap();
894        }
895        let data = enc.finish();
896
897        let mut dec = Decoder::new(&data).unwrap();
898        // Take just 2 frames (schema + first event), don't decode the rest
899        let first_two: Vec<_> = dec.by_ref().take(2).collect::<Result<Vec<_>, _>>().unwrap();
900        assert_eq!(first_two.len(), 2);
901        // Decoder should still have remaining data
902        let next = dec.next();
903        assert!(next.is_some());
904    }
905
906    #[test]
907    fn events_iterator_skips_schema() {
908        let mut enc = Encoder::new();
909        let schema = enc
910            .register_schema(
911                "Ev",
912                vec![FieldDef {
913                    name: "v".into(),
914                    field_type: FieldType::Varint,
915                }],
916            )
917            .unwrap();
918        enc.write_event(&schema, 1_000, &[FieldValue::Varint(42)])
919            .unwrap();
920        enc.write_event(&schema, 2_000, &[FieldValue::Varint(99)])
921            .unwrap();
922        let data = enc.finish();
923
924        let mut dec = Decoder::new(&data).unwrap();
925        let events: Vec<_> = dec.events().collect::<Result<Vec<_>, _>>().unwrap();
926        // Only events, no schema frame
927        assert_eq!(events.len(), 2);
928        for ev in &events {
929            assert!(matches!(ev, DecodedFrameRef::Event { .. }));
930        }
931    }
932
933    #[test]
934    fn events_iterator_first_event_only() {
935        let mut enc = Encoder::new();
936        let schema = enc
937            .register_schema(
938                "Ev",
939                vec![FieldDef {
940                    name: "v".into(),
941                    field_type: FieldType::Varint,
942                }],
943            )
944            .unwrap();
945        for i in 0..5u64 {
946            enc.write_event(&schema, i * 1000, &[FieldValue::Varint(i)])
947                .unwrap();
948        }
949        let data = enc.finish();
950
951        let mut dec = Decoder::new(&data).unwrap();
952        // Get just the first event — schema is consumed internally
953        let first = dec.events().next().unwrap().unwrap();
954        assert!(matches!(first, DecodedFrameRef::Event { .. }));
955    }
956}