Skip to main content

moqtap_trace/
writer.rs

1use std::io::{BufWriter, Write};
2
3use ciborium::Value;
4
5use crate::error::MoqTraceError;
6use crate::event::TraceEvent;
7use crate::header::TraceHeader;
8
9/// Magic bytes identifying a `.moqtrace` file.
10pub const MOQTRACE_MAGIC: &[u8; 8] = b"MOQTRACE";
11
12/// Current format version.
13pub const MOQTRACE_VERSION: u32 = 1;
14
15/// Streaming writer for `.moqtrace` files.
16///
17/// Writes the preamble (magic, version, header) on construction, then
18/// accepts events one at a time via [`write_event`](Self::write_event).
19///
20/// The inner writer is wrapped in a [`BufWriter`] so events can be appended
21/// at line rate without incurring one syscall per event. Call
22/// [`flush`](Self::flush) or [`into_inner`](Self::into_inner) to drain the
23/// buffer.
24#[derive(Debug)]
25pub struct MoqTraceWriter<W: Write> {
26    inner: BufWriter<W>,
27}
28
29impl<W: Write> MoqTraceWriter<W> {
30    /// Create a new writer, writing the file preamble and header.
31    pub fn new(writer: W, header: &TraceHeader) -> Result<Self, MoqTraceError> {
32        let mut writer = BufWriter::new(writer);
33
34        // 1. Magic bytes
35        writer.write_all(MOQTRACE_MAGIC)?;
36
37        // 2. Format version (u32 LE)
38        writer.write_all(&MOQTRACE_VERSION.to_le_bytes())?;
39
40        // 3. CBOR-encode header
41        let header_value: Value = header.into();
42        let mut header_bytes = Vec::with_capacity(128);
43        ciborium::into_writer(&header_value, &mut header_bytes)?;
44
45        // 4. Header length (u32 LE)
46        let header_len = header_bytes.len() as u32;
47        writer.write_all(&header_len.to_le_bytes())?;
48
49        // 5. Header CBOR bytes
50        writer.write_all(&header_bytes)?;
51
52        Ok(Self { inner: writer })
53    }
54
55    /// Append a single event to the file.
56    pub fn write_event(&mut self, event: &TraceEvent) -> Result<(), MoqTraceError> {
57        ciborium::into_writer(event, &mut self.inner)?;
58        Ok(())
59    }
60
61    /// Flush the underlying writer.
62    pub fn flush(&mut self) -> Result<(), MoqTraceError> {
63        self.inner.flush()?;
64        Ok(())
65    }
66
67    /// Consume the writer and return the inner writer.
68    ///
69    /// Flushes any buffered bytes. Returns an error if the flush fails.
70    pub fn into_inner(self) -> Result<W, MoqTraceError> {
71        self.inner.into_inner().map_err(|e| MoqTraceError::Io(e.into_error()))
72    }
73}