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}