summaryrefslogtreecommitdiffstats
path: root/third_party/rust/qlog
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /third_party/rust/qlog
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/qlog')
-rw-r--r--third_party/rust/qlog/.cargo-checksum.json1
-rw-r--r--third_party/rust/qlog/Cargo.toml46
-rw-r--r--third_party/rust/qlog/README.md316
-rw-r--r--third_party/rust/qlog/src/events/connectivity.rs147
-rw-r--r--third_party/rust/qlog/src/events/h3.rs245
-rw-r--r--third_party/rust/qlog/src/events/mod.rs755
-rw-r--r--third_party/rust/qlog/src/events/qpack.rs276
-rw-r--r--third_party/rust/qlog/src/events/quic.rs811
-rw-r--r--third_party/rust/qlog/src/events/security.rs81
-rw-r--r--third_party/rust/qlog/src/lib.rs971
-rw-r--r--third_party/rust/qlog/src/reader.rs111
-rw-r--r--third_party/rust/qlog/src/streamer.rs544
12 files changed, 4304 insertions, 0 deletions
diff --git a/third_party/rust/qlog/.cargo-checksum.json b/third_party/rust/qlog/.cargo-checksum.json
new file mode 100644
index 0000000000..17ad1f8978
--- /dev/null
+++ b/third_party/rust/qlog/.cargo-checksum.json
@@ -0,0 +1 @@
+{"files":{"Cargo.toml":"15c2606defff66515f2ded1ace2aeb729229a3558d9a026c058e51a7518e6859","README.md":"597691eb766c2cbd7a6591bda56d3e70e6836b62b6327fb73497523eabd5b53d","src/events/connectivity.rs":"116993412e200e375c97762980ffb638d2244197fd752b9569b5b20baf574308","src/events/h3.rs":"45dfa1dea722f3c8adb989f04ff24e8c39550a65a35325885b3a915cafd3a550","src/events/mod.rs":"75f57b4717fa9777e19d61b99b6a79164f0e8bca9b4681c3ab11b204320c8c55","src/events/qpack.rs":"5c7267c45e3fb947cdfa946f9f9692d3e3e36a166f70124ba293dc27534267d0","src/events/quic.rs":"88b884f5788c671ffee79a3448f367c18f95ee30531262fcc14310d80e662f4a","src/events/security.rs":"e9852d7de16851b62c3e0a886a2c1a31d237e62574ef88428ef62dd179b0b008","src/lib.rs":"bbc190a6d0f484fd723f9df6c1b2a4596f826e0282ad40ee17a0822ea28a5626","src/reader.rs":"4e0069c24aca9cb99d75075c9b784fa02855ea449d2f1528bea944a4e02a9af5","src/streamer.rs":"4774c2abde1a5b0f4448aac06c62c7927208c12f338c46981f80c98703b54074"},"package":null} \ No newline at end of file
diff --git a/third_party/rust/qlog/Cargo.toml b/third_party/rust/qlog/Cargo.toml
new file mode 100644
index 0000000000..665559312f
--- /dev/null
+++ b/third_party/rust/qlog/Cargo.toml
@@ -0,0 +1,46 @@
+# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
+#
+# When uploading crates to the registry Cargo will automatically
+# "normalize" Cargo.toml files for maximal compatibility
+# with all versions of Cargo and also rewrite `path` dependencies
+# to registry (e.g., crates.io) dependencies.
+#
+# If you are reading this file be aware that the original Cargo.toml
+# will likely look very different (and much more reasonable).
+# See Cargo.toml.orig for the original contents.
+
+[package]
+edition = "2018"
+name = "qlog"
+version = "0.11.0"
+authors = ["Lucas Pardue <lucaspardue.24.7@gmail.com>"]
+description = "qlog data model for QUIC and HTTP/3"
+readme = "README.md"
+keywords = [
+ "qlog",
+ "quic",
+ "http3",
+]
+categories = ["network-programming"]
+license = "BSD-2-Clause"
+repository = "https://github.com/cloudflare/quiche"
+
+[dependencies]
+serde_derive = "1.0"
+
+[dependencies.serde]
+version = "1.0.139"
+features = ["derive"]
+
+[dependencies.serde_json]
+version = "1.0"
+features = ["preserve_order"]
+
+[dependencies.serde_with]
+version = "3.0.0"
+features = ["macros"]
+default-features = false
+
+[dependencies.smallvec]
+version = "1.10"
+features = ["serde"]
diff --git a/third_party/rust/qlog/README.md b/third_party/rust/qlog/README.md
new file mode 100644
index 0000000000..44df39274e
--- /dev/null
+++ b/third_party/rust/qlog/README.md
@@ -0,0 +1,316 @@
+The qlog crate is an implementation of the qlog [main logging schema],
+[QUIC event definitions], and [HTTP/3 and QPACK event definitions].
+The crate provides a qlog data model that can be used for traces with
+events. It supports serialization and deserialization but defers logging IO
+choices to applications.
+
+The crate uses Serde for conversion between Rust and JSON.
+
+[main logging schema]: https://datatracker.ietf.org/doc/html/draft-ietf-quic-qlog-main-schema
+[QUIC event definitions]:
+https://datatracker.ietf.org/doc/html/draft-ietf-quic-qlog-quic-events.html
+[HTTP/3 and QPACK event definitions]:
+https://datatracker.ietf.org/doc/html/draft-ietf-quic-qlog-h3-events.html
+
+Overview
+--------
+qlog is a hierarchical logging format, with a rough structure of:
+
+* Log
+ * Trace(s)
+ * Event(s)
+
+In practice, a single QUIC connection maps to a single Trace file with one
+or more Events. Applications can decide whether to combine Traces from
+different connections into the same Log.
+
+## Buffered Traces with standard JSON
+
+A [`Trace`] is a single JSON object. It contains metadata such as the
+[`VantagePoint`] of capture and the [`Configuration`], and protocol event
+data in the [`Event`] array.
+
+JSON Traces allow applications to appends events to them before eventually
+being serialized as a complete JSON object.
+
+### Creating a Trace
+
+```rust
+let mut trace = qlog::Trace::new(
+ qlog::VantagePoint {
+ name: Some("Example client".to_string()),
+ ty: qlog::VantagePointType::Client,
+ flow: None,
+ },
+ Some("Example qlog trace".to_string()),
+ Some("Example qlog trace description".to_string()),
+ Some(qlog::Configuration {
+ time_offset: Some(0.0),
+ original_uris: None,
+ }),
+ None,
+);
+```
+
+### Adding events to a Trace
+
+Qlog `Event` objects are added to `qlog::Trace.events`.
+
+The following example demonstrates how to log a qlog QUIC `packet_sent` event
+containing a single Crypto frame. It constructs the necessary elements of the
+[`Event`], then appends it to the trace with [`push_event()`].
+
+```rust
+let scid = [0x7e, 0x37, 0xe4, 0xdc, 0xc6, 0x68, 0x2d, 0xa8];
+let dcid = [0x36, 0xce, 0x10, 0x4e, 0xee, 0x50, 0x10, 0x1c];
+
+let pkt_hdr = qlog::events::quic::PacketHeader::new(
+ qlog::events::quic::PacketType::Initial,
+ 0, // packet_number
+ None, // flags
+ None, // token
+ None, // length
+ Some(0x00000001), // version
+ Some(&scid),
+ Some(&dcid),
+);
+
+let frames = vec![qlog::events::quic::QuicFrame::Crypto {
+ offset: 0,
+ length: 0,
+}];
+
+let raw = qlog::events::RawInfo {
+ length: Some(1251),
+ payload_length: Some(1224),
+ data: None,
+};
+
+let event_data =
+ qlog::events::EventData::PacketSent(qlog::events::quic::PacketSent {
+ header: pkt_hdr,
+ frames: Some(frames.into()),
+ is_coalesced: None,
+ retry_token: None,
+ stateless_reset_token: None,
+ supported_versions: None,
+ raw: Some(raw),
+ datagram_id: None,
+ });
+
+trace.push_event(qlog::events::Event::with_time(0.0, event_data));
+```
+
+### Serializing
+
+The qlog crate has only been tested with `serde_json`, however other serializer
+targets might work.
+
+For example, serializing the trace created above:
+
+```rust
+serde_json::to_string_pretty(&trace).unwrap();
+```
+
+would generate the following:
+
+```
+{
+ "vantage_point": {
+ "name": "Example client",
+ "type": "client"
+ },
+ "title": "Example qlog trace",
+ "description": "Example qlog trace description",
+ "configuration": {
+ "time_offset": 0.0
+ },
+ "events": [
+ {
+ "time": 0.0,
+ "name": "transport:packet_sent",
+ "data": {
+ "header": {
+ "packet_type": "initial",
+ "packet_number": 0,
+ "version": "1",
+ "scil": 8,
+ "dcil": 8,
+ "scid": "7e37e4dcc6682da8",
+ "dcid": "36ce104eee50101c"
+ },
+ "raw": {
+ "length": 1251,
+ "payload_length": 1224
+ },
+ "frames": [
+ {
+ "frame_type": "crypto",
+ "offset": 0,
+ "length": 0
+ }
+ ]
+ }
+ }
+ ]
+}
+```
+
+## Streaming Traces JSON Text Sequences (JSON-SEQ)
+
+To help support streaming serialization of qlogs,
+draft-ietf-quic-qlog-main-schema-01 introduced support for RFC 7464 JSON
+Text Sequences (JSON-SEQ). The qlog crate supports this format and provides
+utilities that aid streaming.
+
+A [`TraceSeq`] contains metadata such as the [`VantagePoint`] of capture and
+the [`Configuration`]. However, protocol event data is handled as separate
+lines containing a record separator character, a serialized [`Event`], and a
+newline.
+
+### Creating a TraceSeq
+
+``` rust
+let mut trace = qlog::TraceSeq::new(
+ qlog::VantagePoint {
+ name: Some("Example client".to_string()),
+ ty: qlog::VantagePointType::Client,
+ flow: None,
+ },
+ Some("Example qlog trace".to_string()),
+ Some("Example qlog trace description".to_string()),
+ Some(qlog::Configuration {
+ time_offset: Some(0.0),
+ original_uris: None,
+ }),
+ None,
+);
+```
+
+Create an object with the [`Write`] trait:
+```
+let mut file = std::fs::File::create("foo.sqlog").unwrap();
+```
+
+Create a [`QlogStreamer`] and start serialization to foo.sqlog
+using [`start_log()`]:
+
+```rust
+let mut streamer = qlog::QlogStreamer::new(
+ qlog::QLOG_VERSION.to_string(),
+ Some("Example qlog".to_string()),
+ Some("Example qlog description".to_string()),
+ None,
+ std::time::Instant::now(),
+ trace,
+ qlog::EventImportance::Base,
+ Box::new(file),
+);
+
+streamer.start_log().ok();
+```
+
+### Adding simple events
+
+Once logging has started you can stream events. Simple events can be written in
+one step using [`add_event()`]:
+
+```rust
+let event_data = qlog::events::EventData::MetricsUpdated(
+ qlog::events::quic::MetricsUpdated {
+ min_rtt: Some(1.0),
+ smoothed_rtt: Some(1.0),
+ latest_rtt: Some(1.0),
+ rtt_variance: Some(1.0),
+ pto_count: Some(1),
+ congestion_window: Some(1234),
+ bytes_in_flight: Some(5678),
+ ssthresh: None,
+ packets_in_flight: None,
+ pacing_rate: None,
+ },
+);
+
+let event = qlog::events::Event::with_time(0.0, event_data);
+streamer.add_event(event).ok();
+```
+
+### Adding events with frames
+Some events contain optional arrays of QUIC frames. If the event has
+`Some(Vec<QuicFrame>)`, even if it is empty, the streamer enters a frame
+serializing mode that must be finalized before other events can be logged.
+
+In this example, a `PacketSent` event is created with an empty frame array and
+frames are written out later:
+
+```rust
+let scid = [0x7e, 0x37, 0xe4, 0xdc, 0xc6, 0x68, 0x2d, 0xa8];
+let dcid = [0x36, 0xce, 0x10, 0x4e, 0xee, 0x50, 0x10, 0x1c];
+
+let pkt_hdr = qlog::events::quic::PacketHeader::with_type(
+ qlog::events::quic::PacketType::OneRtt,
+ 0,
+ Some(0x00000001),
+ Some(&scid),
+ Some(&dcid),
+);
+
+let event_data =
+ qlog::events::EventData::PacketSent(qlog::events::quic::PacketSent {
+ header: pkt_hdr,
+ frames: Some(vec![]),
+ is_coalesced: None,
+ retry_token: None,
+ stateless_reset_token: None,
+ supported_versions: None,
+ raw: None,
+ datagram_id: None,
+};
+
+let event = qlog::events::Event::with_time(0.0, event_data);
+
+streamer.add_event(event).ok();
+```
+
+In this example, the frames contained in the QUIC packet
+are PING and PADDING. Each frame is written using the
+[`add_frame()`] method. Frame writing is concluded with
+[`finish_frames()`].
+
+```rust
+let ping = qlog::events::quic::QuicFrame::Ping;
+let padding = qlog::events::quic::QuicFrame::Padding;
+
+streamer.add_frame(ping, false).ok();
+streamer.add_frame(padding, false).ok();
+
+streamer.finish_frames().ok();
+```
+
+Once all events have been written, the log
+can be finalized with [`finish_log()`]:
+
+```rust
+streamer.finish_log().ok();
+```
+
+### Serializing
+
+Serialization to JSON occurs as methods on the [`QlogStreamer`]
+are called. No additional steps are required.
+
+[`Trace`]: struct.Trace.html
+[`TraceSeq`]: struct.TraceSeq.html
+[`VantagePoint`]: struct.VantagePoint.html
+[`Configuration`]: struct.Configuration.html
+[`qlog::Trace.events`]: struct.Trace.html#structfield.events
+[`push_event()`]: struct.Trace.html#method.push_event
+[`packet_sent_min()`]: event/struct.Event.html#method.packet_sent_min
+[`QuicFrame::crypto()`]: enum.QuicFrame.html#variant.Crypto
+[`QlogStreamer`]: struct.QlogStreamer.html
+[`Write`]: https://doc.rust-lang.org/std/io/trait.Write.html
+[`start_log()`]: struct.QlogStreamer.html#method.start_log
+[`add_event()`]: struct.QlogStreamer.html#method.add_event
+[`add_frame()`]: struct.QlogStreamer.html#method.add_frame
+[`finish_frames()`]: struct.QlogStreamer.html#method.finish_frames
+[`finish_log()`]: struct.QlogStreamer.html#method.finish_log \ No newline at end of file
diff --git a/third_party/rust/qlog/src/events/connectivity.rs b/third_party/rust/qlog/src/events/connectivity.rs
new file mode 100644
index 0000000000..1b579232b1
--- /dev/null
+++ b/third_party/rust/qlog/src/events/connectivity.rs
@@ -0,0 +1,147 @@
+// Copyright (C) 2021, Cloudflare, Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+use serde::Deserialize;
+use serde::Serialize;
+
+use super::ApplicationErrorCode;
+use super::Bytes;
+use super::ConnectionErrorCode;
+
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
+#[serde(rename_all = "snake_case")]
+pub enum TransportOwner {
+ Local,
+ Remote,
+}
+
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
+#[serde(rename_all = "snake_case")]
+pub enum ConnectionState {
+ Attempted,
+ PeerValidated,
+ HandshakeStarted,
+ EarlyWrite,
+ HandshakeCompleted,
+ HandshakeConfirmed,
+ Closing,
+ Draining,
+ Closed,
+}
+
+#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Debug)]
+#[serde(rename_all = "snake_case")]
+pub enum ConnectivityEventType {
+ ServerListening,
+ ConnectionStarted,
+ ConnectionClosed,
+ ConnectionIdUpdated,
+ SpinBitUpdated,
+ ConnectionStateUpdated,
+ MtuUpdated,
+}
+
+#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Debug)]
+#[serde(rename_all = "snake_case")]
+pub enum ConnectionClosedTrigger {
+ Clean,
+ HandshakeTimeout,
+ IdleTimeout,
+ Error,
+ StatelessReset,
+ VersionMismatch,
+ Application,
+}
+
+#[serde_with::skip_serializing_none]
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
+pub struct ServerListening {
+ pub ip_v4: Option<String>, // human-readable or bytes
+ pub ip_v6: Option<String>, // human-readable or bytes
+ pub port_v4: Option<u16>,
+ pub port_v6: Option<u16>,
+
+ retry_required: Option<bool>,
+}
+
+#[serde_with::skip_serializing_none]
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
+pub struct ConnectionStarted {
+ pub ip_version: Option<String>, // "v4" or "v6"
+ pub src_ip: String, // human-readable or bytes
+ pub dst_ip: String, // human-readable or bytes
+
+ pub protocol: Option<String>,
+ pub src_port: Option<u16>,
+ pub dst_port: Option<u16>,
+
+ pub src_cid: Option<Bytes>,
+ pub dst_cid: Option<Bytes>,
+}
+
+#[serde_with::skip_serializing_none]
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
+pub struct ConnectionClosed {
+ pub owner: Option<TransportOwner>,
+
+ pub connection_code: Option<ConnectionErrorCode>,
+ pub application_code: Option<ApplicationErrorCode>,
+ pub internal_code: Option<u32>,
+
+ pub reason: Option<String>,
+
+ pub trigger: Option<ConnectionClosedTrigger>,
+}
+
+#[serde_with::skip_serializing_none]
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
+pub struct ConnectionIdUpdated {
+ pub owner: Option<TransportOwner>,
+
+ pub old: Option<Bytes>,
+ pub new: Option<Bytes>,
+}
+
+#[serde_with::skip_serializing_none]
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
+pub struct SpinBitUpdated {
+ pub state: bool,
+}
+
+#[serde_with::skip_serializing_none]
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
+pub struct ConnectionStateUpdated {
+ pub old: Option<ConnectionState>,
+ pub new: ConnectionState,
+}
+
+#[serde_with::skip_serializing_none]
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
+pub struct MtuUpdated {
+ pub old: Option<u16>,
+ pub new: u16,
+ pub done: Option<bool>,
+}
diff --git a/third_party/rust/qlog/src/events/h3.rs b/third_party/rust/qlog/src/events/h3.rs
new file mode 100644
index 0000000000..eaf3cadf36
--- /dev/null
+++ b/third_party/rust/qlog/src/events/h3.rs
@@ -0,0 +1,245 @@
+// Copyright (C) 2021, Cloudflare, Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+use serde::Deserialize;
+use serde::Serialize;
+
+use super::RawInfo;
+
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
+#[serde(rename_all = "snake_case")]
+pub enum H3Owner {
+ Local,
+ Remote,
+}
+
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
+#[serde(rename_all = "snake_case")]
+pub enum H3StreamType {
+ Request,
+ Control,
+ Push,
+ Reserved,
+ Unknown,
+ QpackEncode,
+ QpackDecode,
+}
+
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
+#[serde(rename_all = "snake_case")]
+pub enum H3PushDecision {
+ Claimed,
+ Abandoned,
+}
+
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
+#[serde(rename_all = "snake_case")]
+pub enum H3PriorityTargetStreamType {
+ Request,
+ Push,
+}
+
+#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Debug)]
+#[serde(rename_all = "snake_case")]
+pub enum Http3EventType {
+ ParametersSet,
+ ParametersRestored,
+ StreamTypeSet,
+ FrameCreated,
+ FrameParsed,
+ PushResolved,
+}
+
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
+#[serde(rename_all = "snake_case")]
+pub enum ApplicationError {
+ HttpNoError,
+ HttpGeneralProtocolError,
+ HttpInternalError,
+ HttpRequestCancelled,
+ HttpIncompleteRequest,
+ HttpConnectError,
+ HttpFrameError,
+ HttpExcessiveLoad,
+ HttpVersionFallback,
+ HttpIdError,
+ HttpStreamCreationError,
+ HttpClosedCriticalStream,
+ HttpEarlyResponse,
+ HttpMissingSettings,
+ HttpUnexpectedFrame,
+ HttpRequestRejection,
+ HttpSettingsError,
+ Unknown,
+}
+
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
+pub struct HttpHeader {
+ pub name: String,
+ pub value: String,
+}
+
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
+pub struct Setting {
+ pub name: String,
+ pub value: u64,
+}
+
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
+#[serde(rename_all = "snake_case")]
+pub enum Http3FrameTypeName {
+ Data,
+ Headers,
+ CancelPush,
+ Settings,
+ PushPromise,
+ Goaway,
+ MaxPushId,
+ DuplicatePush,
+ Reserved,
+ Unknown,
+}
+
+#[serde_with::skip_serializing_none]
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
+#[serde(tag = "frame_type")]
+#[serde(rename_all = "snake_case")]
+// Strictly, the qlog spec says that all these frame types have a frame_type
+// field. But instead of making that a rust object property, just use serde to
+// ensure it goes out on the wire. This means that deserialization of frames
+// also works automatically.
+pub enum Http3Frame {
+ Data {
+ raw: Option<RawInfo>,
+ },
+
+ Headers {
+ headers: Vec<HttpHeader>,
+ },
+
+ CancelPush {
+ push_id: u64,
+ },
+
+ Settings {
+ settings: Vec<Setting>,
+ },
+
+ PushPromise {
+ push_id: u64,
+ headers: Vec<HttpHeader>,
+ },
+
+ Goaway {
+ id: u64,
+ },
+
+ MaxPushId {
+ push_id: u64,
+ },
+
+ PriorityUpdate {
+ target_stream_type: H3PriorityTargetStreamType,
+ prioritized_element_id: u64,
+ priority_field_value: String,
+ },
+
+ Reserved {
+ length: Option<u64>,
+ },
+
+ Unknown {
+ frame_type_value: u64,
+ raw: Option<RawInfo>,
+ },
+}
+
+#[serde_with::skip_serializing_none]
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
+pub struct H3ParametersSet {
+ pub owner: Option<H3Owner>,
+
+ #[serde(alias = "max_header_list_size")]
+ pub max_field_section_size: Option<u64>,
+ pub max_table_capacity: Option<u64>,
+ pub blocked_streams_count: Option<u64>,
+ pub enable_connect_protocol: Option<u64>,
+ pub h3_datagram: Option<u64>,
+
+ // qlog-defined
+ pub waits_for_settings: Option<bool>,
+}
+
+#[serde_with::skip_serializing_none]
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
+pub struct H3ParametersRestored {
+ #[serde(alias = "max_header_list_size")]
+ pub max_field_section_size: Option<u64>,
+ pub max_table_capacity: Option<u64>,
+ pub blocked_streams_count: Option<u64>,
+ pub enable_connect_protocol: Option<u64>,
+ pub h3_datagram: Option<u64>,
+}
+
+#[serde_with::skip_serializing_none]
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
+pub struct H3StreamTypeSet {
+ pub owner: Option<H3Owner>,
+ pub stream_id: u64,
+
+ pub stream_type: H3StreamType,
+
+ pub associated_push_id: Option<u64>,
+}
+
+#[serde_with::skip_serializing_none]
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
+pub struct H3FrameCreated {
+ pub stream_id: u64,
+ pub length: Option<u64>,
+ pub frame: Http3Frame,
+
+ pub raw: Option<RawInfo>,
+}
+
+#[serde_with::skip_serializing_none]
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
+pub struct H3FrameParsed {
+ pub stream_id: u64,
+ pub length: Option<u64>,
+ pub frame: Http3Frame,
+
+ pub raw: Option<RawInfo>,
+}
+
+#[serde_with::skip_serializing_none]
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
+pub struct H3PushResolved {
+ push_id: Option<u64>,
+ stream_id: Option<u64>,
+
+ decision: Option<H3PushDecision>,
+}
diff --git a/third_party/rust/qlog/src/events/mod.rs b/third_party/rust/qlog/src/events/mod.rs
new file mode 100644
index 0000000000..ac18276fd0
--- /dev/null
+++ b/third_party/rust/qlog/src/events/mod.rs
@@ -0,0 +1,755 @@
+// Copyright (C) 2021, Cloudflare, Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+use crate::Bytes;
+use crate::Token;
+use h3::*;
+use qpack::*;
+use quic::*;
+
+use connectivity::ConnectivityEventType;
+
+use serde::Deserialize;
+use serde::Serialize;
+
+use std::collections::BTreeMap;
+
+pub type ExData = BTreeMap<String, serde_json::Value>;
+
+#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Debug, Default)]
+#[serde(untagged)]
+pub enum EventType {
+ ConnectivityEventType(ConnectivityEventType),
+
+ TransportEventType(TransportEventType),
+
+ SecurityEventType(SecurityEventType),
+
+ RecoveryEventType(RecoveryEventType),
+
+ Http3EventType(Http3EventType),
+
+ QpackEventType(QpackEventType),
+
+ GenericEventType(GenericEventType),
+
+ #[default]
+ None,
+}
+
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
+pub enum TimeFormat {
+ Absolute,
+ Delta,
+ Relative,
+}
+
+#[serde_with::skip_serializing_none]
+#[derive(Serialize, Deserialize, Clone, Debug)]
+pub struct Event {
+ pub time: f32,
+
+ // Strictly, the qlog 02 spec says we should have a name field in the
+ // `Event` structure. However, serde's autogenerated Deserialize code
+ // struggles to read Events properly because the `EventData` types often
+ // alias. In order to work around that, we use can use a trick that will
+ // give serde autogen all the information that it needs while also produced
+ // a legal qlog. Specifically, strongly linking an EventData enum variant
+ // with the wire-format name.
+ //
+ // The trick is to use Adjacent Tagging
+ // (https://serde.rs/enum-representations.html#adjacently-tagged) with
+ // Struct flattening (https://serde.rs/attr-flatten.html). At a high level
+ // this first creates an `EventData` JSON object:
+ //
+ // {name: <enum variant name>, data: enum variant data }
+ //
+ // and then flattens those fields into the `Event` object.
+ #[serde(flatten)]
+ pub data: EventData,
+
+ #[serde(flatten)]
+ pub ex_data: ExData,
+
+ pub protocol_type: Option<String>,
+ pub group_id: Option<String>,
+
+ pub time_format: Option<TimeFormat>,
+
+ #[serde(skip)]
+ ty: EventType,
+}
+
+impl Event {
+ /// Returns a new `Event` object with the provided time and data.
+ pub fn with_time(time: f32, data: EventData) -> Self {
+ Self::with_time_ex(time, data, Default::default())
+ }
+
+ /// Returns a new `Event` object with the provided time, data and ex_data.
+ pub fn with_time_ex(time: f32, data: EventData, ex_data: ExData) -> Self {
+ let ty = EventType::from(&data);
+ Event {
+ time,
+ data,
+ ex_data,
+ protocol_type: Default::default(),
+ group_id: Default::default(),
+ time_format: Default::default(),
+ ty,
+ }
+ }
+}
+
+impl Eventable for Event {
+ fn importance(&self) -> EventImportance {
+ self.ty.into()
+ }
+
+ fn set_time(&mut self, time: f32) {
+ self.time = time;
+ }
+}
+
+impl PartialEq for Event {
+ // custom comparison to skip over the `ty` field
+ fn eq(&self, other: &Event) -> bool {
+ self.time == other.time &&
+ self.data == other.data &&
+ self.ex_data == other.ex_data &&
+ self.protocol_type == other.protocol_type &&
+ self.group_id == other.group_id &&
+ self.time_format == other.time_format
+ }
+}
+
+#[derive(Serialize, Deserialize, Clone, Debug)]
+pub struct JsonEvent {
+ pub time: f32,
+
+ #[serde(skip)]
+ pub importance: EventImportance,
+
+ pub name: String,
+ pub data: serde_json::Value,
+}
+
+impl Eventable for JsonEvent {
+ fn importance(&self) -> EventImportance {
+ self.importance
+ }
+
+ fn set_time(&mut self, time: f32) {
+ self.time = time;
+ }
+}
+
+#[derive(Clone, Copy, Debug, Default)]
+pub enum EventImportance {
+ #[default]
+ Core,
+ Base,
+ Extra,
+}
+
+impl EventImportance {
+ /// Returns true if this importance level is included by `other`.
+ pub fn is_contained_in(&self, other: &EventImportance) -> bool {
+ match (other, self) {
+ (EventImportance::Core, EventImportance::Core) => true,
+
+ (EventImportance::Base, EventImportance::Core) |
+ (EventImportance::Base, EventImportance::Base) => true,
+
+ (EventImportance::Extra, EventImportance::Core) |
+ (EventImportance::Extra, EventImportance::Base) |
+ (EventImportance::Extra, EventImportance::Extra) => true,
+
+ (..) => false,
+ }
+ }
+}
+
+impl From<EventType> for EventImportance {
+ fn from(ty: EventType) -> Self {
+ match ty {
+ EventType::ConnectivityEventType(
+ ConnectivityEventType::ServerListening,
+ ) => EventImportance::Extra,
+ EventType::ConnectivityEventType(
+ ConnectivityEventType::ConnectionStarted,
+ ) => EventImportance::Base,
+ EventType::ConnectivityEventType(
+ ConnectivityEventType::ConnectionIdUpdated,
+ ) => EventImportance::Base,
+ EventType::ConnectivityEventType(
+ ConnectivityEventType::SpinBitUpdated,
+ ) => EventImportance::Base,
+ EventType::ConnectivityEventType(
+ ConnectivityEventType::ConnectionStateUpdated,
+ ) => EventImportance::Base,
+ EventType::ConnectivityEventType(
+ ConnectivityEventType::MtuUpdated,
+ ) => EventImportance::Extra,
+
+ EventType::SecurityEventType(SecurityEventType::KeyUpdated) =>
+ EventImportance::Base,
+ EventType::SecurityEventType(SecurityEventType::KeyDiscarded) =>
+ EventImportance::Base,
+
+ EventType::TransportEventType(
+ TransportEventType::VersionInformation,
+ ) => EventImportance::Core,
+ EventType::TransportEventType(
+ TransportEventType::AlpnInformation,
+ ) => EventImportance::Core,
+ EventType::TransportEventType(TransportEventType::ParametersSet) =>
+ EventImportance::Core,
+ EventType::TransportEventType(
+ TransportEventType::ParametersRestored,
+ ) => EventImportance::Base,
+ EventType::TransportEventType(
+ TransportEventType::DatagramsReceived,
+ ) => EventImportance::Extra,
+ EventType::TransportEventType(TransportEventType::DatagramsSent) =>
+ EventImportance::Extra,
+ EventType::TransportEventType(
+ TransportEventType::DatagramDropped,
+ ) => EventImportance::Extra,
+ EventType::TransportEventType(TransportEventType::PacketReceived) =>
+ EventImportance::Core,
+ EventType::TransportEventType(TransportEventType::PacketSent) =>
+ EventImportance::Core,
+ EventType::TransportEventType(TransportEventType::PacketDropped) =>
+ EventImportance::Base,
+ EventType::TransportEventType(TransportEventType::PacketBuffered) =>
+ EventImportance::Base,
+ EventType::TransportEventType(TransportEventType::PacketsAcked) =>
+ EventImportance::Extra,
+ EventType::TransportEventType(
+ TransportEventType::StreamStateUpdated,
+ ) => EventImportance::Base,
+ EventType::TransportEventType(
+ TransportEventType::FramesProcessed,
+ ) => EventImportance::Extra,
+ EventType::TransportEventType(TransportEventType::DataMoved) =>
+ EventImportance::Base,
+
+ EventType::RecoveryEventType(RecoveryEventType::ParametersSet) =>
+ EventImportance::Base,
+ EventType::RecoveryEventType(RecoveryEventType::MetricsUpdated) =>
+ EventImportance::Core,
+ EventType::RecoveryEventType(
+ RecoveryEventType::CongestionStateUpdated,
+ ) => EventImportance::Base,
+ EventType::RecoveryEventType(RecoveryEventType::LossTimerUpdated) =>
+ EventImportance::Extra,
+ EventType::RecoveryEventType(RecoveryEventType::PacketLost) =>
+ EventImportance::Core,
+ EventType::RecoveryEventType(
+ RecoveryEventType::MarkedForRetransmit,
+ ) => EventImportance::Extra,
+
+ EventType::Http3EventType(Http3EventType::ParametersSet) =>
+ EventImportance::Base,
+ EventType::Http3EventType(Http3EventType::StreamTypeSet) =>
+ EventImportance::Base,
+ EventType::Http3EventType(Http3EventType::FrameCreated) =>
+ EventImportance::Core,
+ EventType::Http3EventType(Http3EventType::FrameParsed) =>
+ EventImportance::Core,
+ EventType::Http3EventType(Http3EventType::PushResolved) =>
+ EventImportance::Extra,
+
+ EventType::QpackEventType(QpackEventType::StateUpdated) =>
+ EventImportance::Base,
+ EventType::QpackEventType(QpackEventType::StreamStateUpdated) =>
+ EventImportance::Base,
+ EventType::QpackEventType(QpackEventType::DynamicTableUpdated) =>
+ EventImportance::Extra,
+ EventType::QpackEventType(QpackEventType::HeadersEncoded) =>
+ EventImportance::Base,
+ EventType::QpackEventType(QpackEventType::HeadersDecoded) =>
+ EventImportance::Base,
+ EventType::QpackEventType(QpackEventType::InstructionCreated) =>
+ EventImportance::Base,
+ EventType::QpackEventType(QpackEventType::InstructionParsed) =>
+ EventImportance::Base,
+
+ _ => unimplemented!(),
+ }
+ }
+}
+
+pub trait Eventable {
+ fn importance(&self) -> EventImportance;
+
+ fn set_time(&mut self, time: f32);
+}
+
+#[derive(Serialize, Deserialize, Clone, Debug)]
+#[serde(rename_all = "snake_case")]
+pub enum EventCategory {
+ Connectivity,
+ Security,
+ Transport,
+ Recovery,
+ Http,
+ Qpack,
+
+ Error,
+ Warning,
+ Info,
+ Debug,
+ Verbose,
+ Simulation,
+}
+
+impl std::fmt::Display for EventCategory {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ let v = match self {
+ EventCategory::Connectivity => "connectivity",
+ EventCategory::Security => "security",
+ EventCategory::Transport => "transport",
+ EventCategory::Recovery => "recovery",
+ EventCategory::Http => "http",
+ EventCategory::Qpack => "qpack",
+ EventCategory::Error => "error",
+ EventCategory::Warning => "warning",
+ EventCategory::Info => "info",
+ EventCategory::Debug => "debug",
+ EventCategory::Verbose => "verbose",
+ EventCategory::Simulation => "simulation",
+ };
+
+ write!(f, "{v}",)
+ }
+}
+
+impl From<EventType> for EventCategory {
+ fn from(ty: EventType) -> Self {
+ match ty {
+ EventType::ConnectivityEventType(_) => EventCategory::Connectivity,
+ EventType::SecurityEventType(_) => EventCategory::Security,
+ EventType::TransportEventType(_) => EventCategory::Transport,
+ EventType::RecoveryEventType(_) => EventCategory::Recovery,
+ EventType::Http3EventType(_) => EventCategory::Http,
+ EventType::QpackEventType(_) => EventCategory::Qpack,
+
+ _ => unimplemented!(),
+ }
+ }
+}
+
+impl From<&EventData> for EventType {
+ fn from(event_data: &EventData) -> Self {
+ match event_data {
+ EventData::ServerListening { .. } =>
+ EventType::ConnectivityEventType(
+ ConnectivityEventType::ServerListening,
+ ),
+ EventData::ConnectionStarted { .. } =>
+ EventType::ConnectivityEventType(
+ ConnectivityEventType::ConnectionStarted,
+ ),
+ EventData::ConnectionClosed { .. } =>
+ EventType::ConnectivityEventType(
+ ConnectivityEventType::ConnectionClosed,
+ ),
+ EventData::ConnectionIdUpdated { .. } =>
+ EventType::ConnectivityEventType(
+ ConnectivityEventType::ConnectionIdUpdated,
+ ),
+ EventData::SpinBitUpdated { .. } => EventType::ConnectivityEventType(
+ ConnectivityEventType::SpinBitUpdated,
+ ),
+ EventData::ConnectionStateUpdated { .. } =>
+ EventType::ConnectivityEventType(
+ ConnectivityEventType::ConnectionStateUpdated,
+ ),
+ EventData::MtuUpdated { .. } => EventType::ConnectivityEventType(
+ ConnectivityEventType::MtuUpdated,
+ ),
+
+ EventData::KeyUpdated { .. } =>
+ EventType::SecurityEventType(SecurityEventType::KeyUpdated),
+ EventData::KeyDiscarded { .. } =>
+ EventType::SecurityEventType(SecurityEventType::KeyDiscarded),
+
+ EventData::VersionInformation { .. } =>
+ EventType::TransportEventType(
+ TransportEventType::VersionInformation,
+ ),
+ EventData::AlpnInformation { .. } =>
+ EventType::TransportEventType(TransportEventType::AlpnInformation),
+ EventData::TransportParametersSet { .. } =>
+ EventType::TransportEventType(TransportEventType::ParametersSet),
+ EventData::TransportParametersRestored { .. } =>
+ EventType::TransportEventType(
+ TransportEventType::ParametersRestored,
+ ),
+ EventData::DatagramsReceived { .. } => EventType::TransportEventType(
+ TransportEventType::DatagramsReceived,
+ ),
+ EventData::DatagramsSent { .. } =>
+ EventType::TransportEventType(TransportEventType::DatagramsSent),
+ EventData::DatagramDropped { .. } =>
+ EventType::TransportEventType(TransportEventType::DatagramDropped),
+ EventData::PacketReceived { .. } =>
+ EventType::TransportEventType(TransportEventType::PacketReceived),
+ EventData::PacketSent { .. } =>
+ EventType::TransportEventType(TransportEventType::PacketSent),
+ EventData::PacketDropped { .. } =>
+ EventType::TransportEventType(TransportEventType::PacketDropped),
+ EventData::PacketBuffered { .. } =>
+ EventType::TransportEventType(TransportEventType::PacketBuffered),
+ EventData::PacketsAcked { .. } =>
+ EventType::TransportEventType(TransportEventType::PacketsAcked),
+ EventData::StreamStateUpdated { .. } =>
+ EventType::TransportEventType(
+ TransportEventType::StreamStateUpdated,
+ ),
+ EventData::FramesProcessed { .. } =>
+ EventType::TransportEventType(TransportEventType::FramesProcessed),
+ EventData::DataMoved { .. } =>
+ EventType::TransportEventType(TransportEventType::DataMoved),
+
+ EventData::RecoveryParametersSet { .. } =>
+ EventType::RecoveryEventType(RecoveryEventType::ParametersSet),
+ EventData::MetricsUpdated { .. } =>
+ EventType::RecoveryEventType(RecoveryEventType::MetricsUpdated),
+ EventData::CongestionStateUpdated { .. } =>
+ EventType::RecoveryEventType(
+ RecoveryEventType::CongestionStateUpdated,
+ ),
+ EventData::LossTimerUpdated { .. } =>
+ EventType::RecoveryEventType(RecoveryEventType::LossTimerUpdated),
+ EventData::PacketLost { .. } =>
+ EventType::RecoveryEventType(RecoveryEventType::PacketLost),
+ EventData::MarkedForRetransmit { .. } =>
+ EventType::RecoveryEventType(
+ RecoveryEventType::MarkedForRetransmit,
+ ),
+
+ EventData::H3ParametersSet { .. } =>
+ EventType::Http3EventType(Http3EventType::ParametersSet),
+ EventData::H3ParametersRestored { .. } =>
+ EventType::Http3EventType(Http3EventType::ParametersRestored),
+ EventData::H3StreamTypeSet { .. } =>
+ EventType::Http3EventType(Http3EventType::StreamTypeSet),
+ EventData::H3FrameCreated { .. } =>
+ EventType::Http3EventType(Http3EventType::FrameCreated),
+ EventData::H3FrameParsed { .. } =>
+ EventType::Http3EventType(Http3EventType::FrameParsed),
+ EventData::H3PushResolved { .. } =>
+ EventType::Http3EventType(Http3EventType::PushResolved),
+
+ EventData::QpackStateUpdated { .. } =>
+ EventType::QpackEventType(QpackEventType::StateUpdated),
+ EventData::QpackStreamStateUpdated { .. } =>
+ EventType::QpackEventType(QpackEventType::StreamStateUpdated),
+ EventData::QpackDynamicTableUpdated { .. } =>
+ EventType::QpackEventType(QpackEventType::DynamicTableUpdated),
+ EventData::QpackHeadersEncoded { .. } =>
+ EventType::QpackEventType(QpackEventType::HeadersEncoded),
+ EventData::QpackHeadersDecoded { .. } =>
+ EventType::QpackEventType(QpackEventType::HeadersDecoded),
+ EventData::QpackInstructionCreated { .. } =>
+ EventType::QpackEventType(QpackEventType::InstructionCreated),
+ EventData::QpackInstructionParsed { .. } =>
+ EventType::QpackEventType(QpackEventType::InstructionParsed),
+
+ EventData::ConnectionError { .. } =>
+ EventType::GenericEventType(GenericEventType::ConnectionError),
+ EventData::ApplicationError { .. } =>
+ EventType::GenericEventType(GenericEventType::ApplicationError),
+ EventData::InternalError { .. } =>
+ EventType::GenericEventType(GenericEventType::InternalError),
+ EventData::InternalWarning { .. } =>
+ EventType::GenericEventType(GenericEventType::InternalError),
+ EventData::Message { .. } =>
+ EventType::GenericEventType(GenericEventType::Message),
+ EventData::Marker { .. } =>
+ EventType::GenericEventType(GenericEventType::Marker),
+ }
+ }
+}
+
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
+#[serde(rename_all = "snake_case")]
+pub enum DataRecipient {
+ User,
+ Application,
+ Transport,
+ Network,
+ Dropped,
+}
+
+#[serde_with::skip_serializing_none]
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
+pub struct RawInfo {
+ pub length: Option<u64>,
+ pub payload_length: Option<u64>,
+
+ pub data: Option<Bytes>,
+}
+
+#[serde_with::skip_serializing_none]
+#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
+#[serde(tag = "name", content = "data")]
+#[allow(clippy::large_enum_variant)]
+pub enum EventData {
+ // Connectivity
+ #[serde(rename = "connectivity:server_listening")]
+ ServerListening(connectivity::ServerListening),
+
+ #[serde(rename = "connectivity:connection_started")]
+ ConnectionStarted(connectivity::ConnectionStarted),
+
+ #[serde(rename = "connectivity:connection_closed")]
+ ConnectionClosed(connectivity::ConnectionClosed),
+
+ #[serde(rename = "connectivity:connection_id_updated")]
+ ConnectionIdUpdated(connectivity::ConnectionIdUpdated),
+
+ #[serde(rename = "connectivity:spin_bit_updated")]
+ SpinBitUpdated(connectivity::SpinBitUpdated),
+
+ #[serde(rename = "connectivity:connection_state_updated")]
+ ConnectionStateUpdated(connectivity::ConnectionStateUpdated),
+
+ #[serde(rename = "connectivity:mtu_updated")]
+ MtuUpdated(connectivity::MtuUpdated),
+
+ // Security
+ #[serde(rename = "security:key_updated")]
+ KeyUpdated(security::KeyUpdated),
+
+ #[serde(rename = "security:key_retired")]
+ KeyDiscarded(security::KeyDiscarded),
+
+ // Transport
+ #[serde(rename = "transport:version_information")]
+ VersionInformation(quic::VersionInformation),
+
+ #[serde(rename = "transport:alpn_information")]
+ AlpnInformation(quic::AlpnInformation),
+
+ #[serde(rename = "transport:parameters_set")]
+ TransportParametersSet(quic::TransportParametersSet),
+
+ #[serde(rename = "transport:parameters_restored")]
+ TransportParametersRestored(quic::TransportParametersRestored),
+
+ #[serde(rename = "transport:datagrams_received")]
+ DatagramsReceived(quic::DatagramsReceived),
+
+ #[serde(rename = "transport:datagrams_sent")]
+ DatagramsSent(quic::DatagramsSent),
+
+ #[serde(rename = "transport:datagram_dropped")]
+ DatagramDropped(quic::DatagramDropped),
+
+ #[serde(rename = "transport:packet_received")]
+ PacketReceived(quic::PacketReceived),
+
+ #[serde(rename = "transport:packet_sent")]
+ PacketSent(quic::PacketSent),
+
+ #[serde(rename = "transport:packet_dropped")]
+ PacketDropped(quic::PacketDropped),
+
+ #[serde(rename = "transport:packet_buffered")]
+ PacketBuffered(quic::PacketBuffered),
+
+ #[serde(rename = "transport:packets_acked")]
+ PacketsAcked(quic::PacketsAcked),
+
+ #[serde(rename = "transport:stream_state_updated")]
+ StreamStateUpdated(quic::StreamStateUpdated),
+
+ #[serde(rename = "transport:frames_processed")]
+ FramesProcessed(quic::FramesProcessed),
+
+ #[serde(rename = "transport:data_moved")]
+ DataMoved(quic::DataMoved),
+
+ // Recovery
+ #[serde(rename = "recovery:parameters_set")]
+ RecoveryParametersSet(quic::RecoveryParametersSet),
+
+ #[serde(rename = "recovery:metrics_updated")]
+ MetricsUpdated(quic::MetricsUpdated),
+
+ #[serde(rename = "recovery:congestion_state_updated")]
+ CongestionStateUpdated(quic::CongestionStateUpdated),
+
+ #[serde(rename = "recovery:loss_timer_updated")]
+ LossTimerUpdated(quic::LossTimerUpdated),
+
+ #[serde(rename = "recovery:packet_lost")]
+ PacketLost(quic::PacketLost),
+
+ #[serde(rename = "recovery:marked_for_retransmit")]
+ MarkedForRetransmit(quic::MarkedForRetransmit),
+
+ // HTTP/3
+ #[serde(rename = "http:parameters_set")]
+ H3ParametersSet(h3::H3ParametersSet),
+
+ #[serde(rename = "http:parameters_restored")]
+ H3ParametersRestored(h3::H3ParametersRestored),
+
+ #[serde(rename = "http:stream_type_set")]
+ H3StreamTypeSet(h3::H3StreamTypeSet),
+
+ #[serde(rename = "http:frame_created")]
+ H3FrameCreated(h3::H3FrameCreated),
+
+ #[serde(rename = "http:frame_parsed")]
+ H3FrameParsed(h3::H3FrameParsed),
+
+ #[serde(rename = "http:push_resolved")]
+ H3PushResolved(h3::H3PushResolved),
+
+ // QPACK
+ #[serde(rename = "qpack:state_updated")]
+ QpackStateUpdated(qpack::QpackStateUpdated),
+
+ #[serde(rename = "qpack:stream_state_updated")]
+ QpackStreamStateUpdated(qpack::QpackStreamStateUpdated),
+
+ #[serde(rename = "qpack:dynamic_table_updated")]
+ QpackDynamicTableUpdated(qpack::QpackDynamicTableUpdated),
+
+ #[serde(rename = "qpack:headers_encoded")]
+ QpackHeadersEncoded(qpack::QpackHeadersEncoded),
+
+ #[serde(rename = "qpack:headers_decoded")]
+ QpackHeadersDecoded(qpack::QpackHeadersDecoded),
+
+ #[serde(rename = "qpack:instruction_created")]
+ QpackInstructionCreated(qpack::QpackInstructionCreated),
+
+ #[serde(rename = "qpack:instruction_parsed")]
+ QpackInstructionParsed(qpack::QpackInstructionParsed),
+
+ // Generic
+ #[serde(rename = "generic:connection_error")]
+ ConnectionError {
+ code: Option<ConnectionErrorCode>,
+ description: Option<String>,
+ },
+
+ #[serde(rename = "generic:application_error")]
+ ApplicationError {
+ code: Option<ApplicationErrorCode>,
+ description: Option<String>,
+ },
+
+ #[serde(rename = "generic:internal_error")]
+ InternalError {
+ code: Option<u64>,
+ description: Option<String>,
+ },
+
+ #[serde(rename = "generic:internal_warning")]
+ InternalWarning {
+ code: Option<u64>,
+ description: Option<String>,
+ },
+
+ #[serde(rename = "generic:message")]
+ Message { message: String },
+
+ #[serde(rename = "generic:marker")]
+ Marker {
+ marker_type: String,
+ message: Option<String>,
+ },
+}
+
+impl EventData {
+ /// Returns size of `EventData` array of `QuicFrame`s if it exists.
+ pub fn contains_quic_frames(&self) -> Option<usize> {
+ // For some EventData variants, the frame array is optional
+ // but for others it is mandatory.
+ match self {
+ EventData::PacketSent(pkt) => pkt.frames.as_ref().map(|f| f.len()),
+
+ EventData::PacketReceived(pkt) =>
+ pkt.frames.as_ref().map(|f| f.len()),
+
+ EventData::PacketLost(pkt) => pkt.frames.as_ref().map(|f| f.len()),
+
+ EventData::MarkedForRetransmit(ev) => Some(ev.frames.len()),
+ EventData::FramesProcessed(ev) => Some(ev.frames.len()),
+
+ _ => None,
+ }
+ }
+}
+
+#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Debug)]
+#[serde(rename_all = "snake_case")]
+pub enum GenericEventType {
+ ConnectionError,
+ ApplicationError,
+ InternalError,
+ InternalWarning,
+
+ Message,
+ Marker,
+}
+
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
+#[serde(untagged)]
+pub enum ConnectionErrorCode {
+ TransportError(TransportError),
+ CryptoError(CryptoError),
+ Value(u64),
+}
+
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
+#[serde(untagged)]
+pub enum ApplicationErrorCode {
+ ApplicationError(ApplicationError),
+ Value(u64),
+}
+
+// TODO
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
+#[serde(rename_all = "snake_case")]
+pub enum CryptoError {
+ Prefix,
+}
+
+pub mod quic;
+
+pub mod connectivity;
+pub mod h3;
+pub mod qpack;
+pub mod security;
diff --git a/third_party/rust/qlog/src/events/qpack.rs b/third_party/rust/qlog/src/events/qpack.rs
new file mode 100644
index 0000000000..4cb90769c0
--- /dev/null
+++ b/third_party/rust/qlog/src/events/qpack.rs
@@ -0,0 +1,276 @@
+// Copyright (C) 2021, Cloudflare, Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+use serde::Deserialize;
+use serde::Serialize;
+
+use super::h3::HttpHeader;
+use super::RawInfo;
+
+#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Debug)]
+#[serde(rename_all = "snake_case")]
+pub enum QpackEventType {
+ StateUpdated,
+ StreamStateUpdated,
+ DynamicTableUpdated,
+ HeadersEncoded,
+ HeadersDecoded,
+ InstructionCreated,
+ InstructionParsed,
+}
+
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
+#[serde(rename_all = "snake_case")]
+pub enum QpackOwner {
+ Local,
+ Remote,
+}
+
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
+#[serde(rename_all = "snake_case")]
+pub enum QpackStreamState {
+ Blocked,
+ Unblocked,
+}
+
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
+#[serde(rename_all = "snake_case")]
+pub enum QpackUpdateType {
+ Added,
+ Evicted,
+}
+
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
+pub struct QpackDynamicTableEntry {
+ pub index: u64,
+ pub name: Option<String>,
+ pub value: Option<String>,
+}
+
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
+pub struct QpackHeaderBlockPrefix {
+ pub required_insert_count: u64,
+ pub sign_bit: bool,
+ pub delta_base: u64,
+}
+
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
+#[serde(rename_all = "snake_case")]
+pub enum QpackInstructionTypeName {
+ SetDynamicTableCapacityInstruction,
+ InsertWithNameReferenceInstruction,
+ InsertWithoutNameReferenceInstruction,
+ DuplicateInstruction,
+ HeaderAcknowledgementInstruction,
+ StreamCancellationInstruction,
+ InsertCountIncrementInstruction,
+}
+
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
+#[serde(rename_all = "snake_case")]
+pub enum QpackTableType {
+ Static,
+ Dynamic,
+}
+
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
+pub enum QPackInstruction {
+ SetDynamicTableCapacityInstruction {
+ instruction_type: QpackInstructionTypeName,
+
+ capacity: u64,
+ },
+
+ InsertWithNameReferenceInstruction {
+ instruction_type: QpackInstructionTypeName,
+
+ table_type: QpackTableType,
+
+ name_index: u64,
+
+ huffman_encoded_value: bool,
+ value_length: u64,
+ value: String,
+ },
+
+ InsertWithoutNameReferenceInstruction {
+ instruction_type: QpackInstructionTypeName,
+
+ huffman_encoded_name: bool,
+ name_length: u64,
+ name: String,
+
+ huffman_encoded_value: bool,
+ value_length: u64,
+ value: String,
+ },
+
+ DuplicateInstruction {
+ instruction_type: QpackInstructionTypeName,
+
+ index: u64,
+ },
+
+ HeaderAcknowledgementInstruction {
+ instruction_type: QpackInstructionTypeName,
+
+ stream_id: String,
+ },
+
+ StreamCancellationInstruction {
+ instruction_type: QpackInstructionTypeName,
+
+ stream_id: String,
+ },
+
+ InsertCountIncrementInstruction {
+ instruction_type: QpackInstructionTypeName,
+
+ increment: u64,
+ },
+}
+
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
+#[serde(rename_all = "snake_case")]
+pub enum QpackHeaderBlockRepresentationTypeName {
+ IndexedHeaderField,
+ LiteralHeaderFieldWithName,
+ LiteralHeaderFieldWithoutName,
+}
+
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
+pub enum QpackHeaderBlockRepresentation {
+ IndexedHeaderField {
+ header_field_type: QpackHeaderBlockRepresentationTypeName,
+
+ table_type: QpackTableType,
+ index: u64,
+
+ is_post_base: Option<bool>,
+ },
+
+ LiteralHeaderFieldWithName {
+ header_field_type: QpackHeaderBlockRepresentationTypeName,
+
+ preserve_literal: bool,
+ table_type: QpackTableType,
+ name_index: u64,
+
+ huffman_encoded_value: bool,
+ value_length: u64,
+ value: String,
+
+ is_post_base: Option<bool>,
+ },
+
+ LiteralHeaderFieldWithoutName {
+ header_field_type: QpackHeaderBlockRepresentationTypeName,
+
+ preserve_literal: bool,
+ table_type: QpackTableType,
+ name_index: u64,
+
+ huffman_encoded_name: bool,
+ name_length: u64,
+ name: String,
+
+ huffman_encoded_value: bool,
+ value_length: u64,
+ value: String,
+
+ is_post_base: Option<bool>,
+ },
+}
+
+#[serde_with::skip_serializing_none]
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
+pub struct QpackStateUpdated {
+ pub owner: Option<QpackOwner>,
+
+ pub dynamic_table_capacity: Option<u64>,
+ pub dynamic_table_size: Option<u64>,
+
+ pub known_received_count: Option<u64>,
+ pub current_insert_count: Option<u64>,
+}
+
+#[serde_with::skip_serializing_none]
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
+pub struct QpackStreamStateUpdated {
+ pub stream_id: u64,
+
+ pub state: QpackStreamState,
+}
+
+#[serde_with::skip_serializing_none]
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
+pub struct QpackDynamicTableUpdated {
+ pub update_type: QpackUpdateType,
+
+ pub entries: Vec<QpackDynamicTableEntry>,
+}
+
+#[serde_with::skip_serializing_none]
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
+pub struct QpackHeadersEncoded {
+ pub stream_id: Option<u64>,
+
+ pub headers: Option<HttpHeader>,
+
+ pub block_prefix: QpackHeaderBlockPrefix,
+ pub header_block: Vec<QpackHeaderBlockRepresentation>,
+
+ pub raw: Option<RawInfo>,
+}
+
+#[serde_with::skip_serializing_none]
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
+pub struct QpackHeadersDecoded {
+ pub stream_id: Option<u64>,
+
+ pub headers: Option<HttpHeader>,
+
+ pub block_prefix: QpackHeaderBlockPrefix,
+ pub header_block: Vec<QpackHeaderBlockRepresentation>,
+
+ pub raw: Option<RawInfo>,
+}
+
+#[serde_with::skip_serializing_none]
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
+pub struct QpackInstructionCreated {
+ pub instruction: QPackInstruction,
+
+ pub raw: Option<RawInfo>,
+}
+
+#[serde_with::skip_serializing_none]
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
+pub struct QpackInstructionParsed {
+ pub instruction: QPackInstruction,
+
+ pub raw: Option<RawInfo>,
+}
diff --git a/third_party/rust/qlog/src/events/quic.rs b/third_party/rust/qlog/src/events/quic.rs
new file mode 100644
index 0000000000..a7c1fa3225
--- /dev/null
+++ b/third_party/rust/qlog/src/events/quic.rs
@@ -0,0 +1,811 @@
+// Copyright (C) 2021, Cloudflare, Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+use serde::Deserialize;
+use serde::Serialize;
+
+use smallvec::SmallVec;
+
+use super::connectivity::TransportOwner;
+use super::Bytes;
+use super::DataRecipient;
+use super::RawInfo;
+use super::Token;
+use crate::HexSlice;
+use crate::StatelessResetToken;
+
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
+#[serde(rename_all = "snake_case")]
+pub enum PacketType {
+ Initial,
+ Handshake,
+
+ #[serde(rename = "0RTT")]
+ ZeroRtt,
+
+ #[serde(rename = "1RTT")]
+ OneRtt,
+
+ Retry,
+ VersionNegotiation,
+ Unknown,
+}
+
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
+#[serde(rename_all = "snake_case")]
+pub enum PacketNumberSpace {
+ Initial,
+ Handshake,
+ ApplicationData,
+}
+
+#[serde_with::skip_serializing_none]
+#[derive(Clone, Serialize, Deserialize, PartialEq, Eq, Debug)]
+pub struct PacketHeader {
+ pub packet_type: PacketType,
+ pub packet_number: Option<u64>,
+
+ pub flags: Option<u8>,
+ pub token: Option<Token>,
+
+ pub length: Option<u16>,
+
+ pub version: Option<Bytes>,
+
+ pub scil: Option<u8>,
+ pub dcil: Option<u8>,
+ pub scid: Option<Bytes>,
+ pub dcid: Option<Bytes>,
+}
+
+impl PacketHeader {
+ #[allow(clippy::too_many_arguments)]
+ /// Creates a new PacketHeader.
+ pub fn new(
+ packet_type: PacketType, packet_number: Option<u64>, flags: Option<u8>,
+ token: Option<Token>, length: Option<u16>, version: Option<u32>,
+ scid: Option<&[u8]>, dcid: Option<&[u8]>,
+ ) -> Self {
+ let (scil, scid) = match scid {
+ Some(cid) => (
+ Some(cid.len() as u8),
+ Some(format!("{}", HexSlice::new(&cid))),
+ ),
+
+ None => (None, None),
+ };
+
+ let (dcil, dcid) = match dcid {
+ Some(cid) => (
+ Some(cid.len() as u8),
+ Some(format!("{}", HexSlice::new(&cid))),
+ ),
+
+ None => (None, None),
+ };
+
+ let version = version.map(|v| format!("{v:x?}"));
+
+ PacketHeader {
+ packet_type,
+ packet_number,
+ flags,
+ token,
+ length,
+ version,
+ scil,
+ dcil,
+ scid,
+ dcid,
+ }
+ }
+
+ /// Creates a new PacketHeader.
+ ///
+ /// Once a QUIC connection has formed, version, dcid and scid are stable, so
+ /// there are space benefits to not logging them in every packet, especially
+ /// PacketType::OneRtt.
+ pub fn with_type(
+ ty: PacketType, packet_number: Option<u64>, version: Option<u32>,
+ scid: Option<&[u8]>, dcid: Option<&[u8]>,
+ ) -> Self {
+ match ty {
+ PacketType::OneRtt => PacketHeader::new(
+ ty,
+ packet_number,
+ None,
+ None,
+ None,
+ None,
+ None,
+ None,
+ ),
+
+ _ => PacketHeader::new(
+ ty,
+ packet_number,
+ None,
+ None,
+ None,
+ version,
+ scid,
+ dcid,
+ ),
+ }
+ }
+}
+
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
+#[serde(rename_all = "snake_case")]
+pub enum StreamType {
+ Bidirectional,
+ Unidirectional,
+}
+
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
+#[serde(rename_all = "snake_case")]
+pub enum StreamSide {
+ Sending,
+ Receiving,
+}
+
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
+#[serde(rename_all = "snake_case")]
+pub enum StreamState {
+ // bidirectional stream states, draft-23 3.4.
+ Idle,
+ Open,
+ HalfClosedLocal,
+ HalfClosedRemote,
+ Closed,
+
+ // sending-side stream states, draft-23 3.1.
+ Ready,
+ Send,
+ DataSent,
+ ResetSent,
+ ResetReceived,
+
+ // receive-side stream states, draft-23 3.2.
+ Receive,
+ SizeKnown,
+ DataRead,
+ ResetRead,
+
+ // both-side states
+ DataReceived,
+
+ // qlog-defined
+ Destroyed,
+}
+
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
+#[serde(rename_all = "snake_case")]
+pub enum ErrorSpace {
+ TransportError,
+ ApplicationError,
+}
+
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
+#[serde(rename_all = "snake_case")]
+pub enum TransportError {
+ NoError,
+ InternalError,
+ ConnectionRefused,
+ FlowControlError,
+ StreamLimitError,
+ StreamStateError,
+ FinalSizeError,
+ FrameEncodingError,
+ TransportParameterError,
+ ConnectionIdLimitError,
+ ProtocolViolation,
+ InvalidToken,
+ ApplicationError,
+ CryptoBufferExceeded,
+ KeyUpdateError,
+ AeadLimitReached,
+ NoViablePath,
+}
+
+#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Debug)]
+#[serde(rename_all = "snake_case")]
+pub enum TransportEventType {
+ VersionInformation,
+ AlpnInformation,
+
+ ParametersSet,
+ ParametersRestored,
+
+ DatagramsSent,
+ DatagramsReceived,
+ DatagramDropped,
+
+ PacketSent,
+ PacketReceived,
+ PacketDropped,
+ PacketBuffered,
+ PacketsAcked,
+
+ FramesProcessed,
+
+ StreamStateUpdated,
+
+ DataMoved,
+}
+
+#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Debug)]
+#[serde(rename_all = "snake_case")]
+pub enum PacketSentTrigger {
+ RetransmitReordered,
+ RetransmitTimeout,
+ PtoProbe,
+ RetransmitCrypto,
+ CcBandwidthProbe,
+}
+
+#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Debug)]
+#[serde(rename_all = "snake_case")]
+pub enum PacketReceivedTrigger {
+ KeysUnavailable,
+}
+
+#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Debug)]
+#[serde(rename_all = "snake_case")]
+pub enum PacketDroppedTrigger {
+ InternalError,
+ Rejected,
+ Unsupported,
+ Invalid,
+ ConnectionUnknown,
+ DecryptionFailure,
+ General,
+}
+
+#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Debug)]
+#[serde(rename_all = "snake_case")]
+pub enum PacketBufferedTrigger {
+ Backpressure,
+ KeysUnavailable,
+}
+
+#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Debug)]
+#[serde(rename_all = "snake_case")]
+pub enum SecurityEventType {
+ KeyUpdated,
+ KeyDiscarded,
+}
+
+#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Debug)]
+#[serde(rename_all = "snake_case")]
+pub enum RecoveryEventType {
+ ParametersSet,
+ MetricsUpdated,
+ CongestionStateUpdated,
+ LossTimerUpdated,
+ PacketLost,
+ MarkedForRetransmit,
+}
+
+#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Debug)]
+#[serde(rename_all = "snake_case")]
+pub enum CongestionStateUpdatedTrigger {
+ PersistentCongestion,
+ Ecn,
+}
+
+#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Debug)]
+#[serde(rename_all = "snake_case")]
+pub enum PacketLostTrigger {
+ ReorderingThreshold,
+ TimeThreshold,
+ PtoExpired,
+}
+
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
+#[serde(rename_all = "snake_case")]
+pub enum LossTimerEventType {
+ Set,
+ Expired,
+ Cancelled,
+}
+
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
+#[serde(rename_all = "snake_case")]
+pub enum TimerType {
+ Ack,
+ Pto,
+}
+
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
+#[serde(untagged)]
+pub enum AckedRanges {
+ Single(Vec<Vec<u64>>),
+ Double(Vec<(u64, u64)>),
+}
+
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
+#[serde(rename_all = "snake_case")]
+pub enum QuicFrameTypeName {
+ Padding,
+ Ping,
+ Ack,
+ ResetStream,
+ StopSending,
+ Crypto,
+ NewToken,
+ Stream,
+ MaxData,
+ MaxStreamData,
+ MaxStreams,
+ DataBlocked,
+ StreamDataBlocked,
+ StreamsBlocked,
+ NewConnectionId,
+ RetireConnectionId,
+ PathChallenge,
+ PathResponse,
+ ConnectionClose,
+ ApplicationClose,
+ HandshakeDone,
+ Datagram,
+ Unknown,
+}
+
+#[serde_with::skip_serializing_none]
+#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
+#[serde(tag = "frame_type")]
+#[serde(rename_all = "snake_case")]
+// Strictly, the qlog spec says that all these frame types have a frame_type
+// field. But instead of making that a rust object property, just use serde to
+// ensure it goes out on the wire. This means that deserialization of frames
+// also works automatically.
+pub enum QuicFrame {
+ Padding,
+
+ Ping,
+
+ Ack {
+ ack_delay: Option<f32>,
+ acked_ranges: Option<AckedRanges>,
+
+ ect1: Option<u64>,
+
+ ect0: Option<u64>,
+
+ ce: Option<u64>,
+ },
+
+ ResetStream {
+ stream_id: u64,
+ error_code: u64,
+ final_size: u64,
+ },
+
+ StopSending {
+ stream_id: u64,
+ error_code: u64,
+ },
+
+ Crypto {
+ offset: u64,
+ length: u64,
+ },
+
+ NewToken {
+ token: Token,
+ },
+
+ Stream {
+ stream_id: u64,
+ offset: u64,
+ length: u64,
+ fin: Option<bool>,
+
+ raw: Option<RawInfo>,
+ },
+
+ MaxData {
+ maximum: u64,
+ },
+
+ MaxStreamData {
+ stream_id: u64,
+ maximum: u64,
+ },
+
+ MaxStreams {
+ stream_type: StreamType,
+ maximum: u64,
+ },
+
+ DataBlocked {
+ limit: u64,
+ },
+
+ StreamDataBlocked {
+ stream_id: u64,
+ limit: u64,
+ },
+
+ StreamsBlocked {
+ stream_type: StreamType,
+ limit: u64,
+ },
+
+ NewConnectionId {
+ sequence_number: u32,
+ retire_prior_to: u32,
+ connection_id_length: Option<u8>,
+ connection_id: Bytes,
+ stateless_reset_token: Option<StatelessResetToken>,
+ },
+
+ RetireConnectionId {
+ sequence_number: u32,
+ },
+
+ PathChallenge {
+ data: Option<Bytes>,
+ },
+
+ PathResponse {
+ data: Option<Bytes>,
+ },
+
+ ConnectionClose {
+ error_space: Option<ErrorSpace>,
+ error_code: Option<u64>,
+ error_code_value: Option<u64>,
+ reason: Option<String>,
+
+ trigger_frame_type: Option<u64>,
+ },
+
+ HandshakeDone,
+
+ Datagram {
+ length: u64,
+
+ raw: Option<Bytes>,
+ },
+
+ Unknown {
+ raw_frame_type: u64,
+ frame_type_value: Option<u64>,
+ raw: Option<RawInfo>,
+ },
+}
+
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
+pub struct PreferredAddress {
+ pub ip_v4: String,
+ pub ip_v6: String,
+
+ pub port_v4: u16,
+ pub port_v6: u16,
+
+ pub connection_id: Bytes,
+ pub stateless_reset_token: StatelessResetToken,
+}
+
+#[serde_with::skip_serializing_none]
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
+pub struct VersionInformation {
+ pub server_versions: Option<Vec<Bytes>>,
+ pub client_versions: Option<Vec<Bytes>>,
+ pub chosen_version: Option<Bytes>,
+}
+
+#[serde_with::skip_serializing_none]
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
+pub struct AlpnInformation {
+ pub server_alpns: Option<Vec<Bytes>>,
+ pub client_alpns: Option<Vec<Bytes>>,
+ pub chosen_alpn: Option<Bytes>,
+}
+
+#[serde_with::skip_serializing_none]
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
+pub struct TransportParametersSet {
+ pub owner: Option<TransportOwner>,
+
+ pub resumption_allowed: Option<bool>,
+ pub early_data_enabled: Option<bool>,
+ pub tls_cipher: Option<String>,
+ pub aead_tag_length: Option<u8>,
+
+ pub original_destination_connection_id: Option<Bytes>,
+ pub initial_source_connection_id: Option<Bytes>,
+ pub retry_source_connection_id: Option<Bytes>,
+ pub stateless_reset_token: Option<StatelessResetToken>,
+ pub disable_active_migration: Option<bool>,
+
+ pub max_idle_timeout: Option<u64>,
+ pub max_udp_payload_size: Option<u32>,
+ pub ack_delay_exponent: Option<u16>,
+ pub max_ack_delay: Option<u16>,
+ pub active_connection_id_limit: Option<u32>,
+
+ pub initial_max_data: Option<u64>,
+ pub initial_max_stream_data_bidi_local: Option<u64>,
+ pub initial_max_stream_data_bidi_remote: Option<u64>,
+ pub initial_max_stream_data_uni: Option<u64>,
+ pub initial_max_streams_bidi: Option<u64>,
+ pub initial_max_streams_uni: Option<u64>,
+
+ pub preferred_address: Option<PreferredAddress>,
+}
+
+#[serde_with::skip_serializing_none]
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
+pub struct TransportParametersRestored {
+ pub disable_active_migration: Option<bool>,
+
+ pub max_idle_timeout: Option<u64>,
+ pub max_udp_payload_size: Option<u32>,
+ pub active_connection_id_limit: Option<u32>,
+
+ pub initial_max_data: Option<u64>,
+ pub initial_max_stream_data_bidi_local: Option<u64>,
+ pub initial_max_stream_data_bidi_remote: Option<u64>,
+ pub initial_max_stream_data_uni: Option<u64>,
+ pub initial_max_streams_bidi: Option<u64>,
+ pub initial_max_streams_uni: Option<u64>,
+}
+
+#[serde_with::skip_serializing_none]
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
+pub struct DatagramsReceived {
+ pub count: Option<u16>,
+
+ pub raw: Option<Vec<RawInfo>>,
+
+ pub datagram_ids: Option<Vec<u32>>,
+}
+
+#[serde_with::skip_serializing_none]
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
+pub struct DatagramsSent {
+ pub count: Option<u16>,
+
+ pub raw: Option<Vec<RawInfo>>,
+
+ pub datagram_ids: Option<Vec<u32>>,
+}
+
+#[serde_with::skip_serializing_none]
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
+pub struct DatagramDropped {
+ pub raw: Option<RawInfo>,
+}
+
+#[serde_with::skip_serializing_none]
+#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
+pub struct PacketReceived {
+ pub header: PacketHeader,
+ // `frames` is defined here in the QLog schema specification. However,
+ // our streaming serializer requires serde to put the object at the end,
+ // so we define it there and depend on serde's preserve_order feature.
+ pub is_coalesced: Option<bool>,
+
+ pub retry_token: Option<Token>,
+
+ pub stateless_reset_token: Option<StatelessResetToken>,
+
+ pub supported_versions: Option<Vec<Bytes>>,
+
+ pub raw: Option<RawInfo>,
+ pub datagram_id: Option<u32>,
+
+ pub trigger: Option<PacketReceivedTrigger>,
+
+ pub frames: Option<Vec<QuicFrame>>,
+}
+
+#[serde_with::skip_serializing_none]
+#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
+pub struct PacketSent {
+ pub header: PacketHeader,
+ // `frames` is defined here in the QLog schema specification. However,
+ // our streaming serializer requires serde to put the object at the end,
+ // so we define it there and depend on serde's preserve_order feature.
+ pub is_coalesced: Option<bool>,
+
+ pub retry_token: Option<Token>,
+
+ pub stateless_reset_token: Option<StatelessResetToken>,
+
+ pub supported_versions: Option<Vec<Bytes>>,
+
+ pub raw: Option<RawInfo>,
+ pub datagram_id: Option<u32>,
+
+ pub trigger: Option<PacketSentTrigger>,
+
+ pub send_at_time: Option<f32>,
+
+ pub frames: Option<SmallVec<[QuicFrame; 1]>>,
+}
+
+#[serde_with::skip_serializing_none]
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
+pub struct PacketDropped {
+ pub header: Option<PacketHeader>,
+
+ pub raw: Option<RawInfo>,
+ pub datagram_id: Option<u32>,
+
+ pub details: Option<String>,
+
+ pub trigger: Option<PacketDroppedTrigger>,
+}
+
+#[serde_with::skip_serializing_none]
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
+pub struct PacketBuffered {
+ pub header: Option<PacketHeader>,
+
+ pub raw: Option<RawInfo>,
+ pub datagram_id: Option<u32>,
+
+ pub trigger: Option<PacketBufferedTrigger>,
+}
+
+#[serde_with::skip_serializing_none]
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
+pub struct PacketsAcked {
+ pub packet_number_space: Option<PacketNumberSpace>,
+ pub packet_numbers: Option<Vec<u64>>,
+}
+
+#[serde_with::skip_serializing_none]
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
+pub struct StreamStateUpdated {
+ pub stream_id: u64,
+ pub stream_type: Option<StreamType>,
+
+ pub old: Option<StreamState>,
+ pub new: StreamState,
+
+ pub stream_side: Option<StreamSide>,
+}
+
+#[serde_with::skip_serializing_none]
+#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
+pub struct FramesProcessed {
+ pub frames: Vec<QuicFrame>,
+
+ pub packet_number: Option<u64>,
+}
+
+#[serde_with::skip_serializing_none]
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
+pub struct DataMoved {
+ pub stream_id: Option<u64>,
+ pub offset: Option<u64>,
+ pub length: Option<u64>,
+
+ pub from: Option<DataRecipient>,
+ pub to: Option<DataRecipient>,
+
+ pub raw: Option<RawInfo>,
+}
+
+#[serde_with::skip_serializing_none]
+#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
+pub struct RecoveryParametersSet {
+ pub reordering_threshold: Option<u16>,
+ pub time_threshold: Option<f32>,
+ pub timer_granularity: Option<u16>,
+ pub initial_rtt: Option<f32>,
+
+ pub max_datagram_size: Option<u32>,
+ pub initial_congestion_window: Option<u64>,
+ pub minimum_congestion_window: Option<u32>,
+ pub loss_reduction_factor: Option<f32>,
+ pub persistent_congestion_threshold: Option<u16>,
+}
+
+#[serde_with::skip_serializing_none]
+#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
+pub struct MetricsUpdated {
+ pub min_rtt: Option<f32>,
+ pub smoothed_rtt: Option<f32>,
+ pub latest_rtt: Option<f32>,
+ pub rtt_variance: Option<f32>,
+
+ pub pto_count: Option<u16>,
+
+ pub congestion_window: Option<u64>,
+ pub bytes_in_flight: Option<u64>,
+
+ pub ssthresh: Option<u64>,
+
+ // qlog defined
+ pub packets_in_flight: Option<u64>,
+
+ pub pacing_rate: Option<u64>,
+}
+
+#[serde_with::skip_serializing_none]
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
+pub struct CongestionStateUpdated {
+ pub old: Option<String>,
+ pub new: String,
+
+ pub trigger: Option<CongestionStateUpdatedTrigger>,
+}
+
+#[serde_with::skip_serializing_none]
+#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
+pub struct LossTimerUpdated {
+ pub timer_type: Option<TimerType>,
+ pub packet_number_space: Option<PacketNumberSpace>,
+
+ pub event_type: LossTimerEventType,
+
+ pub delta: Option<f32>,
+}
+
+#[serde_with::skip_serializing_none]
+#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
+pub struct PacketLost {
+ pub header: Option<PacketHeader>,
+
+ pub frames: Option<Vec<QuicFrame>>,
+
+ pub trigger: Option<PacketLostTrigger>,
+}
+
+#[serde_with::skip_serializing_none]
+#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
+pub struct MarkedForRetransmit {
+ pub frames: Vec<QuicFrame>,
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::testing::*;
+
+ #[test]
+ fn packet_header() {
+ let pkt_hdr = make_pkt_hdr(PacketType::Initial);
+
+ let log_string = r#"{
+ "packet_type": "initial",
+ "packet_number": 0,
+ "version": "1",
+ "scil": 8,
+ "dcil": 8,
+ "scid": "7e37e4dcc6682da8",
+ "dcid": "36ce104eee50101c"
+}"#;
+
+ assert_eq!(serde_json::to_string_pretty(&pkt_hdr).unwrap(), log_string);
+ }
+}
diff --git a/third_party/rust/qlog/src/events/security.rs b/third_party/rust/qlog/src/events/security.rs
new file mode 100644
index 0000000000..97e86376aa
--- /dev/null
+++ b/third_party/rust/qlog/src/events/security.rs
@@ -0,0 +1,81 @@
+// Copyright (C) 2021, Cloudflare, Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+use serde::Deserialize;
+use serde::Serialize;
+
+use super::Bytes;
+
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
+#[serde(rename_all = "snake_case")]
+pub enum KeyType {
+ ServerInitialSecret,
+ ClientInitialSecret,
+
+ ServerHandshakeSecret,
+ ClientHandshakeSecret,
+
+ #[serde(rename = "server_0rtt_secret")]
+ Server0RttSecret,
+ #[serde(rename = "client_0rtt_secret")]
+ Client0RttSecret,
+ #[serde(rename = "server_1rtt_secret")]
+ Server1RttSecret,
+ #[serde(rename = "client_1rtt_secret")]
+ Client1RttSecret,
+}
+
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
+#[serde(rename_all = "snake_case")]
+pub enum KeyUpdateOrRetiredTrigger {
+ Tls,
+ RemoteUpdate,
+ LocalUpdate,
+}
+
+#[serde_with::skip_serializing_none]
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
+pub struct KeyUpdated {
+ pub key_type: KeyType,
+
+ pub old: Option<Bytes>,
+ pub new: Bytes,
+
+ pub generation: Option<u32>,
+
+ pub trigger: Option<KeyUpdateOrRetiredTrigger>,
+}
+
+#[serde_with::skip_serializing_none]
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
+pub struct KeyDiscarded {
+ pub key_type: KeyType,
+ pub key: Option<Bytes>,
+
+ pub generation: Option<u32>,
+
+ pub trigger: Option<KeyUpdateOrRetiredTrigger>,
+}
diff --git a/third_party/rust/qlog/src/lib.rs b/third_party/rust/qlog/src/lib.rs
new file mode 100644
index 0000000000..68ff278fcd
--- /dev/null
+++ b/third_party/rust/qlog/src/lib.rs
@@ -0,0 +1,971 @@
+// Copyright (C) 2019, Cloudflare, Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+//! The qlog crate is an implementation of the qlog [main logging schema],
+//! [QUIC event definitions], and [HTTP/3 and QPACK event definitions].
+//! The crate provides a qlog data model that can be used for traces with
+//! events. It supports serialization and deserialization but defers logging IO
+//! choices to applications.
+//!
+//! Serialization operates in either a [buffered mode] or a [streaming mode].
+//!
+//! The crate uses Serde for conversion between Rust and JSON.
+//!
+//! [main logging schema]: https://datatracker.ietf.org/doc/html/draft-ietf-quic-qlog-main-schema
+//! [QUIC event definitions]:
+//! https://datatracker.ietf.org/doc/html/draft-ietf-quic-qlog-quic-events.html
+//! [HTTP/3 and QPACK event definitions]:
+//! https://datatracker.ietf.org/doc/html/draft-ietf-quic-qlog-h3-events.html
+//! [buffered mode]: #buffered-traces-with-standard-json
+//! [streaming mode]: #streaming-traces-with-json-seq
+//!
+//! Overview
+//! ---------------
+//! qlog is a hierarchical logging format, with a rough structure of:
+//!
+//! * Log
+//! * Trace(s)
+//! * Event(s)
+//!
+//! In practice, a single QUIC connection maps to a single Trace file with one
+//! or more Events. Applications can decide whether to combine Traces from
+//! different connections into the same Log.
+//!
+//! ## Buffered Traces with standard JSON
+//!
+//! A [`Trace`] is a single JSON object. It contains metadata such as the
+//! [`VantagePoint`] of capture and the [`Configuration`], and protocol event
+//! data in the [`Event`] array.
+//!
+//! JSON Traces allow applications to appends events to them before eventually
+//! being serialized as a complete JSON object.
+//!
+//! ### Creating a Trace
+//!
+//! ```
+//! let mut trace = qlog::Trace::new(
+//! qlog::VantagePoint {
+//! name: Some("Example client".to_string()),
+//! ty: qlog::VantagePointType::Client,
+//! flow: None,
+//! },
+//! Some("Example qlog trace".to_string()),
+//! Some("Example qlog trace description".to_string()),
+//! Some(qlog::Configuration {
+//! time_offset: Some(0.0),
+//! original_uris: None,
+//! }),
+//! None,
+//! );
+//! ```
+//!
+//! ### Adding events to a Trace
+//!
+//! Qlog [`Event`] objects are added to [`qlog::Trace.events`].
+//!
+//! The following example demonstrates how to log a qlog QUIC `packet_sent`
+//! event containing a single Crypto frame. It constructs the necessary elements
+//! of the [`Event`], then appends it to the trace with [`push_event()`].
+//!
+//! ```
+//! # let mut trace = qlog::Trace::new (
+//! # qlog::VantagePoint {
+//! # name: Some("Example client".to_string()),
+//! # ty: qlog::VantagePointType::Client,
+//! # flow: None,
+//! # },
+//! # Some("Example qlog trace".to_string()),
+//! # Some("Example qlog trace description".to_string()),
+//! # Some(qlog::Configuration {
+//! # time_offset: Some(0.0),
+//! # original_uris: None,
+//! # }),
+//! # None
+//! # );
+//!
+//! let scid = [0x7e, 0x37, 0xe4, 0xdc, 0xc6, 0x68, 0x2d, 0xa8];
+//! let dcid = [0x36, 0xce, 0x10, 0x4e, 0xee, 0x50, 0x10, 0x1c];
+//!
+//! let pkt_hdr = qlog::events::quic::PacketHeader::new(
+//! qlog::events::quic::PacketType::Initial,
+//! Some(0), // packet_number
+//! None, // flags
+//! None, // token
+//! None, // length
+//! Some(0x00000001), // version
+//! Some(&scid),
+//! Some(&dcid),
+//! );
+//!
+//! let frames = vec![qlog::events::quic::QuicFrame::Crypto {
+//! offset: 0,
+//! length: 0,
+//! }];
+//!
+//! let raw = qlog::events::RawInfo {
+//! length: Some(1251),
+//! payload_length: Some(1224),
+//! data: None,
+//! };
+//!
+//! let event_data =
+//! qlog::events::EventData::PacketSent(qlog::events::quic::PacketSent {
+//! header: pkt_hdr,
+//! frames: Some(frames.into()),
+//! is_coalesced: None,
+//! retry_token: None,
+//! stateless_reset_token: None,
+//! supported_versions: None,
+//! raw: Some(raw),
+//! datagram_id: None,
+//! send_at_time: None,
+//! trigger: None,
+//! });
+//!
+//! trace.push_event(qlog::events::Event::with_time(0.0, event_data));
+//! ```
+//!
+//! ### Serializing
+//!
+//! The qlog crate has only been tested with `serde_json`, however
+//! other serializer targets might work.
+//!
+//! For example, serializing the trace created above:
+//!
+//! ```
+//! # let mut trace = qlog::Trace::new (
+//! # qlog::VantagePoint {
+//! # name: Some("Example client".to_string()),
+//! # ty: qlog::VantagePointType::Client,
+//! # flow: None,
+//! # },
+//! # Some("Example qlog trace".to_string()),
+//! # Some("Example qlog trace description".to_string()),
+//! # Some(qlog::Configuration {
+//! # time_offset: Some(0.0),
+//! # original_uris: None,
+//! # }),
+//! # None
+//! # );
+//! serde_json::to_string_pretty(&trace).unwrap();
+//! ```
+//!
+//! which would generate the following:
+//!
+//! ```ignore
+//! {
+//! "vantage_point": {
+//! "name": "Example client",
+//! "type": "client"
+//! },
+//! "title": "Example qlog trace",
+//! "description": "Example qlog trace description",
+//! "configuration": {
+//! "time_offset": 0.0
+//! },
+//! "events": [
+//! {
+//! "time": 0.0,
+//! "name": "transport:packet_sent",
+//! "data": {
+//! "header": {
+//! "packet_type": "initial",
+//! "packet_number": 0,
+//! "version": "1",
+//! "scil": 8,
+//! "dcil": 8,
+//! "scid": "7e37e4dcc6682da8",
+//! "dcid": "36ce104eee50101c"
+//! },
+//! "raw": {
+//! "length": 1251,
+//! "payload_length": 1224
+//! },
+//! "frames": [
+//! {
+//! "frame_type": "crypto",
+//! "offset": 0,
+//! "length": 0
+//! }
+//! ]
+//! }
+//! }
+//! ]
+//! }
+//! ```
+//!
+//! ## Streaming Traces with JSON-SEQ
+//!
+//! To help support streaming serialization of qlogs,
+//! draft-ietf-quic-qlog-main-schema-01 introduced support for RFC 7464 JSON
+//! Text Sequences (JSON-SEQ). The qlog crate supports this format and provides
+//! utilities that aid streaming.
+//!
+//! A [`TraceSeq`] contains metadata such as the [`VantagePoint`] of capture and
+//! the [`Configuration`]. However, protocol event data is handled as separate
+//! lines containing a record separator character, a serialized [`Event`], and a
+//! newline.
+//!
+//! ### Creating a TraceSeq
+//!
+//! ```
+//! let mut trace = qlog::TraceSeq::new(
+//! qlog::VantagePoint {
+//! name: Some("Example client".to_string()),
+//! ty: qlog::VantagePointType::Client,
+//! flow: None,
+//! },
+//! Some("Example qlog trace".to_string()),
+//! Some("Example qlog trace description".to_string()),
+//! Some(qlog::Configuration {
+//! time_offset: Some(0.0),
+//! original_uris: None,
+//! }),
+//! None,
+//! );
+//! ```
+//!
+//! Create an object with the [`Write`] trait:
+//!
+//! ```
+//! let mut file = std::fs::File::create("foo.sqlog").unwrap();
+//! ```
+//!
+//! Create a [`QlogStreamer`] and start serialization to foo.sqlog
+//! using [`start_log()`]:
+//!
+//! ```
+//! # let mut trace = qlog::TraceSeq::new(
+//! # qlog::VantagePoint {
+//! # name: Some("Example client".to_string()),
+//! # ty: qlog::VantagePointType::Client,
+//! # flow: None,
+//! # },
+//! # Some("Example qlog trace".to_string()),
+//! # Some("Example qlog trace description".to_string()),
+//! # Some(qlog::Configuration {
+//! # time_offset: Some(0.0),
+//! # original_uris: None,
+//! # }),
+//! # None,
+//! # );
+//! # let mut file = std::fs::File::create("foo.sqlog").unwrap();
+//! let mut streamer = qlog::streamer::QlogStreamer::new(
+//! qlog::QLOG_VERSION.to_string(),
+//! Some("Example qlog".to_string()),
+//! Some("Example qlog description".to_string()),
+//! None,
+//! std::time::Instant::now(),
+//! trace,
+//! qlog::events::EventImportance::Base,
+//! Box::new(file),
+//! );
+//!
+//! streamer.start_log().ok();
+//! ```
+//!
+//! ### Adding events
+//!
+//! Once logging has started you can stream events. Events
+//! are written in one step using one of [`add_event()`],
+//! [`add_event_with_instant()`], [`add_event_now()`],
+//! [`add_event_data_with_instant()`], or [`add_event_data_now()`] :
+//!
+//! ```
+//! # let mut trace = qlog::TraceSeq::new(
+//! # qlog::VantagePoint {
+//! # name: Some("Example client".to_string()),
+//! # ty: qlog::VantagePointType::Client,
+//! # flow: None,
+//! # },
+//! # Some("Example qlog trace".to_string()),
+//! # Some("Example qlog trace description".to_string()),
+//! # Some(qlog::Configuration {
+//! # time_offset: Some(0.0),
+//! # original_uris: None,
+//! # }),
+//! # None,
+//! # );
+//! # let mut file = std::fs::File::create("foo.qlog").unwrap();
+//! # let mut streamer = qlog::streamer::QlogStreamer::new(
+//! # qlog::QLOG_VERSION.to_string(),
+//! # Some("Example qlog".to_string()),
+//! # Some("Example qlog description".to_string()),
+//! # None,
+//! # std::time::Instant::now(),
+//! # trace,
+//! # qlog::events::EventImportance::Base,
+//! # Box::new(file),
+//! # );
+//!
+//! let scid = [0x7e, 0x37, 0xe4, 0xdc, 0xc6, 0x68, 0x2d, 0xa8];
+//! let dcid = [0x36, 0xce, 0x10, 0x4e, 0xee, 0x50, 0x10, 0x1c];
+//!
+//! let pkt_hdr = qlog::events::quic::PacketHeader::with_type(
+//! qlog::events::quic::PacketType::OneRtt,
+//! Some(0),
+//! Some(0x00000001),
+//! Some(&scid),
+//! Some(&dcid),
+//! );
+//!
+//! let ping = qlog::events::quic::QuicFrame::Ping;
+//! let padding = qlog::events::quic::QuicFrame::Padding;
+//!
+//! let event_data =
+//! qlog::events::EventData::PacketSent(qlog::events::quic::PacketSent {
+//! header: pkt_hdr,
+//! frames: Some(vec![ping, padding].into()),
+//! is_coalesced: None,
+//! retry_token: None,
+//! stateless_reset_token: None,
+//! supported_versions: None,
+//! raw: None,
+//! datagram_id: None,
+//! send_at_time: None,
+//! trigger: None,
+//! });
+//!
+//! let event = qlog::events::Event::with_time(0.0, event_data);
+//!
+//! streamer.add_event(event).ok();
+//! ```
+//!
+//! Once all events have been written, the log
+//! can be finalized with [`finish_log()`]:
+//!
+//! ```
+//! # let mut trace = qlog::TraceSeq::new(
+//! # qlog::VantagePoint {
+//! # name: Some("Example client".to_string()),
+//! # ty: qlog::VantagePointType::Client,
+//! # flow: None,
+//! # },
+//! # Some("Example qlog trace".to_string()),
+//! # Some("Example qlog trace description".to_string()),
+//! # Some(qlog::Configuration {
+//! # time_offset: Some(0.0),
+//! # original_uris: None,
+//! # }),
+//! # None,
+//! # );
+//! # let mut file = std::fs::File::create("foo.qlog").unwrap();
+//! # let mut streamer = qlog::streamer::QlogStreamer::new(
+//! # qlog::QLOG_VERSION.to_string(),
+//! # Some("Example qlog".to_string()),
+//! # Some("Example qlog description".to_string()),
+//! # None,
+//! # std::time::Instant::now(),
+//! # trace,
+//! # qlog::events::EventImportance::Base,
+//! # Box::new(file),
+//! # );
+//! streamer.finish_log().ok();
+//! ```
+//!
+//! ### Serializing
+//!
+//! Serialization to JSON occurs as methods on the [`QlogStreamer`]
+//! are called. No additional steps are required.
+//!
+//! [`Trace`]: struct.Trace.html
+//! [`TraceSeq`]: struct.TraceSeq.html
+//! [`VantagePoint`]: struct.VantagePoint.html
+//! [`Configuration`]: struct.Configuration.html
+//! [`qlog::Trace.events`]: struct.Trace.html#structfield.events
+//! [`push_event()`]: struct.Trace.html#method.push_event
+//! [`QlogStreamer`]: struct.QlogStreamer.html
+//! [`Write`]: https://doc.rust-lang.org/std/io/trait.Write.html
+//! [`start_log()`]: streamer/struct.QlogStreamer.html#method.start_log
+//! [`add_event()`]: streamer/struct.QlogStreamer.html#method.add_event
+//! [`add_event_with_instant()`]: streamer/struct.QlogStreamer.html#method.add_event_with_instant
+//! [`add_event_now()`]: streamer/struct.QlogStreamer.html#method.add_event_now
+//! [`add_event_data_with_instant()`]: streamer/struct.QlogStreamer.html#method.add_event_data_with_instant
+//! [`add_event_data_now()`]: streamer/struct.QlogStreamer.html#method.add_event_data_now
+//! [`finish_log()`]: streamer/struct.QlogStreamer.html#method.finish_log
+
+use crate::events::quic::PacketHeader;
+use crate::events::Event;
+
+use serde::Deserialize;
+use serde::Serialize;
+
+/// A quiche qlog error.
+#[derive(Debug)]
+pub enum Error {
+ /// There is no more work to do.
+ Done,
+
+ /// The operation cannot be completed because it was attempted
+ /// in an invalid state.
+ InvalidState,
+
+ // Invalid Qlog format
+ InvalidFormat,
+
+ /// I/O error.
+ IoError(std::io::Error),
+}
+
+impl std::fmt::Display for Error {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ write!(f, "{self:?}")
+ }
+}
+
+impl std::error::Error for Error {
+ fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
+ None
+ }
+}
+
+impl std::convert::From<std::io::Error> for Error {
+ fn from(err: std::io::Error) -> Self {
+ Error::IoError(err)
+ }
+}
+
+pub const QLOG_VERSION: &str = "0.3";
+
+pub type Bytes = String;
+pub type StatelessResetToken = Bytes;
+
+/// A specialized [`Result`] type for quiche qlog operations.
+///
+/// This type is used throughout the public API for any operation that
+/// can produce an error.
+///
+/// [`Result`]: https://doc.rust-lang.org/std/result/enum.Result.html
+pub type Result<T> = std::result::Result<T, Error>;
+
+#[serde_with::skip_serializing_none]
+#[derive(Serialize, Deserialize, Clone)]
+pub struct Qlog {
+ pub qlog_version: String,
+ pub qlog_format: String,
+ pub title: Option<String>,
+ pub description: Option<String>,
+ pub summary: Option<String>,
+
+ pub traces: Vec<Trace>,
+}
+#[serde_with::skip_serializing_none]
+#[derive(Serialize, Deserialize, Clone, Debug)]
+pub struct QlogSeq {
+ pub qlog_version: String,
+ pub qlog_format: String,
+ pub title: Option<String>,
+ pub description: Option<String>,
+ pub summary: Option<String>,
+
+ pub trace: TraceSeq,
+}
+
+#[derive(Clone, Copy)]
+pub enum ImportanceLogLevel {
+ Core = 0,
+ Base = 1,
+ Extra = 2,
+}
+
+// We now commence data definitions heavily styled on the QLOG
+// schema definition. Data is serialized using serde.
+#[serde_with::skip_serializing_none]
+#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
+pub struct Trace {
+ pub vantage_point: VantagePoint,
+ pub title: Option<String>,
+ pub description: Option<String>,
+
+ pub configuration: Option<Configuration>,
+
+ pub common_fields: Option<CommonFields>,
+
+ pub events: Vec<Event>,
+}
+
+/// Helper functions for using a qlog [Trace].
+impl Trace {
+ /// Creates a new qlog [Trace]
+ pub fn new(
+ vantage_point: VantagePoint, title: Option<String>,
+ description: Option<String>, configuration: Option<Configuration>,
+ common_fields: Option<CommonFields>,
+ ) -> Self {
+ Trace {
+ vantage_point,
+ title,
+ description,
+ configuration,
+ common_fields,
+ events: Vec::new(),
+ }
+ }
+
+ /// Append an [Event] to a [Trace]
+ pub fn push_event(&mut self, event: Event) {
+ self.events.push(event);
+ }
+}
+
+#[serde_with::skip_serializing_none]
+#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
+pub struct TraceSeq {
+ pub vantage_point: VantagePoint,
+ pub title: Option<String>,
+ pub description: Option<String>,
+
+ pub configuration: Option<Configuration>,
+
+ pub common_fields: Option<CommonFields>,
+}
+
+/// Helper functions for using a qlog [TraceSeq].
+impl TraceSeq {
+ /// Creates a new qlog [TraceSeq]
+ pub fn new(
+ vantage_point: VantagePoint, title: Option<String>,
+ description: Option<String>, configuration: Option<Configuration>,
+ common_fields: Option<CommonFields>,
+ ) -> Self {
+ TraceSeq {
+ vantage_point,
+ title,
+ description,
+ configuration,
+ common_fields,
+ }
+ }
+}
+
+#[serde_with::skip_serializing_none]
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
+pub struct VantagePoint {
+ pub name: Option<String>,
+
+ #[serde(rename = "type")]
+ pub ty: VantagePointType,
+
+ pub flow: Option<VantagePointType>,
+}
+
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
+#[serde(rename_all = "snake_case")]
+pub enum VantagePointType {
+ Client,
+ Server,
+ Network,
+ Unknown,
+}
+
+#[serde_with::skip_serializing_none]
+#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
+pub struct Configuration {
+ pub time_offset: Option<f64>,
+
+ pub original_uris: Option<Vec<String>>,
+ // TODO: additionalUserSpecifiedProperty
+}
+
+impl Default for Configuration {
+ fn default() -> Self {
+ Configuration {
+ time_offset: Some(0.0),
+ original_uris: None,
+ }
+ }
+}
+
+#[serde_with::skip_serializing_none]
+#[derive(Serialize, Deserialize, Clone, Default, PartialEq, Debug)]
+pub struct CommonFields {
+ pub group_id: Option<String>,
+ pub protocol_type: Option<Vec<String>>,
+
+ pub reference_time: Option<f64>,
+ pub time_format: Option<String>,
+ // TODO: additionalUserSpecifiedProperty
+}
+
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
+#[serde(rename_all = "snake_case")]
+pub enum TokenType {
+ Retry,
+ Resumption,
+}
+
+#[serde_with::skip_serializing_none]
+#[derive(Clone, Serialize, Deserialize, PartialEq, Eq, Debug)]
+pub struct Token {
+ #[serde(rename(serialize = "type"))]
+ pub ty: Option<TokenType>,
+
+ pub details: Option<String>,
+
+ pub raw: Option<events::RawInfo>,
+}
+
+pub struct HexSlice<'a>(&'a [u8]);
+
+impl<'a> HexSlice<'a> {
+ pub fn new<T>(data: &'a T) -> HexSlice<'a>
+ where
+ T: ?Sized + AsRef<[u8]> + 'a,
+ {
+ HexSlice(data.as_ref())
+ }
+
+ pub fn maybe_string<T>(data: Option<&'a T>) -> Option<String>
+ where
+ T: ?Sized + AsRef<[u8]> + 'a,
+ {
+ data.map(|d| format!("{}", HexSlice::new(d)))
+ }
+}
+
+impl<'a> std::fmt::Display for HexSlice<'a> {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ for byte in self.0 {
+ write!(f, "{byte:02x}")?;
+ }
+ Ok(())
+ }
+}
+
+#[doc(hidden)]
+pub mod testing {
+ use super::*;
+ use crate::events::quic::PacketType;
+
+ pub fn make_pkt_hdr(packet_type: PacketType) -> PacketHeader {
+ let scid = [0x7e, 0x37, 0xe4, 0xdc, 0xc6, 0x68, 0x2d, 0xa8];
+ let dcid = [0x36, 0xce, 0x10, 0x4e, 0xee, 0x50, 0x10, 0x1c];
+
+ // Some(1251),
+ // Some(1224),
+
+ PacketHeader::new(
+ packet_type,
+ Some(0),
+ None,
+ None,
+ None,
+ Some(0x0000_0001),
+ Some(&scid),
+ Some(&dcid),
+ )
+ }
+
+ pub fn make_trace() -> Trace {
+ Trace::new(
+ VantagePoint {
+ name: None,
+ ty: VantagePointType::Server,
+ flow: None,
+ },
+ Some("Quiche qlog trace".to_string()),
+ Some("Quiche qlog trace description".to_string()),
+ Some(Configuration {
+ time_offset: Some(0.0),
+ original_uris: None,
+ }),
+ None,
+ )
+ }
+
+ pub fn make_trace_seq() -> TraceSeq {
+ TraceSeq::new(
+ VantagePoint {
+ name: None,
+ ty: VantagePointType::Server,
+ flow: None,
+ },
+ Some("Quiche qlog trace".to_string()),
+ Some("Quiche qlog trace description".to_string()),
+ Some(Configuration {
+ time_offset: Some(0.0),
+ original_uris: None,
+ }),
+ None,
+ )
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::events::quic::PacketSent;
+ use crate::events::quic::PacketType;
+ use crate::events::quic::QuicFrame;
+ use crate::events::EventData;
+ use crate::events::RawInfo;
+ use testing::*;
+
+ #[test]
+ fn packet_sent_event_no_frames() {
+ let log_string = r#"{
+ "time": 0.0,
+ "name": "transport:packet_sent",
+ "data": {
+ "header": {
+ "packet_type": "initial",
+ "packet_number": 0,
+ "version": "1",
+ "scil": 8,
+ "dcil": 8,
+ "scid": "7e37e4dcc6682da8",
+ "dcid": "36ce104eee50101c"
+ },
+ "raw": {
+ "length": 1251,
+ "payload_length": 1224
+ }
+ }
+}"#;
+
+ let pkt_hdr = make_pkt_hdr(PacketType::Initial);
+ let ev_data = EventData::PacketSent(PacketSent {
+ header: pkt_hdr,
+ frames: None,
+ is_coalesced: None,
+ retry_token: None,
+ stateless_reset_token: None,
+ supported_versions: None,
+ raw: Some(RawInfo {
+ length: Some(1251),
+ payload_length: Some(1224),
+ data: None,
+ }),
+ datagram_id: None,
+ send_at_time: None,
+ trigger: None,
+ });
+
+ let ev = Event::with_time(0.0, ev_data);
+
+ assert_eq!(serde_json::to_string_pretty(&ev).unwrap(), log_string);
+ }
+
+ #[test]
+ fn packet_sent_event_some_frames() {
+ let log_string = r#"{
+ "time": 0.0,
+ "name": "transport:packet_sent",
+ "data": {
+ "header": {
+ "packet_type": "initial",
+ "packet_number": 0,
+ "version": "1",
+ "scil": 8,
+ "dcil": 8,
+ "scid": "7e37e4dcc6682da8",
+ "dcid": "36ce104eee50101c"
+ },
+ "raw": {
+ "length": 1251,
+ "payload_length": 1224
+ },
+ "frames": [
+ {
+ "frame_type": "padding"
+ },
+ {
+ "frame_type": "ping"
+ },
+ {
+ "frame_type": "stream",
+ "stream_id": 0,
+ "offset": 0,
+ "length": 100,
+ "fin": true
+ }
+ ]
+ }
+}"#;
+
+ let pkt_hdr = make_pkt_hdr(PacketType::Initial);
+
+ let frames =
+ vec![QuicFrame::Padding, QuicFrame::Ping, QuicFrame::Stream {
+ stream_id: 0,
+ offset: 0,
+ length: 100,
+ fin: Some(true),
+ raw: None,
+ }];
+
+ let ev_data = EventData::PacketSent(PacketSent {
+ header: pkt_hdr,
+ frames: Some(frames.into()),
+ is_coalesced: None,
+ retry_token: None,
+ stateless_reset_token: None,
+ supported_versions: None,
+ raw: Some(RawInfo {
+ length: Some(1251),
+ payload_length: Some(1224),
+ data: None,
+ }),
+ datagram_id: None,
+ send_at_time: None,
+ trigger: None,
+ });
+
+ let ev = Event::with_time(0.0, ev_data);
+ assert_eq!(serde_json::to_string_pretty(&ev).unwrap(), log_string);
+ }
+
+ #[test]
+ fn trace_no_events() {
+ let log_string = r#"{
+ "vantage_point": {
+ "type": "server"
+ },
+ "title": "Quiche qlog trace",
+ "description": "Quiche qlog trace description",
+ "configuration": {
+ "time_offset": 0.0
+ },
+ "events": []
+}"#;
+
+ let trace = make_trace();
+
+ let serialized = serde_json::to_string_pretty(&trace).unwrap();
+ assert_eq!(serialized, log_string);
+
+ let deserialized: Trace = serde_json::from_str(&serialized).unwrap();
+ assert_eq!(deserialized, trace);
+ }
+
+ #[test]
+ fn trace_seq_no_events() {
+ let log_string = r#"{
+ "vantage_point": {
+ "type": "server"
+ },
+ "title": "Quiche qlog trace",
+ "description": "Quiche qlog trace description",
+ "configuration": {
+ "time_offset": 0.0
+ }
+}"#;
+
+ let trace = make_trace_seq();
+
+ let serialized = serde_json::to_string_pretty(&trace).unwrap();
+ assert_eq!(serialized, log_string);
+
+ let deserialized: TraceSeq = serde_json::from_str(&serialized).unwrap();
+ assert_eq!(deserialized, trace);
+ }
+
+ #[test]
+ fn trace_single_transport_event() {
+ let log_string = r#"{
+ "vantage_point": {
+ "type": "server"
+ },
+ "title": "Quiche qlog trace",
+ "description": "Quiche qlog trace description",
+ "configuration": {
+ "time_offset": 0.0
+ },
+ "events": [
+ {
+ "time": 0.0,
+ "name": "transport:packet_sent",
+ "data": {
+ "header": {
+ "packet_type": "initial",
+ "packet_number": 0,
+ "version": "1",
+ "scil": 8,
+ "dcil": 8,
+ "scid": "7e37e4dcc6682da8",
+ "dcid": "36ce104eee50101c"
+ },
+ "raw": {
+ "length": 1251,
+ "payload_length": 1224
+ },
+ "frames": [
+ {
+ "frame_type": "stream",
+ "stream_id": 0,
+ "offset": 0,
+ "length": 100,
+ "fin": true
+ }
+ ]
+ }
+ }
+ ]
+}"#;
+
+ let mut trace = make_trace();
+
+ let pkt_hdr = make_pkt_hdr(PacketType::Initial);
+
+ let frames = vec![QuicFrame::Stream {
+ stream_id: 0,
+ offset: 0,
+ length: 100,
+ fin: Some(true),
+ raw: None,
+ }];
+ let event_data = EventData::PacketSent(PacketSent {
+ header: pkt_hdr,
+ frames: Some(frames.into()),
+ is_coalesced: None,
+ retry_token: None,
+ stateless_reset_token: None,
+ supported_versions: None,
+ raw: Some(RawInfo {
+ length: Some(1251),
+ payload_length: Some(1224),
+ data: None,
+ }),
+ datagram_id: None,
+ send_at_time: None,
+ trigger: None,
+ });
+
+ let ev = Event::with_time(0.0, event_data);
+
+ trace.push_event(ev);
+
+ let serialized = serde_json::to_string_pretty(&trace).unwrap();
+ assert_eq!(serialized, log_string);
+
+ let deserialized: Trace = serde_json::from_str(&serialized).unwrap();
+ assert_eq!(deserialized, trace);
+ }
+}
+
+pub mod events;
+pub mod reader;
+pub mod streamer;
diff --git a/third_party/rust/qlog/src/reader.rs b/third_party/rust/qlog/src/reader.rs
new file mode 100644
index 0000000000..89a8e12a44
--- /dev/null
+++ b/third_party/rust/qlog/src/reader.rs
@@ -0,0 +1,111 @@
+// Copyright (C) 2023, Cloudflare, Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+use crate::QlogSeq;
+
+/// Represents the format of the read event.
+#[allow(clippy::large_enum_variant)]
+pub enum Event {
+ /// A native qlog event type.
+ Qlog(crate::events::Event),
+
+ // An extended JSON event type.
+ Json(crate::events::JsonEvent),
+}
+
+/// A helper object specialized for reading JSON-SEQ qlog from a [`BufRead`]
+/// trait.
+///
+/// [`BufRead`]: https://doc.rust-lang.org/std/io/trait.BufRead.html
+pub struct QlogSeqReader {
+ pub qlog: QlogSeq,
+ reader: Box<dyn std::io::BufRead + Send + Sync>,
+}
+
+impl QlogSeqReader {
+ pub fn new(
+ mut reader: Box<dyn std::io::BufRead + Send + Sync>,
+ ) -> Result<Self, Box<dyn std::error::Error>> {
+ // "null record" skip it
+ Self::read_record(reader.as_mut());
+
+ let header = Self::read_record(reader.as_mut()).ok_or_else(|| {
+ std::io::Error::new(
+ std::io::ErrorKind::Other,
+ "error reading file header bytes",
+ )
+ })?;
+
+ let res: Result<QlogSeq, serde_json::Error> =
+ serde_json::from_slice(&header);
+ match res {
+ Ok(qlog) => Ok(Self { qlog, reader }),
+
+ Err(e) => Err(e.into()),
+ }
+ }
+
+ fn read_record(
+ reader: &mut (dyn std::io::BufRead + Send + Sync),
+ ) -> Option<Vec<u8>> {
+ let mut buf = Vec::<u8>::new();
+ let size = reader.read_until(b'', &mut buf).unwrap();
+ if size <= 1 {
+ return None;
+ }
+
+ buf.truncate(buf.len() - 1);
+
+ Some(buf)
+ }
+}
+
+impl Iterator for QlogSeqReader {
+ type Item = Event;
+
+ #[inline]
+ fn next(&mut self) -> Option<Self::Item> {
+ // Attempt to deserialize events but skip them if that fails for any
+ // reason, ensuring we always read all bytes in the reader.
+ while let Some(bytes) = Self::read_record(&mut self.reader) {
+ let r: serde_json::Result<crate::events::Event> =
+ serde_json::from_slice(&bytes);
+
+ if let Ok(event) = r {
+ return Some(Event::Qlog(event));
+ }
+
+ let r: serde_json::Result<crate::events::JsonEvent> =
+ serde_json::from_slice(&bytes);
+
+ if let Ok(event) = r {
+ return Some(Event::Json(event));
+ }
+ }
+
+ None
+ }
+}
diff --git a/third_party/rust/qlog/src/streamer.rs b/third_party/rust/qlog/src/streamer.rs
new file mode 100644
index 0000000000..16761b3a67
--- /dev/null
+++ b/third_party/rust/qlog/src/streamer.rs
@@ -0,0 +1,544 @@
+// Copyright (C) 2021, Cloudflare, Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+use crate::events::EventData;
+use crate::events::EventImportance;
+use crate::events::EventType;
+use crate::events::Eventable;
+use crate::events::ExData;
+
+/// A helper object specialized for streaming JSON-serialized qlog to a
+/// [`Write`] trait.
+///
+/// The object is responsible for the `Qlog` object that contains the
+/// provided `Trace`.
+///
+/// Serialization is progressively driven by method calls; once log streaming
+/// is started, `event::Events` can be written using `add_event()`.
+///
+/// [`Write`]: https://doc.rust-lang.org/std/io/trait.Write.html
+use super::*;
+
+#[derive(PartialEq, Eq, Debug)]
+pub enum StreamerState {
+ Initial,
+ Ready,
+ Finished,
+}
+
+pub struct QlogStreamer {
+ start_time: std::time::Instant,
+ writer: Box<dyn std::io::Write + Send + Sync>,
+ qlog: QlogSeq,
+ state: StreamerState,
+ log_level: EventImportance,
+}
+
+impl QlogStreamer {
+ /// Creates a [QlogStreamer] object.
+ ///
+ /// It owns a [QlogSeq] object that contains the provided [TraceSeq]
+ /// containing [Event]s.
+ ///
+ /// All serialization will be written to the provided [`Write`] using the
+ /// JSON-SEQ format.
+ ///
+ /// [`Write`]: https://doc.rust-lang.org/std/io/trait.Write.html
+ #[allow(clippy::too_many_arguments)]
+ pub fn new(
+ qlog_version: String, title: Option<String>, description: Option<String>,
+ summary: Option<String>, start_time: std::time::Instant, trace: TraceSeq,
+ log_level: EventImportance,
+ writer: Box<dyn std::io::Write + Send + Sync>,
+ ) -> Self {
+ let qlog = QlogSeq {
+ qlog_version,
+ qlog_format: "JSON-SEQ".to_string(),
+ title,
+ description,
+ summary,
+ trace,
+ };
+
+ QlogStreamer {
+ start_time,
+ writer,
+ qlog,
+ state: StreamerState::Initial,
+ log_level,
+ }
+ }
+
+ /// Starts qlog streaming serialization.
+ ///
+ /// This writes out the JSON-SEQ-serialized form of all initial qlog
+ /// information. [Event]s are separately appended using [add_event()],
+ /// [add_event_with_instant()], [add_event_now()],
+ /// [add_event_data_with_instant()], or [add_event_data_now()].
+ ///
+ /// [add_event()]: #method.add_event
+ /// [add_event_with_instant()]: #method.add_event_with_instant
+ /// [add_event_now()]: #method.add_event_now
+ /// [add_event_data_with_instant()]: #method.add_event_data_with_instant
+ /// [add_event_data_now()]: #method.add_event_data_now
+ pub fn start_log(&mut self) -> Result<()> {
+ if self.state != StreamerState::Initial {
+ return Err(Error::Done);
+ }
+
+ self.writer.as_mut().write_all(b"")?;
+ serde_json::to_writer(self.writer.as_mut(), &self.qlog)
+ .map_err(|_| Error::Done)?;
+ self.writer.as_mut().write_all(b"\n")?;
+
+ self.state = StreamerState::Ready;
+
+ Ok(())
+ }
+
+ /// Finishes qlog streaming serialization.
+ ///
+ /// After this is called, no more serialization will occur.
+ pub fn finish_log(&mut self) -> Result<()> {
+ if self.state == StreamerState::Initial ||
+ self.state == StreamerState::Finished
+ {
+ return Err(Error::InvalidState);
+ }
+
+ self.state = StreamerState::Finished;
+
+ self.writer.as_mut().flush()?;
+
+ Ok(())
+ }
+
+ /// Writes a serializable to a JSON-SEQ record using
+ /// [std::time::Instant::now()].
+ pub fn add_event_now<E: Serialize + Eventable>(
+ &mut self, event: E,
+ ) -> Result<()> {
+ let now = std::time::Instant::now();
+
+ self.add_event_with_instant(event, now)
+ }
+
+ /// Writes a serializable to a JSON-SEQ record using the provided
+ /// [std::time::Instant].
+ pub fn add_event_with_instant<E: Serialize + Eventable>(
+ &mut self, mut event: E, now: std::time::Instant,
+ ) -> Result<()> {
+ if self.state != StreamerState::Ready {
+ return Err(Error::InvalidState);
+ }
+
+ if !event.importance().is_contained_in(&self.log_level) {
+ return Err(Error::Done);
+ }
+
+ let dur = if cfg!(test) {
+ std::time::Duration::from_secs(0)
+ } else {
+ now.duration_since(self.start_time)
+ };
+
+ let rel_time = dur.as_secs_f32() * 1000.0;
+ event.set_time(rel_time);
+
+ self.add_event(event)
+ }
+
+ /// Writes an [Event] based on the provided [EventData] to a JSON-SEQ record
+ /// at time [std::time::Instant::now()].
+ pub fn add_event_data_now(&mut self, event_data: EventData) -> Result<()> {
+ self.add_event_data_ex_now(event_data, Default::default())
+ }
+
+ /// Writes an [Event] based on the provided [EventData] and [ExData] to a
+ /// JSON-SEQ record at time [std::time::Instant::now()].
+ pub fn add_event_data_ex_now(
+ &mut self, event_data: EventData, ex_data: ExData,
+ ) -> Result<()> {
+ let now = std::time::Instant::now();
+
+ self.add_event_data_ex_with_instant(event_data, ex_data, now)
+ }
+
+ /// Writes an [Event] based on the provided [EventData] and
+ /// [std::time::Instant] to a JSON-SEQ record.
+ pub fn add_event_data_with_instant(
+ &mut self, event_data: EventData, now: std::time::Instant,
+ ) -> Result<()> {
+ self.add_event_data_ex_with_instant(event_data, Default::default(), now)
+ }
+
+ /// Writes an [Event] based on the provided [EventData], [ExData], and
+ /// [std::time::Instant] to a JSON-SEQ record.
+ pub fn add_event_data_ex_with_instant(
+ &mut self, event_data: EventData, ex_data: ExData,
+ now: std::time::Instant,
+ ) -> Result<()> {
+ if self.state != StreamerState::Ready {
+ return Err(Error::InvalidState);
+ }
+
+ let ty = EventType::from(&event_data);
+ if !EventImportance::from(ty).is_contained_in(&self.log_level) {
+ return Err(Error::Done);
+ }
+
+ let dur = if cfg!(test) {
+ std::time::Duration::from_secs(0)
+ } else {
+ now.duration_since(self.start_time)
+ };
+
+ let rel_time = dur.as_secs_f32() * 1000.0;
+ let event = Event::with_time_ex(rel_time, event_data, ex_data);
+
+ self.add_event(event)
+ }
+
+ /// Writes a JSON-SEQ-serialized [Event] using the provided [Event].
+ pub fn add_event<E: Serialize + Eventable>(
+ &mut self, event: E,
+ ) -> Result<()> {
+ if self.state != StreamerState::Ready {
+ return Err(Error::InvalidState);
+ }
+
+ if !event.importance().is_contained_in(&self.log_level) {
+ return Err(Error::Done);
+ }
+
+ self.writer.as_mut().write_all(b"")?;
+ serde_json::to_writer(self.writer.as_mut(), &event)
+ .map_err(|_| Error::Done)?;
+ self.writer.as_mut().write_all(b"\n")?;
+
+ Ok(())
+ }
+
+ /// Returns the writer.
+ #[allow(clippy::borrowed_box)]
+ pub fn writer(&self) -> &Box<dyn std::io::Write + Send + Sync> {
+ &self.writer
+ }
+
+ pub fn start_time(&self) -> std::time::Instant {
+ self.start_time
+ }
+}
+
+impl Drop for QlogStreamer {
+ fn drop(&mut self) {
+ let _ = self.finish_log();
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use std::collections::BTreeMap;
+
+ use super::*;
+ use crate::events::quic;
+ use crate::events::quic::QuicFrame;
+ use crate::events::RawInfo;
+ use smallvec::smallvec;
+ use testing::*;
+
+ use serde_json::json;
+
+ #[test]
+ fn serialization_states() {
+ let v: Vec<u8> = Vec::new();
+ let buff = std::io::Cursor::new(v);
+ let writer = Box::new(buff);
+
+ let trace = make_trace_seq();
+ let pkt_hdr = make_pkt_hdr(quic::PacketType::Handshake);
+ let raw = Some(RawInfo {
+ length: Some(1251),
+ payload_length: Some(1224),
+ data: None,
+ });
+
+ let frame1 = QuicFrame::Stream {
+ stream_id: 40,
+ offset: 40,
+ length: 400,
+ fin: Some(true),
+ raw: None,
+ };
+
+ let event_data1 = EventData::PacketSent(quic::PacketSent {
+ header: pkt_hdr.clone(),
+ frames: Some(smallvec![frame1]),
+ is_coalesced: None,
+ retry_token: None,
+ stateless_reset_token: None,
+ supported_versions: None,
+ raw: raw.clone(),
+ datagram_id: None,
+ send_at_time: None,
+ trigger: None,
+ });
+
+ let ev1 = Event::with_time(0.0, event_data1);
+
+ let frame2 = QuicFrame::Stream {
+ stream_id: 0,
+ offset: 0,
+ length: 100,
+ fin: Some(true),
+ raw: None,
+ };
+
+ let frame3 = QuicFrame::Stream {
+ stream_id: 0,
+ offset: 0,
+ length: 100,
+ fin: Some(true),
+ raw: None,
+ };
+
+ let event_data2 = EventData::PacketSent(quic::PacketSent {
+ header: pkt_hdr.clone(),
+ frames: Some(smallvec![frame2]),
+ is_coalesced: None,
+ retry_token: None,
+ stateless_reset_token: None,
+ supported_versions: None,
+ raw: raw.clone(),
+ datagram_id: None,
+ send_at_time: None,
+ trigger: None,
+ });
+
+ let ev2 = Event::with_time(0.0, event_data2);
+
+ let event_data3 = EventData::PacketSent(quic::PacketSent {
+ header: pkt_hdr,
+ frames: Some(smallvec![frame3]),
+ is_coalesced: None,
+ retry_token: None,
+ stateless_reset_token: Some("reset_token".to_string()),
+ supported_versions: None,
+ raw,
+ datagram_id: None,
+ send_at_time: None,
+ trigger: None,
+ });
+
+ let ev3 = Event::with_time(0.0, event_data3);
+
+ let mut s = streamer::QlogStreamer::new(
+ "version".to_string(),
+ Some("title".to_string()),
+ Some("description".to_string()),
+ None,
+ std::time::Instant::now(),
+ trace,
+ EventImportance::Base,
+ writer,
+ );
+
+ // Before the log is started all other operations should fail.
+ assert!(matches!(s.add_event(ev2.clone()), Err(Error::InvalidState)));
+ assert!(matches!(s.finish_log(), Err(Error::InvalidState)));
+
+ // Start log and add a simple event.
+ assert!(matches!(s.start_log(), Ok(())));
+ assert!(matches!(s.add_event(ev1), Ok(())));
+
+ // Add some more events.
+ assert!(matches!(s.add_event(ev2), Ok(())));
+ assert!(matches!(s.add_event(ev3.clone()), Ok(())));
+
+ // Adding an event with an external time should work too.
+ // For tests, it will resolve to 0 but we care about proving the API
+ // here, not timing specifics.
+ let now = std::time::Instant::now();
+
+ assert!(matches!(s.add_event_with_instant(ev3, now), Ok(())));
+
+ assert!(matches!(s.finish_log(), Ok(())));
+
+ let r = s.writer();
+ #[allow(clippy::borrowed_box)]
+ let w: &Box<std::io::Cursor<Vec<u8>>> = unsafe { std::mem::transmute(r) };
+
+ let log_string = r#"{"qlog_version":"version","qlog_format":"JSON-SEQ","title":"title","description":"description","trace":{"vantage_point":{"type":"server"},"title":"Quiche qlog trace","description":"Quiche qlog trace description","configuration":{"time_offset":0.0}}}
+{"time":0.0,"name":"transport:packet_sent","data":{"header":{"packet_type":"handshake","packet_number":0,"version":"1","scil":8,"dcil":8,"scid":"7e37e4dcc6682da8","dcid":"36ce104eee50101c"},"raw":{"length":1251,"payload_length":1224},"frames":[{"frame_type":"stream","stream_id":40,"offset":40,"length":400,"fin":true}]}}
+{"time":0.0,"name":"transport:packet_sent","data":{"header":{"packet_type":"handshake","packet_number":0,"version":"1","scil":8,"dcil":8,"scid":"7e37e4dcc6682da8","dcid":"36ce104eee50101c"},"raw":{"length":1251,"payload_length":1224},"frames":[{"frame_type":"stream","stream_id":0,"offset":0,"length":100,"fin":true}]}}
+{"time":0.0,"name":"transport:packet_sent","data":{"header":{"packet_type":"handshake","packet_number":0,"version":"1","scil":8,"dcil":8,"scid":"7e37e4dcc6682da8","dcid":"36ce104eee50101c"},"stateless_reset_token":"reset_token","raw":{"length":1251,"payload_length":1224},"frames":[{"frame_type":"stream","stream_id":0,"offset":0,"length":100,"fin":true}]}}
+{"time":0.0,"name":"transport:packet_sent","data":{"header":{"packet_type":"handshake","packet_number":0,"version":"1","scil":8,"dcil":8,"scid":"7e37e4dcc6682da8","dcid":"36ce104eee50101c"},"stateless_reset_token":"reset_token","raw":{"length":1251,"payload_length":1224},"frames":[{"frame_type":"stream","stream_id":0,"offset":0,"length":100,"fin":true}]}}
+"#;
+
+ let written_string = std::str::from_utf8(w.as_ref().get_ref()).unwrap();
+
+ assert_eq!(log_string, written_string);
+ }
+
+ #[test]
+ fn stream_json_event() {
+ let data = json!({"foo": "Bar", "hello": 123});
+ let ev = events::JsonEvent {
+ time: 0.0,
+ importance: events::EventImportance::Core,
+ name: "jsonevent:sample".into(),
+ data,
+ };
+
+ let v: Vec<u8> = Vec::new();
+ let buff = std::io::Cursor::new(v);
+ let writer = Box::new(buff);
+
+ let trace = make_trace_seq();
+
+ let mut s = streamer::QlogStreamer::new(
+ "version".to_string(),
+ Some("title".to_string()),
+ Some("description".to_string()),
+ None,
+ std::time::Instant::now(),
+ trace,
+ EventImportance::Base,
+ writer,
+ );
+
+ assert!(matches!(s.start_log(), Ok(())));
+ assert!(matches!(s.add_event(ev), Ok(())));
+ assert!(matches!(s.finish_log(), Ok(())));
+
+ let r = s.writer();
+ #[allow(clippy::borrowed_box)]
+ let w: &Box<std::io::Cursor<Vec<u8>>> = unsafe { std::mem::transmute(r) };
+
+ let log_string = r#"{"qlog_version":"version","qlog_format":"JSON-SEQ","title":"title","description":"description","trace":{"vantage_point":{"type":"server"},"title":"Quiche qlog trace","description":"Quiche qlog trace description","configuration":{"time_offset":0.0}}}
+{"time":0.0,"name":"jsonevent:sample","data":{"foo":"Bar","hello":123}}
+"#;
+
+ let written_string = std::str::from_utf8(w.as_ref().get_ref()).unwrap();
+
+ assert_eq!(log_string, written_string);
+ }
+
+ #[test]
+ fn stream_data_ex() {
+ let v: Vec<u8> = Vec::new();
+ let buff = std::io::Cursor::new(v);
+ let writer = Box::new(buff);
+
+ let trace = make_trace_seq();
+ let pkt_hdr = make_pkt_hdr(quic::PacketType::Handshake);
+ let raw = Some(RawInfo {
+ length: Some(1251),
+ payload_length: Some(1224),
+ data: None,
+ });
+
+ let frame1 = QuicFrame::Stream {
+ stream_id: 40,
+ offset: 40,
+ length: 400,
+ fin: Some(true),
+ raw: None,
+ };
+
+ let event_data1 = EventData::PacketSent(quic::PacketSent {
+ header: pkt_hdr.clone(),
+ frames: Some(smallvec![frame1]),
+ is_coalesced: None,
+ retry_token: None,
+ stateless_reset_token: None,
+ supported_versions: None,
+ raw: raw.clone(),
+ datagram_id: None,
+ send_at_time: None,
+ trigger: None,
+ });
+ let j1 = json!({"foo": "Bar", "hello": 123});
+ let j2 = json!({"baz": [1,2,3,4]});
+ let mut ex_data = BTreeMap::new();
+ ex_data.insert("first".to_string(), j1);
+ ex_data.insert("second".to_string(), j2);
+
+ let ev1 = Event::with_time_ex(0.0, event_data1, ex_data);
+
+ let frame2 = QuicFrame::Stream {
+ stream_id: 1,
+ offset: 0,
+ length: 100,
+ fin: Some(true),
+ raw: None,
+ };
+
+ let event_data2 = EventData::PacketSent(quic::PacketSent {
+ header: pkt_hdr.clone(),
+ frames: Some(smallvec![frame2]),
+ is_coalesced: None,
+ retry_token: None,
+ stateless_reset_token: None,
+ supported_versions: None,
+ raw: raw.clone(),
+ datagram_id: None,
+ send_at_time: None,
+ trigger: None,
+ });
+
+ let ev2 = Event::with_time(0.0, event_data2);
+
+ let mut s = streamer::QlogStreamer::new(
+ "version".to_string(),
+ Some("title".to_string()),
+ Some("description".to_string()),
+ None,
+ std::time::Instant::now(),
+ trace,
+ EventImportance::Base,
+ writer,
+ );
+
+ assert!(matches!(s.start_log(), Ok(())));
+ assert!(matches!(s.add_event(ev1), Ok(())));
+ assert!(matches!(s.add_event(ev2), Ok(())));
+ assert!(matches!(s.finish_log(), Ok(())));
+
+ let r = s.writer();
+ #[allow(clippy::borrowed_box)]
+ let w: &Box<std::io::Cursor<Vec<u8>>> = unsafe { std::mem::transmute(r) };
+
+ let log_string = r#"{"qlog_version":"version","qlog_format":"JSON-SEQ","title":"title","description":"description","trace":{"vantage_point":{"type":"server"},"title":"Quiche qlog trace","description":"Quiche qlog trace description","configuration":{"time_offset":0.0}}}
+{"time":0.0,"name":"transport:packet_sent","data":{"header":{"packet_type":"handshake","packet_number":0,"version":"1","scil":8,"dcil":8,"scid":"7e37e4dcc6682da8","dcid":"36ce104eee50101c"},"raw":{"length":1251,"payload_length":1224},"frames":[{"frame_type":"stream","stream_id":40,"offset":40,"length":400,"fin":true}]},"first":{"foo":"Bar","hello":123},"second":{"baz":[1,2,3,4]}}
+{"time":0.0,"name":"transport:packet_sent","data":{"header":{"packet_type":"handshake","packet_number":0,"version":"1","scil":8,"dcil":8,"scid":"7e37e4dcc6682da8","dcid":"36ce104eee50101c"},"raw":{"length":1251,"payload_length":1224},"frames":[{"frame_type":"stream","stream_id":1,"offset":0,"length":100,"fin":true}]}}
+"#;
+
+ let written_string = std::str::from_utf8(w.as_ref().get_ref()).unwrap();
+
+ assert_eq!(log_string, written_string);
+ }
+}