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