Skip to content

.moqtrace File Format

.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.

Offset Length Content
------ ------ -------
0 8 Magic bytes: "MOQTRACE" (0x4d4f515452414345)
8 4 Format version (uint32 LE) — currently 1
12 4 Header length (uint32 LE) — byte length of the CBOR header blob
16 N Header (CBOR map) — session metadata
16+N ... Event stream (CBOR sequence) — concatenated CBOR items, one per event

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.

uint32 little-endian. Currently 1. Readers MUST reject files with a version they don’t support.

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.

A single CBOR map with the following keys:

KeyCBOR typeRequiredDescription
protocoltext stringYesMoQT version identifier
perspectivetext stringYesRecording viewpoint: client, server, or observer
detailtext stringYesDetail level (see below)
startTimeintegerYesRecording start time (Unix epoch milliseconds)
endTimeintegerNoRecording end time (Unix epoch milliseconds). Set when trace is finalized.
transporttext stringNoTransport type (e.g., webtransport, raw-quic)
sourcetext stringNoSoftware that produced the trace (e.g., moqtap-devtools/0.1.0)
endpointtext stringNoRemote peer URI (e.g., https://relay.example.com/moq)
sessionIdtext stringNoOpaque identifier for correlating traces from the same session across multiple capture points
custommapNoUser-defined metadata (arbitrary key-value pairs)

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

Describes the vantage point of the recording:

  • client — captured at the MoQT client (QUIC connection initiator)
  • server — captured at the MoQT server or relay
  • observer — passive capture (e.g., DevTools extension, network tap) that intercepts traffic without participating in the session

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 levelWhat’s recordedTypical use case
controlControl messages only (setup, subscribe, publish, goaway, etc.). No data stream events, no object payloads.Lightweight protocol flow analysis
headersControl messages + data stream headers (subgroup/fetch/datagram headers, object metadata: group ID, object ID, priority, status). No payload bytes.Delivery pattern analysis, timing
headers+sizesEverything in headers + payload byte lengths for each object.Bandwidth analysis without storing media
headers+dataEverything in headers + full payload bytes for each object.Full session replay, media corruption debugging
fullEverything 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.

After the header, the remainder of the file is a CBOR sequence (RFC 8742) — concatenated CBOR items with no array wrapper and no separators.

Every event is a CBOR map with these fields:

KeyCBOR typeDescription
nunsigned intMonotonically increasing sequence number (0-based). Disambiguates events with identical timestamps.
tintegerTimestamp in microseconds since startTime in the header
eintegerEvent type (see below)

Short key names are intentional — traces can contain hundreds of thousands of events, and CBOR encodes short strings more compactly.

e valueNameDescription
0Control messageA control-stream message was sent or received
1Stream openedA QUIC stream was opened
2Stream closedA QUIC stream was closed
3Object headerAn object header was parsed from a data stream
4Object payloadObject payload bytes were received/sent
5State changeSession FSM phase transition
6ErrorProtocol or transport error
7AnnotationUser-defined event (custom label + data)
KeyTypeDetail levelDescription
dintegercontrol+Direction: 0 = sent (tx), 1 = received (rx)
mtintegercontrol+Wire message type ID (e.g., 0x03 for SUBSCRIBE)
msgmapcontrol+Decoded message fields (draft-specific structure)
rawbyte stringfull onlyRaw wire bytes including type and length prefix
KeyTypeDetail levelDescription
sidintegerheaders+QUIC stream ID
dintegerheaders+Direction: 0 = outgoing, 1 = incoming
stintegerheaders+Stream type: 0 = subgroup, 1 = datagram, 2 = fetch
KeyTypeDetail levelDescription
sidintegerheaders+QUIC stream ID
ecintegerheaders+Error code (0 = clean close)

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.

KeyTypeDetail levelDescription
sidintegerheaders+Stream ID this object arrived on
gintegerheaders+Group ID
ointegerheaders+Object ID
ppintegerheaders+Publisher priority
osintegerheaders+Object status: 0 = normal, 1 = end-of-group, 2 = end-of-track, 3 = does-not-exist
KeyTypeDetail levelDescription
sidintegerheaders+sizes+Stream ID
gintegerheaders+sizes+Group ID
ointegerheaders+sizes+Object ID
szintegerheaders+sizes+Payload size in bytes
plbyte stringheaders+data+Payload bytes (or zeroed if masked)
KeyTypeDetail levelDescription
fromtext stringcontrol+Previous session phase
totext stringcontrol+New session phase
KeyTypeDetail levelDescription
ecintegercontrol+Error code
reasontext stringcontrol+Human-readable reason
KeyTypeDetail levelDescription
labeltext stringanyUser-defined label
dataanyanyUser-defined data (any CBOR type)

A parser should follow these steps:

  1. Read 8 bytes, verify they equal MOQTRACE (hex 4d4f515452414345). Reject otherwise.
  2. Read 4 bytes as uint32 little-endian — this is the format version. Reject if unsupported.
  3. Read 4 bytes as uint32 little-endian — this is the header length N.
  4. Read N bytes and CBOR-decode them into a map. This is the trace header.
  5. 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.

  1. Write the 8 magic bytes MOQTRACE.
  2. Write format version 1 as uint32 little-endian.
  3. CBOR-encode the header map to get N header bytes.
  4. Write N as uint32 little-endian.
  5. Write the N header bytes.
  6. For each event, CBOR-encode the event map and append to the file.
  7. When finalizing, optionally seek back and update the header (e.g., set endTime), or accept that endTime may be absent in crash-truncated files.
  • 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 (e values) 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.

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.