#[non_exhaustive]pub enum EventData {
ControlMessage {
direction: Direction,
message_type: u64,
message: Value,
stream_id: Option<u64>,
raw: Option<Vec<u8>>,
},
StreamOpened {
stream_id: u64,
direction: Direction,
stream_type: StreamType,
track_alias: Option<u64>,
subgroup_id: Option<u64>,
fetch_request_id: Option<u64>,
group_id: Option<u64>,
},
StreamClosed {
stream_id: u64,
error_code: u64,
},
ObjectHeader {
stream_id: u64,
group: u64,
object: u64,
publisher_priority: u64,
object_status: u64,
},
ObjectPayload {
stream_id: u64,
group: u64,
object: u64,
size: u64,
payload: Option<Vec<u8>>,
},
StateChange {
from: String,
to: String,
},
Error {
error_code: u64,
reason: String,
stream_id: Option<u64>,
kind: Option<ErrorKind>,
raw_len: Option<u64>,
raw: Option<Vec<u8>>,
},
Annotation {
label: String,
data: Value,
},
PeerConnected {
endpoint: Option<String>,
transport: Option<String>,
role: Option<PeerRole>,
side: Option<Side>,
},
PeerDisconnected {
error_code: u64,
reason: Option<String>,
},
SubscriptionDerivation {
upstream: SubscriptionRef,
downstream: Vec<SubscriptionRef>,
kind: DerivationKind,
trace_id: Option<[u8; 16]>,
namespace: Option<Vec<Vec<u8>>>,
track_name: Option<Vec<u8>>,
t_downstream_received: Option<i64>,
t_upstream_sent: Option<i64>,
t_upstream_ok_received: Option<i64>,
t_downstream_ok_sent: Option<i64>,
},
Unknown {
event_type: u64,
fields: Vec<(Value, Value)>,
},
}Expand description
Event-specific payload, discriminated by type.
Variants (Non-exhaustive)§
This enum is marked as non-exhaustive
ControlMessage
A control-stream message was sent or received (event type 0).
Fields
message: ValueDecoded message fields, and not necessarily a map: match on it
rather than assuming, or use TraceEvent::request_id.
A recorder MUST write a CBOR map keyed in snake_case here, and an
empty map when it decoded nothing. A reader has to take what
earlier writers produced, and every capture-* case in the
conformance corpus holds a text rendering of the message instead.
Such a value is handed back verbatim — unaddressable by key, but
not a reason to reject an event the format forbids dropping — and
is written back unchanged, because replacing it would destroy the
only record of a message nobody will see again.
Unchanged binds what the value says and not how it is encoded.
This is a value the reader never looked at, so the two encoding
rules reach it on the way out like any other:
TraceEvent::extra has the whole of it.
stream_id: Option<u64>QUIC stream the message travelled on, when the recorder knows it.
None means unknown, which a reader must not confuse with stream
0. Recorders should fill it in: from draft-17 each request has its
own bidirectional stream and responses carry no request ID, so the
stream is the only thing pairing a response with its request.
StreamOpened
A QUIC stream was opened (event type 1).
The four optional identifiers are what a headers recording has
instead of the stream header bytes. No detail level records the bytes
of a SUBGROUP_HEADER, a fetch header or a datagram header, so a value
carried only there has nothing left to be re-parsed from — and before
these fields existed the model could express group, object, priority
and status and nothing else, which left a headers trace unable to say
which track a stream belonged to.
Each is None when the recorder did not know it — and also when the
file carried the key with a value that is not an unsigned integer, in
which case the entry is kept verbatim in TraceEvent::extra instead
of being read here. A reader must not refuse a stream for lacking one:
every recording predating the keys lacks all four.
Fields
stream_type: StreamTypeStream type.
subgroup_id: Option<u64>Subgroup ID, on a StreamType::Subgroup stream.
fetch_request_id: Option<u64>Fetch request ID, on a StreamType::Fetch stream.
A recorder must write it there. It is the only correlation between
the stream and the FETCH that asked for it, and unlike a subgroup
stream a fetch stream carries no track alias to identify it by
instead — so without it a fetch stream in a headers trace names
nothing at all.
group_id: Option<u64>Group ID, on a StreamType::Datagram stream.
Scoped to datagrams because on a subgroup stream every object
carries the group of the stream by construction, so a copy here
would be a second field with no independent source. Where it does
appear alongside an ObjectHeader for
the same stream, the object header is authoritative: this copy
serves a reader that has not yet seen an object, and a
disagreement between the two is not corruption.
StreamClosed
A QUIC stream was closed (event type 2).
ObjectHeader
An object header was parsed from a data stream (event type 3).
Fields
ObjectPayload
Object payload bytes were received or sent (event type 4).
Fields
StateChange
Session FSM phase transition (event type 5).
Error
Protocol or transport error (event type 6).
The four optional fields are what makes the event evidence rather than
an assertion. A peer that sends something malformed is one of the few
things a shared trace is uniquely good for — the recording party can
see it and the sending party cannot — and until these existed the only
field in the format able to hold bytes was a control message’s "raw",
so a recorder wanting to keep the offending bytes had to record the
violation as a decodable message in order to have somewhere to put
them.
Each is None when the recorder did not write it — and also when the
file carried the key with a value the field cannot hold, in which case
the entry stays verbatim in TraceEvent::extra instead of being read
here.
error_observed builds one of these from
bytes a recorder has just seen, which is where the cap on raw and the
detail levels the two byte-bearing fields sit at are applied.
Fields
stream_id: Option<u64>QUIC stream the error was observed on, when there was one and the recorder knows it.
Optional on the same terms as a control message’s "sid": None
means there was no stream or none is known, which a reader must not
confuse with stream 0.
raw_len: Option<u64>Byte length of the input the recorder held for this error, before any truncation.
Recorded from headers+sizes upwards, one level below the bytes
themselves: how large a malformed message was is often enough on
its own to tell a truncated message from a mistyped one, and it
carries no content. It is still a size, and this format gates sizes
deliberately, so it does not reach a control-level trace.
Where both are present, a value larger than raw’s length is how a
reader learns the capture is partial and by how much. Where this is
absent, a raw of exactly ERROR_RAW_CAP bytes is the one length
the cap makes ambiguous and must be taken as possibly truncated.
raw: Option<Vec<u8>>The offending bytes, at full detail only.
Payload-bearing, and gated a level above the event’s own
control+: an error naming a data stream has subgroup framing
and object payload behind it, so inheriting the event’s level would
have put media into traces whose declared level excludes payloads
outright.
A recorder caps this at ERROR_RAW_CAP bytes. A reader does not:
a longer one read from a file is neither shortened nor refused.
Annotation
User-defined annotation (event type 7).
Fields
data: ValueUser-defined data (any CBOR type).
Opaque: nothing here reads it, and it is written back saying what
it said. Its encoding is the writer’s, though — see
TraceEvent::extra.
PeerConnected
A new peer session was established (event type 8).
The identifier for the peer lives on TraceEvent::peer; every later
event for that peer repeats it.
Fields
PeerDisconnected
A peer session ended (event type 9).
Fields
SubscriptionDerivation
An upstream subscription was created or extended in causal response to downstream ones (event type 10).
This is the primitive multi-hop correlation is built from: a collector reconstructs an end-to-end tree from these links plus the trace IDs propagated along them.
The four timestamps share the timestamp field’s timebase — the
emitting source’s own clock. Differences between them are therefore
meaningful with no cross-hop clock agreement, which is the point: they
measure how long this hop took. Never subtract one source’s
timestamp from another’s.
A source emits the event as soon as the downstream SUBSCRIBE arrives, carrying whichever timestamps it has, and may emit it again for the same pair as the rest arrive. A consumer treats the later event as an update to the earlier one, not as a second derivation.
Fields
upstream: SubscriptionRefThe upstream subscription.
downstream: Vec<SubscriptionRef>Every downstream subscription currently served by upstream.
kind: DerivationKindWhether the upstream sub was created here or was already running.
trace_id: Option<[u8; 16]>Trace ID propagated along the subscription chain, carried as raw bytes at every layer so two implementations produce identical values for the same chain.
t_upstream_sent: Option<i64>When the upstream SUBSCRIBE was transmitted. Absent for terminal subscriptions, where this source is the content origin.
Unknown
An event whose type this version of the crate does not know.
New event types may be added without a format version bump, so a
reader that rejected them would turn every future addition into a
breaking change. The fields are kept verbatim, which means an unknown
event survives a read-modify-write round trip intact — intact in what
it says, since the encoding written back is this crate’s own. See
TraceEvent::extra.
Implementations§
Source§impl EventData
impl EventData
Sourcepub fn error_observed(
error_code: u64,
reason: impl Into<String>,
kind: Option<ErrorKind>,
stream_id: Option<u64>,
observed: &[u8],
detail: &DetailLevel,
) -> Self
pub fn error_observed( error_code: u64, reason: impl Into<String>, kind: Option<ErrorKind>, stream_id: Option<u64>, observed: &[u8], detail: &DetailLevel, ) -> Self
An Error built from the bytes a recorder has just
observed, for a trace recorded at detail.
This is where the cap on "raw" belongs and the only place this crate
applies it: the party constructing an event out of traffic it just saw
is the one SPEC.md addresses, and it is the only party that can tell a
fresh event from one that arrived by being read. Serializing does not
cap, and reading does not refuse — see ERROR_RAW_CAP.
observed is the whole input the recorder held, uncapped. What comes
back depends on detail, because the two byte-bearing fields sit at
different levels and that is deliberate:
raw_lenis the full length ofobserved, fromheaders+sizesupwards. It is a size, and sizes are gated; it is not gated with the bytes, so it is available in every trace where the bytes must not appear, which is the point of having it.rawis the firstERROR_RAW_CAPbytes ofobserved, atfullonly. Below that level nothing is copied.
A level this crate cannot place yields neither.
Where both come back, comparing them is how a reader learns the capture
was truncated: raw_len is the length before the cap bit, not after.