summaryrefslogtreecommitdiffstats
path: root/third_party/rust/qlog
diff options
context:
space:
mode:
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.toml36
-rw-r--r--third_party/rust/qlog/README.md304
-rw-r--r--third_party/rust/qlog/src/event.rs988
-rw-r--r--third_party/rust/qlog/src/lib.rs2970
5 files changed, 4299 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..f0db87c5fe
--- /dev/null
+++ b/third_party/rust/qlog/.cargo-checksum.json
@@ -0,0 +1 @@
+{"files":{"Cargo.toml":"3e92e34343c4777cb0249185b1f693a2e21321b090136be6d5287cadf353db1c","README.md":"c74fdbabf7262695e42a9af0b8c0c2f089c188dde8b13bc71847551f126397b4","src/event.rs":"866b2cb41af83e1552dd10ae8e07a9ae40b65bd8ca63c10537b38e9be0abfe3c","src/lib.rs":"c557479cf4c1f6c44c3fd708338eebe5fb2e01f106259db58578a9a1b55ef358"},"package":"8777d5490145d6907198d48b3a907447689ce80e071b3d8a16a9d9fb3df02bc1"} \ 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..a1304cd9ab
--- /dev/null
+++ b/third_party/rust/qlog/Cargo.toml
@@ -0,0 +1,36 @@
+# 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 believe there's an error in this file please file an
+# issue against the rust-lang/cargo repository. If you're
+# editing this file be aware that the upstream Cargo.toml
+# will likely look very different (and much more reasonable)
+
+[package]
+edition = "2018"
+name = "qlog"
+version = "0.4.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]
+version = "1"
+features = ["derive"]
+
+[dependencies.serde_derive]
+version = "1.0"
+
+[dependencies.serde_json]
+version = "1.0"
+features = ["preserve_order"]
+
+[dependencies.serde_with]
+version = "1.3.1"
diff --git a/third_party/rust/qlog/README.md b/third_party/rust/qlog/README.md
new file mode 100644
index 0000000000..cc420a17d7
--- /dev/null
+++ b/third_party/rust/qlog/README.md
@@ -0,0 +1,304 @@
+The qlog crate is an implementation of the [qlog main schema] and [qlog QUIC and
+HTTP/3 events] that attempts to closely follow the format of the qlog
+[TypeScript schema]. This is just a data model and no support is provided for
+logging IO, applications can decide themselves the most appropriate method.
+
+The crate uses Serde for conversion between Rust and JSON.
+
+[qlog main schema]: https://tools.ietf.org/html/draft-marx-qlog-main-schema
+[qlog QUIC and HTTP/3 events]: https://quiclog.github.io/internet-drafts/draft-marx-qlog-event-definitions-quic-h3
+[TypeScript schema]: https://github.com/quiclog/qlog/blob/master/TypeScript/draft-01/QLog.ts
+
+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.
+
+## Traces
+
+A [`Trace`] contains metadata such as the [`VantagePoint`] of capture and
+the [`Configuration`] of the `Trace`.
+
+A very important part of the `Trace` is the definition of `event_fields`. A
+qlog Event is a vector of [`EventField`]; this provides great flexibility to
+log events with any number of `EventFields` in any order. The `event_fields`
+property describes the format of event logging and it is important that
+events comply with that format. Failing to do so it going to cause problems
+for qlog analysis tools. For information is available at
+https://tools.ietf.org/html/draft-marx-qlog-main-schema-01#section-3.3.4
+
+In order to make using qlog a bit easier, this crate expects a qlog Event to
+consist of the following EventFields in the following order:
+[`EventField::RelativeTime`], [`EventField::Category`],
+[`EventField::Event`] and [`EventField::Data`]. A set of methods are
+provided to assist in creating a Trace and appending events to it in this
+format.
+
+## Writing out logs
+As events occur during the connection, the application appends them to the
+trace. The qlog crate supports two modes of writing logs: the buffered mode
+stores everything in memory and requires the application to serialize and write
+the output, the streaming mode progressively writes serialized JSON output to a
+writer designated by the application.
+
+Buffered Mode
+---------------
+
+Create the 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".to_string()),
+ time_units: Some(qlog::TimeUnits::Ms),
+ original_uris: None,
+ }),
+ None,
+);
+```
+
+### Adding events
+
+Qlog Events are added to `qlog::Trace.events`.
+
+It is recommended to use the provided utility methods to append semantically
+valid events to a trace. However, there is nothing preventing you from
+creating the events manually.
+
+The following example demonstrates how to log a QUIC packet
+containing a single Crypto frame. It uses the [`QuicFrame::crypto()`],
+[`packet_sent_min()`] and [`push_event()`] methods to create and log a
+PacketSent event and its EventData.
+
+```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::PacketHeader::new(
+ 0,
+ Some(1251),
+ Some(1224),
+ Some(0xff00001b),
+ Some(b"7e37e4dcc6682da8"),
+ Some(&dcid),
+);
+
+let frames =
+ vec![qlog::QuicFrame::crypto("0".to_string(), "1000".to_string())];
+
+let event = qlog::event::Event::packet_sent_min(
+ qlog::PacketType::Initial,
+ pkt_hdr,
+ Some(frames),
+);
+
+trace.push_event(std::time::Duration::new(0, 0), event);
+```
+
+### 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_units": "ms",
+ "time_offset": "0"
+ },
+ "event_fields": [
+ "relative_time",
+ "category",
+ "event",
+ "data"
+ ],
+ "events": [
+ [
+ "0",
+ "transport",
+ "packet_sent",
+ {
+ "packet_type": "initial",
+ "header": {
+ "packet_number": "0",
+ "packet_size": 1251,
+ "payload_length": 1224,
+ "version": "0xff00001b",
+ "scil": "8",
+ "dcil": "8",
+ "scid": "7e37e4dcc6682da8",
+ "dcid": "36ce104eee50101c"
+ },
+ "frames": [
+ {
+ "frame_type": "crypto",
+ "offset": "0",
+ "length": "100",
+ }
+ ]
+ }
+ ]
+ ]
+}
+```
+
+Streaming Mode
+---------------
+
+Create the 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".to_string()),
+ time_units: Some(qlog::TimeUnits::Ms),
+ original_uris: None,
+ }),
+ None,
+);
+```
+
+Create an object with the [`Write`] trait:
+
+```rust
+let mut file = std::fs::File::create("foo.qlog").unwrap();
+```
+
+Create a [`QlogStreamer`] and start serialization to foo.qlog
+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,
+ 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 = qlog::event::Event::metrics_updated_min();
+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 qlog_pkt_hdr = qlog::PacketHeader::with_type(
+ qlog::PacketType::OneRtt,
+ 0,
+ Some(1251),
+ Some(1224),
+ Some(0xff00001b),
+ Some(b"7e37e4dcc6682da8"),
+ Some(b"36ce104eee50101c"),
+);
+
+let event = qlog::event::Event::packet_sent_min(
+ qlog::PacketType::OneRtt,
+ qlog_pkt_hdr,
+ Some(Vec::new()),
+);
+
+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::QuicFrame::ping();
+let padding = qlog::QuicFrame::padding();
+
+streamer.add_frame(ping, false).ok();
+streamer.add_frame(padding, false).ok();
+
+streamer.finish_frames().ok();
+```
+
+Once all events have 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
+[`VantagePoint`]: struct.VantagePoint.html
+[`Configuration`]: struct.Configuration.html
+[`EventField`]: enum.EventField.html
+[`EventField::RelativeTime`]: enum.EventField.html#variant.RelativeTime
+[`EventField::Category`]: enum.EventField.html#variant.Category
+[`EventField::Type`]: enum.EventField.html#variant.Type
+[`EventField::Data`]: enum.EventField.html#variant.Data
+[`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/event.rs b/third_party/rust/qlog/src/event.rs
new file mode 100644
index 0000000000..5748bf82d5
--- /dev/null
+++ b/third_party/rust/qlog/src/event.rs
@@ -0,0 +1,988 @@
+// Copyright (C) 2020, 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.
+
+//! Module-defined Event and functions to work with qlog data structures.
+//!
+//! The Qlog data structures are very flexible, which makes working with them
+//! liable to simple semantic errors. This module provides some helper functions
+//! that focus on creating valid types, while also reducing some of the
+//! verbosity of using the raw data structures.
+
+use super::*;
+
+/// A representation of qlog events that couple EventCategory, EventType and
+/// EventData.
+///
+/// Functions are provided to help construct valid events. Most events consist
+/// of several optional fields, so minimal versions of these functions are
+/// provided, which accept only mandatory qlog parameters. Minimal functions are
+/// identified by a `_min` suffix.
+#[derive(Clone)]
+pub struct Event {
+ pub category: EventCategory,
+ pub ty: EventType,
+ pub data: EventData,
+}
+
+#[allow(clippy::too_many_arguments)]
+impl Event {
+ // Connectivity events.
+
+ /// Returns:
+ /// * `EventCategory`=`Connectivity`
+ /// * `EventType`=`ConnectivityEventType::ServerListening`
+ /// * `EventData`=`ServerListening`.
+ pub fn server_listening(
+ ip_v4: Option<String>, ip_v6: Option<String>, port_v4: u64, port_v6: u64,
+ quic_versions: Option<Vec<String>>, alpn_values: Option<Vec<String>>,
+ stateless_reset_required: Option<bool>,
+ ) -> Self {
+ Event {
+ category: EventCategory::Connectivity,
+ ty: EventType::ConnectivityEventType(
+ ConnectivityEventType::ServerListening,
+ ),
+ data: EventData::ServerListening {
+ ip_v4,
+ ip_v6,
+ port_v4,
+ port_v6,
+ quic_versions,
+ alpn_values,
+ stateless_reset_required,
+ },
+ }
+ }
+
+ pub fn server_listening_min(port_v4: u64, port_v6: u64) -> Self {
+ Event::server_listening(None, None, port_v4, port_v6, None, None, None)
+ }
+
+ /// Returns:
+ /// * `EventCategory`=`Connectivity`
+ /// * `EventType`=`ConnectivityEventType::ConnectionStarted`
+ /// * `EventData`=`ConnectionStarted`.
+ pub fn connection_started(
+ ip_version: String, src_ip: String, dst_ip: String,
+ protocol: Option<String>, src_port: u64, dst_port: u64,
+ quic_version: Option<String>, src_cid: Option<String>,
+ dst_cid: Option<String>,
+ ) -> Self {
+ Event {
+ category: EventCategory::Connectivity,
+ ty: EventType::ConnectivityEventType(
+ ConnectivityEventType::ConnectionStarted,
+ ),
+ data: EventData::ConnectionStarted {
+ ip_version,
+ src_ip,
+ dst_ip,
+ protocol,
+ src_port,
+ dst_port,
+ quic_version,
+ src_cid,
+ dst_cid,
+ },
+ }
+ }
+
+ pub fn connection_started_min(
+ ip_version: String, src_ip: String, dst_ip: String, src_port: u64,
+ dst_port: u64,
+ ) -> Self {
+ Event::connection_started(
+ ip_version, src_ip, dst_ip, None, src_port, dst_port, None, None,
+ None,
+ )
+ }
+
+ /// Returns:
+ /// * `EventCategory`=`Connectivity`
+ /// * `EventType`=`ConnectivityEventType::ConnectionIdUpdated`
+ /// * `EventData`=`ConnectionIdUpdated`.
+ pub fn connection_id_updated(
+ src_old: Option<String>, src_new: Option<String>,
+ dst_old: Option<String>, dst_new: Option<String>,
+ ) -> Self {
+ Event {
+ category: EventCategory::Connectivity,
+ ty: EventType::ConnectivityEventType(
+ ConnectivityEventType::ConnectionIdUpdated,
+ ),
+ data: EventData::ConnectionIdUpdated {
+ src_old,
+ src_new,
+ dst_old,
+ dst_new,
+ },
+ }
+ }
+
+ pub fn connection_id_updated_min() -> Self {
+ Event::connection_id_updated(None, None, None, None)
+ }
+
+ /// Returns:
+ /// * `EventCategory`=`Connectivity`
+ /// * `EventType`=`ConnectivityEventType::SpinBitUpdated`
+ /// * `EventData`=`SpinBitUpdated`.
+ pub fn spinbit_updated(state: bool) -> Self {
+ Event {
+ category: EventCategory::Connectivity,
+ ty: EventType::ConnectivityEventType(
+ ConnectivityEventType::SpinBitUpdated,
+ ),
+ data: EventData::SpinBitUpdated { state },
+ }
+ }
+
+ /// Returns:
+ /// * `EventCategory`=`Connectivity`
+ /// * `EventType`=`ConnectivityEventType::ConnectionState`
+ /// * `EventData`=`ConnectionState`.
+ pub fn connection_state_updated(
+ old: Option<ConnectionState>, new: ConnectionState,
+ ) -> Self {
+ Event {
+ category: EventCategory::Connectivity,
+ ty: EventType::ConnectivityEventType(
+ ConnectivityEventType::ConnectionStateUpdated,
+ ),
+ data: EventData::ConnectionStateUpdated { old, new },
+ }
+ }
+
+ pub fn connection_state_updated_min(new: ConnectionState) -> Self {
+ Event::connection_state_updated(None, new)
+ }
+
+ // Transport events.
+
+ /// Returns:
+ /// * `EventCategory`=`Transport`
+ /// * `EventType`=`TransportEventType::ParametersSet`
+ /// * `EventData`=`ParametersSet`.
+ pub fn transport_parameters_set(
+ owner: Option<TransportOwner>, resumption_allowed: Option<bool>,
+ early_data_enabled: Option<bool>, alpn: Option<String>,
+ version: Option<String>, tls_cipher: Option<String>,
+ original_connection_id: Option<String>,
+ stateless_reset_token: Option<String>,
+ disable_active_migration: Option<bool>, idle_timeout: Option<u64>,
+ max_packet_size: Option<u64>, ack_delay_exponent: Option<u64>,
+ max_ack_delay: Option<u64>, active_connection_id_limit: Option<u64>,
+ initial_max_data: Option<String>,
+ initial_max_stream_data_bidi_local: Option<String>,
+ initial_max_stream_data_bidi_remote: Option<String>,
+ initial_max_stream_data_uni: Option<String>,
+ initial_max_streams_bidi: Option<String>,
+ initial_max_streams_uni: Option<String>,
+ preferred_address: Option<PreferredAddress>,
+ ) -> Self {
+ Event {
+ category: EventCategory::Transport,
+ ty: EventType::TransportEventType(TransportEventType::ParametersSet),
+ data: EventData::TransportParametersSet {
+ owner,
+
+ resumption_allowed,
+ early_data_enabled,
+ alpn,
+ version,
+ tls_cipher,
+
+ original_connection_id,
+ stateless_reset_token,
+ disable_active_migration,
+
+ idle_timeout,
+ max_packet_size,
+ ack_delay_exponent,
+ max_ack_delay,
+ active_connection_id_limit,
+
+ initial_max_data,
+ initial_max_stream_data_bidi_local,
+ initial_max_stream_data_bidi_remote,
+ initial_max_stream_data_uni,
+ initial_max_streams_bidi,
+ initial_max_streams_uni,
+
+ preferred_address,
+ },
+ }
+ }
+
+ pub fn transport_parameters_set_min() -> Self {
+ Event::transport_parameters_set(
+ None, None, None, None, None, None, None, None, None, None, None,
+ None, None, None, None, None, None, None, None, None, None,
+ )
+ }
+
+ /// Returns:
+ /// * `EventCategory`=`Transport`
+ /// * `EventType`=`TransportEventType::DatagramsReceived`
+ /// * `EventData`=`DatagramsReceived`.
+ pub fn datagrams_received(
+ count: Option<u64>, byte_length: Option<u64>,
+ ) -> Self {
+ Event {
+ category: EventCategory::Transport,
+ ty: EventType::TransportEventType(
+ TransportEventType::DatagramsReceived,
+ ),
+ data: EventData::DatagramsReceived { count, byte_length },
+ }
+ }
+
+ pub fn datagrams_received_min() -> Self {
+ Event::datagrams_received(None, None)
+ }
+
+ /// Returns:
+ /// * `EventCategory`=`Transport`
+ /// * `EventType`=`TransportEventType::DatagramsSent`
+ /// * `EventData`=`DatagramsSent`.
+ pub fn datagrams_sent(count: Option<u64>, byte_length: Option<u64>) -> Self {
+ Event {
+ category: EventCategory::Transport,
+ ty: EventType::TransportEventType(TransportEventType::DatagramsSent),
+ data: EventData::DatagramsSent { count, byte_length },
+ }
+ }
+
+ pub fn datagrams_sent_min() -> Self {
+ Event::datagrams_sent(None, None)
+ }
+
+ /// Returns:
+ /// * `EventCategory`=`Transport`
+ /// * `EventType`=`TransportEventType::DatagramDropped`
+ /// * `EventData`=`DatagramDropped`.
+ pub fn datagram_dropped(byte_length: Option<u64>) -> Self {
+ Event {
+ category: EventCategory::Transport,
+ ty: EventType::TransportEventType(
+ TransportEventType::DatagramDropped,
+ ),
+ data: EventData::DatagramDropped { byte_length },
+ }
+ }
+
+ pub fn datagram_dropped_min() -> Self {
+ Event::datagram_dropped(None)
+ }
+
+ /// Returns:
+ /// * `EventCategory`=`Transport`
+ /// * `EventType`=`TransportEventType::PacketReceived`
+ /// * `EventData`=`PacketReceived`.
+ pub fn packet_received(
+ packet_type: PacketType, header: PacketHeader,
+ frames: Option<Vec<QuicFrame>>, is_coalesced: Option<bool>,
+ raw_encrypted: Option<String>, raw_decrypted: Option<String>,
+ ) -> Self {
+ Event {
+ category: EventCategory::Transport,
+ ty: EventType::TransportEventType(TransportEventType::PacketReceived),
+ data: EventData::PacketReceived {
+ packet_type,
+ header,
+ frames,
+
+ is_coalesced,
+
+ raw_encrypted,
+ raw_decrypted,
+ },
+ }
+ }
+
+ pub fn packet_received_min(
+ packet_type: PacketType, header: PacketHeader,
+ ) -> Self {
+ Event::packet_received(packet_type, header, None, None, None, None)
+ }
+
+ /// Returns:
+ /// * `EventCategory`=`Transport`
+ /// * `EventType`=`TransportEventType::PacketSent`
+ /// * `EventData`=`PacketSent`.
+ pub fn packet_sent(
+ packet_type: PacketType, header: PacketHeader,
+ frames: Option<Vec<QuicFrame>>, is_coalesced: Option<bool>,
+ raw_encrypted: Option<String>, raw_decrypted: Option<String>,
+ ) -> Self {
+ Event {
+ category: EventCategory::Transport,
+ ty: EventType::TransportEventType(TransportEventType::PacketSent),
+ data: EventData::PacketSent {
+ packet_type,
+ header,
+ frames,
+
+ is_coalesced,
+
+ raw_encrypted,
+ raw_decrypted,
+ },
+ }
+ }
+
+ pub fn packet_sent_min(
+ packet_type: PacketType, header: PacketHeader,
+ frames: Option<Vec<QuicFrame>>,
+ ) -> Self {
+ Event::packet_sent(packet_type, header, frames, None, None, None)
+ }
+
+ /// Returns:
+ /// * `EventCategory`=`Transport`
+ /// * `EventType`=`TransportEventType::PacketDropped`
+ /// * `EventData`=`PacketDropped`.
+ pub fn packet_dropped(
+ packet_type: Option<PacketType>, packet_size: Option<u64>,
+ raw: Option<String>,
+ ) -> Self {
+ Event {
+ category: EventCategory::Transport,
+ ty: EventType::TransportEventType(TransportEventType::PacketDropped),
+ data: EventData::PacketDropped {
+ packet_type,
+ packet_size,
+ raw,
+ },
+ }
+ }
+
+ pub fn packet_dropped_min() -> Self {
+ Event::packet_dropped(None, None, None)
+ }
+
+ /// Returns:
+ /// * `EventCategory`=`Transport`
+ /// * `EventType`=`TransportEventType::PacketBuffered`
+ /// * `EventData`=`PacketBuffered`.
+ pub fn packet_buffered(
+ packet_type: PacketType, packet_number: String,
+ ) -> Self {
+ Event {
+ category: EventCategory::Transport,
+ ty: EventType::TransportEventType(TransportEventType::PacketBuffered),
+ data: EventData::PacketBuffered {
+ packet_type,
+ packet_number,
+ },
+ }
+ }
+
+ /// Returns:
+ /// * `EventCategory`=`Transport`
+ /// * `EventType`=`TransportEventType::StreamStateUpdated`
+ /// * `EventData`=`StreamStateUpdated`.
+ pub fn stream_state_updated(
+ stream_id: String, stream_type: Option<StreamType>,
+ old: Option<StreamState>, new: StreamState,
+ stream_side: Option<StreamSide>,
+ ) -> Self {
+ Event {
+ category: EventCategory::Connectivity,
+ ty: EventType::TransportEventType(
+ TransportEventType::StreamStateUpdated,
+ ),
+ data: EventData::StreamStateUpdated {
+ stream_id,
+ stream_type,
+ old,
+ new,
+ stream_side,
+ },
+ }
+ }
+
+ pub fn stream_state_updated_min(stream_id: String, new: StreamState) -> Self {
+ Event::stream_state_updated(stream_id, None, None, new, None)
+ }
+
+ /// Returns:
+ /// * `EventCategory`=`Transport`
+ /// * `EventType`=`TransportEventType::FramesProcessed`
+ /// * `EventData`=`FramesProcessed`.
+ pub fn frames_processed(frames: Vec<QuicFrame>) -> Self {
+ Event {
+ category: EventCategory::Transport,
+ ty: EventType::TransportEventType(
+ TransportEventType::FramesProcessed,
+ ),
+ data: EventData::FramesProcessed { frames },
+ }
+ }
+
+ // Recovery events.
+
+ /// Returns:
+ /// * `EventCategory`=`Recovery`
+ /// * `EventType`=`RecoveryEventType::ParametersSet`
+ /// * `EventData`=`RecoveryParametersSet`.
+ pub fn recovery_parameters_set(
+ reordering_threshold: Option<u64>, time_threshold: Option<u64>,
+ timer_granularity: Option<u64>, initial_rtt: Option<u64>,
+ max_datagram_size: Option<u64>, initial_congestion_window: Option<u64>,
+ minimum_congestion_window: Option<u64>,
+ loss_reduction_factor: Option<u64>,
+ persistent_congestion_threshold: Option<u64>,
+ ) -> Self {
+ Event {
+ category: EventCategory::Recovery,
+ ty: EventType::RecoveryEventType(RecoveryEventType::ParametersSet),
+ data: EventData::RecoveryParametersSet {
+ reordering_threshold,
+ time_threshold,
+ timer_granularity,
+ initial_rtt,
+ max_datagram_size,
+ initial_congestion_window,
+ minimum_congestion_window,
+ loss_reduction_factor,
+ persistent_congestion_threshold,
+ },
+ }
+ }
+
+ pub fn recovery_parameters_set_min() -> Self {
+ Event::recovery_parameters_set(
+ None, None, None, None, None, None, None, None, None,
+ )
+ }
+
+ /// Returns:
+ /// * `EventCategory`=`Recovery`
+ /// * `EventType`=`RecoveryEventType::MetricsUpdated`
+ /// * `EventData`=`MetricsUpdated`.
+ pub fn metrics_updated(
+ min_rtt: Option<u64>, smoothed_rtt: Option<u64>, latest_rtt: Option<u64>,
+ rtt_variance: Option<u64>, max_ack_delay: Option<u64>,
+ pto_count: Option<u64>, congestion_window: Option<u64>,
+ bytes_in_flight: Option<u64>, ssthresh: Option<u64>,
+ packets_in_flight: Option<u64>, in_recovery: Option<bool>,
+ pacing_rate: Option<u64>,
+ ) -> Self {
+ Event {
+ category: EventCategory::Recovery,
+ ty: EventType::RecoveryEventType(RecoveryEventType::MetricsUpdated),
+ data: EventData::MetricsUpdated {
+ min_rtt,
+ smoothed_rtt,
+ latest_rtt,
+ rtt_variance,
+ max_ack_delay,
+ pto_count,
+ congestion_window,
+ bytes_in_flight,
+ ssthresh,
+ packets_in_flight,
+ in_recovery,
+ pacing_rate,
+ },
+ }
+ }
+
+ pub fn metrics_updated_min() -> Self {
+ Event::metrics_updated(
+ None, None, None, None, None, None, None, None, None, None, None,
+ None,
+ )
+ }
+
+ /// Returns:
+ /// * `EventCategory`=`Recovery`
+ /// * `EventType`=`RecoveryEventType::CongestionStateUpdated`
+ /// * `EventData`=`CongestionStateUpdated`.
+ pub fn congestion_state_updated(old: Option<String>, new: String) -> Self {
+ Event {
+ category: EventCategory::Recovery,
+ ty: EventType::RecoveryEventType(
+ RecoveryEventType::CongestionStateUpdated,
+ ),
+ data: EventData::CongestionStateUpdated { old, new },
+ }
+ }
+
+ pub fn congestion_state_updated_min(new: String) -> Self {
+ Event::congestion_state_updated(None, new)
+ }
+
+ /// Returns:
+ /// * `EventCategory`=`Recovery`
+ /// * `EventType`=`RecoveryEventType::LossTimerSet`
+ /// * `EventData`=`LossTimerSet`.
+ pub fn loss_timer_set(
+ timer_type: Option<TimerType>, timeout: Option<String>,
+ ) -> Self {
+ Event {
+ category: EventCategory::Recovery,
+ ty: EventType::RecoveryEventType(RecoveryEventType::LossTimerSet),
+ data: EventData::LossTimerSet {
+ timer_type,
+ timeout,
+ },
+ }
+ }
+
+ pub fn loss_timer_set_min() -> Self {
+ Event::loss_timer_set(None, None)
+ }
+
+ /// Returns:
+ /// * `EventCategory`=`Recovery`
+ /// * `EventType`=`RecoveryEventType::PacketLost`
+ /// * `EventData`=`PacketLost`.
+ pub fn packet_lost(
+ packet_type: PacketType, packet_number: String,
+ header: Option<PacketHeader>, frames: Vec<QuicFrame>,
+ ) -> Self {
+ Event {
+ category: EventCategory::Recovery,
+ ty: EventType::RecoveryEventType(RecoveryEventType::PacketLost),
+ data: EventData::PacketLost {
+ packet_type,
+ packet_number,
+ header,
+ frames,
+ },
+ }
+ }
+
+ pub fn packet_lost_min(
+ packet_type: PacketType, packet_number: String, frames: Vec<QuicFrame>,
+ ) -> Self {
+ Event::packet_lost(packet_type, packet_number, None, frames)
+ }
+
+ /// Returns:
+ /// * `EventCategory`=`Recovery`
+ /// * `EventType`=`RecoveryEventType::MarkedForRetransmit`
+ /// * `EventData`=`MarkedForRetransmit`.
+ pub fn marked_for_retransmit(frames: Vec<QuicFrame>) -> Self {
+ Event {
+ category: EventCategory::Recovery,
+ ty: EventType::RecoveryEventType(
+ RecoveryEventType::MarkedForRetransmit,
+ ),
+ data: EventData::MarkedForRetransmit { frames },
+ }
+ }
+
+ // HTTP/3 events.
+
+ /// Returns:
+ /// * `EventCategory`=`Http`
+ /// * `EventType`=`Http3EventType::ParametersSet`
+ /// * `EventData`=`H3ParametersSet`.
+ pub fn h3_parameters_set(
+ owner: Option<H3Owner>, max_header_list_size: Option<u64>,
+ max_table_capacity: Option<u64>, blocked_streams_count: Option<u64>,
+ push_allowed: Option<bool>, waits_for_settings: Option<bool>,
+ ) -> Self {
+ Event {
+ category: EventCategory::Http,
+ ty: EventType::Http3EventType(Http3EventType::ParametersSet),
+ data: EventData::H3ParametersSet {
+ owner,
+ max_header_list_size,
+ max_table_capacity,
+ blocked_streams_count,
+ push_allowed,
+ waits_for_settings,
+ },
+ }
+ }
+
+ pub fn h3_parameters_set_min() -> Self {
+ Event::h3_parameters_set(None, None, None, None, None, None)
+ }
+
+ /// Returns:
+ /// * `EventCategory`=`Http`
+ /// * `EventType`=`Http3EventType::StreamTypeSet`
+ /// * `EventData`=`H3StreamTypeSet`.
+ pub fn h3_stream_type_set(
+ stream_id: String, owner: Option<H3Owner>, old: Option<H3StreamType>,
+ new: H3StreamType,
+ ) -> Self {
+ Event {
+ category: EventCategory::Http,
+ ty: EventType::Http3EventType(Http3EventType::StreamTypeSet),
+ data: EventData::H3StreamTypeSet {
+ stream_id,
+ owner,
+ old,
+ new,
+ },
+ }
+ }
+
+ pub fn h3_stream_type_set_min(stream_id: String, new: H3StreamType) -> Self {
+ Event::h3_stream_type_set(stream_id, None, None, new)
+ }
+
+ /// Returns:
+ /// * `EventCategory`=`Http`
+ /// * `EventType`=`Http3EventType::FrameCreated`
+ /// * `EventData`=`H3FrameCreated`.
+ pub fn h3_frame_created(
+ stream_id: String, frame: Http3Frame, byte_length: Option<String>,
+ raw: Option<String>,
+ ) -> Self {
+ Event {
+ category: EventCategory::Http,
+ ty: EventType::Http3EventType(Http3EventType::FrameCreated),
+ data: EventData::H3FrameCreated {
+ stream_id,
+ frame,
+ byte_length,
+ raw,
+ },
+ }
+ }
+
+ pub fn h3_frame_created_min(stream_id: String, frame: Http3Frame) -> Self {
+ Event::h3_frame_created(stream_id, frame, None, None)
+ }
+
+ /// Returns:
+ /// * `EventCategory`=`Http`
+ /// * `EventType`=`Http3EventType::FrameParsed`
+ /// * `EventData`=`H3FrameParsed`.
+ pub fn h3_frame_parsed(
+ stream_id: String, frame: Http3Frame, byte_length: Option<String>,
+ raw: Option<String>,
+ ) -> Self {
+ Event {
+ category: EventCategory::Http,
+ ty: EventType::Http3EventType(Http3EventType::FrameParsed),
+ data: EventData::H3FrameParsed {
+ stream_id,
+ frame,
+ byte_length,
+ raw,
+ },
+ }
+ }
+
+ pub fn h3_frame_parsed_min(stream_id: String, frame: Http3Frame) -> Self {
+ Event::h3_frame_parsed(stream_id, frame, None, None)
+ }
+
+ /// Returns:
+ /// * `EventCategory`=`Http`
+ /// * `EventType`=`Http3EventType::DataMoved`
+ /// * `EventData`=`H3DataMoved`.
+ pub fn h3_data_moved(
+ stream_id: String, offset: Option<String>, length: Option<u64>,
+ from: Option<H3DataRecipient>, to: Option<H3DataRecipient>,
+ raw: Option<String>,
+ ) -> Self {
+ Event {
+ category: EventCategory::Http,
+ ty: EventType::Http3EventType(Http3EventType::DataMoved),
+ data: EventData::H3DataMoved {
+ stream_id,
+ offset,
+ length,
+ from,
+ to,
+ raw,
+ },
+ }
+ }
+
+ pub fn h3_data_moved_min(stream_id: String) -> Self {
+ Event::h3_data_moved(stream_id, None, None, None, None, None)
+ }
+
+ /// Returns:
+ /// * `EventCategory`=`Http`
+ /// * `EventType`=`Http3EventType::PushResolved`
+ /// * `EventData`=`H3PushResolved`.
+ pub fn h3_push_resolved(
+ push_id: Option<String>, stream_id: Option<String>,
+ decision: Option<H3PushDecision>,
+ ) -> Self {
+ Event {
+ category: EventCategory::Http,
+ ty: EventType::Http3EventType(Http3EventType::PushResolved),
+ data: EventData::H3PushResolved {
+ push_id,
+ stream_id,
+ decision,
+ },
+ }
+ }
+
+ pub fn h3_push_resolved_min() -> Self {
+ Event::h3_push_resolved(None, None, None)
+ }
+
+ // QPACK events.
+
+ /// Returns:
+ /// * `EventCategory`=`Qpack`
+ /// * `EventType`=`QpackEventType::StateUpdated`
+ /// * `EventData`=`QpackStateUpdated`.
+ pub fn qpack_state_updated(
+ owner: Option<QpackOwner>, dynamic_table_capacity: Option<u64>,
+ dynamic_table_size: Option<u64>, known_received_count: Option<u64>,
+ current_insert_count: Option<u64>,
+ ) -> Self {
+ Event {
+ category: EventCategory::Qpack,
+ ty: EventType::QpackEventType(QpackEventType::StateUpdated),
+ data: EventData::QpackStateUpdated {
+ owner,
+ dynamic_table_capacity,
+ dynamic_table_size,
+ known_received_count,
+ current_insert_count,
+ },
+ }
+ }
+
+ pub fn qpack_state_updated_min() -> Self {
+ Event::qpack_state_updated(None, None, None, None, None)
+ }
+
+ /// Returns:
+ /// * `EventCategory`=`Qpack`
+ /// * `EventType`=`QpackEventType::StreamStateUpdated`
+ /// * `EventData`=`QpackStreamStateUpdated`.
+ pub fn qpack_stream_state_updated(
+ stream_id: String, state: QpackStreamState,
+ ) -> Self {
+ Event {
+ category: EventCategory::Qpack,
+ ty: EventType::QpackEventType(QpackEventType::StreamStateUpdated),
+ data: EventData::QpackStreamStateUpdated { stream_id, state },
+ }
+ }
+
+ /// Returns:
+ /// * `EventCategory`=`Qpack`
+ /// * `EventType`=`QpackEventType::DynamicTableUpdated`
+ /// * `EventData`=`QpackDynamicTableUpdated`.
+ pub fn qpack_dynamic_table_updated(
+ update_type: QpackUpdateType, entries: Vec<QpackDynamicTableEntry>,
+ ) -> Self {
+ Event {
+ category: EventCategory::Qpack,
+ ty: EventType::QpackEventType(QpackEventType::DynamicTableUpdated),
+ data: EventData::QpackDynamicTableUpdated {
+ update_type,
+ entries,
+ },
+ }
+ }
+
+ /// Returns:
+ /// * `EventCategory`=`Qpack`
+ /// * `EventType`=`QpackEventType::HeadersEncoded`
+ /// * `EventData`=`QpackHeadersEncoded`.
+ pub fn qpack_headers_encoded(
+ stream_id: Option<String>, headers: Option<HttpHeader>,
+ block_prefix: QpackHeaderBlockPrefix,
+ header_block: Vec<QpackHeaderBlockRepresentation>, raw: Option<String>,
+ ) -> Self {
+ Event {
+ category: EventCategory::Qpack,
+ ty: EventType::QpackEventType(QpackEventType::HeadersEncoded),
+ data: EventData::QpackHeadersEncoded {
+ stream_id,
+ headers,
+ block_prefix,
+ header_block,
+ raw,
+ },
+ }
+ }
+
+ pub fn qpack_headers_encoded_min(
+ block_prefix: QpackHeaderBlockPrefix,
+ header_block: Vec<QpackHeaderBlockRepresentation>,
+ ) -> Self {
+ Event::qpack_headers_encoded(None, None, block_prefix, header_block, None)
+ }
+
+ /// Returns:
+ /// * `EventCategory`=`Qpack`
+ /// * `EventType`=`QpackEventType::HeadersDecoded`
+ /// * `EventData`=`QpackHeadersDecoded`.
+ pub fn qpack_headers_decoded(
+ stream_id: Option<String>, headers: Option<HttpHeader>,
+ block_prefix: QpackHeaderBlockPrefix,
+ header_block: Vec<QpackHeaderBlockRepresentation>, raw: Option<String>,
+ ) -> Self {
+ Event {
+ category: EventCategory::Qpack,
+ ty: EventType::QpackEventType(QpackEventType::HeadersDecoded),
+ data: EventData::QpackHeadersDecoded {
+ stream_id,
+ headers,
+ block_prefix,
+ header_block,
+ raw,
+ },
+ }
+ }
+
+ pub fn qpack_headers_decoded_min(
+ block_prefix: QpackHeaderBlockPrefix,
+ header_block: Vec<QpackHeaderBlockRepresentation>,
+ ) -> Self {
+ Event::qpack_headers_decoded(None, None, block_prefix, header_block, None)
+ }
+
+ /// Returns:
+ /// * `EventCategory`=`Qpack`
+ /// * `EventType`=`QpackEventType::InstructionSent`
+ /// * `EventData`=`QpackInstructionSent`.
+ pub fn qpack_instruction_sent(
+ instruction: QPackInstruction, byte_length: Option<String>,
+ raw: Option<String>,
+ ) -> Self {
+ Event {
+ category: EventCategory::Qpack,
+ ty: EventType::QpackEventType(QpackEventType::InstructionSent),
+ data: EventData::QpackInstructionSent {
+ instruction,
+ byte_length,
+ raw,
+ },
+ }
+ }
+
+ pub fn qpack_instruction_sent_min(instruction: QPackInstruction) -> Self {
+ Event::qpack_instruction_sent(instruction, None, None)
+ }
+
+ /// Returns:
+ /// * `EventCategory`=`Qpack`
+ /// * `EventType`=`QpackEventType::InstructionReceived`
+ /// * `EventData`=`QpackInstructionReceived`.
+ pub fn qpack_instruction_received(
+ instruction: QPackInstruction, byte_length: Option<String>,
+ raw: Option<String>,
+ ) -> Self {
+ Event {
+ category: EventCategory::Qpack,
+ ty: EventType::QpackEventType(QpackEventType::InstructionReceived),
+ data: EventData::QpackInstructionReceived {
+ instruction,
+ byte_length,
+ raw,
+ },
+ }
+ }
+
+ pub fn qpack_instruction_received_min(instruction: QPackInstruction) -> Self {
+ Event::qpack_instruction_received(instruction, None, None)
+ }
+
+ /// Checks if the the combination of `EventCategory`, `EventType` and
+ /// `EventData` is valid.
+ pub fn is_valid(&self) -> bool {
+ match (&self.category, &self.ty) {
+ (
+ EventCategory::Connectivity,
+ EventType::ConnectivityEventType(_),
+ ) => matches!(&self.data,
+ EventData::ServerListening { .. } |
+ EventData::ConnectionStarted { .. } |
+ EventData::ConnectionIdUpdated { .. } |
+ EventData::SpinBitUpdated { .. } |
+ EventData::ConnectionStateUpdated { .. }),
+
+ (EventCategory::Transport, EventType::TransportEventType(_)) =>
+ matches!(&self.data,
+ EventData::TransportParametersSet { .. } |
+ EventData::DatagramsReceived { .. } |
+ EventData::DatagramsSent { .. } |
+ EventData::DatagramDropped { .. } |
+ EventData::PacketReceived { .. } |
+ EventData::PacketSent { .. } |
+ EventData::PacketDropped { .. } |
+ EventData::PacketBuffered { .. } |
+ EventData::StreamStateUpdated { .. } |
+ EventData::FramesProcessed { .. }),
+
+ (EventCategory::Security, EventType::SecurityEventType(_)) =>
+ matches!(&self.data,
+ EventData::KeyUpdated { .. } |
+ EventData::KeyRetired { .. }),
+
+ (EventCategory::Recovery, EventType::RecoveryEventType(_)) =>
+ matches!(&self.data,
+ EventData::RecoveryParametersSet { .. } |
+ EventData::MetricsUpdated { .. } |
+ EventData::CongestionStateUpdated { .. } |
+ EventData::LossTimerSet { .. } |
+ EventData::PacketLost { .. } |
+ EventData::MarkedForRetransmit { .. }),
+
+ (EventCategory::Http, EventType::Http3EventType(_)) =>
+ matches!(&self.data,
+ EventData::H3ParametersSet { .. } |
+ EventData::H3StreamTypeSet { .. } |
+ EventData::H3FrameCreated { .. } |
+ EventData::H3FrameParsed { .. } |
+ EventData::H3DataMoved { .. } |
+ EventData::H3PushResolved { .. }),
+
+ (EventCategory::Qpack, EventType::QpackEventType(_)) =>
+ matches!(&self.data,
+ EventData::QpackStateUpdated { .. } |
+ EventData::QpackStreamStateUpdated { .. } |
+ EventData::QpackDynamicTableUpdated { .. } |
+ EventData::QpackHeadersEncoded { .. } |
+ EventData::QpackHeadersDecoded { .. } |
+ EventData::QpackInstructionSent { .. } |
+ EventData::QpackInstructionReceived { .. }),
+
+ // TODO: in qlog-01 there is no sane default category defined for
+ // GenericEventType
+ (_, EventType::GenericEventType(_)) => matches!(&self.data,
+ EventData::ConnectionError { .. } |
+ EventData::ApplicationError { .. } |
+ EventData::InternalError { .. } |
+ EventData::InternalWarning { .. } |
+ EventData::Message { .. } |
+ EventData::Marker { .. }),
+
+ _ => false,
+ }
+ }
+}
diff --git a/third_party/rust/qlog/src/lib.rs b/third_party/rust/qlog/src/lib.rs
new file mode 100644
index 0000000000..d4a3bb45d0
--- /dev/null
+++ b/third_party/rust/qlog/src/lib.rs
@@ -0,0 +1,2970 @@
+// 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 schema] and [qlog QUIC
+//! and HTTP/3 events] that attempts to closely follow the format of the qlog
+//! [TypeScript schema]. This is just a data model and no support is provided
+//! for logging IO, applications can decide themselves the most appropriate
+//! method.
+//!
+//! The crate uses Serde for conversion between Rust and JSON.
+//!
+//! [qlog main schema]: https://tools.ietf.org/html/draft-marx-qlog-main-schema
+//! [qlog QUIC and HTTP/3 events]:
+//! https://quiclog.github.io/internet-drafts/draft-marx-qlog-event-definitions-quic-h3
+//! [TypeScript schema]:
+//! https://github.com/quiclog/qlog/blob/master/TypeScript/draft-01/QLog.ts
+//!
+//! 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.
+//!
+//! ## Traces
+//!
+//! A [`Trace`] contains metadata such as the [`VantagePoint`] of capture and
+//! the [`Configuration`] of the `Trace`.
+//!
+//! A very important part of the `Trace` is the definition of `event_fields`. A
+//! qlog Event is a vector of [`EventField`]; this provides great flexibility to
+//! log events with any number of `EventFields` in any order. The `event_fields`
+//! property describes the format of event logging and it is important that
+//! events comply with that format. Failing to do so it going to cause problems
+//! for qlog analysis tools. For information is available at
+//! https://tools.ietf.org/html/draft-marx-qlog-main-schema-01#section-3.3.4
+//!
+//! In order to make using qlog a bit easier, this crate expects a qlog Event to
+//! consist of the following EventFields in the following order:
+//! [`EventField::RelativeTime`], [`EventField::Category`],
+//! [`EventField::Event`] and [`EventField::Data`]. A set of methods are
+//! provided to assist in creating a Trace and appending events to it in this
+//! format.
+//!
+//! ## Writing out logs
+//! As events occur during the connection, the application appends them to the
+//! trace. The qlog crate supports two modes of writing logs: the buffered mode
+//! stores everything in memory and requires the application to serialize and
+//! write the output, the streaming mode progressively writes serialized JSON
+//! output to a writer designated by the application.
+//!
+//! ### Creating a Trace
+//!
+//! A typical application needs a single qlog [`Trace`] that it appends QUIC
+//! and/or HTTP/3 events to:
+//!
+//! ```
+//! 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".to_string()),
+//! time_units: Some(qlog::TimeUnits::Ms),
+//! original_uris: None,
+//! }),
+//! None,
+//! );
+//! ```
+//!
+//! ## Adding events
+//!
+//! Qlog Events are added to [`qlog::Trace.events`].
+//!
+//! It is recommended to use the provided utility methods to append semantically
+//! valid events to a trace. However, there is nothing preventing you from
+//! creating the events manually.
+//!
+//! The following example demonstrates how to log a QUIC packet
+//! containing a single Crypto frame. It uses the [`QuicFrame::crypto()`],
+//! [`packet_sent_min()`] and [`push_event()`] methods to create and log a
+//! PacketSent event and its EventData.
+//!
+//! ```
+//! # 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".to_string()),
+//! # time_units: Some(qlog::TimeUnits::Ms),
+//! # 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::PacketHeader::new(
+//! 0,
+//! Some(1251),
+//! Some(1224),
+//! Some(0xff00001b),
+//! Some(b"7e37e4dcc6682da8"),
+//! Some(&dcid),
+//! );
+//!
+//! let frames =
+//! vec![qlog::QuicFrame::crypto("0".to_string(), "1000".to_string())];
+//!
+//! let event = qlog::event::Event::packet_sent_min(
+//! qlog::PacketType::Initial,
+//! pkt_hdr,
+//! Some(frames),
+//! );
+//!
+//! trace.push_event(std::time::Duration::new(0, 0), event);
+//! ```
+//!
+//! ### 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".to_string()),
+//! # time_units: Some(qlog::TimeUnits::Ms),
+//! # 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_units": "ms",
+//! "time_offset": "0"
+//! },
+//! "event_fields": [
+//! "relative_time",
+//! "category",
+//! "event",
+//! "data"
+//! ],
+//! "events": [
+//! [
+//! "0",
+//! "transport",
+//! "packet_sent",
+//! {
+//! "packet_type": "initial",
+//! "header": {
+//! "packet_number": "0",
+//! "packet_size": 1251,
+//! "payload_length": 1224,
+//! "version": "ff00001b",
+//! "scil": "8",
+//! "dcil": "8",
+//! "scid": "7e37e4dcc6682da8",
+//! "dcid": "36ce104eee50101c"
+//! },
+//! "frames": [
+//! {
+//! "frame_type": "crypto",
+//! "offset": "0",
+//! "length": "100",
+//! }
+//! ]
+//! }
+//! ]
+//! ]
+//! }
+//! ```
+//!
+//! Streaming Mode
+//! --------------
+//!
+//! Create the 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".to_string()),
+//! time_units: Some(qlog::TimeUnits::Ms),
+//! original_uris: None,
+//! }),
+//! None,
+//! );
+//! ```
+//! Create an object with the [`Write`] trait:
+//!
+//! ```
+//! let mut file = std::fs::File::create("foo.qlog").unwrap();
+//! ```
+//!
+//! Create a [`QlogStreamer`] and start serialization to foo.qlog
+//! using [`start_log()`]:
+//!
+//! ```
+//! # 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".to_string()),
+//! # time_units: Some(qlog::TimeUnits::Ms),
+//! # original_uris: None,
+//! # }),
+//! # None,
+//! # );
+//! # let mut file = std::fs::File::create("foo.qlog").unwrap();
+//! 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,
+//! 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()`]:
+//!
+//! ```
+//! # 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".to_string()),
+//! # time_units: Some(qlog::TimeUnits::Ms),
+//! # original_uris: None,
+//! # }),
+//! # None,
+//! # );
+//! # let mut file = std::fs::File::create("foo.qlog").unwrap();
+//! # 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,
+//! # Box::new(file),
+//! # );
+//! let event = qlog::event::Event::metrics_updated_min();
+//! 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:
+//!
+//! ```
+//! # 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".to_string()),
+//! # time_units: Some(qlog::TimeUnits::Ms),
+//! # original_uris: None,
+//! # }),
+//! # None,
+//! # );
+//! # let mut file = std::fs::File::create("foo.qlog").unwrap();
+//! # 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,
+//! # Box::new(file),
+//! # );
+//! let qlog_pkt_hdr = qlog::PacketHeader::with_type(
+//! qlog::PacketType::OneRtt,
+//! 0,
+//! Some(1251),
+//! Some(1224),
+//! Some(0xff00001b),
+//! Some(b"7e37e4dcc6682da8"),
+//! Some(b"36ce104eee50101c"),
+//! );
+//!
+//! let event = qlog::event::Event::packet_sent_min(
+//! qlog::PacketType::OneRtt,
+//! qlog_pkt_hdr,
+//! Some(Vec::new()),
+//! );
+//!
+//! 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()`].
+//!
+//! ```
+//! # 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".to_string()),
+//! # time_units: Some(qlog::TimeUnits::Ms),
+//! # original_uris: None,
+//! # }),
+//! # None,
+//! # );
+//! # let mut file = std::fs::File::create("foo.qlog").unwrap();
+//! # 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,
+//! # Box::new(file),
+//! # );
+//!
+//! let ping = qlog::QuicFrame::ping();
+//! let padding = qlog::QuicFrame::padding();
+//!
+//! streamer.add_frame(ping, false).ok();
+//! streamer.add_frame(padding, false).ok();
+//!
+//! streamer.finish_frames().ok();
+//! ```
+//!
+//! Once all events have have been written, the log
+//! can be finalized with [`finish_log()`]:
+//!
+//! ```
+//! # 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".to_string()),
+//! # time_units: Some(qlog::TimeUnits::Ms),
+//! # original_uris: None,
+//! # }),
+//! # None,
+//! # );
+//! # let mut file = std::fs::File::create("foo.qlog").unwrap();
+//! # 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,
+//! # 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
+//! [`VantagePoint`]: struct.VantagePoint.html
+//! [`Configuration`]: struct.Configuration.html
+//! [`EventField`]: enum.EventField.html
+//! [`EventField::RelativeTime`]: enum.EventField.html#variant.RelativeTime
+//! [`EventField::Category`]: enum.EventField.html#variant.Category
+//! [`EventField::Type`]: enum.EventField.html#variant.Type
+//! [`EventField::Data`]: enum.EventField.html#variant.Data
+//! [`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
+
+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,
+
+ /// 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 = "draft-02-wip";
+
+/// 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, Clone)]
+pub struct Qlog {
+ pub qlog_version: String,
+ pub title: Option<String>,
+ pub description: Option<String>,
+ pub summary: Option<String>,
+
+ pub traces: Vec<Trace>,
+}
+
+impl Default for Qlog {
+ fn default() -> Self {
+ Qlog {
+ qlog_version: QLOG_VERSION.to_string(),
+ title: Some("Default qlog title".to_string()),
+ description: Some("Default qlog description".to_string()),
+ summary: Some("Default qlog title".to_string()),
+ traces: Vec::new(),
+ }
+ }
+}
+
+#[derive(PartialEq)]
+pub enum StreamerState {
+ Initial,
+ Ready,
+ WritingFrames,
+ Finished,
+}
+
+/// 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()`. Some events
+/// can contain an array of `QuicFrame`s, when writing such an event, the
+/// streamer enters a frame-serialization mode where frames are be progressively
+/// written using `add_frame()`. This mode is concluded using
+/// `finished_frames()`. While serializing frames, any attempts to log
+/// additional events are ignored.
+///
+/// [`Write`]: https://doc.rust-lang.org/std/io/trait.Write.html
+pub struct QlogStreamer {
+ start_time: std::time::Instant,
+ writer: Box<dyn std::io::Write + Send + Sync>,
+ qlog: Qlog,
+ state: StreamerState,
+ first_event: bool,
+ first_frame: bool,
+}
+
+impl QlogStreamer {
+ /// Creates a QlogStreamer object.
+ ///
+ /// It owns a `Qlog` object that contains the provided `Trace`, which must
+ /// have the following ordered-set of names EventFields:
+ ///
+ /// ["relative_time", "category", "event".to_string(), "data"]
+ ///
+ /// All serialization will be written to the provided `Write`.
+ pub fn new(
+ qlog_version: String, title: Option<String>, description: Option<String>,
+ summary: Option<String>, start_time: std::time::Instant, trace: Trace,
+ writer: Box<dyn std::io::Write + Send + Sync>,
+ ) -> Self {
+ let qlog = Qlog {
+ qlog_version,
+ title,
+ description,
+ summary,
+ traces: vec![trace],
+ };
+
+ QlogStreamer {
+ start_time,
+ writer,
+ qlog,
+ state: StreamerState::Initial,
+ first_event: true,
+ first_frame: false,
+ }
+ }
+
+ /// Starts qlog streaming serialization.
+ ///
+ /// This writes out the JSON-serialized form of all information up to qlog
+ /// `Trace`'s array of `EventField`s. EventFields are separately appended
+ /// using functions that accept and `event::Event`.
+ pub fn start_log(&mut self) -> Result<()> {
+ if self.state != StreamerState::Initial {
+ return Err(Error::Done);
+ }
+
+ // A qlog contains a trace holding a vector of events that we want to
+ // serialize in a streaming manner. So at the start of serialization,
+ // take off all closing delimiters, and leave us in a state to accept
+ // new events.
+ match serde_json::to_string(&self.qlog) {
+ Ok(mut out) => {
+ out.truncate(out.len() - 4);
+
+ self.writer.as_mut().write_all(out.as_bytes())?;
+
+ self.state = StreamerState::Ready;
+
+ self.first_event = self.qlog.traces[0].events.is_empty();
+ },
+
+ _ => return Err(Error::Done),
+ }
+
+ Ok(())
+ }
+
+ /// Finishes qlog streaming serialization.
+ ///
+ /// The JSON-serialized output has remaining close delimiters added.
+ /// 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.writer.as_mut().write_all(b"]}]}")?;
+
+ self.state = StreamerState::Finished;
+
+ self.writer.as_mut().flush()?;
+
+ Ok(())
+ }
+
+ /// Writes a JSON-serialized `EventField`s.
+ ///
+ /// Some qlog events can contain `QuicFrames`. If this is detected `true` is
+ /// returned and the streamer enters a frame-serialization mode that is only
+ /// concluded by `finish_frames()`. In this mode, attempts to log additional
+ /// events are ignored.
+ ///
+ /// If the event contains no array of `QuicFrames` return `false`.
+ pub fn add_event(&mut self, event: event::Event) -> Result<bool> {
+ if self.state != StreamerState::Ready {
+ return Err(Error::InvalidState);
+ }
+
+ let event_time = if cfg!(test) {
+ std::time::Duration::from_secs(0)
+ } else {
+ self.start_time.elapsed()
+ };
+
+ let rel = match &self.qlog.traces[0].configuration {
+ Some(conf) => match conf.time_units {
+ Some(TimeUnits::Ms) => event_time.as_millis().to_string(),
+
+ Some(TimeUnits::Us) => event_time.as_micros().to_string(),
+
+ None => String::from(""),
+ },
+
+ None => String::from(""),
+ };
+
+ let (ev_data, contains_frames) = match serde_json::to_string(&event.data)
+ {
+ Ok(mut ev_data_out) =>
+ if let Some(f) = event.data.contains_quic_frames() {
+ ev_data_out.truncate(ev_data_out.len() - 2);
+
+ if f == 0 {
+ self.first_frame = true;
+ }
+
+ (ev_data_out, true)
+ } else {
+ (ev_data_out, false)
+ },
+
+ _ => return Err(Error::Done),
+ };
+
+ let maybe_comma = if self.first_event {
+ self.first_event = false;
+ ""
+ } else {
+ ","
+ };
+
+ let maybe_terminate = if contains_frames { "" } else { "]" };
+
+ let ev_time = serde_json::to_string(&EventField::RelativeTime(rel)).ok();
+ let ev_cat =
+ serde_json::to_string(&EventField::Category(event.category)).ok();
+ let ev_ty = serde_json::to_string(&EventField::Event(event.ty)).ok();
+
+ if let (Some(ev_time), Some(ev_cat), Some(ev_ty)) =
+ (ev_time, ev_cat, ev_ty)
+ {
+ let out = format!(
+ "{}[{},{},{},{}{}",
+ maybe_comma, ev_time, ev_cat, ev_ty, ev_data, maybe_terminate
+ );
+
+ self.writer.as_mut().write_all(out.as_bytes())?;
+
+ if contains_frames {
+ self.state = StreamerState::WritingFrames
+ } else {
+ self.state = StreamerState::Ready
+ };
+
+ return Ok(contains_frames);
+ }
+
+ Err(Error::Done)
+ }
+
+ /// Writes a JSON-serialized `QuicFrame`.
+ ///
+ /// Only valid while in the frame-serialization mode.
+ pub fn add_frame(&mut self, frame: QuicFrame, last: bool) -> Result<()> {
+ if self.state != StreamerState::WritingFrames {
+ return Err(Error::InvalidState);
+ }
+
+ match serde_json::to_string(&frame) {
+ Ok(mut out) => {
+ if !self.first_frame {
+ out.insert(0, ',');
+ } else {
+ self.first_frame = false;
+ }
+
+ self.writer.as_mut().write_all(out.as_bytes())?;
+
+ if last {
+ self.finish_frames()?;
+ }
+ },
+
+ _ => return Err(Error::Done),
+ }
+
+ Ok(())
+ }
+
+ /// Concludes `QuicFrame` streaming serialization.
+ ///
+ /// Only valid while in the frame-serialization mode.
+ pub fn finish_frames(&mut self) -> Result<()> {
+ if self.state != StreamerState::WritingFrames {
+ return Err(Error::InvalidState);
+ }
+
+ self.writer.as_mut().write_all(b"]}]")?;
+ self.state = StreamerState::Ready;
+
+ Ok(())
+ }
+
+ /// Returns the writer.
+ #[allow(clippy::borrowed_box)]
+ pub fn writer(&self) -> &Box<dyn std::io::Write + Send + Sync> {
+ &self.writer
+ }
+}
+
+#[serde_with::skip_serializing_none]
+#[derive(Serialize, Clone)]
+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 event_fields: Vec<String>,
+
+ pub events: Vec<Vec<EventField>>,
+}
+
+/// Helper functions for using a qlog trace.
+impl Trace {
+ /// Creates a new qlog trace with the hard-coded event_fields
+ /// ["relative_time", "category", "event", "data"]
+ 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,
+ event_fields: vec![
+ "relative_time".to_string(),
+ "category".to_string(),
+ "event".to_string(),
+ "data".to_string(),
+ ],
+ events: Vec::new(),
+ }
+ }
+
+ pub fn push_event(
+ &mut self, relative_time: std::time::Duration, event: crate::event::Event,
+ ) {
+ let rel = match &self.configuration {
+ Some(conf) => match conf.time_units {
+ Some(TimeUnits::Ms) => relative_time.as_millis().to_string(),
+
+ Some(TimeUnits::Us) => relative_time.as_micros().to_string(),
+
+ None => String::from(""),
+ },
+
+ None => String::from(""),
+ };
+
+ self.events.push(vec![
+ EventField::RelativeTime(rel),
+ EventField::Category(event.category),
+ EventField::Event(event.ty),
+ EventField::Data(event.data),
+ ]);
+ }
+}
+
+#[serde_with::skip_serializing_none]
+#[derive(Serialize, Clone)]
+pub struct VantagePoint {
+ pub name: Option<String>,
+
+ #[serde(rename = "type")]
+ pub ty: VantagePointType,
+
+ pub flow: Option<VantagePointType>,
+}
+
+#[derive(Serialize, Clone)]
+#[serde(rename_all = "snake_case")]
+pub enum VantagePointType {
+ Client,
+ Server,
+ Network,
+ Unknown,
+}
+
+#[derive(Serialize, Clone)]
+#[serde(rename_all = "snake_case")]
+pub enum TimeUnits {
+ Ms,
+ Us,
+}
+
+#[serde_with::skip_serializing_none]
+#[derive(Serialize, Clone)]
+pub struct Configuration {
+ pub time_units: Option<TimeUnits>,
+ pub time_offset: Option<String>,
+
+ pub original_uris: Option<Vec<String>>,
+ /* TODO
+ * additionalUserSpecifiedProperty */
+}
+
+impl Default for Configuration {
+ fn default() -> Self {
+ Configuration {
+ time_units: Some(TimeUnits::Ms),
+ time_offset: Some("0".to_string()),
+ original_uris: None,
+ }
+ }
+}
+
+#[serde_with::skip_serializing_none]
+#[derive(Serialize, Clone, Default)]
+pub struct CommonFields {
+ pub group_id: Option<String>,
+ pub protocol_type: Option<String>,
+
+ pub reference_time: Option<String>,
+ /* TODO
+ * additionalUserSpecifiedProperty */
+}
+
+#[derive(Serialize, Clone)]
+#[serde(untagged)]
+pub enum EventType {
+ ConnectivityEventType(ConnectivityEventType),
+
+ TransportEventType(TransportEventType),
+
+ SecurityEventType(SecurityEventType),
+
+ RecoveryEventType(RecoveryEventType),
+
+ Http3EventType(Http3EventType),
+
+ QpackEventType(QpackEventType),
+
+ GenericEventType(GenericEventType),
+}
+
+#[derive(Serialize, Clone)]
+#[serde(untagged)]
+#[allow(clippy::large_enum_variant)]
+pub enum EventField {
+ RelativeTime(String),
+
+ Category(EventCategory),
+
+ Event(EventType),
+
+ Data(EventData),
+}
+
+#[derive(Serialize, Clone)]
+#[serde(rename_all = "snake_case")]
+pub enum EventCategory {
+ Connectivity,
+ Security,
+ Transport,
+ Recovery,
+ Http,
+ Qpack,
+
+ Error,
+ Warning,
+ Info,
+ Debug,
+ Verbose,
+ Simulation,
+}
+
+#[derive(Serialize, Clone)]
+#[serde(rename_all = "snake_case")]
+pub enum ConnectivityEventType {
+ ServerListening,
+ ConnectionStarted,
+ ConnectionIdUpdated,
+ SpinBitUpdated,
+ ConnectionStateUpdated,
+}
+
+#[derive(Serialize, Clone)]
+#[serde(rename_all = "snake_case")]
+pub enum TransportEventType {
+ ParametersSet,
+
+ DatagramsSent,
+ DatagramsReceived,
+ DatagramDropped,
+
+ PacketSent,
+ PacketReceived,
+ PacketDropped,
+ PacketBuffered,
+
+ FramesProcessed,
+
+ StreamStateUpdated,
+}
+
+#[derive(Serialize, Clone)]
+#[serde(rename_all = "snake_case")]
+pub enum TransportEventTrigger {
+ Line,
+ Retransmit,
+ KeysUnavailable,
+}
+
+#[derive(Serialize, Clone)]
+#[serde(rename_all = "snake_case")]
+pub enum SecurityEventType {
+ KeyUpdated,
+ KeyRetired,
+}
+
+#[derive(Serialize, Clone)]
+#[serde(rename_all = "snake_case")]
+pub enum SecurityEventTrigger {
+ Tls,
+ Implicit,
+ RemoteUpdate,
+ LocalUpdate,
+}
+
+#[derive(Serialize, Clone)]
+#[serde(rename_all = "snake_case")]
+pub enum RecoveryEventType {
+ ParametersSet,
+ MetricsUpdated,
+ CongestionStateUpdated,
+ LossTimerSet,
+ LossTimerTriggered,
+ PacketLost,
+ MarkedForRetransmit,
+}
+
+#[derive(Serialize, Clone)]
+#[serde(rename_all = "snake_case")]
+pub enum RecoveryEventTrigger {
+ AckReceived,
+ PacketSent,
+ Alarm,
+ Unknown,
+}
+
+// ================================================================== //
+
+#[derive(Serialize, Clone)]
+#[serde(rename_all = "snake_case")]
+pub enum KeyType {
+ ServerInitialSecret,
+ ClientInitialSecret,
+
+ ServerHandshakeSecret,
+ ClientHandshakeSecret,
+
+ Server0RttSecret,
+ Client0RttSecret,
+
+ Server1RttSecret,
+ Client1RttSecret,
+}
+
+#[derive(Serialize, Clone)]
+#[serde(rename_all = "snake_case")]
+pub enum ConnectionState {
+ Attempted,
+ Reset,
+ Handshake,
+ Active,
+ Keepalive,
+ Draining,
+ Closed,
+}
+
+#[derive(Serialize, Clone)]
+#[serde(rename_all = "snake_case")]
+pub enum TransportOwner {
+ Local,
+ Remote,
+}
+
+#[derive(Serialize, Clone)]
+pub struct PreferredAddress {
+ pub ip_v4: String,
+ pub ip_v6: String,
+
+ pub port_v4: u64,
+ pub port_v6: u64,
+
+ pub connection_id: String,
+ pub stateless_reset_token: String,
+}
+
+#[derive(Serialize, Clone)]
+#[serde(rename_all = "snake_case")]
+pub enum StreamSide {
+ Sending,
+ Receiving,
+}
+
+#[derive(Serialize, Clone)]
+#[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, Clone)]
+#[serde(rename_all = "snake_case")]
+pub enum TimerType {
+ Ack,
+ Pto,
+}
+
+#[derive(Serialize, Clone)]
+#[serde(rename_all = "snake_case")]
+pub enum H3Owner {
+ Local,
+ Remote,
+}
+
+#[derive(Serialize, Clone)]
+#[serde(rename_all = "snake_case")]
+pub enum H3StreamType {
+ Data,
+ Control,
+ Push,
+ Reserved,
+ QpackEncode,
+ QpackDecode,
+}
+
+#[derive(Serialize, Clone)]
+#[serde(rename_all = "snake_case")]
+pub enum H3DataRecipient {
+ Application,
+ Transport,
+}
+
+#[derive(Serialize, Clone)]
+#[serde(rename_all = "snake_case")]
+pub enum H3PushDecision {
+ Claimed,
+ Abandoned,
+}
+
+#[derive(Serialize, Clone)]
+#[serde(rename_all = "snake_case")]
+pub enum QpackOwner {
+ Local,
+ Remote,
+}
+
+#[derive(Serialize, Clone)]
+#[serde(rename_all = "snake_case")]
+pub enum QpackStreamState {
+ Blocked,
+ Unblocked,
+}
+
+#[derive(Serialize, Clone)]
+#[serde(rename_all = "snake_case")]
+pub enum QpackUpdateType {
+ Added,
+ Evicted,
+}
+
+#[derive(Serialize, Clone)]
+pub struct QpackDynamicTableEntry {
+ pub index: u64,
+ pub name: Option<String>,
+ pub value: Option<String>,
+}
+
+#[derive(Serialize, Clone)]
+pub struct QpackHeaderBlockPrefix {
+ pub required_insert_count: u64,
+ pub sign_bit: bool,
+ pub delta_base: u64,
+}
+
+#[serde_with::skip_serializing_none]
+#[derive(Serialize, Clone)]
+#[serde(untagged)]
+#[allow(clippy::large_enum_variant)]
+pub enum EventData {
+ // ================================================================== //
+ // CONNECTIVITY
+ ServerListening {
+ ip_v4: Option<String>,
+ ip_v6: Option<String>,
+ port_v4: u64,
+ port_v6: u64,
+
+ quic_versions: Option<Vec<String>>,
+ alpn_values: Option<Vec<String>>,
+
+ stateless_reset_required: Option<bool>,
+ },
+
+ ConnectionStarted {
+ ip_version: String,
+ src_ip: String,
+ dst_ip: String,
+
+ protocol: Option<String>,
+ src_port: u64,
+ dst_port: u64,
+
+ quic_version: Option<String>,
+ src_cid: Option<String>,
+ dst_cid: Option<String>,
+ },
+
+ ConnectionIdUpdated {
+ src_old: Option<String>,
+ src_new: Option<String>,
+
+ dst_old: Option<String>,
+ dst_new: Option<String>,
+ },
+
+ SpinBitUpdated {
+ state: bool,
+ },
+
+ ConnectionStateUpdated {
+ old: Option<ConnectionState>,
+ new: ConnectionState,
+ },
+
+ // ================================================================== //
+ // SECURITY
+ KeyUpdated {
+ key_type: KeyType,
+ old: Option<String>,
+ new: String,
+ generation: Option<u64>,
+ },
+
+ KeyRetired {
+ key_type: KeyType,
+ key: Option<String>,
+ generation: Option<u64>,
+ },
+
+ // ================================================================== //
+ // TRANSPORT
+ TransportParametersSet {
+ owner: Option<TransportOwner>,
+
+ resumption_allowed: Option<bool>,
+ early_data_enabled: Option<bool>,
+ alpn: Option<String>,
+ version: Option<String>,
+ tls_cipher: Option<String>,
+
+ original_connection_id: Option<String>,
+ stateless_reset_token: Option<String>,
+ disable_active_migration: Option<bool>,
+
+ idle_timeout: Option<u64>,
+ max_packet_size: Option<u64>,
+ ack_delay_exponent: Option<u64>,
+ max_ack_delay: Option<u64>,
+ active_connection_id_limit: Option<u64>,
+
+ initial_max_data: Option<String>,
+ initial_max_stream_data_bidi_local: Option<String>,
+ initial_max_stream_data_bidi_remote: Option<String>,
+ initial_max_stream_data_uni: Option<String>,
+ initial_max_streams_bidi: Option<String>,
+ initial_max_streams_uni: Option<String>,
+
+ preferred_address: Option<PreferredAddress>,
+ },
+
+ DatagramsReceived {
+ count: Option<u64>,
+ byte_length: Option<u64>,
+ },
+
+ DatagramsSent {
+ count: Option<u64>,
+ byte_length: Option<u64>,
+ },
+
+ DatagramDropped {
+ byte_length: Option<u64>,
+ },
+
+ PacketReceived {
+ packet_type: PacketType,
+ 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.
+ is_coalesced: Option<bool>,
+
+ raw_encrypted: Option<String>,
+ raw_decrypted: Option<String>,
+ frames: Option<Vec<QuicFrame>>,
+ },
+
+ PacketSent {
+ packet_type: PacketType,
+ 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.
+ is_coalesced: Option<bool>,
+
+ raw_encrypted: Option<String>,
+ raw_decrypted: Option<String>,
+ frames: Option<Vec<QuicFrame>>,
+ },
+
+ PacketDropped {
+ packet_type: Option<PacketType>,
+ packet_size: Option<u64>,
+
+ raw: Option<String>,
+ },
+
+ PacketBuffered {
+ packet_type: PacketType,
+ packet_number: String,
+ },
+
+ StreamStateUpdated {
+ stream_id: String,
+ stream_type: Option<StreamType>,
+
+ old: Option<StreamState>,
+ new: StreamState,
+
+ stream_side: Option<StreamSide>,
+ },
+
+ FramesProcessed {
+ frames: Vec<QuicFrame>,
+ },
+
+ // ================================================================== //
+ // RECOVERY
+ RecoveryParametersSet {
+ reordering_threshold: Option<u64>,
+ time_threshold: Option<u64>,
+ timer_granularity: Option<u64>,
+ initial_rtt: Option<u64>,
+
+ max_datagram_size: Option<u64>,
+ initial_congestion_window: Option<u64>,
+ minimum_congestion_window: Option<u64>,
+ loss_reduction_factor: Option<u64>,
+ persistent_congestion_threshold: Option<u64>,
+ },
+
+ MetricsUpdated {
+ min_rtt: Option<u64>,
+ smoothed_rtt: Option<u64>,
+ latest_rtt: Option<u64>,
+ rtt_variance: Option<u64>,
+
+ max_ack_delay: Option<u64>,
+ pto_count: Option<u64>,
+
+ congestion_window: Option<u64>,
+ bytes_in_flight: Option<u64>,
+
+ ssthresh: Option<u64>,
+
+ // qlog defined
+ packets_in_flight: Option<u64>,
+ in_recovery: Option<bool>,
+
+ pacing_rate: Option<u64>,
+ },
+
+ CongestionStateUpdated {
+ old: Option<String>,
+ new: String,
+ },
+
+ LossTimerSet {
+ timer_type: Option<TimerType>,
+ timeout: Option<String>,
+ },
+
+ PacketLost {
+ packet_type: PacketType,
+ packet_number: String,
+
+ header: Option<PacketHeader>,
+ frames: Vec<QuicFrame>,
+ },
+
+ MarkedForRetransmit {
+ frames: Vec<QuicFrame>,
+ },
+
+ // ================================================================== //
+ // HTTP/3
+ H3ParametersSet {
+ owner: Option<H3Owner>,
+
+ max_header_list_size: Option<u64>,
+ max_table_capacity: Option<u64>,
+ blocked_streams_count: Option<u64>,
+
+ push_allowed: Option<bool>,
+
+ waits_for_settings: Option<bool>,
+ },
+
+ H3StreamTypeSet {
+ stream_id: String,
+ owner: Option<H3Owner>,
+
+ old: Option<H3StreamType>,
+ new: H3StreamType,
+ },
+
+ H3FrameCreated {
+ stream_id: String,
+ frame: Http3Frame,
+ byte_length: Option<String>,
+
+ raw: Option<String>,
+ },
+
+ H3FrameParsed {
+ stream_id: String,
+ frame: Http3Frame,
+ byte_length: Option<String>,
+
+ raw: Option<String>,
+ },
+
+ H3DataMoved {
+ stream_id: String,
+ offset: Option<String>,
+ length: Option<u64>,
+
+ from: Option<H3DataRecipient>,
+ to: Option<H3DataRecipient>,
+
+ raw: Option<String>,
+ },
+
+ H3PushResolved {
+ push_id: Option<String>,
+ stream_id: Option<String>,
+
+ decision: Option<H3PushDecision>,
+ },
+
+ // ================================================================== //
+ // QPACK
+ QpackStateUpdated {
+ owner: Option<QpackOwner>,
+
+ dynamic_table_capacity: Option<u64>,
+ dynamic_table_size: Option<u64>,
+
+ known_received_count: Option<u64>,
+ current_insert_count: Option<u64>,
+ },
+
+ QpackStreamStateUpdated {
+ stream_id: String,
+
+ state: QpackStreamState,
+ },
+
+ QpackDynamicTableUpdated {
+ update_type: QpackUpdateType,
+
+ entries: Vec<QpackDynamicTableEntry>,
+ },
+
+ QpackHeadersEncoded {
+ stream_id: Option<String>,
+
+ headers: Option<HttpHeader>,
+
+ block_prefix: QpackHeaderBlockPrefix,
+ header_block: Vec<QpackHeaderBlockRepresentation>,
+
+ raw: Option<String>,
+ },
+
+ QpackHeadersDecoded {
+ stream_id: Option<String>,
+
+ headers: Option<HttpHeader>,
+
+ block_prefix: QpackHeaderBlockPrefix,
+ header_block: Vec<QpackHeaderBlockRepresentation>,
+
+ raw: Option<String>,
+ },
+
+ QpackInstructionSent {
+ instruction: QPackInstruction,
+ byte_length: Option<String>,
+
+ raw: Option<String>,
+ },
+
+ QpackInstructionReceived {
+ instruction: QPackInstruction,
+ byte_length: Option<String>,
+
+ raw: Option<String>,
+ },
+
+ // ================================================================== //
+ // Generic
+ ConnectionError {
+ code: Option<ConnectionErrorCode>,
+ description: Option<String>,
+ },
+
+ ApplicationError {
+ code: Option<ApplicationErrorCode>,
+ description: Option<String>,
+ },
+
+ InternalError {
+ code: Option<u64>,
+ description: Option<String>,
+ },
+
+ InternalWarning {
+ code: Option<u64>,
+ description: Option<String>,
+ },
+
+ Message {
+ message: String,
+ },
+
+ 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 { frames, .. } |
+ EventData::PacketReceived { frames, .. } =>
+ if let Some(f) = frames {
+ Some(f.len())
+ } else {
+ None
+ },
+
+ EventData::PacketLost { frames, .. } |
+ EventData::MarkedForRetransmit { frames } |
+ EventData::FramesProcessed { frames } => Some(frames.len()),
+
+ _ => None,
+ }
+ }
+}
+
+#[derive(Serialize, Clone)]
+#[serde(rename_all = "snake_case")]
+pub enum PacketType {
+ Initial,
+ Handshake,
+
+ #[serde(rename = "0RTT")]
+ ZeroRtt,
+
+ #[serde(rename = "1RTT")]
+ OneRtt,
+
+ Retry,
+ VersionNegotiation,
+ Unknown,
+}
+
+#[derive(Serialize, Clone)]
+#[serde(rename_all = "snake_case")]
+pub enum Http3EventType {
+ ParametersSet,
+ StreamTypeSet,
+ FrameCreated,
+ FrameParsed,
+ DataMoved,
+ PushResolved,
+}
+
+#[derive(Serialize, Clone)]
+#[serde(rename_all = "snake_case")]
+pub enum QpackEventType {
+ StateUpdated,
+ StreamStateUpdated,
+ DynamicTableUpdated,
+ HeadersEncoded,
+ HeadersDecoded,
+ InstructionSent,
+ InstructionReceived,
+}
+
+#[derive(Serialize, Clone)]
+#[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,
+}
+
+// TODO: search for pub enum Error { to see how best to encode errors in qlog.
+#[serde_with::skip_serializing_none]
+#[derive(Clone, Serialize)]
+pub struct PacketHeader {
+ pub packet_number: String,
+ pub packet_size: Option<u64>,
+ pub payload_length: Option<u64>,
+ pub version: Option<String>,
+ pub scil: Option<String>,
+ pub dcil: Option<String>,
+ pub scid: Option<String>,
+ pub dcid: Option<String>,
+}
+
+impl PacketHeader {
+ /// Creates a new PacketHeader.
+ pub fn new(
+ packet_number: u64, packet_size: Option<u64>,
+ payload_length: Option<u64>, version: Option<u32>, scid: Option<&[u8]>,
+ dcid: Option<&[u8]>,
+ ) -> Self {
+ let (scil, scid) = match scid {
+ Some(cid) => (
+ Some(cid.len().to_string()),
+ Some(format!("{}", HexSlice::new(&cid))),
+ ),
+
+ None => (None, None),
+ };
+
+ let (dcil, dcid) = match dcid {
+ Some(cid) => (
+ Some(cid.len().to_string()),
+ Some(format!("{}", HexSlice::new(&cid))),
+ ),
+
+ None => (None, None),
+ };
+
+ let version = match version {
+ Some(v) => Some(format!("{:x?}", v)),
+
+ None => None,
+ };
+
+ PacketHeader {
+ packet_number: packet_number.to_string(),
+ packet_size,
+ payload_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: u64, packet_size: Option<u64>,
+ payload_length: Option<u64>, version: Option<u32>, scid: Option<&[u8]>,
+ dcid: Option<&[u8]>,
+ ) -> Self {
+ match ty {
+ PacketType::OneRtt => PacketHeader::new(
+ packet_number,
+ packet_size,
+ payload_length,
+ None,
+ None,
+ None,
+ ),
+
+ _ => PacketHeader::new(
+ packet_number,
+ packet_size,
+ payload_length,
+ version,
+ scid,
+ dcid,
+ ),
+ }
+ }
+}
+
+#[derive(Serialize, Clone)]
+#[serde(rename_all = "snake_case")]
+pub enum StreamType {
+ Bidirectional,
+ Unidirectional,
+}
+
+#[derive(Serialize, Clone)]
+#[serde(rename_all = "snake_case")]
+pub enum ErrorSpace {
+ TransportError,
+ ApplicationError,
+}
+
+#[derive(Serialize, Clone)]
+#[serde(rename_all = "snake_case")]
+pub enum GenericEventType {
+ ConnectionError,
+ ApplicationError,
+ InternalError,
+ InternalWarning,
+
+ Message,
+ Marker,
+}
+
+#[derive(Serialize, Clone)]
+#[serde(untagged)]
+pub enum ConnectionErrorCode {
+ TransportError(TransportError),
+ CryptoError(CryptoError),
+ Value(u64),
+}
+
+#[derive(Serialize, Clone)]
+#[serde(untagged)]
+pub enum ApplicationErrorCode {
+ ApplicationError(ApplicationError),
+ Value(u64),
+}
+
+#[derive(Serialize, Clone)]
+#[serde(rename_all = "snake_case")]
+pub enum TransportError {
+ NoError,
+ InternalError,
+ ServerBusy,
+ FlowControlError,
+ StreamLimitError,
+ StreamStateError,
+ FinalSizeError,
+ FrameEncodingError,
+ TransportParameterError,
+ ProtocolViolation,
+ InvalidMigration,
+ CryptoBufferExceeded,
+ Unknown,
+}
+
+// TODO
+#[derive(Serialize, Clone)]
+#[serde(rename_all = "snake_case")]
+pub enum CryptoError {
+ Prefix,
+}
+
+#[derive(Serialize, Clone)]
+#[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,
+}
+
+#[serde_with::skip_serializing_none]
+#[derive(Serialize, Clone)]
+#[serde(untagged)]
+pub enum QuicFrame {
+ Padding {
+ frame_type: QuicFrameTypeName,
+ },
+
+ Ping {
+ frame_type: QuicFrameTypeName,
+ },
+
+ Ack {
+ frame_type: QuicFrameTypeName,
+ ack_delay: Option<String>,
+ acked_ranges: Option<Vec<(u64, u64)>>,
+
+ ect1: Option<String>,
+
+ ect0: Option<String>,
+
+ ce: Option<String>,
+ },
+
+ ResetStream {
+ frame_type: QuicFrameTypeName,
+ stream_id: String,
+ error_code: u64,
+ final_size: String,
+ },
+
+ StopSending {
+ frame_type: QuicFrameTypeName,
+ stream_id: String,
+ error_code: u64,
+ },
+
+ Crypto {
+ frame_type: QuicFrameTypeName,
+ offset: String,
+ length: String,
+ },
+
+ NewToken {
+ frame_type: QuicFrameTypeName,
+ length: String,
+ token: String,
+ },
+
+ Stream {
+ frame_type: QuicFrameTypeName,
+ stream_id: String,
+ offset: String,
+ length: String,
+ fin: bool,
+
+ raw: Option<String>,
+ },
+
+ MaxData {
+ frame_type: QuicFrameTypeName,
+ maximum: String,
+ },
+
+ MaxStreamData {
+ frame_type: QuicFrameTypeName,
+ stream_id: String,
+ maximum: String,
+ },
+
+ MaxStreams {
+ frame_type: QuicFrameTypeName,
+ stream_type: StreamType,
+ maximum: String,
+ },
+
+ DataBlocked {
+ frame_type: QuicFrameTypeName,
+ limit: String,
+ },
+
+ StreamDataBlocked {
+ frame_type: QuicFrameTypeName,
+ stream_id: String,
+ limit: String,
+ },
+
+ StreamsBlocked {
+ frame_type: QuicFrameTypeName,
+ stream_type: StreamType,
+ limit: String,
+ },
+
+ NewConnectionId {
+ frame_type: QuicFrameTypeName,
+ sequence_number: String,
+ retire_prior_to: String,
+ length: u64,
+ connection_id: String,
+ reset_token: String,
+ },
+
+ RetireConnectionId {
+ frame_type: QuicFrameTypeName,
+ sequence_number: String,
+ },
+
+ PathChallenge {
+ frame_type: QuicFrameTypeName,
+
+ data: Option<String>,
+ },
+
+ PathResponse {
+ frame_type: QuicFrameTypeName,
+
+ data: Option<String>,
+ },
+
+ ConnectionClose {
+ frame_type: QuicFrameTypeName,
+ error_space: ErrorSpace,
+ error_code: u64,
+ raw_error_code: u64,
+ reason: String,
+
+ trigger_frame_type: Option<String>,
+ },
+
+ HandshakeDone {
+ frame_type: QuicFrameTypeName,
+ },
+
+ Datagram {
+ frame_type: QuicFrameTypeName,
+ length: String,
+
+ raw: Option<String>,
+ },
+
+ Unknown {
+ frame_type: QuicFrameTypeName,
+ raw_frame_type: u64,
+ },
+}
+
+impl QuicFrame {
+ pub fn padding() -> Self {
+ QuicFrame::Padding {
+ frame_type: QuicFrameTypeName::Padding,
+ }
+ }
+
+ pub fn ping() -> Self {
+ QuicFrame::Ping {
+ frame_type: QuicFrameTypeName::Ping,
+ }
+ }
+
+ pub fn ack(
+ ack_delay: Option<String>, acked_ranges: Option<Vec<(u64, u64)>>,
+ ect1: Option<String>, ect0: Option<String>, ce: Option<String>,
+ ) -> Self {
+ QuicFrame::Ack {
+ frame_type: QuicFrameTypeName::Ack,
+ ack_delay,
+ acked_ranges,
+ ect1,
+ ect0,
+ ce,
+ }
+ }
+
+ pub fn reset_stream(
+ stream_id: String, error_code: u64, final_size: String,
+ ) -> Self {
+ QuicFrame::ResetStream {
+ frame_type: QuicFrameTypeName::ResetStream,
+ stream_id,
+ error_code,
+ final_size,
+ }
+ }
+
+ pub fn stop_sending(stream_id: String, error_code: u64) -> Self {
+ QuicFrame::StopSending {
+ frame_type: QuicFrameTypeName::StopSending,
+ stream_id,
+ error_code,
+ }
+ }
+
+ pub fn crypto(offset: String, length: String) -> Self {
+ QuicFrame::Crypto {
+ frame_type: QuicFrameTypeName::Crypto,
+ offset,
+ length,
+ }
+ }
+
+ pub fn new_token(length: String, token: String) -> Self {
+ QuicFrame::NewToken {
+ frame_type: QuicFrameTypeName::NewToken,
+ length,
+ token,
+ }
+ }
+
+ pub fn stream(
+ stream_id: String, offset: String, length: String, fin: bool,
+ raw: Option<String>,
+ ) -> Self {
+ QuicFrame::Stream {
+ frame_type: QuicFrameTypeName::Stream,
+ stream_id,
+ offset,
+ length,
+ fin,
+ raw,
+ }
+ }
+
+ pub fn max_data(maximum: String) -> Self {
+ QuicFrame::MaxData {
+ frame_type: QuicFrameTypeName::MaxData,
+ maximum,
+ }
+ }
+
+ pub fn max_stream_data(stream_id: String, maximum: String) -> Self {
+ QuicFrame::MaxStreamData {
+ frame_type: QuicFrameTypeName::MaxStreamData,
+ stream_id,
+ maximum,
+ }
+ }
+
+ pub fn max_streams(stream_type: StreamType, maximum: String) -> Self {
+ QuicFrame::MaxStreams {
+ frame_type: QuicFrameTypeName::MaxStreams,
+ stream_type,
+ maximum,
+ }
+ }
+
+ pub fn data_blocked(limit: String) -> Self {
+ QuicFrame::DataBlocked {
+ frame_type: QuicFrameTypeName::DataBlocked,
+ limit,
+ }
+ }
+
+ pub fn stream_data_blocked(stream_id: String, limit: String) -> Self {
+ QuicFrame::StreamDataBlocked {
+ frame_type: QuicFrameTypeName::StreamDataBlocked,
+ stream_id,
+ limit,
+ }
+ }
+
+ pub fn streams_blocked(stream_type: StreamType, limit: String) -> Self {
+ QuicFrame::StreamsBlocked {
+ frame_type: QuicFrameTypeName::StreamsBlocked,
+ stream_type,
+ limit,
+ }
+ }
+
+ pub fn new_connection_id(
+ sequence_number: String, retire_prior_to: String, length: u64,
+ connection_id: String, reset_token: String,
+ ) -> Self {
+ QuicFrame::NewConnectionId {
+ frame_type: QuicFrameTypeName::NewConnectionId,
+ sequence_number,
+ retire_prior_to,
+ length,
+ connection_id,
+ reset_token,
+ }
+ }
+
+ pub fn retire_connection_id(sequence_number: String) -> Self {
+ QuicFrame::RetireConnectionId {
+ frame_type: QuicFrameTypeName::RetireConnectionId,
+ sequence_number,
+ }
+ }
+
+ pub fn path_challenge(data: Option<String>) -> Self {
+ QuicFrame::PathChallenge {
+ frame_type: QuicFrameTypeName::PathChallenge,
+ data,
+ }
+ }
+
+ pub fn path_response(data: Option<String>) -> Self {
+ QuicFrame::PathResponse {
+ frame_type: QuicFrameTypeName::PathResponse,
+ data,
+ }
+ }
+
+ pub fn connection_close(
+ error_space: ErrorSpace, error_code: u64, raw_error_code: u64,
+ reason: String, trigger_frame_type: Option<String>,
+ ) -> Self {
+ QuicFrame::ConnectionClose {
+ frame_type: QuicFrameTypeName::ConnectionClose,
+ error_space,
+ error_code,
+ raw_error_code,
+ reason,
+ trigger_frame_type,
+ }
+ }
+
+ pub fn handshake_done() -> Self {
+ QuicFrame::HandshakeDone {
+ frame_type: QuicFrameTypeName::HandshakeDone,
+ }
+ }
+
+ pub fn datagram(length: String, raw: Option<String>) -> Self {
+ QuicFrame::Datagram {
+ frame_type: QuicFrameTypeName::Datagram,
+ length,
+ raw,
+ }
+ }
+
+ pub fn unknown(raw_frame_type: u64) -> Self {
+ QuicFrame::Unknown {
+ frame_type: QuicFrameTypeName::Unknown,
+ raw_frame_type,
+ }
+ }
+}
+
+// ================================================================== //
+#[derive(Serialize, Clone)]
+#[serde(rename_all = "snake_case")]
+pub enum Http3FrameTypeName {
+ Data,
+ Headers,
+ CancelPush,
+ Settings,
+ PushPromise,
+ Goaway,
+ MaxPushId,
+ DuplicatePush,
+ Reserved,
+ Unknown,
+}
+
+#[derive(Serialize, Clone)]
+pub struct HttpHeader {
+ pub name: String,
+ pub value: String,
+}
+
+#[derive(Serialize, Clone)]
+pub struct Setting {
+ pub name: String,
+ pub value: String,
+}
+
+#[derive(Serialize, Clone)]
+pub enum Http3Frame {
+ Data {
+ frame_type: Http3FrameTypeName,
+
+ raw: Option<String>,
+ },
+
+ Headers {
+ frame_type: Http3FrameTypeName,
+ headers: Vec<HttpHeader>,
+ },
+
+ CancelPush {
+ frame_type: Http3FrameTypeName,
+ push_id: String,
+ },
+
+ Settings {
+ frame_type: Http3FrameTypeName,
+ settings: Vec<Setting>,
+ },
+
+ PushPromise {
+ frame_type: Http3FrameTypeName,
+ push_id: String,
+ headers: Vec<HttpHeader>,
+ },
+
+ Goaway {
+ frame_type: Http3FrameTypeName,
+ stream_id: String,
+ },
+
+ MaxPushId {
+ frame_type: Http3FrameTypeName,
+ push_id: String,
+ },
+
+ DuplicatePush {
+ frame_type: Http3FrameTypeName,
+ push_id: String,
+ },
+
+ Reserved {
+ frame_type: Http3FrameTypeName,
+ },
+
+ Unknown {
+ frame_type: Http3FrameTypeName,
+ },
+}
+
+impl Http3Frame {
+ pub fn data(raw: Option<String>) -> Self {
+ Http3Frame::Data {
+ frame_type: Http3FrameTypeName::Data,
+ raw,
+ }
+ }
+
+ pub fn headers(headers: Vec<HttpHeader>) -> Self {
+ Http3Frame::Headers {
+ frame_type: Http3FrameTypeName::Headers,
+ headers,
+ }
+ }
+
+ pub fn cancel_push(push_id: String) -> Self {
+ Http3Frame::CancelPush {
+ frame_type: Http3FrameTypeName::CancelPush,
+ push_id,
+ }
+ }
+
+ pub fn settings(settings: Vec<Setting>) -> Self {
+ Http3Frame::Settings {
+ frame_type: Http3FrameTypeName::Settings,
+ settings,
+ }
+ }
+
+ pub fn push_promise(push_id: String, headers: Vec<HttpHeader>) -> Self {
+ Http3Frame::PushPromise {
+ frame_type: Http3FrameTypeName::PushPromise,
+ push_id,
+ headers,
+ }
+ }
+
+ pub fn goaway(stream_id: String) -> Self {
+ Http3Frame::Goaway {
+ frame_type: Http3FrameTypeName::Goaway,
+ stream_id,
+ }
+ }
+
+ pub fn max_push_id(push_id: String) -> Self {
+ Http3Frame::MaxPushId {
+ frame_type: Http3FrameTypeName::MaxPushId,
+ push_id,
+ }
+ }
+
+ pub fn duplicate_push(push_id: String) -> Self {
+ Http3Frame::DuplicatePush {
+ frame_type: Http3FrameTypeName::DuplicatePush,
+ push_id,
+ }
+ }
+
+ pub fn reserved() -> Self {
+ Http3Frame::Reserved {
+ frame_type: Http3FrameTypeName::Reserved,
+ }
+ }
+
+ pub fn unknown() -> Self {
+ Http3Frame::Unknown {
+ frame_type: Http3FrameTypeName::Unknown,
+ }
+ }
+}
+
+#[derive(Serialize, Clone)]
+#[serde(rename_all = "snake_case")]
+pub enum QpackInstructionTypeName {
+ SetDynamicTableCapacityInstruction,
+ InsertWithNameReferenceInstruction,
+ InsertWithoutNameReferenceInstruction,
+ DuplicateInstruction,
+ HeaderAcknowledgementInstruction,
+ StreamCancellationInstruction,
+ InsertCountIncrementInstruction,
+}
+
+#[derive(Serialize, Clone)]
+#[serde(rename_all = "snake_case")]
+pub enum QpackTableType {
+ Static,
+ Dynamic,
+}
+
+#[derive(Serialize, Clone)]
+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, Clone)]
+#[serde(rename_all = "snake_case")]
+pub enum QpackHeaderBlockRepresentationTypeName {
+ IndexedHeaderField,
+ LiteralHeaderFieldWithName,
+ LiteralHeaderFieldWithoutName,
+}
+
+#[derive(Serialize, Clone)]
+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>,
+ },
+}
+
+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,
+ {
+ match data {
+ Some(d) => Some(format!("{}", HexSlice::new(d))),
+
+ None => None,
+ }
+ }
+}
+
+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, "{:02x}", byte)?;
+ }
+ Ok(())
+ }
+}
+
+#[doc(hidden)]
+pub mod testing {
+ use super::*;
+
+ pub fn make_pkt_hdr() -> PacketHeader {
+ let scid = [0x7e, 0x37, 0xe4, 0xdc, 0xc6, 0x68, 0x2d, 0xa8];
+ let dcid = [0x36, 0xce, 0x10, 0x4e, 0xee, 0x50, 0x10, 0x1c];
+
+ PacketHeader::new(
+ 0,
+ Some(1251),
+ Some(1224),
+ Some(0xff00_0018),
+ 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".to_string()),
+ time_units: Some(TimeUnits::Ms),
+ original_uris: None,
+ }),
+ None,
+ )
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use testing::*;
+
+ #[test]
+ fn packet_header() {
+ let pkt_hdr = make_pkt_hdr();
+
+ let log_string = r#"{
+ "packet_number": "0",
+ "packet_size": 1251,
+ "payload_length": 1224,
+ "version": "ff000018",
+ "scil": "8",
+ "dcil": "8",
+ "scid": "7e37e4dcc6682da8",
+ "dcid": "36ce104eee50101c"
+}"#;
+
+ assert_eq!(serde_json::to_string_pretty(&pkt_hdr).unwrap(), log_string);
+ }
+
+ #[test]
+ fn packet_sent_event_no_frames() {
+ let log_string = r#"{
+ "packet_type": "initial",
+ "header": {
+ "packet_number": "0",
+ "packet_size": 1251,
+ "payload_length": 1224,
+ "version": "ff00001b",
+ "scil": "8",
+ "dcil": "8",
+ "scid": "7e37e4dcc6682da8",
+ "dcid": "36ce104eee50101c"
+ }
+}"#;
+
+ let scid = [0x7e, 0x37, 0xe4, 0xdc, 0xc6, 0x68, 0x2d, 0xa8];
+ let dcid = [0x36, 0xce, 0x10, 0x4e, 0xee, 0x50, 0x10, 0x1c];
+ let pkt_hdr = PacketHeader::new(
+ 0,
+ Some(1251),
+ Some(1224),
+ Some(0xff00001b),
+ Some(&scid),
+ Some(&dcid),
+ );
+
+ let pkt_sent_evt = EventData::PacketSent {
+ raw_encrypted: None,
+ raw_decrypted: None,
+ packet_type: PacketType::Initial,
+ header: pkt_hdr.clone(),
+ frames: None,
+ is_coalesced: None,
+ };
+
+ assert_eq!(
+ serde_json::to_string_pretty(&pkt_sent_evt).unwrap(),
+ log_string
+ );
+ }
+
+ #[test]
+ fn packet_sent_event_some_frames() {
+ let log_string = r#"{
+ "packet_type": "initial",
+ "header": {
+ "packet_number": "0",
+ "packet_size": 1251,
+ "payload_length": 1224,
+ "version": "ff000018",
+ "scil": "8",
+ "dcil": "8",
+ "scid": "7e37e4dcc6682da8",
+ "dcid": "36ce104eee50101c"
+ },
+ "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();
+
+ let mut frames = Vec::new();
+ frames.push(QuicFrame::padding());
+
+ frames.push(QuicFrame::ping());
+
+ frames.push(QuicFrame::stream(
+ "0".to_string(),
+ "0".to_string(),
+ "100".to_string(),
+ true,
+ None,
+ ));
+
+ let pkt_sent_evt = EventData::PacketSent {
+ raw_encrypted: None,
+ raw_decrypted: None,
+ packet_type: PacketType::Initial,
+ header: pkt_hdr.clone(),
+ frames: Some(frames),
+ is_coalesced: None,
+ };
+
+ assert_eq!(
+ serde_json::to_string_pretty(&pkt_sent_evt).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_units": "ms",
+ "time_offset": "0"
+ },
+ "event_fields": [
+ "relative_time",
+ "category",
+ "event",
+ "data"
+ ],
+ "events": []
+}"#;
+
+ let trace = make_trace();
+
+ assert_eq!(serde_json::to_string_pretty(&trace).unwrap(), log_string);
+ }
+
+ #[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_units": "ms",
+ "time_offset": "0"
+ },
+ "event_fields": [
+ "relative_time",
+ "category",
+ "event",
+ "data"
+ ],
+ "events": [
+ [
+ "0",
+ "transport",
+ "packet_sent",
+ {
+ "packet_type": "initial",
+ "header": {
+ "packet_number": "0",
+ "packet_size": 1251,
+ "payload_length": 1224,
+ "version": "ff000018",
+ "scil": "8",
+ "dcil": "8",
+ "scid": "7e37e4dcc6682da8",
+ "dcid": "36ce104eee50101c"
+ },
+ "frames": [
+ {
+ "frame_type": "stream",
+ "stream_id": "0",
+ "offset": "0",
+ "length": "100",
+ "fin": true
+ }
+ ]
+ }
+ ]
+ ]
+}"#;
+
+ let mut trace = make_trace();
+
+ let pkt_hdr = make_pkt_hdr();
+
+ let frames = vec![QuicFrame::stream(
+ "0".to_string(),
+ "0".to_string(),
+ "100".to_string(),
+ true,
+ None,
+ )];
+ let event = event::Event::packet_sent_min(
+ PacketType::Initial,
+ pkt_hdr,
+ Some(frames),
+ );
+
+ trace.push_event(std::time::Duration::new(0, 0), event);
+
+ assert_eq!(serde_json::to_string_pretty(&trace).unwrap(), log_string);
+ }
+
+ #[test]
+ fn test_event_validity() {
+ // Test a single event in each category
+
+ let ev = event::Event::server_listening_min(443, 443);
+ assert!(ev.is_valid());
+
+ let ev = event::Event::transport_parameters_set_min();
+ assert!(ev.is_valid());
+
+ let ev = event::Event::recovery_parameters_set_min();
+ assert!(ev.is_valid());
+
+ let ev = event::Event::h3_parameters_set_min();
+ assert!(ev.is_valid());
+
+ let ev = event::Event::qpack_state_updated_min();
+ assert!(ev.is_valid());
+
+ let ev = event::Event {
+ category: EventCategory::Error,
+ ty: EventType::GenericEventType(GenericEventType::ConnectionError),
+ data: EventData::ConnectionError {
+ code: None,
+ description: None,
+ },
+ };
+
+ assert!(ev.is_valid());
+ }
+
+ #[test]
+ fn bogus_event_validity() {
+ // Test a single event in each category
+
+ let mut ev = event::Event::server_listening_min(443, 443);
+ ev.category = EventCategory::Simulation;
+ assert!(!ev.is_valid());
+
+ let mut ev = event::Event::transport_parameters_set_min();
+ ev.category = EventCategory::Simulation;
+ assert!(!ev.is_valid());
+
+ let mut ev = event::Event::recovery_parameters_set_min();
+ ev.category = EventCategory::Simulation;
+ assert!(!ev.is_valid());
+
+ let mut ev = event::Event::h3_parameters_set_min();
+ ev.category = EventCategory::Simulation;
+ assert!(!ev.is_valid());
+
+ let mut ev = event::Event::qpack_state_updated_min();
+ ev.category = EventCategory::Simulation;
+ assert!(!ev.is_valid());
+
+ let ev = event::Event {
+ category: EventCategory::Error,
+ ty: EventType::GenericEventType(GenericEventType::ConnectionError),
+ data: EventData::FramesProcessed { frames: Vec::new() },
+ };
+
+ assert!(!ev.is_valid());
+ }
+
+ #[test]
+ fn serialization_states() {
+ let v: Vec<u8> = Vec::new();
+ let buff = std::io::Cursor::new(v);
+ let writer = Box::new(buff);
+
+ let mut trace = make_trace();
+ let pkt_hdr = make_pkt_hdr();
+
+ let frame1 = QuicFrame::stream(
+ "40".to_string(),
+ "40".to_string(),
+ "400".to_string(),
+ true,
+ None,
+ );
+
+ let event1 = event::Event::packet_sent_min(
+ PacketType::Handshake,
+ pkt_hdr.clone(),
+ Some(vec![frame1]),
+ );
+
+ trace.push_event(std::time::Duration::new(0, 0), event1);
+
+ let frame2 = QuicFrame::stream(
+ "0".to_string(),
+ "0".to_string(),
+ "100".to_string(),
+ true,
+ None,
+ );
+
+ let frame3 = QuicFrame::stream(
+ "0".to_string(),
+ "0".to_string(),
+ "100".to_string(),
+ true,
+ None,
+ );
+
+ let event2 = event::Event::packet_sent_min(
+ PacketType::Initial,
+ pkt_hdr.clone(),
+ Some(Vec::new()),
+ );
+
+ let event3 = event::Event::packet_sent(
+ PacketType::Initial,
+ pkt_hdr,
+ Some(Vec::new()),
+ None,
+ Some("encrypted_foo".to_string()),
+ Some("decrypted_foo".to_string()),
+ );
+
+ let mut s = QlogStreamer::new(
+ "version".to_string(),
+ Some("title".to_string()),
+ Some("description".to_string()),
+ None,
+ std::time::Instant::now(),
+ trace,
+ writer,
+ );
+
+ // Before the log is started all other operations should fail.
+ assert!(match s.add_event(event2.clone()) {
+ Err(Error::InvalidState) => true,
+ _ => false,
+ });
+ assert!(match s.add_frame(frame2.clone(), false) {
+ Err(Error::InvalidState) => true,
+ _ => false,
+ });
+ assert!(match s.finish_frames() {
+ Err(Error::InvalidState) => true,
+ _ => false,
+ });
+ assert!(match s.finish_log() {
+ Err(Error::InvalidState) => true,
+ _ => false,
+ });
+
+ // Once a log is started, can't write frames before an event.
+ assert!(match s.start_log() {
+ Ok(()) => true,
+ _ => false,
+ });
+ assert!(match s.add_frame(frame2.clone(), true) {
+ Err(Error::InvalidState) => true,
+ _ => false,
+ });
+ assert!(match s.finish_frames() {
+ Err(Error::InvalidState) => true,
+ _ => false,
+ });
+
+ // Some events hold frames; can't write any more events until frame
+ // writing is concluded.
+ assert!(match s.add_event(event2.clone()) {
+ Ok(true) => true,
+ _ => false,
+ });
+ assert!(match s.add_event(event2.clone()) {
+ Err(Error::InvalidState) => true,
+ _ => false,
+ });
+
+ // While writing frames, can't write events.
+ assert!(match s.add_frame(frame2.clone(), false) {
+ Ok(()) => true,
+ _ => false,
+ });
+
+ assert!(match s.add_event(event2.clone()) {
+ Err(Error::InvalidState) => true,
+ _ => false,
+ });
+ assert!(match s.finish_frames() {
+ Ok(()) => true,
+ _ => false,
+ });
+
+ // Adding an event that includes both frames and raw data should
+ // be allowed.
+ assert!(match s.add_event(event3.clone()) {
+ Ok(true) => true,
+ _ => false,
+ });
+ assert!(match s.add_frame(frame3.clone(), false) {
+ Ok(()) => true,
+ _ => false,
+ });
+ assert!(match s.finish_frames() {
+ Ok(()) => true,
+ _ => false,
+ });
+
+ assert!(match s.finish_log() {
+ Ok(()) => true,
+ _ => false,
+ });
+
+ let r = s.writer();
+ let w: &Box<std::io::Cursor<Vec<u8>>> = unsafe { std::mem::transmute(r) };
+
+ let log_string = r#"{"qlog_version":"version","title":"title","description":"description","traces":[{"vantage_point":{"type":"server"},"title":"Quiche qlog trace","description":"Quiche qlog trace description","configuration":{"time_units":"ms","time_offset":"0"},"event_fields":["relative_time","category","event","data"],"events":[["0","transport","packet_sent",{"packet_type":"handshake","header":{"packet_number":"0","packet_size":1251,"payload_length":1224,"version":"ff000018","scil":"8","dcil":"8","scid":"7e37e4dcc6682da8","dcid":"36ce104eee50101c"},"frames":[{"frame_type":"stream","stream_id":"40","offset":"40","length":"400","fin":true}]}],["0","transport","packet_sent",{"packet_type":"initial","header":{"packet_number":"0","packet_size":1251,"payload_length":1224,"version":"ff000018","scil":"8","dcil":"8","scid":"7e37e4dcc6682da8","dcid":"36ce104eee50101c"},"frames":[{"frame_type":"stream","stream_id":"0","offset":"0","length":"100","fin":true}]}],["0","transport","packet_sent",{"packet_type":"initial","header":{"packet_number":"0","packet_size":1251,"payload_length":1224,"version":"ff000018","scil":"8","dcil":"8","scid":"7e37e4dcc6682da8","dcid":"36ce104eee50101c"},"raw_encrypted":"encrypted_foo","raw_decrypted":"decrypted_foo","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);
+ }
+}
+
+pub mod event;