Skip to main content

moqtap_trace/
reader.rs

1use std::io::Read;
2
3use ciborium::Value;
4
5use crate::error::MoqTraceError;
6use crate::event::TraceEvent;
7use crate::header::TraceHeader;
8use crate::writer::{MOQTRACE_MAGIC, MOQTRACE_VERSIONS_SUPPORTED};
9
10/// A `Read` that can look ahead without consuming, and counts what it has
11/// handed out.
12///
13/// Both are needed for segmented traces: the lookahead is how a segment
14/// boundary is spotted before an event decoder eats the magic bytes, and the
15/// count is how a clean end-of-file is told apart from a stream that stopped
16/// half-way through an event.
17#[derive(Debug)]
18struct PeekReader<R: Read> {
19    inner: R,
20    /// Bytes read from `inner` but not yet handed to a caller of `read`.
21    lookahead: Vec<u8>,
22    lookahead_pos: usize,
23    /// Bytes handed out so far, excluding whatever is still in `lookahead`.
24    count: u64,
25}
26
27impl<R: Read> PeekReader<R> {
28    fn new(inner: R) -> Self {
29        Self { inner, lookahead: Vec::new(), lookahead_pos: 0, count: 0 }
30    }
31
32    /// Borrow the next `n` bytes without consuming them. The returned slice
33    /// is shorter than `n` only at end of stream.
34    fn peek(&mut self, n: usize) -> std::io::Result<&[u8]> {
35        if self.lookahead_pos > 0 {
36            self.lookahead.drain(..self.lookahead_pos);
37            self.lookahead_pos = 0;
38        }
39        while self.lookahead.len() < n {
40            let start = self.lookahead.len();
41            self.lookahead.resize(n, 0);
42            let got = self.inner.read(&mut self.lookahead[start..])?;
43            self.lookahead.truncate(start + got);
44            if got == 0 {
45                break;
46            }
47        }
48        Ok(&self.lookahead[..n.min(self.lookahead.len())])
49    }
50
51    /// Consume and return one byte, or `None` at end of stream.
52    fn next_byte(&mut self) -> std::io::Result<Option<u8>> {
53        let mut b = [0u8; 1];
54        loop {
55            return match self.read(&mut b) {
56                Ok(0) => Ok(None),
57                Ok(_) => Ok(Some(b[0])),
58                Err(e) if e.kind() == std::io::ErrorKind::Interrupted => continue,
59                Err(e) => Err(e),
60            };
61        }
62    }
63}
64
65impl<R: Read> Read for PeekReader<R> {
66    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
67        let buffered = self.lookahead.len() - self.lookahead_pos;
68        if buffered > 0 {
69            let n = buffered.min(buf.len());
70            buf[..n].copy_from_slice(&self.lookahead[self.lookahead_pos..self.lookahead_pos + n]);
71            self.lookahead_pos += n;
72            if self.lookahead_pos == self.lookahead.len() {
73                self.lookahead.clear();
74                self.lookahead_pos = 0;
75            }
76            self.count += n as u64;
77            return Ok(n);
78        }
79        let n = self.inner.read(buf)?;
80        self.count += n as u64;
81        Ok(n)
82    }
83}
84
85/// One item from a `.moqtrace` stream.
86// `Segment` is the larger variant by more than clippy's threshold now that a
87// header carries three unrecognised-key stores. Boxing it is the lint's
88// suggestion and is not taken: it changes the shape of a public variant every
89// caller matches on, to save moving a couple of hundred bytes on the one item
90// per segment rather than on the one per event.
91#[allow(clippy::large_enum_variant)]
92#[derive(Debug, Clone, PartialEq)]
93pub enum ReadItem {
94    /// An event in the current segment.
95    Event(TraceEvent),
96    /// A new segment began; this is its header. Every item after it belongs
97    /// to that segment until the next `Segment`.
98    Segment(TraceHeader),
99}
100
101/// Streaming reader for `.moqtrace` files.
102///
103/// Validates the preamble and parses the first segment's header on
104/// construction. Use [`read_event`](Self::read_event), or the iterator it
105/// backs, for code that does not care about segment boundaries — it advances
106/// through them silently. Use [`read_next`](Self::read_next) to see them.
107#[derive(Debug)]
108pub struct MoqTraceReader<R: Read> {
109    inner: PeekReader<R>,
110    header: TraceHeader,
111    version: u32,
112    /// Set when a segment header could not be built, and the stream is
113    /// therefore parked on the events of a segment there is no header for.
114    ///
115    /// Nothing may be decoded from that position under
116    /// [`header`](MoqTraceReader::header), which still describes the segment
117    /// before it: those events belong to a segment this reader could not read,
118    /// and handing them back under the previous segment's header presents that
119    /// segment as read under a header the file never gave it. Because `"n"`
120    /// and `"t"` are segment-local and global order is `(segment.sequence,
121    /// n)`, it also misorders every event so recovered, silently.
122    ///
123    /// The next read resynchronizes to the next segment instead. See
124    /// [`read_next`](MoqTraceReader::read_next).
125    faulted: bool,
126}
127
128impl<R: Read> MoqTraceReader<R> {
129    /// Open a reader, validating the preamble and parsing the first segment's
130    /// header.
131    pub fn new(reader: R) -> Result<Self, MoqTraceError> {
132        let mut inner = PeekReader::new(reader);
133        let (version, header) = read_preamble(&mut inner)?;
134        Ok(Self { inner, header, version, faulted: false })
135    }
136
137    /// The current segment's header.
138    ///
139    /// After a segment header this reader could not build — reported once by
140    /// [`read_next`](Self::read_next) — this still names the last segment that
141    /// *was* read, until the next read reaches the segment after the fault.
142    /// No event is handed back under it in the meantime, which is the property
143    /// that matters: the header a caller holds always belongs to the events it
144    /// has been given.
145    pub fn header(&self) -> &TraceHeader {
146        &self.header
147    }
148
149    /// The format version the current segment declared.
150    pub fn version(&self) -> u32 {
151        self.version
152    }
153
154    /// Read the next item: an event in the current segment, or the header of
155    /// a segment that starts here.
156    ///
157    /// Returns `Ok(None)` at a clean end of file. A stream that stops
158    /// part-way through an item yields [`MoqTraceError::Truncated`] instead,
159    /// which names the offset the incomplete item began at; everything
160    /// returned before it stands.
161    ///
162    /// # A segment header this reader cannot build
163    ///
164    /// The error is returned once, and the segment it names is skipped whole:
165    /// the next call resynchronizes to the segment after it, exactly as
166    /// [`resync_to_next_segment`](Self::resync_to_next_segment) would, and
167    /// reports it as a [`ReadItem::Segment`] like any other.
168    ///
169    /// Reading on from where the bad preamble left off is the one thing that
170    /// must not happen. The preamble is consumed before the header is built,
171    /// so the stream is parked on the *next* segment's events with no header
172    /// for them; decoding them leaves the previous segment's header standing,
173    /// and SPEC.md is explicit that a reader "MUST report it and MUST NOT
174    /// present the segment as read". Handing back its events under the
175    /// previous header presents it as read and gets the header wrong, and
176    /// since `"n"` and `"t"` are segment-local while global order is
177    /// `(segment.sequence, n)`, every event so recovered is also misordered —
178    /// with nothing in the returned values to say so. A caller that keeps only
179    /// the `Ok`s of the iterator sees no fault at all.
180    ///
181    /// Skipping rather than refusing to go on matches the JavaScript reader's
182    /// `recover` path over the same file, and leaves both idioms honest: a
183    /// `collect::<Result<Vec<_>, _>>()` still stops at the error, and a caller
184    /// that filters errors out gets the segments it can trust and none of the
185    /// events from the one it cannot.
186    pub fn read_next(&mut self) -> Result<Option<ReadItem>, MoqTraceError> {
187        if self.faulted {
188            // Reported on the call that faulted. What is left is a run of
189            // events belonging to a segment with no readable header, which
190            // ends at the next preamble or at end of file.
191            self.faulted = false;
192            return Ok(self.resync_to_next_segment()?.map(ReadItem::Segment));
193        }
194
195        let peek = self.inner.peek(MOQTRACE_MAGIC.len())?;
196        if peek.is_empty() {
197            return Ok(None);
198        }
199        // No event can be mistaken for a segment boundary: an event is a CBOR
200        // map, and `M` (0x4d) opens a byte string.
201        if peek == MOQTRACE_MAGIC.as_slice() {
202            let start_offset = self.inner.count;
203            let (version, header) = read_preamble(&mut self.inner).map_err(|e| {
204                self.faulted = true;
205                match e {
206                    MoqTraceError::Io(io) if io.kind() == std::io::ErrorKind::UnexpectedEof => {
207                        MoqTraceError::Truncated { offset: start_offset }
208                    }
209                    other => other,
210                }
211            })?;
212            self.version = version;
213            self.header = header.clone();
214            return Ok(Some(ReadItem::Segment(header)));
215        }
216
217        let start_offset = self.inner.count;
218        match ciborium::from_reader::<Value, _>(&mut self.inner) {
219            Ok(value) => Ok(Some(ReadItem::Event(TraceEvent::try_from(value)?))),
220            Err(ciborium::de::Error::Io(e)) if e.kind() == std::io::ErrorKind::UnexpectedEof => {
221                if self.inner.count == start_offset {
222                    Ok(None)
223                } else {
224                    Err(MoqTraceError::Truncated { offset: start_offset })
225                }
226            }
227            Err(e) => Err(MoqTraceError::CborDecode(e.to_string())),
228        }
229    }
230
231    /// Read the next event, advancing through segment boundaries silently.
232    /// [`header`](Self::header) tracks the segment the event came from.
233    ///
234    /// Returns `Ok(None)` only at a clean end of file.
235    pub fn read_event(&mut self) -> Result<Option<TraceEvent>, MoqTraceError> {
236        loop {
237            match self.read_next()? {
238                Some(ReadItem::Event(event)) => return Ok(Some(event)),
239                Some(ReadItem::Segment(_)) => continue,
240                None => return Ok(None),
241            }
242        }
243    }
244
245    /// Scan forward to the next segment and resume there, returning its
246    /// header — or `Ok(None)` if the stream ends before one is found.
247    ///
248    /// This is the recovery path a segmented trace exists to offer: after a
249    /// [`Truncated`](MoqTraceError::Truncated) or a decode error, the current
250    /// segment is unreadable from that point on, but the segments after it
251    /// are intact and independently parseable. Everything skipped is
252    /// discarded — the bytes between the failure and the next segment are the
253    /// corrupt region.
254    ///
255    /// A header this reader cannot build is one of those failures rather than
256    /// a way out of one: if the segment found here has one, the error is
257    /// returned and the segment after it is where the next read picks up.
258    pub fn resync_to_next_segment(&mut self) -> Result<Option<TraceHeader>, MoqTraceError> {
259        // Whatever brought us here, this is the recovery, and it starts from
260        // the current position rather than from the fault.
261        self.faulted = false;
262        let mut window: Vec<u8> = Vec::with_capacity(MOQTRACE_MAGIC.len());
263        while let Some(byte) = self.inner.next_byte()? {
264            if window.len() == MOQTRACE_MAGIC.len() {
265                window.remove(0);
266            }
267            window.push(byte);
268            if window == MOQTRACE_MAGIC.as_slice() {
269                // The magic is consumed; the rest of the preamble follows.
270                let (version, header) = read_version_and_header(&mut self.inner).inspect_err(
271                    // Same fault as in `read_next`, and the same recovery: the
272                    // events after this preamble have no header of their own,
273                    // and must not be read under the one still held here.
274                    |_| self.faulted = true,
275                )?;
276                self.version = version;
277                self.header = header.clone();
278                return Ok(Some(header));
279            }
280        }
281        Ok(None)
282    }
283
284    /// Iterate over events, advancing through segment boundaries silently.
285    pub fn into_event_iter(self) -> MoqTraceEventIterator<R> {
286        MoqTraceEventIterator { reader: self }
287    }
288
289    /// Iterate over items — events and segment boundaries both.
290    pub fn into_item_iter(self) -> MoqTraceItemIterator<R> {
291        MoqTraceItemIterator { reader: self }
292    }
293}
294
295fn read_preamble<R: Read>(reader: &mut PeekReader<R>) -> Result<(u32, TraceHeader), MoqTraceError> {
296    let mut magic = [0u8; 8];
297    reader.read_exact(&mut magic)?;
298    if &magic != MOQTRACE_MAGIC {
299        return Err(MoqTraceError::InvalidMagic);
300    }
301    read_version_and_header(reader)
302}
303
304fn read_version_and_header<R: Read>(
305    reader: &mut PeekReader<R>,
306) -> Result<(u32, TraceHeader), MoqTraceError> {
307    let mut version_bytes = [0u8; 4];
308    reader.read_exact(&mut version_bytes)?;
309    let version = u32::from_le_bytes(version_bytes);
310    if !MOQTRACE_VERSIONS_SUPPORTED.contains(&version) {
311        return Err(MoqTraceError::UnsupportedVersion(version));
312    }
313
314    let mut len_bytes = [0u8; 4];
315    reader.read_exact(&mut len_bytes)?;
316    let header_len = u32::from_le_bytes(len_bytes) as usize;
317
318    let mut header_bytes = vec![0u8; header_len];
319    reader.read_exact(&mut header_bytes)?;
320
321    let header_value: Value = ciborium::from_reader(&header_bytes[..])?;
322    Ok((version, TraceHeader::try_from(header_value)?))
323}
324
325/// Yields events, advancing through segment boundaries silently.
326impl<R: Read> IntoIterator for MoqTraceReader<R> {
327    type Item = Result<TraceEvent, MoqTraceError>;
328    type IntoIter = MoqTraceEventIterator<R>;
329
330    fn into_iter(self) -> Self::IntoIter {
331        self.into_event_iter()
332    }
333}
334
335/// Iterator over the events in a `.moqtrace` file.
336pub struct MoqTraceEventIterator<R: Read> {
337    reader: MoqTraceReader<R>,
338}
339
340impl<R: Read> MoqTraceEventIterator<R> {
341    /// The header of the segment the last event came from.
342    pub fn header(&self) -> &TraceHeader {
343        self.reader.header()
344    }
345}
346
347impl<R: Read> Iterator for MoqTraceEventIterator<R> {
348    type Item = Result<TraceEvent, MoqTraceError>;
349
350    fn next(&mut self) -> Option<Self::Item> {
351        match self.reader.read_event() {
352            Ok(Some(event)) => Some(Ok(event)),
353            Ok(None) => None,
354            Err(e) => Some(Err(e)),
355        }
356    }
357}
358
359/// Iterator over the items in a `.moqtrace` file, segment boundaries included.
360pub struct MoqTraceItemIterator<R: Read> {
361    reader: MoqTraceReader<R>,
362}
363
364impl<R: Read> Iterator for MoqTraceItemIterator<R> {
365    type Item = Result<ReadItem, MoqTraceError>;
366
367    fn next(&mut self) -> Option<Self::Item> {
368        match self.reader.read_next() {
369            Ok(Some(item)) => Some(Ok(item)),
370            Ok(None) => None,
371            Err(e) => Some(Err(e)),
372        }
373    }
374}