.moqtrace File Format
Overview
Section titled “Overview”.moqtrace is a binary file format for recording MoQT sessions. It captures protocol-level events at configurable detail levels, from lightweight control-message-only traces to full wire-level recordings with payload bytes.
Design goals:
- Streamable — events are appended as they occur; no backpatching required
- Compact — CBOR encoding handles binary data natively (no base64 overhead)
- Self-describing — the header declares the detail level, protocol version, and recording context so readers know what to expect without scanning events
- Cross-language — readable by any language with a CBOR library
The format is shared between @moqtap/trace (JavaScript, using cbor-x) and moqtap-trace (Rust, using ciborium). A file written by either implementation is readable by both.
File layout
Section titled “File layout”Offset Length Content------ ------ -------0 8 Magic bytes: "MOQTRACE" (0x4d4f515452414345)8 4 Format version (uint32 LE) — currently 112 4 Header length (uint32 LE) — byte length of the CBOR header blob16 N Header (CBOR map) — session metadata16+N ... Event stream (CBOR sequence) — concatenated CBOR items, one per eventMagic bytes
Section titled “Magic bytes”The 8-byte ASCII string MOQTRACE (hex 4d 4f 51 54 52 41 43 45) identifies the file format. Any tool can check these bytes before attempting to parse.
Format version
Section titled “Format version”uint32 little-endian. Currently 1. Readers MUST reject files with a version they don’t support.
Header length
Section titled “Header length”uint32 little-endian. The byte length of the CBOR-encoded header that immediately follows. This lets readers extract metadata without scanning the event stream — useful for file browsers, search indexing, and quick filtering.
Header
Section titled “Header”A single CBOR map with the following keys:
| Key | CBOR type | Required | Description |
|---|---|---|---|
protocol | text string | Yes | MoQT version identifier |
perspective | text string | Yes | Recording viewpoint: client, server, or observer |
detail | text string | Yes | Detail level (see below) |
startTime | integer | Yes | Recording start time (Unix epoch milliseconds) |
endTime | integer | No | Recording end time (Unix epoch milliseconds). Set when trace is finalized. |
transport | text string | No | Transport type (e.g., webtransport, raw-quic) |
source | text string | No | Software that produced the trace (e.g., moqtap-devtools/0.1.0) |
endpoint | text string | No | Remote peer URI (e.g., https://relay.example.com/moq) |
sessionId | text string | No | Opaque identifier for correlating traces from the same session across multiple capture points |
custom | map | No | User-defined metadata (arbitrary key-value pairs) |
Protocol identifier
Section titled “Protocol identifier”The protocol field uses the IETF document name minus the draft-ietf- prefix for drafts, and the RFC number for published standards:
- Draft:
moq-transport-07,moq-transport-14 - RFC:
moq-transport-rfc9999
Perspective
Section titled “Perspective”Describes the vantage point of the recording:
client— captured at the MoQT client (QUIC connection initiator)server— captured at the MoQT server or relayobserver— passive capture (e.g., DevTools extension, network tap) that intercepts traffic without participating in the session
Detail levels
Section titled “Detail levels”Each level is a strict superset of the one above it. Readers can handle any level — they just find more or fewer fields populated per event.
| Detail level | What’s recorded | Typical use case |
|---|---|---|
control | Control messages only (setup, subscribe, publish, goaway, etc.). No data stream events, no object payloads. | Lightweight protocol flow analysis |
headers | Control messages + data stream headers (subgroup/fetch/datagram headers, object metadata: group ID, object ID, priority, status). No payload bytes. | Delivery pattern analysis, timing |
headers+sizes | Everything in headers + payload byte lengths for each object. | Bandwidth analysis without storing media |
headers+data | Everything in headers + full payload bytes for each object. | Full session replay, media corruption debugging |
full | Everything above + raw wire bytes for every message (pre-decode). | Wire-level debugging, compliance testing |
At any detail level, the recorder MAY mask payloads — replacing payload bytes with zeroes to preserve sizes for bandwidth analysis while stripping media content. When active, the header SHOULD include payloadMasked: true in the custom map.
Event stream
Section titled “Event stream”After the header, the remainder of the file is a CBOR sequence (RFC 8742) — concatenated CBOR items with no array wrapper and no separators.
Common fields
Section titled “Common fields”Every event is a CBOR map with these fields:
| Key | CBOR type | Description |
|---|---|---|
n | unsigned int | Monotonically increasing sequence number (0-based). Disambiguates events with identical timestamps. |
t | integer | Timestamp in microseconds since startTime in the header |
e | integer | Event type (see below) |
Short key names are intentional — traces can contain hundreds of thousands of events, and CBOR encodes short strings more compactly.
Event types
Section titled “Event types”e value | Name | Description |
|---|---|---|
| 0 | Control message | A control-stream message was sent or received |
| 1 | Stream opened | A QUIC stream was opened |
| 2 | Stream closed | A QUIC stream was closed |
| 3 | Object header | An object header was parsed from a data stream |
| 4 | Object payload | Object payload bytes were received/sent |
| 5 | State change | Session FSM phase transition |
| 6 | Error | Protocol or transport error |
| 7 | Annotation | User-defined event (custom label + data) |
Event 0: Control message
Section titled “Event 0: Control message”| Key | Type | Detail level | Description |
|---|---|---|---|
d | integer | control+ | Direction: 0 = sent (tx), 1 = received (rx) |
mt | integer | control+ | Wire message type ID (e.g., 0x03 for SUBSCRIBE) |
msg | map | control+ | Decoded message fields (draft-specific structure) |
raw | byte string | full only | Raw wire bytes including type and length prefix |
Event 1: Stream opened
Section titled “Event 1: Stream opened”| Key | Type | Detail level | Description |
|---|---|---|---|
sid | integer | headers+ | QUIC stream ID |
d | integer | headers+ | Direction: 0 = outgoing, 1 = incoming |
st | integer | headers+ | Stream type: 0 = subgroup, 1 = datagram, 2 = fetch |
Event 2: Stream closed
Section titled “Event 2: Stream closed”| Key | Type | Detail level | Description |
|---|---|---|---|
sid | integer | headers+ | QUIC stream ID |
ec | integer | headers+ | Error code (0 = clean close) |
Event 3: Object header
Section titled “Event 3: Object header”Object header and object payload events (event 4) for the same object share (sid, g, o) as a composite key. An object header is always emitted before its corresponding payload.
| Key | Type | Detail level | Description |
|---|---|---|---|
sid | integer | headers+ | Stream ID this object arrived on |
g | integer | headers+ | Group ID |
o | integer | headers+ | Object ID |
pp | integer | headers+ | Publisher priority |
os | integer | headers+ | Object status: 0 = normal, 1 = end-of-group, 2 = end-of-track, 3 = does-not-exist |
Event 4: Object payload
Section titled “Event 4: Object payload”| Key | Type | Detail level | Description |
|---|---|---|---|
sid | integer | headers+sizes+ | Stream ID |
g | integer | headers+sizes+ | Group ID |
o | integer | headers+sizes+ | Object ID |
sz | integer | headers+sizes+ | Payload size in bytes |
pl | byte string | headers+data+ | Payload bytes (or zeroed if masked) |
Event 5: State change
Section titled “Event 5: State change”| Key | Type | Detail level | Description |
|---|---|---|---|
from | text string | control+ | Previous session phase |
to | text string | control+ | New session phase |
Event 6: Error
Section titled “Event 6: Error”| Key | Type | Detail level | Description |
|---|---|---|---|
ec | integer | control+ | Error code |
reason | text string | control+ | Human-readable reason |
Event 7: Annotation
Section titled “Event 7: Annotation”| Key | Type | Detail level | Description |
|---|---|---|---|
label | text string | any | User-defined label |
data | any | any | User-defined data (any CBOR type) |
Reading a .moqtrace file
Section titled “Reading a .moqtrace file”A parser should follow these steps:
- Read 8 bytes, verify they equal
MOQTRACE(hex4d4f515452414345). Reject otherwise. - Read 4 bytes as uint32 little-endian — this is the format version. Reject if unsupported.
- Read 4 bytes as uint32 little-endian — this is the header length
N. - Read
Nbytes and CBOR-decode them into a map. This is the trace header. - Read the remaining bytes as a CBOR sequence: repeatedly decode one CBOR item at a time until EOF. Each item is one event map.
Crash-truncated files are valid up to the last complete CBOR item — the reader simply stops when decoding fails or bytes run out.
Writing a .moqtrace file
Section titled “Writing a .moqtrace file”- Write the 8 magic bytes
MOQTRACE. - Write format version
1as uint32 little-endian. - CBOR-encode the header map to get
Nheader bytes. - Write
Nas uint32 little-endian. - Write the
Nheader bytes. - For each event, CBOR-encode the event map and append to the file.
- When finalizing, optionally seek back and update the header (e.g., set
endTime), or accept thatendTimemay be absent in crash-truncated files.
Versioning and compatibility
Section titled “Versioning and compatibility”- Version 1 is defined by this document.
- Readers MUST check the format version before parsing.
- Unknown keys in header or event maps MUST be ignored (forward-compatible).
- New event types (
evalues) MAY be added in future versions; readers SHOULD skip unknown event types. - The detail level in the header is informational — readers MUST handle missing fields gracefully regardless of declared level.
Why not qlog?
Section titled “Why not qlog?”qlog is designed for QUIC transport events but has no MoQT event schemas. The .moqtrace format is purpose-built for MoQT semantics — it records protocol state transitions, subscription lifecycles, and object delivery alongside transport events.
If the IETF standardizes MoQT qlog event definitions, moqtap will add a qlog export bridge.