Skip to main content

moqtap_trace/
event.rs

1use ciborium::Value;
2
3use crate::error::MoqTraceError;
4use crate::header::{as_i64, as_u64, normalised, store_entries, unrecognised, DetailLevel};
5
6/// Direction of a message or stream relative to the recording endpoint.
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum Direction {
9    /// Sent (outgoing). Wire value: `0`.
10    Send,
11    /// Received (incoming). Wire value: `1`.
12    Receive,
13}
14
15impl Direction {
16    fn from_cbor(v: &Value) -> Result<Self, MoqTraceError> {
17        match as_u64(v) {
18            Some(0) => Ok(Direction::Send),
19            Some(1) => Ok(Direction::Receive),
20            _ => Err(MoqTraceError::InvalidEvent("invalid direction value".into())),
21        }
22    }
23}
24
25/// Data stream type.
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub enum StreamType {
28    /// Subgroup stream. Wire value: `0`.
29    Subgroup,
30    /// Datagram. Wire value: `1`.
31    Datagram,
32    /// Fetch stream. Wire value: `2`.
33    Fetch,
34}
35
36impl StreamType {
37    fn from_cbor(v: &Value) -> Result<Self, MoqTraceError> {
38        match as_u64(v) {
39            Some(0) => Ok(StreamType::Subgroup),
40            Some(1) => Ok(StreamType::Datagram),
41            Some(2) => Ok(StreamType::Fetch),
42            _ => Err(MoqTraceError::InvalidEvent("invalid stream type value".into())),
43        }
44    }
45}
46
47/// What sort of failure an [`Error`](EventData::Error) event records.
48///
49/// An open vocabulary. SPEC.md's Event 6 section names three kinds and says
50/// others may be added without a format version bump, so a spelling this crate
51/// does not know is kept verbatim in [`Other`](ErrorKind::Other) rather than
52/// refused. That is the treatment [`Perspective`](crate::header::Perspective)
53/// gets, and it is the rule SPEC.md points at for this key.
54///
55/// An enum and not a bare `String` because these three are the recorder's own
56/// classification of what it just saw, chosen at the point the event is built:
57/// a misspelling is a value no reader can group by, and the compiler is the
58/// only thing that catches it before the trace is written. The wire spellings
59/// live in [`as_str`](ErrorKind::as_str) alone, so a reader and a writer cannot
60/// disagree about them.
61#[derive(Debug, Clone, PartialEq, Eq)]
62#[non_exhaustive]
63pub enum ErrorKind {
64    /// The peer violated the protocol.
65    Protocol,
66    /// The QUIC or WebTransport layer failed.
67    Transport,
68    /// Bytes that would not parse as any message this recorder knows.
69    Decode,
70    /// A kind this version of the crate does not know, kept verbatim.
71    Other(String),
72}
73
74impl ErrorKind {
75    /// The wire spelling of this kind.
76    pub fn as_str(&self) -> &str {
77        match self {
78            ErrorKind::Protocol => "protocol",
79            ErrorKind::Transport => "transport",
80            ErrorKind::Decode => "decode",
81            ErrorKind::Other(s) => s,
82        }
83    }
84
85    fn parse(s: &str) -> Self {
86        match s {
87            "protocol" => ErrorKind::Protocol,
88            "transport" => ErrorKind::Transport,
89            "decode" => ErrorKind::Decode,
90            other => ErrorKind::Other(other.to_string()),
91        }
92    }
93}
94
95/// Which end of a relay a peer sits on.
96///
97/// This is the direction the *connection* was made in, not the direction
98/// subscriptions flow. A downstream peer may publish and an upstream peer may
99/// subscribe; for subscription causality see
100/// [`EventData::SubscriptionDerivation`].
101#[derive(Debug, Clone, PartialEq, Eq, Hash)]
102#[non_exhaustive]
103pub enum Side {
104    /// The peer connected to this relay.
105    Downstream,
106    /// This relay connected to the peer — an origin or an upstream relay.
107    Upstream,
108    /// A side this version of the crate does not know, kept verbatim.
109    Other(String),
110}
111
112impl Side {
113    /// The wire spelling of this side.
114    pub fn as_str(&self) -> &str {
115        match self {
116            Side::Downstream => "downstream",
117            Side::Upstream => "upstream",
118            Side::Other(s) => s,
119        }
120    }
121
122    fn parse(s: &str) -> Self {
123        match s {
124            "downstream" => Side::Downstream,
125            "upstream" => Side::Upstream,
126            other => Side::Other(other.to_string()),
127        }
128    }
129}
130
131/// Role a peer is acting in at connection time.
132#[derive(Debug, Clone, PartialEq, Eq)]
133#[non_exhaustive]
134pub enum PeerRole {
135    /// Peer is acting as a publisher.
136    Publisher,
137    /// Peer is acting as a subscriber.
138    Subscriber,
139    /// Peer is acting as both.
140    Both,
141    /// A role this version of the crate does not know, kept verbatim.
142    Other(String),
143}
144
145impl PeerRole {
146    /// The wire spelling of this role.
147    pub fn as_str(&self) -> &str {
148        match self {
149            PeerRole::Publisher => "publisher",
150            PeerRole::Subscriber => "subscriber",
151            PeerRole::Both => "both",
152            PeerRole::Other(s) => s,
153        }
154    }
155
156    fn parse(s: &str) -> Self {
157        match s {
158            "publisher" => PeerRole::Publisher,
159            "subscriber" => PeerRole::Subscriber,
160            "both" => PeerRole::Both,
161            other => PeerRole::Other(other.to_string()),
162        }
163    }
164}
165
166/// How an upstream subscription came to serve a downstream one.
167#[derive(Debug, Clone, PartialEq, Eq)]
168#[non_exhaustive]
169pub enum DerivationKind {
170    /// A new upstream subscription was created to satisfy downstream subs.
171    Created,
172    /// An existing upstream subscription now also serves an additional
173    /// downstream sub — subscription fan-in.
174    Shared,
175    /// A kind this version of the crate does not know, kept verbatim.
176    Other(String),
177}
178
179impl DerivationKind {
180    /// The wire spelling of this kind.
181    pub fn as_str(&self) -> &str {
182        match self {
183            DerivationKind::Created => "created",
184            DerivationKind::Shared => "shared",
185            DerivationKind::Other(s) => s,
186        }
187    }
188
189    fn parse(s: &str) -> Self {
190        match s {
191            "created" => DerivationKind::Created,
192            "shared" => DerivationKind::Shared,
193            other => DerivationKind::Other(other.to_string()),
194        }
195    }
196}
197
198/// A `(peer, request id)` reference naming one subscription on one peer.
199///
200/// Request IDs are only unique within a peer's session, so neither half
201/// identifies a subscription on its own.
202#[derive(Debug, Clone, PartialEq, Eq)]
203pub struct SubscriptionRef {
204    /// Source-local peer identifier.
205    pub peer: String,
206    /// Request ID of the subscription on that peer.
207    pub request_id: u64,
208}
209
210impl SubscriptionRef {
211    /// A reference to `request_id` on `peer`.
212    pub fn new(peer: impl Into<String>, request_id: u64) -> Self {
213        SubscriptionRef { peer: peer.into(), request_id }
214    }
215
216    fn to_value(&self) -> Value {
217        Value::Array(vec![Value::Text(self.peer.clone()), Value::Integer(self.request_id.into())])
218    }
219
220    fn from_value(v: &Value) -> Result<Self, MoqTraceError> {
221        let arr = match v {
222            Value::Array(a) => a,
223            _ => {
224                return Err(MoqTraceError::InvalidEvent(
225                    "subscription ref is not a CBOR array".into(),
226                ))
227            }
228        };
229        if arr.len() != 2 {
230            return Err(MoqTraceError::InvalidEvent(
231                "subscription ref must be [peer, request_id]".into(),
232            ));
233        }
234        let peer = arr[0]
235            .as_text()
236            .ok_or_else(|| MoqTraceError::InvalidEvent("subscription ref peer is not text".into()))?
237            .to_string();
238        let request_id = as_u64(&arr[1]).ok_or_else(|| {
239            MoqTraceError::InvalidEvent("subscription ref request_id is not uint".into())
240        })?;
241        Ok(SubscriptionRef { peer, request_id })
242    }
243}
244
245/// Length of a trace ID, in bytes. Fixed by the format.
246pub const TRACE_ID_LEN: usize = 16;
247
248/// The most bytes a recorder may put in an [`Error`](EventData::Error) event's
249/// `"raw"`. Fixed by the format — SPEC.md, Event 6, under the heading about
250/// the cap and `"rawlen"`.
251///
252/// The cap binds the party building an event out of bytes it has just
253/// observed, and nothing else. It is deliberately **not** enforced when an
254/// event is serialized, and this crate does not enforce it there: a serializer
255/// cannot tell a freshly recorded event from one that arrived by being read,
256/// so a cap applied at that point either shortens evidence on a rewrite or
257/// refuses a file the reader was required to accept — whichever it does, it
258/// does to the wrong events. A `"raw"` longer than this reads back at its full
259/// length and is written back at its full length.
260///
261/// [`EventData::error_observed`] is where the cap belongs and where this crate
262/// applies it. A recorder assembling the variant by hand applies it here.
263///
264/// One obligation stays with the recorder and cannot live in a constructor at
265/// all: SPEC.md allows `"raw"` once per flow — per stream where the error names
266/// one, per peer where it does not. Later errors on that flow are still
267/// recorded and simply carry no bytes. That is state across events, so it
268/// belongs to whatever is holding the flow.
269pub const ERROR_RAW_CAP: usize = 4096;
270
271/// Event type discriminants, matching the specification's `"e"` values.
272const EVENT_CONTROL_MESSAGE: u64 = 0;
273const EVENT_STREAM_OPENED: u64 = 1;
274const EVENT_STREAM_CLOSED: u64 = 2;
275const EVENT_OBJECT_HEADER: u64 = 3;
276const EVENT_OBJECT_PAYLOAD: u64 = 4;
277const EVENT_STATE_CHANGE: u64 = 5;
278const EVENT_ERROR: u64 = 6;
279const EVENT_ANNOTATION: u64 = 7;
280const EVENT_PEER_CONNECTED: u64 = 8;
281const EVENT_PEER_DISCONNECTED: u64 = 9;
282const EVENT_SUBSCRIPTION_DERIVATION: u64 = 10;
283
284/// A single event in a `.moqtrace` file.
285#[derive(Debug, Clone, PartialEq)]
286pub struct TraceEvent {
287    /// Monotonically increasing sequence number (0-based). Segment-local in a
288    /// segmented trace, so ordering across segments is `(segment, seq)`.
289    pub seq: u64,
290    /// Timestamp in microseconds since the containing segment's start time.
291    pub timestamp: i64,
292    /// Which peer this event pertains to.
293    ///
294    /// Required when the trace's perspective is
295    /// [`RelayTap`](crate::header::Perspective::RelayTap), where a single
296    /// trace covers many concurrent sessions; omitted otherwise, since a
297    /// single-session trace has only one peer to speak of. The identifier is
298    /// **source-local**: the same string in two traces from different sources
299    /// does not name the same peer.
300    pub peer: Option<String>,
301    /// Event-specific data.
302    pub data: EventData,
303    /// Keys on this event that this version of the crate could not use, kept
304    /// verbatim.
305    ///
306    /// Optional keys may be added to an existing event type without a format
307    /// version bump, so "unknown keys MUST be ignored" is a rule about
308    /// *reading past* them. It is not a licence to drop them: a tool that
309    /// reads a trace and writes it back — a redaction pass, a filter, a
310    /// re-segmentation — would otherwise emit a valid file that looks like it
311    /// never carried them, and one tool's ignorance would become permanent for
312    /// every reader downstream of it.
313    ///
314    /// A key this crate *does* know lands here too when its value is not of a
315    /// type that key can hold — `"ta": "hello"` on an event 1, say. SPEC.md
316    /// treats such a key as unrecognised: the value is ignored for meaning,
317    /// the field that would have held it reads `None`, and the entry is
318    /// written back unchanged. Knowing more about a key must not mean
319    /// preserving it less.
320    ///
321    /// [`EventData::Unknown`] already does this for an event type the crate
322    /// cannot name. This is the same guarantee one level down, for a key on a
323    /// type it can.
324    ///
325    /// Unchanged binds the value and not its encoding, exactly as it does for
326    /// [`TraceHeader::extra`](crate::header::TraceHeader::extra): on the way
327    /// out an integral float in a stored value is written as a CBOR integer
328    /// and a byte string under RFC 8746's tag 64 as major type 2, at any
329    /// depth. SPEC.md's two encoding rules are about every byte a writer
330    /// emits rather than only the keys it understood, and the JavaScript
331    /// implementation's decoder folds both shapes away before its own code
332    /// runs, so it could not emit either however hard it tried. Nothing a
333    /// comparison of the two values can see changes, with the single
334    /// exception SPEC.md names: `-0.0` written as `0` loses its sign.
335    ///
336    /// A CBOR map may not carry one key twice, and this list can: it is an
337    /// ordered list of pairs, not a map. So on the way out an entry naming a
338    /// key the event writes from a field is dropped, and of two entries
339    /// sharing a key the first is written — as it is for a map nested inside
340    /// a stored value, which is a map this crate emits too.
341    ///
342    /// The same two passes govern the other opaque values an event carries:
343    /// a control message's `"msg"`, an annotation's `"data"` and an
344    /// [`EventData::Unknown`]'s fields.
345    ///
346    /// Empty for every event this crate constructs itself.
347    pub extra: Vec<(Value, Value)>,
348}
349
350/// Event-specific payload, discriminated by type.
351#[derive(Debug, Clone, PartialEq)]
352#[non_exhaustive]
353pub enum EventData {
354    /// A control-stream message was sent or received (event type 0).
355    ControlMessage {
356        /// Send or receive.
357        direction: Direction,
358        /// Wire message type ID (e.g. `0x03` for SUBSCRIBE).
359        message_type: u64,
360        /// Decoded message fields, and **not necessarily a map**: match on it
361        /// rather than assuming, or use [`TraceEvent::request_id`].
362        ///
363        /// A *recorder* MUST write a CBOR map keyed in snake_case here, and an
364        /// empty map when it decoded nothing. A *reader* has to take what
365        /// earlier writers produced, and every `capture-*` case in the
366        /// conformance corpus holds a text rendering of the message instead.
367        /// Such a value is handed back verbatim — unaddressable by key, but
368        /// not a reason to reject an event the format forbids dropping — and
369        /// is written back unchanged, because replacing it would destroy the
370        /// only record of a message nobody will see again.
371        ///
372        /// Unchanged binds what the value says and not how it is encoded.
373        /// This is a value the reader never looked at, so the two encoding
374        /// rules reach it on the way out like any other:
375        /// [`TraceEvent::extra`] has the whole of it.
376        message: Value,
377        /// QUIC stream the message travelled on, when the recorder knows it.
378        ///
379        /// `None` means unknown, which a reader must not confuse with stream
380        /// 0. Recorders should fill it in: from draft-17 each request has its
381        /// own bidirectional stream and responses carry no request ID, so the
382        /// stream is the only thing pairing a response with its request.
383        stream_id: Option<u64>,
384        /// Raw wire bytes (only at `full` detail level).
385        raw: Option<Vec<u8>>,
386    },
387    /// A QUIC stream was opened (event type 1).
388    ///
389    /// The four optional identifiers are what a `headers` recording has
390    /// instead of the stream header bytes. No detail level records the bytes
391    /// of a `SUBGROUP_HEADER`, a fetch header or a datagram header, so a value
392    /// carried only there has nothing left to be re-parsed from — and before
393    /// these fields existed the model could express group, object, priority
394    /// and status and nothing else, which left a `headers` trace unable to say
395    /// which track a stream belonged to.
396    ///
397    /// Each is `None` when the recorder did not know it — and also when the
398    /// file carried the key with a value that is not an unsigned integer, in
399    /// which case the entry is kept verbatim in [`TraceEvent::extra`] instead
400    /// of being read here. A reader must not refuse a stream for lacking one:
401    /// every recording predating the keys lacks all four.
402    StreamOpened {
403        /// QUIC stream ID.
404        stream_id: u64,
405        /// Outgoing or incoming.
406        direction: Direction,
407        /// Stream type.
408        stream_type: StreamType,
409        /// Track alias the stream carries. Meaningful on any stream type.
410        track_alias: Option<u64>,
411        /// Subgroup ID, on a [`StreamType::Subgroup`] stream.
412        subgroup_id: Option<u64>,
413        /// Fetch request ID, on a [`StreamType::Fetch`] stream.
414        ///
415        /// A recorder must write it there. It is the only correlation between
416        /// the stream and the FETCH that asked for it, and unlike a subgroup
417        /// stream a fetch stream carries no track alias to identify it by
418        /// instead — so without it a fetch stream in a `headers` trace names
419        /// nothing at all.
420        fetch_request_id: Option<u64>,
421        /// Group ID, on a [`StreamType::Datagram`] stream.
422        ///
423        /// Scoped to datagrams because on a subgroup stream every object
424        /// carries the group of the stream by construction, so a copy here
425        /// would be a second field with no independent source. Where it does
426        /// appear alongside an [`ObjectHeader`](EventData::ObjectHeader) for
427        /// the same stream, the object header is authoritative: this copy
428        /// serves a reader that has not yet seen an object, and a
429        /// disagreement between the two is not corruption.
430        group_id: Option<u64>,
431    },
432    /// A QUIC stream was closed (event type 2).
433    StreamClosed {
434        /// QUIC stream ID.
435        stream_id: u64,
436        /// Error code (0 = clean close).
437        error_code: u64,
438    },
439    /// An object header was parsed from a data stream (event type 3).
440    ObjectHeader {
441        /// Stream ID this object arrived on.
442        stream_id: u64,
443        /// Group ID.
444        group: u64,
445        /// Object ID.
446        object: u64,
447        /// Publisher priority.
448        publisher_priority: u64,
449        /// Object status (0=normal, 1=end-of-group, etc.).
450        object_status: u64,
451    },
452    /// Object payload bytes were received or sent (event type 4).
453    ObjectPayload {
454        /// Stream ID.
455        stream_id: u64,
456        /// Group ID.
457        group: u64,
458        /// Object ID.
459        object: u64,
460        /// Payload size in bytes.
461        size: u64,
462        /// Payload bytes (only at `headers+data` or `full` level).
463        payload: Option<Vec<u8>>,
464    },
465    /// Session FSM phase transition (event type 5).
466    StateChange {
467        /// Previous session phase.
468        from: String,
469        /// New session phase.
470        to: String,
471    },
472    /// Protocol or transport error (event type 6).
473    ///
474    /// The four optional fields are what makes the event evidence rather than
475    /// an assertion. A peer that sends something malformed is one of the few
476    /// things a shared trace is uniquely good for — the recording party can
477    /// see it and the sending party cannot — and until these existed the only
478    /// field in the format able to hold bytes was a control message's `"raw"`,
479    /// so a recorder wanting to keep the offending bytes had to record the
480    /// violation as a decodable message in order to have somewhere to put
481    /// them.
482    ///
483    /// Each is `None` when the recorder did not write it — and also when the
484    /// file carried the key with a value the field cannot hold, in which case
485    /// the entry stays verbatim in [`TraceEvent::extra`] instead of being read
486    /// here.
487    ///
488    /// [`error_observed`](EventData::error_observed) builds one of these from
489    /// bytes a recorder has just seen, which is where the cap on `raw` and the
490    /// detail levels the two byte-bearing fields sit at are applied.
491    Error {
492        /// Error code.
493        error_code: u64,
494        /// Human-readable reason.
495        reason: String,
496        /// QUIC stream the error was observed on, when there was one and the
497        /// recorder knows it.
498        ///
499        /// Optional on the same terms as a control message's `"sid"`: `None`
500        /// means there was no stream or none is known, which a reader must not
501        /// confuse with stream 0.
502        stream_id: Option<u64>,
503        /// What sort of failure this was.
504        kind: Option<ErrorKind>,
505        /// Byte length of the input the recorder held for this error, before
506        /// any truncation.
507        ///
508        /// Recorded from `headers+sizes` upwards, one level below the bytes
509        /// themselves: how large a malformed message was is often enough on
510        /// its own to tell a truncated message from a mistyped one, and it
511        /// carries no content. It is still a size, and this format gates sizes
512        /// deliberately, so it does not reach a `control`-level trace.
513        ///
514        /// Where both are present, a value larger than `raw`'s length is how a
515        /// reader learns the capture is partial and by how much. Where this is
516        /// absent, a `raw` of exactly [`ERROR_RAW_CAP`] bytes is the one length
517        /// the cap makes ambiguous and must be taken as possibly truncated.
518        raw_len: Option<u64>,
519        /// The offending bytes, at `full` detail only.
520        ///
521        /// Payload-bearing, and gated a level above the event's own
522        /// `control`+: an error naming a *data* stream has subgroup framing
523        /// and object payload behind it, so inheriting the event's level would
524        /// have put media into traces whose declared level excludes payloads
525        /// outright.
526        ///
527        /// A recorder caps this at [`ERROR_RAW_CAP`] bytes. A reader does not:
528        /// a longer one read from a file is neither shortened nor refused.
529        raw: Option<Vec<u8>>,
530    },
531    /// User-defined annotation (event type 7).
532    Annotation {
533        /// User-defined label.
534        label: String,
535        /// User-defined data (any CBOR type).
536        ///
537        /// Opaque: nothing here reads it, and it is written back saying what
538        /// it said. Its encoding is the writer's, though — see
539        /// [`TraceEvent::extra`].
540        data: Value,
541    },
542    /// A new peer session was established (event type 8).
543    ///
544    /// The identifier for the peer lives on [`TraceEvent::peer`]; every later
545    /// event for that peer repeats it.
546    PeerConnected {
547        /// Peer-reported endpoint URI or remote address, best-effort.
548        endpoint: Option<String>,
549        /// Transport type (`"webtransport"`, `"raw-quic"`, …).
550        transport: Option<String>,
551        /// Role at connection time, if known then.
552        role: Option<PeerRole>,
553        /// Which end of the relay the peer connected from.
554        side: Option<Side>,
555    },
556    /// A peer session ended (event type 9).
557    PeerDisconnected {
558        /// Error or close code (0 = clean close).
559        error_code: u64,
560        /// Human-readable reason.
561        reason: Option<String>,
562    },
563    /// An upstream subscription was created or extended in causal response to
564    /// downstream ones (event type 10).
565    ///
566    /// This is the primitive multi-hop correlation is built from: a collector
567    /// reconstructs an end-to-end tree from these links plus the trace IDs
568    /// propagated along them.
569    ///
570    /// The four timestamps share the `timestamp` field's timebase — the
571    /// emitting source's own clock. Differences between them are therefore
572    /// meaningful with no cross-hop clock agreement, which is the point: they
573    /// measure how long *this* hop took. Never subtract one source's
574    /// timestamp from another's.
575    ///
576    /// A source emits the event as soon as the downstream SUBSCRIBE arrives,
577    /// carrying whichever timestamps it has, and may emit it again for the
578    /// same pair as the rest arrive. A consumer treats the later event as an
579    /// update to the earlier one, not as a second derivation.
580    SubscriptionDerivation {
581        /// The upstream subscription.
582        upstream: SubscriptionRef,
583        /// Every downstream subscription currently served by `upstream`.
584        downstream: Vec<SubscriptionRef>,
585        /// Whether the upstream sub was created here or was already running.
586        kind: DerivationKind,
587        /// Trace ID propagated along the subscription chain, carried as raw
588        /// bytes at every layer so two implementations produce identical
589        /// values for the same chain.
590        trace_id: Option<[u8; TRACE_ID_LEN]>,
591        /// Track namespace the subscription targets, one entry per field.
592        namespace: Option<Vec<Vec<u8>>>,
593        /// Track name the subscription targets.
594        track_name: Option<Vec<u8>>,
595        /// When the downstream SUBSCRIBE was received.
596        t_downstream_received: Option<i64>,
597        /// When the upstream SUBSCRIBE was transmitted. Absent for terminal
598        /// subscriptions, where this source is the content origin.
599        t_upstream_sent: Option<i64>,
600        /// When SUBSCRIBE_OK was received from upstream. Absent for terminal
601        /// or still-in-flight subscriptions.
602        t_upstream_ok_received: Option<i64>,
603        /// When SUBSCRIBE_OK was transmitted downstream. Absent while the
604        /// subscription is still in flight.
605        t_downstream_ok_sent: Option<i64>,
606    },
607    /// An event whose type this version of the crate does not know.
608    ///
609    /// New event types may be added without a format version bump, so a
610    /// reader that rejected them would turn every future addition into a
611    /// breaking change. The fields are kept verbatim, which means an unknown
612    /// event survives a read-modify-write round trip intact — intact in what
613    /// it says, since the encoding written back is this crate's own. See
614    /// [`TraceEvent::extra`].
615    Unknown {
616        /// The event type discriminant that was read.
617        event_type: u64,
618        /// Every key and value from the event map other than `"n"`, `"t"`
619        /// and `"e"` — and other than `"p"`, when a peer was read from it. A
620        /// `"p"` that is not text is kept here like any other value this crate
621        /// could not use.
622        fields: Vec<(Value, Value)>,
623    },
624}
625
626/// Where `detail` sits in the levels' ordering, or `None` for a level this
627/// crate cannot place.
628///
629/// The levels are a chain — each records everything the one before it does —
630/// so one rank answers every question about which of two a recorder is at.
631/// A level this crate has never heard of has no place in the chain, which is
632/// why this is an `Option` rather than a number with a default: a default
633/// would guess, and the two ways of guessing wrong are not symmetric.
634fn detail_rank(detail: &DetailLevel) -> Option<u8> {
635    match detail {
636        DetailLevel::Control => Some(0),
637        DetailLevel::Headers => Some(1),
638        DetailLevel::HeadersSizes => Some(2),
639        DetailLevel::HeadersData => Some(3),
640        DetailLevel::Full => Some(4),
641        DetailLevel::Other(_) => None,
642    }
643}
644
645/// Whether a recorder at `detail` records everything a recorder at `floor`
646/// does.
647///
648/// `false` when either level is one this crate cannot place. A future level
649/// might sit above `floor` or below it, and answering `true` on a guess would
650/// put payload bytes into a trace whose declared level excludes them — a loss
651/// of one diagnostic against a leak that cannot be taken back.
652fn detail_reaches(detail: &DetailLevel, floor: &DetailLevel) -> bool {
653    match (detail_rank(detail), detail_rank(floor)) {
654        (Some(level), Some(floor)) => level >= floor,
655        _ => false,
656    }
657}
658
659impl EventData {
660    /// An [`Error`](EventData::Error) built from the bytes a recorder has just
661    /// observed, for a trace recorded at `detail`.
662    ///
663    /// This is where the cap on `"raw"` belongs and the only place this crate
664    /// applies it: the party constructing an event out of traffic it just saw
665    /// is the one SPEC.md addresses, and it is the only party that can tell a
666    /// fresh event from one that arrived by being read. Serializing does not
667    /// cap, and reading does not refuse — see [`ERROR_RAW_CAP`].
668    ///
669    /// `observed` is the whole input the recorder held, uncapped. What comes
670    /// back depends on `detail`, because the two byte-bearing fields sit at
671    /// different levels and that is deliberate:
672    ///
673    /// - `raw_len` is the full length of `observed`, from `headers+sizes`
674    ///   upwards. It is a size, and sizes are gated; it is not gated *with*
675    ///   the bytes, so it is available in every trace where the bytes must not
676    ///   appear, which is the point of having it.
677    /// - `raw` is the first [`ERROR_RAW_CAP`] bytes of `observed`, at `full`
678    ///   only. Below that level nothing is copied.
679    ///
680    /// A level this crate cannot place yields neither.
681    ///
682    /// Where both come back, comparing them is how a reader learns the capture
683    /// was truncated: `raw_len` is the length before the cap bit, not after.
684    pub fn error_observed(
685        error_code: u64,
686        reason: impl Into<String>,
687        kind: Option<ErrorKind>,
688        stream_id: Option<u64>,
689        observed: &[u8],
690        detail: &DetailLevel,
691    ) -> Self {
692        let raw_len = detail_reaches(detail, &DetailLevel::HeadersSizes)
693            .then(|| u64::try_from(observed.len()).unwrap_or(u64::MAX));
694        let raw = detail_reaches(detail, &DetailLevel::Full)
695            .then(|| observed[..observed.len().min(ERROR_RAW_CAP)].to_vec());
696        EventData::Error { error_code, reason: reason.into(), stream_id, kind, raw_len, raw }
697    }
698}
699
700impl TraceEvent {
701    /// An event with no peer identifier — the single-session case.
702    pub fn new(seq: u64, timestamp: i64, data: EventData) -> Self {
703        TraceEvent { seq, timestamp, peer: None, data, extra: Vec::new() }
704    }
705
706    /// An event attributed to `peer` — the relay-tap case.
707    pub fn for_peer(seq: u64, timestamp: i64, peer: impl Into<String>, data: EventData) -> Self {
708        TraceEvent { seq, timestamp, peer: Some(peer.into()), data, extra: Vec::new() }
709    }
710
711    /// Attach unrecognised keys, for a caller reconstructing an event it did
712    /// not decode itself.
713    ///
714    /// Keys that collide with ones the event writes from its own fields are
715    /// dropped on serialization rather than written twice, since a CBOR map
716    /// with a repeated key is malformed and the event's own value is the one
717    /// the reader would have produced. A key the event's type merely *defines*
718    /// does not collide: an optional field holding `None` writes nothing, so
719    /// the entry here is the only copy of that key and is written.
720    #[must_use]
721    pub fn with_extra(mut self, extra: Vec<(Value, Value)>) -> Self {
722        self.extra = extra;
723        self
724    }
725
726    /// The event type discriminant this event serializes as.
727    pub fn event_type(&self) -> u64 {
728        match &self.data {
729            EventData::ControlMessage { .. } => EVENT_CONTROL_MESSAGE,
730            EventData::StreamOpened { .. } => EVENT_STREAM_OPENED,
731            EventData::StreamClosed { .. } => EVENT_STREAM_CLOSED,
732            EventData::ObjectHeader { .. } => EVENT_OBJECT_HEADER,
733            EventData::ObjectPayload { .. } => EVENT_OBJECT_PAYLOAD,
734            EventData::StateChange { .. } => EVENT_STATE_CHANGE,
735            EventData::Error { .. } => EVENT_ERROR,
736            EventData::Annotation { .. } => EVENT_ANNOTATION,
737            EventData::PeerConnected { .. } => EVENT_PEER_CONNECTED,
738            EventData::PeerDisconnected { .. } => EVENT_PEER_DISCONNECTED,
739            EventData::SubscriptionDerivation { .. } => EVENT_SUBSCRIPTION_DERIVATION,
740            EventData::Unknown { event_type, .. } => *event_type,
741        }
742    }
743
744    /// Extract the `request_id` from a control message's decoded `"msg"`
745    /// field, if present.
746    ///
747    /// Returns `None` for non-control-message events, for a `"msg"` that is
748    /// not a map, and for a map that names the field something else. Drafts 07
749    /// through 10 call it `subscribe_id`, and this does not answer for them:
750    /// the two names sit on different messages with different meanings, and a
751    /// reader that wants either can ask the map itself.
752    ///
753    /// The key is `request_id`, in the snake_case the drafts use, because that
754    /// is what every writer of these files produces. It read `requestId` until
755    /// draft-20, and matched nothing — not this crate's own corpus, and not a
756    /// trace written by any other implementation.
757    pub fn request_id(&self) -> Option<u64> {
758        if let EventData::ControlMessage { message: Value::Map(ref pairs), .. } = self.data {
759            for (k, v) in pairs {
760                if k.as_text() == Some("request_id") {
761                    return v.as_integer().and_then(|i| u64::try_from(i).ok());
762                }
763            }
764        }
765        None
766    }
767
768    /// Return the message type for control message events.
769    pub fn message_type(&self) -> Option<u64> {
770        if let EventData::ControlMessage { message_type, .. } = self.data {
771            Some(message_type)
772        } else {
773            None
774        }
775    }
776
777    /// Return the direction for events that have one.
778    pub fn direction(&self) -> Option<Direction> {
779        match &self.data {
780            EventData::ControlMessage { direction, .. }
781            | EventData::StreamOpened { direction, .. } => Some(*direction),
782            _ => None,
783        }
784    }
785}
786
787// ── CBOR conversion ────────────────────────────────────────
788
789/// Wrapper that serializes as a CBOR byte string (major type 2). Without this
790/// wrapper, serializing `&[u8]` via generic `Serialize` produces a CBOR array
791/// of u8.
792struct ByteStr<'a>(&'a [u8]);
793
794impl serde::Serialize for ByteStr<'_> {
795    #[inline]
796    fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
797        s.serialize_bytes(self.0)
798    }
799}
800
801impl Direction {
802    #[inline]
803    fn to_u64(self) -> u64 {
804        match self {
805            Direction::Send => 0,
806            Direction::Receive => 1,
807        }
808    }
809}
810
811impl StreamType {
812    #[inline]
813    fn to_u64(self) -> u64 {
814        match self {
815            StreamType::Subgroup => 0,
816            StreamType::Datagram => 1,
817            StreamType::Fetch => 2,
818        }
819    }
820}
821
822/// Build a CBOR [`Value`] from a [`TraceEvent`] by running it through the
823/// crate's `Serialize` impl. Convenience for tests and inspection — the
824/// hot write path in [`MoqTraceWriter`](crate::writer::MoqTraceWriter) uses
825/// `Serialize` directly and never materializes a `Value`.
826impl From<&TraceEvent> for Value {
827    fn from(event: &TraceEvent) -> Self {
828        Value::serialized(event).expect("TraceEvent serialization is infallible")
829    }
830}
831
832impl serde::Serialize for TraceEvent {
833    fn serialize<S: serde::Serializer>(&self, ser: S) -> Result<S::Ok, S::Error> {
834        use serde::ser::SerializeMap;
835
836        // Every value an event carries out of a file rather than out of a
837        // typed field goes out through `normalised`: an unknown event type's
838        // fields and the store here, a control message's `"msg"` and an
839        // annotation's `"data"` below. The two encoding rules bind the bytes a
840        // writer emits and so bind every value it emits, the ones it never
841        // looked at included — SPEC.md, Interoperability. A typed field needs
842        // none of this and gets none: `"raw"`, `"pl"`, `"traceId"`, `"tn"` and
843        // every field of `"ns"` are `Vec<u8>` by the time they reach here and
844        // go out as major type 2 by construction, and every integer field goes
845        // out as a CBOR integer for the same reason.
846        //
847        // Filtered against the common fields on the way out, the same way the
848        // store below is filtered against every field the event writes: an
849        // unknown event type writes `"n"`, `"t"`, `"e"` and `"p"` from the
850        // common fields, so an entry here naming one of those is dropped
851        // rather than putting that key in the map twice. A caller can put one
852        // in this list by hand, and a file that repeated a common key leaves
853        // one here too — that is where the entry `seq` did not take now lives.
854        let unknown_fields = match &self.data {
855            EventData::Unknown { fields, .. } => {
856                store_entries(fields, |key| writes_common_key(self.peer.as_deref(), key))
857            }
858            _ => Vec::new(),
859        };
860
861        // A key this event writes from one of its own fields is written from
862        // there, so an `extra` entry naming it is dropped rather than written
863        // twice: a CBOR map with a duplicate key is malformed, and the field
864        // is what a reader produced. A key the type merely *defines* is not
865        // one of those — an optional field holding `None` writes nothing, and
866        // the entry here is then a value the decode could not use, which has
867        // to go back out.
868        //
869        // The same pass the header's stores go through, for the same three
870        // reasons: the filter above, the encoding rules, and no key twice.
871        let extra = store_entries(&self.extra, |key| {
872            writes_common_key(self.peer.as_deref(), key) || writes_variant_key(&self.data, key)
873        });
874
875        // Pre-count entries so the CBOR map is encoded with a definite length.
876        let variant_entries = match &self.data {
877            EventData::ControlMessage { stream_id, raw, .. } => {
878                3 + usize::from(stream_id.is_some()) + usize::from(raw.is_some())
879            }
880            EventData::StreamOpened {
881                track_alias,
882                subgroup_id,
883                fetch_request_id,
884                group_id,
885                ..
886            } => {
887                3 + usize::from(track_alias.is_some())
888                    + usize::from(subgroup_id.is_some())
889                    + usize::from(fetch_request_id.is_some())
890                    + usize::from(group_id.is_some())
891            }
892            EventData::StreamClosed { .. } => 2,
893            EventData::ObjectHeader { .. } => 5,
894            EventData::ObjectPayload { payload, .. } => 4 + usize::from(payload.is_some()),
895            EventData::StateChange { .. } => 2,
896            EventData::Error { stream_id, kind, raw_len, raw, .. } => {
897                2 + usize::from(stream_id.is_some())
898                    + usize::from(kind.is_some())
899                    + usize::from(raw_len.is_some())
900                    + usize::from(raw.is_some())
901            }
902            EventData::Annotation { .. } => 2,
903            EventData::PeerConnected { endpoint, transport, role, side } => {
904                usize::from(endpoint.is_some())
905                    + usize::from(transport.is_some())
906                    + usize::from(role.is_some())
907                    + usize::from(side.is_some())
908            }
909            EventData::PeerDisconnected { reason, .. } => 1 + usize::from(reason.is_some()),
910            EventData::SubscriptionDerivation {
911                trace_id,
912                namespace,
913                track_name,
914                t_downstream_received,
915                t_upstream_sent,
916                t_upstream_ok_received,
917                t_downstream_ok_sent,
918                ..
919            } => {
920                3 + usize::from(trace_id.is_some())
921                    + usize::from(namespace.is_some())
922                    + usize::from(track_name.is_some())
923                    + usize::from(t_downstream_received.is_some())
924                    + usize::from(t_upstream_sent.is_some())
925                    + usize::from(t_upstream_ok_received.is_some())
926                    + usize::from(t_downstream_ok_sent.is_some())
927            }
928            EventData::Unknown { .. } => unknown_fields.len(),
929        };
930
931        let entries = 3 /* n, t, e */
932            + usize::from(self.peer.is_some())
933            + variant_entries
934            + extra.len();
935
936        let mut map = ser.serialize_map(Some(entries))?;
937        map.serialize_entry("n", &self.seq)?;
938        map.serialize_entry("t", &self.timestamp)?;
939        if let Some(ref peer) = self.peer {
940            map.serialize_entry("p", peer)?;
941        }
942
943        match &self.data {
944            EventData::ControlMessage { direction, message_type, message, stream_id, raw } => {
945                map.serialize_entry("e", &EVENT_CONTROL_MESSAGE)?;
946                map.serialize_entry("d", &direction.to_u64())?;
947                map.serialize_entry("mt", message_type)?;
948                map.serialize_entry("msg", &normalised(message))?;
949                if let Some(sid) = stream_id {
950                    map.serialize_entry("sid", sid)?;
951                }
952                if let Some(raw) = raw {
953                    map.serialize_entry("raw", &ByteStr(raw))?;
954                }
955            }
956            EventData::StreamOpened {
957                stream_id,
958                direction,
959                stream_type,
960                track_alias,
961                subgroup_id,
962                fetch_request_id,
963                group_id,
964            } => {
965                map.serialize_entry("e", &EVENT_STREAM_OPENED)?;
966                map.serialize_entry("sid", stream_id)?;
967                map.serialize_entry("d", &direction.to_u64())?;
968                map.serialize_entry("st", &stream_type.to_u64())?;
969                if let Some(ta) = track_alias {
970                    map.serialize_entry("ta", ta)?;
971                }
972                if let Some(sg) = subgroup_id {
973                    map.serialize_entry("sg", sg)?;
974                }
975                if let Some(fri) = fetch_request_id {
976                    map.serialize_entry("fri", fri)?;
977                }
978                if let Some(g) = group_id {
979                    map.serialize_entry("g", g)?;
980                }
981            }
982            EventData::StreamClosed { stream_id, error_code } => {
983                map.serialize_entry("e", &EVENT_STREAM_CLOSED)?;
984                map.serialize_entry("sid", stream_id)?;
985                map.serialize_entry("ec", error_code)?;
986            }
987            EventData::ObjectHeader {
988                stream_id,
989                group,
990                object,
991                publisher_priority,
992                object_status,
993            } => {
994                map.serialize_entry("e", &EVENT_OBJECT_HEADER)?;
995                map.serialize_entry("sid", stream_id)?;
996                map.serialize_entry("g", group)?;
997                map.serialize_entry("o", object)?;
998                map.serialize_entry("pp", publisher_priority)?;
999                map.serialize_entry("os", object_status)?;
1000            }
1001            EventData::ObjectPayload { stream_id, group, object, size, payload } => {
1002                map.serialize_entry("e", &EVENT_OBJECT_PAYLOAD)?;
1003                map.serialize_entry("sid", stream_id)?;
1004                map.serialize_entry("g", group)?;
1005                map.serialize_entry("o", object)?;
1006                map.serialize_entry("sz", size)?;
1007                if let Some(pl) = payload {
1008                    map.serialize_entry("pl", &ByteStr(pl))?;
1009                }
1010            }
1011            EventData::StateChange { from, to } => {
1012                map.serialize_entry("e", &EVENT_STATE_CHANGE)?;
1013                map.serialize_entry("from", from)?;
1014                map.serialize_entry("to", to)?;
1015            }
1016            EventData::Error { error_code, reason, stream_id, kind, raw_len, raw } => {
1017                map.serialize_entry("e", &EVENT_ERROR)?;
1018                map.serialize_entry("ec", error_code)?;
1019                map.serialize_entry("reason", reason)?;
1020                if let Some(sid) = stream_id {
1021                    map.serialize_entry("sid", sid)?;
1022                }
1023                if let Some(kind) = kind {
1024                    map.serialize_entry("ek", kind.as_str())?;
1025                }
1026                if let Some(raw_len) = raw_len {
1027                    map.serialize_entry("rawlen", raw_len)?;
1028                }
1029                // At whatever length it arrived. The cap is the recorder's,
1030                // and re-applying it here would shorten evidence read out of
1031                // somebody else's file — see [`ERROR_RAW_CAP`].
1032                if let Some(raw) = raw {
1033                    map.serialize_entry("raw", &ByteStr(raw))?;
1034                }
1035            }
1036            EventData::Annotation { label, data } => {
1037                map.serialize_entry("e", &EVENT_ANNOTATION)?;
1038                map.serialize_entry("label", label)?;
1039                map.serialize_entry("data", &normalised(data))?;
1040            }
1041            EventData::PeerConnected { endpoint, transport, role, side } => {
1042                map.serialize_entry("e", &EVENT_PEER_CONNECTED)?;
1043                if let Some(endpoint) = endpoint {
1044                    map.serialize_entry("endpoint", endpoint)?;
1045                }
1046                if let Some(transport) = transport {
1047                    map.serialize_entry("transport", transport)?;
1048                }
1049                if let Some(role) = role {
1050                    map.serialize_entry("role", role.as_str())?;
1051                }
1052                if let Some(side) = side {
1053                    map.serialize_entry("side", side.as_str())?;
1054                }
1055            }
1056            EventData::PeerDisconnected { error_code, reason } => {
1057                map.serialize_entry("e", &EVENT_PEER_DISCONNECTED)?;
1058                map.serialize_entry("ec", error_code)?;
1059                if let Some(reason) = reason {
1060                    map.serialize_entry("reason", reason)?;
1061                }
1062            }
1063            EventData::SubscriptionDerivation {
1064                upstream,
1065                downstream,
1066                kind,
1067                trace_id,
1068                namespace,
1069                track_name,
1070                t_downstream_received,
1071                t_upstream_sent,
1072                t_upstream_ok_received,
1073                t_downstream_ok_sent,
1074            } => {
1075                map.serialize_entry("e", &EVENT_SUBSCRIPTION_DERIVATION)?;
1076                map.serialize_entry("u", &upstream.to_value())?;
1077                let downstream_refs: Vec<Value> =
1078                    downstream.iter().map(SubscriptionRef::to_value).collect();
1079                map.serialize_entry("d", &Value::Array(downstream_refs))?;
1080                map.serialize_entry("kind", kind.as_str())?;
1081                if let Some(trace_id) = trace_id {
1082                    map.serialize_entry("traceId", &ByteStr(trace_id))?;
1083                }
1084                if let Some(namespace) = namespace {
1085                    let fields: Vec<Value> =
1086                        namespace.iter().map(|f| Value::Bytes(f.clone())).collect();
1087                    map.serialize_entry("ns", &Value::Array(fields))?;
1088                }
1089                if let Some(track_name) = track_name {
1090                    map.serialize_entry("tn", &ByteStr(track_name))?;
1091                }
1092                if let Some(t) = t_downstream_received {
1093                    map.serialize_entry("tdr", t)?;
1094                }
1095                if let Some(t) = t_upstream_sent {
1096                    map.serialize_entry("tus", t)?;
1097                }
1098                if let Some(t) = t_upstream_ok_received {
1099                    map.serialize_entry("tuo", t)?;
1100                }
1101                if let Some(t) = t_downstream_ok_sent {
1102                    map.serialize_entry("tdo", t)?;
1103                }
1104            }
1105            EventData::Unknown { event_type, .. } => {
1106                map.serialize_entry("e", event_type)?;
1107                for (k, v) in &unknown_fields {
1108                    map.serialize_entry(k, v)?;
1109                }
1110            }
1111        }
1112
1113        // Last, so the event's own keys keep the positions a reader expects
1114        // and the file stays diffable against one written without them.
1115        for (k, v) in &extra {
1116            map.serialize_entry(k, v)?;
1117        }
1118
1119        map.end()
1120    }
1121}
1122
1123// ── decoding helpers ───────────────────────────────────────
1124
1125/// Helper to extract a u64 from a CBOR map by key.
1126///
1127/// Accepts the float form an encoder may use for a value past 32 bits — see
1128/// [`as_u64`](crate::header::as_u64).
1129fn get_uint(pairs: &[(Value, Value)], key: &str) -> Option<u64> {
1130    pairs.iter().find_map(|(k, v)| if k.as_text() == Some(key) { as_u64(v) } else { None })
1131}
1132
1133/// Helper to extract an i64 from a CBOR map by key. See [`get_uint`].
1134fn get_int(pairs: &[(Value, Value)], key: &str) -> Option<i64> {
1135    pairs.iter().find_map(|(k, v)| if k.as_text() == Some(key) { as_i64(v) } else { None })
1136}
1137
1138/// Helper to extract a text string from a CBOR map by key.
1139fn get_text(pairs: &[(Value, Value)], key: &str) -> Option<String> {
1140    pairs.iter().find_map(|(k, v)| {
1141        if k.as_text() == Some(key) {
1142            v.as_text().map(|s| s.to_string())
1143        } else {
1144            None
1145        }
1146    })
1147}
1148
1149/// Helper to extract a value from a CBOR map by key.
1150fn get_value(pairs: &[(Value, Value)], key: &str) -> Option<Value> {
1151    pairs.iter().find_map(|(k, v)| if k.as_text() == Some(key) { Some(v.clone()) } else { None })
1152}
1153
1154/// Tag 64 marks a byte string as an array of unsigned 8-bit integers.
1155const TAG_UINT8_ARRAY: u64 = 64;
1156
1157/// Read a byte string, unwrapping the typed-array tag some encoders add.
1158///
1159/// A byte string may arrive plain or wrapped in tag 64, and one of the two
1160/// implementations of this format wraps every one it writes. Reading only the
1161/// plain form meant every payload-bearing field that encoder produced — raw
1162/// wire bytes, object payloads, track names, trace ids — read as absent, and
1163/// a payload-bearing field that reads as absent is indistinguishable from one
1164/// the recorder chose not to capture.
1165fn as_bytes(value: &Value) -> Option<Vec<u8>> {
1166    match value {
1167        Value::Bytes(b) => Some(b.clone()),
1168        Value::Tag(TAG_UINT8_ARRAY, inner) => match inner.as_ref() {
1169            Value::Bytes(b) => Some(b.clone()),
1170            _ => None,
1171        },
1172        _ => None,
1173    }
1174}
1175
1176/// Helper to extract byte string from a CBOR map by key.
1177fn get_bytes(pairs: &[(Value, Value)], key: &str) -> Option<Vec<u8>> {
1178    pairs.iter().find_map(|(k, v)| if k.as_text() == Some(key) { as_bytes(v) } else { None })
1179}
1180
1181/// Read a control message's decoded `"msg"` field.
1182///
1183/// An absent key reads as the empty map a writer should have written, not as
1184/// an error. The format requires a writer with nothing decoded — an
1185/// unparseable message type, or a recorder not decoding bodies at all — to
1186/// emit `{}` rather than omit the key, but requires a reader to be more
1187/// tolerant still, and this is why: event 0 is one of the types sampling MUST
1188/// NOT drop, so refusing it over a missing `"msg"` discards exactly the events
1189/// the format promises to keep.
1190///
1191/// How much is lost depends on how the caller drives the reader, and the worse
1192/// case is the idiomatic one. [`MoqTraceReader::read_next`] returns the error,
1193/// so `collect::<Result<Vec<_>, _>>()` — what this crate's own tests use —
1194/// stops at the first offending event and yields none of the ones after it. A
1195/// caller that skips errors and continues loses only the offending events;
1196/// `moqtap trace` does that, and prints each one, so the loss was at least
1197/// visible there.
1198///
1199/// A value that is present is kept whatever its type. Recordings predating
1200/// the rule carry a text rendering of the message here — every `capture-*`
1201/// case in the conformance corpus is such a file — and they stay readable,
1202/// with the field simply not addressable by key.
1203fn get_message(pairs: &[(Value, Value)]) -> Value {
1204    get_value(pairs, "msg").unwrap_or_else(|| Value::Map(Vec::new()))
1205}
1206
1207fn require_uint(pairs: &[(Value, Value)], key: &str) -> Result<u64, MoqTraceError> {
1208    get_uint(pairs, key).ok_or_else(|| MoqTraceError::InvalidEvent(format!("missing '{key}'")))
1209}
1210
1211fn require_int(pairs: &[(Value, Value)], key: &str) -> Result<i64, MoqTraceError> {
1212    get_int(pairs, key).ok_or_else(|| MoqTraceError::InvalidEvent(format!("missing '{key}'")))
1213}
1214
1215fn require_text(pairs: &[(Value, Value)], key: &str) -> Result<String, MoqTraceError> {
1216    get_text(pairs, key).ok_or_else(|| MoqTraceError::InvalidEvent(format!("missing '{key}'")))
1217}
1218
1219fn require_value(pairs: &[(Value, Value)], key: &str) -> Result<Value, MoqTraceError> {
1220    get_value(pairs, key).ok_or_else(|| MoqTraceError::InvalidEvent(format!("missing '{key}'")))
1221}
1222
1223fn require_direction(pairs: &[(Value, Value)], key: &str) -> Result<Direction, MoqTraceError> {
1224    let v = require_value(pairs, key)?;
1225    Direction::from_cbor(&v)
1226}
1227
1228/// Read a trace ID, refusing any length but the one the format fixes.
1229///
1230/// A wrong-length value is malformed rather than something to pad or
1231/// truncate: the identifier exists so that two implementations independently
1232/// derive byte-identical values for one subscription chain, and a reader that
1233/// quietly reshapes it breaks exactly the property it is there for.
1234fn get_trace_id(pairs: &[(Value, Value)]) -> Result<Option<[u8; TRACE_ID_LEN]>, MoqTraceError> {
1235    let Some(bytes) = get_bytes(pairs, "traceId") else {
1236        return Ok(None);
1237    };
1238    <[u8; TRACE_ID_LEN]>::try_from(bytes.as_slice()).map(Some).map_err(|_| {
1239        MoqTraceError::InvalidEvent(format!(
1240            "'traceId' is {} bytes, must be {TRACE_ID_LEN}",
1241            bytes.len()
1242        ))
1243    })
1244}
1245
1246/// Read a track namespace: an array of byte strings, tolerating text entries
1247/// from encoders that lose the distinction.
1248///
1249/// Answers `None` for anything else — a `"ns"` that is not an array, or an
1250/// array holding something that is neither bytes nor text. `"ns"` is optional,
1251/// and the rule for an optional key whose value this reader cannot use is that
1252/// the key is kept as an unrecognised one rather than that the event dies; the
1253/// caller then still has the derivation's upstream and downstream
1254/// subscriptions, which are what make it a derivation at all.
1255///
1256/// It used to return an error, which cost more than the field. `read_next`
1257/// propagates it, so `collect::<Result<Vec<_>, _>>()` — the documented idiom —
1258/// stopped at that event and yielded none of the ones after it. One namespace
1259/// an encoder wrote oddly took the rest of the recording with it.
1260fn get_namespace(pairs: &[(Value, Value)]) -> Option<Vec<Vec<u8>>> {
1261    let Some(Value::Array(items)) = get_value(pairs, "ns") else {
1262        return None;
1263    };
1264    let mut namespace = Vec::with_capacity(items.len());
1265    for item in items {
1266        let field = as_bytes(&item).or_else(|| item.as_text().map(|t| t.as_bytes().to_vec()))?;
1267        namespace.push(field);
1268    }
1269    Some(namespace)
1270}
1271
1272/// The three keys every event carries whatever its type. All are required, so
1273/// a value the reader cannot use is a malformed event rather than something to
1274/// keep: there would be no event left to hang the kept key on.
1275const REQUIRED_COMMON_KEYS: [&str; 3] = ["n", "t", "e"];
1276
1277/// Whether the common event fields account for `key` — the required three
1278/// always, and `"p"` only when a peer was actually read from it.
1279///
1280/// `"p"` is optional, and optional means it can also be *unusable*: a `"p"`
1281/// that is not text leaves [`TraceEvent::peer`] `None`, and the entry then
1282/// belongs in [`TraceEvent::extra`] like any other value the reader could not
1283/// use. Excluding the key unconditionally would delete it instead.
1284fn writes_common_key(peer: Option<&str>, key: &str) -> bool {
1285    REQUIRED_COMMON_KEYS.contains(&key) || (key == "p" && peer.is_some())
1286}
1287
1288/// Whether `key` is written from one of `data`'s own fields — equivalently,
1289/// on an event that was decoded, whether the decode used it. The common keys
1290/// are not this function's business; [`writes_common_key`] answers for those.
1291///
1292/// Deliberately not "does this event type define `key`". The two part company
1293/// on a defined key whose value the decode could not use: SPEC.md treats such
1294/// a key as unrecognised, so its field stays `None` and the entry goes to
1295/// [`TraceEvent::extra`], from where the serializer writes it back unchanged.
1296/// Asking about the type's whole vocabulary instead keeps the key out of
1297/// `extra` while no field holds it either, and merely reading the file deletes
1298/// the value — which is how adding `"ta"`, `"sg"`, `"fri"` and `"g"` to event
1299/// 1 made this reader preserve *less* than while it had never heard of them.
1300///
1301/// The match is exhaustive on purpose: a new variant does not compile until
1302/// its keys are listed, and both ways of getting a list wrong are silent. A
1303/// key left out is written twice, once from the field and once from `extra`,
1304/// and a CBOR map with a duplicate key is malformed; a key wrongly listed is
1305/// dropped from every rewrite.
1306fn writes_variant_key(data: &EventData, key: &str) -> bool {
1307    /// `always` are the keys the variant writes unconditionally; `optional`
1308    /// pairs each of the rest with whether its field holds a value.
1309    fn among(key: &str, always: &[&str], optional: &[(&str, bool)]) -> bool {
1310        always.contains(&key) || optional.iter().any(|&(k, written)| written && k == key)
1311    }
1312
1313    match data {
1314        EventData::ControlMessage { stream_id, raw, .. } => {
1315            among(key, &["d", "mt", "msg"], &[("sid", stream_id.is_some()), ("raw", raw.is_some())])
1316        }
1317        EventData::StreamOpened {
1318            track_alias, subgroup_id, fetch_request_id, group_id, ..
1319        } => among(
1320            key,
1321            &["sid", "d", "st"],
1322            &[
1323                ("ta", track_alias.is_some()),
1324                ("sg", subgroup_id.is_some()),
1325                ("fri", fetch_request_id.is_some()),
1326                ("g", group_id.is_some()),
1327            ],
1328        ),
1329        EventData::StreamClosed { .. } => among(key, &["sid", "ec"], &[]),
1330        EventData::ObjectHeader { .. } => among(key, &["sid", "g", "o", "pp", "os"], &[]),
1331        EventData::ObjectPayload { payload, .. } => {
1332            among(key, &["sid", "g", "o", "sz"], &[("pl", payload.is_some())])
1333        }
1334        EventData::StateChange { .. } => among(key, &["from", "to"], &[]),
1335        EventData::Error { stream_id, kind, raw_len, raw, .. } => among(
1336            key,
1337            &["ec", "reason"],
1338            &[
1339                ("sid", stream_id.is_some()),
1340                ("ek", kind.is_some()),
1341                ("rawlen", raw_len.is_some()),
1342                ("raw", raw.is_some()),
1343            ],
1344        ),
1345        EventData::Annotation { .. } => among(key, &["label", "data"], &[]),
1346        EventData::PeerConnected { endpoint, transport, role, side } => among(
1347            key,
1348            &[],
1349            &[
1350                ("endpoint", endpoint.is_some()),
1351                ("transport", transport.is_some()),
1352                ("role", role.is_some()),
1353                ("side", side.is_some()),
1354            ],
1355        ),
1356        EventData::PeerDisconnected { reason, .. } => {
1357            among(key, &["ec"], &[("reason", reason.is_some())])
1358        }
1359        EventData::SubscriptionDerivation {
1360            trace_id,
1361            namespace,
1362            track_name,
1363            t_downstream_received,
1364            t_upstream_sent,
1365            t_upstream_ok_received,
1366            t_downstream_ok_sent,
1367            ..
1368        } => among(
1369            key,
1370            &["u", "d", "kind"],
1371            &[
1372                ("traceId", trace_id.is_some()),
1373                ("ns", namespace.is_some()),
1374                ("tn", track_name.is_some()),
1375                ("tdr", t_downstream_received.is_some()),
1376                ("tus", t_upstream_sent.is_some()),
1377                ("tuo", t_upstream_ok_received.is_some()),
1378                ("tdo", t_downstream_ok_sent.is_some()),
1379            ],
1380        ),
1381        // An unknown event type writes every key it read straight back out of
1382        // `fields`, so those are the ones an `extra` entry would duplicate.
1383        // Decoding one collects nothing into `extra` — see the `Unknown` arm
1384        // below — but a caller may have attached some by hand.
1385        EventData::Unknown { fields, .. } => fields.iter().any(|(k, _)| k.as_text() == Some(key)),
1386    }
1387}
1388
1389/// Every entry on `pairs` that the event decoded from it does not write back
1390/// out of a field of its own. See [`unrecognised`], which the header uses for
1391/// the same reason.
1392///
1393/// One difference from the header's use of it is worth naming, because it
1394/// decides *which* entry of a duplicate pair survives. A getter here searches
1395/// for the first entry it can **use**, where the header's lookup returns the
1396/// first entry for a key whatever it holds. So on a file repeating a key with
1397/// two different types — `"sid": "x"` and then `"sid": 9` — the field takes
1398/// the second and the walk drops the first. Both were lost before this walk
1399/// existed, and SPEC.md leaves readers free to disagree over which of a
1400/// duplicate pair wins, so nothing may depend on which one it is.
1401fn unrecognised_keys(
1402    pairs: &[(Value, Value)],
1403    peer: Option<&str>,
1404    data: &EventData,
1405) -> Vec<(Value, Value)> {
1406    unrecognised(pairs, |key| writes_common_key(peer, key) || writes_variant_key(data, key))
1407}
1408
1409impl TryFrom<Value> for TraceEvent {
1410    type Error = MoqTraceError;
1411
1412    fn try_from(value: Value) -> Result<Self, MoqTraceError> {
1413        let pairs = match value {
1414            Value::Map(pairs) => pairs,
1415            _ => return Err(MoqTraceError::InvalidEvent("event is not a CBOR map".into())),
1416        };
1417
1418        let seq = require_uint(&pairs, "n")?;
1419        let timestamp = require_int(&pairs, "t")?;
1420        let peer = get_text(&pairs, "p");
1421        let event_type = require_uint(&pairs, "e")?;
1422
1423        let data = match event_type {
1424            EVENT_CONTROL_MESSAGE => EventData::ControlMessage {
1425                direction: require_direction(&pairs, "d")?,
1426                message_type: require_uint(&pairs, "mt")?,
1427                message: get_message(&pairs),
1428                stream_id: get_uint(&pairs, "sid"),
1429                raw: get_bytes(&pairs, "raw"),
1430            },
1431            EVENT_STREAM_OPENED => {
1432                let st_val = require_value(&pairs, "st")?;
1433                EventData::StreamOpened {
1434                    stream_id: require_uint(&pairs, "sid")?,
1435                    direction: require_direction(&pairs, "d")?,
1436                    stream_type: StreamType::from_cbor(&st_val)?,
1437                    // Read whatever is usable, including a key outside the
1438                    // stream type it is scoped to. A writer must not produce
1439                    // one, but "ignore" is the same read-past-and-keep rule
1440                    // that governs `extra`: dropping it here would make this
1441                    // reader's opinion permanent for every reader downstream.
1442                    // A value that is not an unsigned integer is not usable —
1443                    // the field stays `None` and the key goes to `extra`,
1444                    // which is that same rule one step over. Neither reading
1445                    // past a key nor failing to understand its value is a
1446                    // licence to delete it.
1447                    track_alias: get_uint(&pairs, "ta"),
1448                    subgroup_id: get_uint(&pairs, "sg"),
1449                    fetch_request_id: get_uint(&pairs, "fri"),
1450                    group_id: get_uint(&pairs, "g"),
1451                }
1452            }
1453            EVENT_STREAM_CLOSED => EventData::StreamClosed {
1454                stream_id: require_uint(&pairs, "sid")?,
1455                error_code: require_uint(&pairs, "ec")?,
1456            },
1457            EVENT_OBJECT_HEADER => EventData::ObjectHeader {
1458                stream_id: require_uint(&pairs, "sid")?,
1459                group: require_uint(&pairs, "g")?,
1460                object: require_uint(&pairs, "o")?,
1461                publisher_priority: require_uint(&pairs, "pp")?,
1462                object_status: require_uint(&pairs, "os")?,
1463            },
1464            EVENT_OBJECT_PAYLOAD => EventData::ObjectPayload {
1465                stream_id: require_uint(&pairs, "sid")?,
1466                group: require_uint(&pairs, "g")?,
1467                object: require_uint(&pairs, "o")?,
1468                size: require_uint(&pairs, "sz")?,
1469                payload: get_bytes(&pairs, "pl"),
1470            },
1471            EVENT_STATE_CHANGE => EventData::StateChange {
1472                from: require_text(&pairs, "from")?,
1473                to: require_text(&pairs, "to")?,
1474            },
1475            EVENT_ERROR => EventData::Error {
1476                error_code: require_uint(&pairs, "ec")?,
1477                reason: require_text(&pairs, "reason")?,
1478                // All four optional, and every recording made before they
1479                // existed carries none of them. A `"raw"` longer than the cap
1480                // is read at the length the file gave it: the cap is addressed
1481                // to a recorder building an event from what it observed, and
1482                // re-truncating here would destroy evidence to make somebody
1483                // else's file conform to a rule it was never handed. Report
1484                // the non-conformance if it is worth reporting; do not repair
1485                // it. SPEC.md, Event 6.
1486                stream_id: get_uint(&pairs, "sid"),
1487                kind: get_text(&pairs, "ek").as_deref().map(ErrorKind::parse),
1488                raw_len: get_uint(&pairs, "rawlen"),
1489                raw: get_bytes(&pairs, "raw"),
1490            },
1491            EVENT_ANNOTATION => EventData::Annotation {
1492                label: require_text(&pairs, "label")?,
1493                data: require_value(&pairs, "data")?,
1494            },
1495            EVENT_PEER_CONNECTED => EventData::PeerConnected {
1496                endpoint: get_text(&pairs, "endpoint"),
1497                transport: get_text(&pairs, "transport"),
1498                role: get_text(&pairs, "role").as_deref().map(PeerRole::parse),
1499                side: get_text(&pairs, "side").as_deref().map(Side::parse),
1500            },
1501            EVENT_PEER_DISCONNECTED => EventData::PeerDisconnected {
1502                error_code: require_uint(&pairs, "ec")?,
1503                reason: get_text(&pairs, "reason"),
1504            },
1505            EVENT_SUBSCRIPTION_DERIVATION => {
1506                let upstream = SubscriptionRef::from_value(&require_value(&pairs, "u")?)?;
1507                let downstream = match require_value(&pairs, "d")? {
1508                    Value::Array(items) => items
1509                        .iter()
1510                        .map(SubscriptionRef::from_value)
1511                        .collect::<Result<Vec<_>, _>>()?,
1512                    _ => {
1513                        return Err(MoqTraceError::InvalidEvent(
1514                            "'d' (downstream subs) is not a CBOR array".into(),
1515                        ))
1516                    }
1517                };
1518                EventData::SubscriptionDerivation {
1519                    upstream,
1520                    downstream,
1521                    kind: DerivationKind::parse(&require_text(&pairs, "kind")?),
1522                    trace_id: get_trace_id(&pairs)?,
1523                    namespace: get_namespace(&pairs),
1524                    track_name: get_bytes(&pairs, "tn")
1525                        .or_else(|| get_text(&pairs, "tn").map(String::into_bytes)),
1526                    t_downstream_received: get_int(&pairs, "tdr"),
1527                    t_upstream_sent: get_int(&pairs, "tus"),
1528                    t_upstream_ok_received: get_int(&pairs, "tuo"),
1529                    t_downstream_ok_sent: get_int(&pairs, "tdo"),
1530                }
1531            }
1532            other => EventData::Unknown {
1533                event_type: other,
1534                // The same walk `unrecognised_keys` does, with only the common
1535                // fields to ask about: an unknown event type writes every
1536                // other key straight back out of here. It had the same defect
1537                // for the same reason — a repeated `"n"` was filtered out by
1538                // name, so the entry `seq` did not take reached neither — and
1539                // this variant collects nothing into `extra`, which makes
1540                // `fields` the only place such an entry can land.
1541                fields: unrecognised(&pairs, |key| writes_common_key(peer.as_deref(), key)),
1542            },
1543        };
1544
1545        let extra = match &data {
1546            // `fields` already holds every key on this event the common
1547            // fields did not consume, a `"p"` this crate could not use among
1548            // them. Collecting them into `extra` as well would write each one
1549            // twice and produce a CBOR map with duplicate keys.
1550            EventData::Unknown { .. } => Vec::new(),
1551            other => unrecognised_keys(&pairs, peer.as_deref(), other),
1552        };
1553        Ok(TraceEvent { seq, timestamp, peer, data, extra })
1554    }
1555}