qlog/
lib.rs

1// Copyright (C) 2019, Cloudflare, Inc.
2// All rights reserved.
3//
4// Redistribution and use in source and binary forms, with or without
5// modification, are permitted provided that the following conditions are
6// met:
7//
8//     * Redistributions of source code must retain the above copyright notice,
9//       this list of conditions and the following disclaimer.
10//
11//     * Redistributions in binary form must reproduce the above copyright
12//       notice, this list of conditions and the following disclaimer in the
13//       documentation and/or other materials provided with the distribution.
14//
15// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
16// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
17// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
19// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
22// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
23// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
24// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
27//! The qlog crate is an implementation of the qlog [main logging schema],
28//! [QUIC event definitions], and [HTTP/3 and QPACK event definitions].
29//! The crate provides a qlog data model that can be used for traces with
30//! events. It supports serialization and deserialization but defers logging IO
31//! choices to applications.
32//!
33//! Serialization operates in either a [buffered mode] or a [streaming mode].
34//!
35//! The crate uses Serde for conversion between Rust and JSON.
36//!
37//! [main logging schema]: https://datatracker.ietf.org/doc/html/draft-ietf-quic-qlog-main-schema
38//! [QUIC event definitions]:
39//! https://datatracker.ietf.org/doc/html/draft-ietf-quic-qlog-quic-events.html
40//! [HTTP/3 and QPACK event definitions]:
41//! https://datatracker.ietf.org/doc/html/draft-ietf-quic-qlog-h3-events.html
42//! [buffered mode]: #buffered-traces-with-standard-json
43//! [streaming mode]: #streaming-traces-with-json-seq
44//!
45//! Overview
46//! ---------------
47//! qlog is a hierarchical logging format, with a rough structure of:
48//!
49//! * Log
50//!   * Trace(s)
51//!     * Event(s)
52//!
53//! In practice, a single QUIC connection maps to a single Trace file with one
54//! or more Events. Applications can decide whether to combine Traces from
55//! different connections into the same Log.
56//!
57//! ## Buffered Traces with standard JSON
58//!
59//! A [`Trace`] is a single JSON object. It contains metadata such as the
60//! [`VantagePoint`] of capture and the [`Configuration`], and protocol event
61//! data in the [`Event`] array.
62//!
63//! JSON Traces allow applications to appends events to them before eventually
64//! being serialized as a complete JSON object.
65//!
66//! ### Creating a Trace
67//!
68//! ```
69//! let mut trace = qlog::Trace::new(
70//!     qlog::VantagePoint {
71//!         name: Some("Example client".to_string()),
72//!         ty: qlog::VantagePointType::Client,
73//!         flow: None,
74//!     },
75//!     Some("Example qlog trace".to_string()),
76//!     Some("Example qlog trace description".to_string()),
77//!     Some(qlog::Configuration {
78//!         time_offset: Some(0.0),
79//!         original_uris: None,
80//!     }),
81//!     None,
82//! );
83//! ```
84//!
85//! ### Adding events to a Trace
86//!
87//! Qlog [`Event`] objects are added to [`qlog::Trace.events`].
88//!
89//! The following example demonstrates how to log a qlog QUIC `packet_sent`
90//! event containing a single Crypto frame. It constructs the necessary elements
91//! of the [`Event`], then appends it to the trace with [`push_event()`].
92//!
93//! ```
94//! # let mut trace = qlog::Trace::new (
95//! #     qlog::VantagePoint {
96//! #         name: Some("Example client".to_string()),
97//! #         ty: qlog::VantagePointType::Client,
98//! #         flow: None,
99//! #     },
100//! #     Some("Example qlog trace".to_string()),
101//! #     Some("Example qlog trace description".to_string()),
102//! #     Some(qlog::Configuration {
103//! #         time_offset: Some(0.0),
104//! #         original_uris: None,
105//! #     }),
106//! #     None
107//! # );
108//!
109//! let scid = [0x7e, 0x37, 0xe4, 0xdc, 0xc6, 0x68, 0x2d, 0xa8];
110//! let dcid = [0x36, 0xce, 0x10, 0x4e, 0xee, 0x50, 0x10, 0x1c];
111//!
112//! let pkt_hdr = qlog::events::quic::PacketHeader::new(
113//!     qlog::events::quic::PacketType::Initial,
114//!     Some(0),          // packet_number
115//!     None,             // flags
116//!     None,             // token
117//!     None,             // length
118//!     Some(0x00000001), // version
119//!     Some(&scid),
120//!     Some(&dcid),
121//! );
122//!
123//! let frames = vec![qlog::events::quic::QuicFrame::Crypto {
124//!     offset: 0,
125//!     length: 0,
126//! }];
127//!
128//! let raw = qlog::events::RawInfo {
129//!     length: Some(1251),
130//!     payload_length: Some(1224),
131//!     data: None,
132//! };
133//!
134//! let event_data =
135//!     qlog::events::EventData::PacketSent(qlog::events::quic::PacketSent {
136//!         header: pkt_hdr,
137//!         frames: Some(frames.into()),
138//!         is_coalesced: None,
139//!         retry_token: None,
140//!         stateless_reset_token: None,
141//!         supported_versions: None,
142//!         raw: Some(raw),
143//!         datagram_id: None,
144//!         send_at_time: None,
145//!         trigger: None,
146//!     });
147//!
148//! trace.push_event(qlog::events::Event::with_time(0.0, event_data));
149//! ```
150//!
151//! ### Serializing
152//!
153//! The qlog crate has only been tested with `serde_json`, however
154//! other serializer targets might work.
155//!
156//! For example, serializing the trace created above:
157//!
158//! ```
159//! # let mut trace = qlog::Trace::new (
160//! #     qlog::VantagePoint {
161//! #         name: Some("Example client".to_string()),
162//! #         ty: qlog::VantagePointType::Client,
163//! #         flow: None,
164//! #     },
165//! #     Some("Example qlog trace".to_string()),
166//! #     Some("Example qlog trace description".to_string()),
167//! #     Some(qlog::Configuration {
168//! #         time_offset: Some(0.0),
169//! #         original_uris: None,
170//! #     }),
171//! #     None
172//! # );
173//! serde_json::to_string_pretty(&trace).unwrap();
174//! ```
175//!
176//! which would generate the following:
177//!
178//! ```ignore
179//! {
180//!   "vantage_point": {
181//!     "name": "Example client",
182//!     "type": "client"
183//!   },
184//!   "title": "Example qlog trace",
185//!   "description": "Example qlog trace description",
186//!   "configuration": {
187//!     "time_offset": 0.0
188//!   },
189//!   "events": [
190//!     {
191//!       "time": 0.0,
192//!       "name": "transport:packet_sent",
193//!       "data": {
194//!         "header": {
195//!           "packet_type": "initial",
196//!           "packet_number": 0,
197//!           "version": "1",
198//!           "scil": 8,
199//!           "dcil": 8,
200//!           "scid": "7e37e4dcc6682da8",
201//!           "dcid": "36ce104eee50101c"
202//!         },
203//!         "raw": {
204//!           "length": 1251,
205//!           "payload_length": 1224
206//!         },
207//!         "frames": [
208//!           {
209//!             "frame_type": "crypto",
210//!             "offset": 0,
211//!             "length": 0
212//!           }
213//!         ]
214//!       }
215//!     }
216//!   ]
217//! }
218//! ```
219//!
220//! ## Streaming Traces with JSON-SEQ
221//!
222//! To help support streaming serialization of qlogs,
223//! draft-ietf-quic-qlog-main-schema-01 introduced support for RFC 7464 JSON
224//! Text Sequences (JSON-SEQ). The qlog crate supports this format and provides
225//! utilities that aid streaming.
226//!
227//! A [`TraceSeq`] contains metadata such as the [`VantagePoint`] of capture and
228//! the [`Configuration`]. However, protocol event data is handled as separate
229//! lines containing a record separator character, a serialized [`Event`], and a
230//! newline.
231//!
232//! ### Creating a TraceSeq
233//!
234//! ```
235//! let mut trace = qlog::TraceSeq::new(
236//!     qlog::VantagePoint {
237//!         name: Some("Example client".to_string()),
238//!         ty: qlog::VantagePointType::Client,
239//!         flow: None,
240//!     },
241//!     Some("Example qlog trace".to_string()),
242//!     Some("Example qlog trace description".to_string()),
243//!     Some(qlog::Configuration {
244//!         time_offset: Some(0.0),
245//!         original_uris: None,
246//!     }),
247//!     None,
248//! );
249//! ```
250//!
251//! Create an object with the [`Write`] trait:
252//!
253//! ```
254//! let mut file = std::fs::File::create("foo.sqlog").unwrap();
255//! ```
256//!
257//! Create a [`QlogStreamer`] and start serialization to foo.sqlog
258//! using [`start_log()`]:
259//!
260//! ```
261//! # let mut trace = qlog::TraceSeq::new(
262//! #    qlog::VantagePoint {
263//! #        name: Some("Example client".to_string()),
264//! #        ty: qlog::VantagePointType::Client,
265//! #        flow: None,
266//! #    },
267//! #    Some("Example qlog trace".to_string()),
268//! #    Some("Example qlog trace description".to_string()),
269//! #    Some(qlog::Configuration {
270//! #        time_offset: Some(0.0),
271//! #        original_uris: None,
272//! #    }),
273//! #    None,
274//! # );
275//! # let mut file = std::fs::File::create("foo.sqlog").unwrap();
276//! let mut streamer = qlog::streamer::QlogStreamer::new(
277//!     qlog::QLOG_VERSION.to_string(),
278//!     Some("Example qlog".to_string()),
279//!     Some("Example qlog description".to_string()),
280//!     None,
281//!     std::time::Instant::now(),
282//!     trace,
283//!     qlog::events::EventImportance::Base,
284//!     Box::new(file),
285//! );
286//!
287//! streamer.start_log().ok();
288//! ```
289//!
290//! ### Adding events
291//!
292//! Once logging has started you can stream events. Events
293//! are written in one step using one of [`add_event()`],
294//! [`add_event_with_instant()`], [`add_event_now()`],
295//! [`add_event_data_with_instant()`], or [`add_event_data_now()`] :
296//!
297//! ```
298//! # let mut trace = qlog::TraceSeq::new(
299//! #    qlog::VantagePoint {
300//! #        name: Some("Example client".to_string()),
301//! #        ty: qlog::VantagePointType::Client,
302//! #        flow: None,
303//! #    },
304//! #    Some("Example qlog trace".to_string()),
305//! #    Some("Example qlog trace description".to_string()),
306//! #    Some(qlog::Configuration {
307//! #        time_offset: Some(0.0),
308//! #        original_uris: None,
309//! #    }),
310//! #    None,
311//! # );
312//! # let mut file = std::fs::File::create("foo.qlog").unwrap();
313//! # let mut streamer = qlog::streamer::QlogStreamer::new(
314//! #     qlog::QLOG_VERSION.to_string(),
315//! #     Some("Example qlog".to_string()),
316//! #     Some("Example qlog description".to_string()),
317//! #     None,
318//! #     std::time::Instant::now(),
319//! #     trace,
320//! #     qlog::events::EventImportance::Base,
321//! #     Box::new(file),
322//! # );
323//!
324//! let scid = [0x7e, 0x37, 0xe4, 0xdc, 0xc6, 0x68, 0x2d, 0xa8];
325//! let dcid = [0x36, 0xce, 0x10, 0x4e, 0xee, 0x50, 0x10, 0x1c];
326//!
327//! let pkt_hdr = qlog::events::quic::PacketHeader::with_type(
328//!     qlog::events::quic::PacketType::OneRtt,
329//!     Some(0),
330//!     Some(0x00000001),
331//!     Some(&scid),
332//!     Some(&dcid),
333//! );
334//!
335//! let ping = qlog::events::quic::QuicFrame::Ping {
336//!     length: None,
337//!     payload_length: None,
338//! };
339//! let padding = qlog::events::quic::QuicFrame::Padding {
340//!     length: None,
341//!     payload_length: 1234,
342//! };
343//!
344//! let event_data =
345//!     qlog::events::EventData::PacketSent(qlog::events::quic::PacketSent {
346//!         header: pkt_hdr,
347//!         frames: Some(vec![ping, padding].into()),
348//!         is_coalesced: None,
349//!         retry_token: None,
350//!         stateless_reset_token: None,
351//!         supported_versions: None,
352//!         raw: None,
353//!         datagram_id: None,
354//!         send_at_time: None,
355//!         trigger: None,
356//!     });
357//!
358//! let event = qlog::events::Event::with_time(0.0, event_data);
359//!
360//! streamer.add_event(event).ok();
361//! ```
362//!
363//! Once all events have been written, the log
364//! can be finalized with [`finish_log()`]:
365//!
366//! ```
367//! # let mut trace = qlog::TraceSeq::new(
368//! #    qlog::VantagePoint {
369//! #        name: Some("Example client".to_string()),
370//! #        ty: qlog::VantagePointType::Client,
371//! #        flow: None,
372//! #    },
373//! #    Some("Example qlog trace".to_string()),
374//! #    Some("Example qlog trace description".to_string()),
375//! #    Some(qlog::Configuration {
376//! #        time_offset: Some(0.0),
377//! #        original_uris: None,
378//! #    }),
379//! #    None,
380//! # );
381//! # let mut file = std::fs::File::create("foo.qlog").unwrap();
382//! # let mut streamer = qlog::streamer::QlogStreamer::new(
383//! #     qlog::QLOG_VERSION.to_string(),
384//! #     Some("Example qlog".to_string()),
385//! #     Some("Example qlog description".to_string()),
386//! #     None,
387//! #     std::time::Instant::now(),
388//! #     trace,
389//! #     qlog::events::EventImportance::Base,
390//! #     Box::new(file),
391//! # );
392//! streamer.finish_log().ok();
393//! ```
394//!
395//! ### Serializing
396//!
397//! Serialization to JSON occurs as methods on the [`QlogStreamer`]
398//! are called. No additional steps are required.
399//!
400//! [`Trace`]: struct.Trace.html
401//! [`TraceSeq`]: struct.TraceSeq.html
402//! [`VantagePoint`]: struct.VantagePoint.html
403//! [`Configuration`]: struct.Configuration.html
404//! [`qlog::Trace.events`]: struct.Trace.html#structfield.events
405//! [`push_event()`]: struct.Trace.html#method.push_event
406//! [`QlogStreamer`]: struct.QlogStreamer.html
407//! [`Write`]: https://doc.rust-lang.org/std/io/trait.Write.html
408//! [`start_log()`]: streamer/struct.QlogStreamer.html#method.start_log
409//! [`add_event()`]: streamer/struct.QlogStreamer.html#method.add_event
410//! [`add_event_with_instant()`]: streamer/struct.QlogStreamer.html#method.add_event_with_instant
411//! [`add_event_now()`]: streamer/struct.QlogStreamer.html#method.add_event_now
412//! [`add_event_data_with_instant()`]: streamer/struct.QlogStreamer.html#method.add_event_data_with_instant
413//! [`add_event_data_now()`]: streamer/struct.QlogStreamer.html#method.add_event_data_now
414//! [`finish_log()`]: streamer/struct.QlogStreamer.html#method.finish_log
415
416use crate::events::quic::PacketHeader;
417use crate::events::Event;
418
419use serde::Deserialize;
420use serde::Serialize;
421
422/// A quiche qlog error.
423#[derive(Debug)]
424pub enum Error {
425    /// There is no more work to do.
426    Done,
427
428    /// The operation cannot be completed because it was attempted
429    /// in an invalid state.
430    InvalidState,
431
432    // Invalid Qlog format
433    InvalidFormat,
434
435    /// I/O error.
436    IoError(std::io::Error),
437}
438
439impl std::fmt::Display for Error {
440    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
441        write!(f, "{self:?}")
442    }
443}
444
445impl std::error::Error for Error {
446    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
447        None
448    }
449}
450
451impl std::convert::From<std::io::Error> for Error {
452    fn from(err: std::io::Error) -> Self {
453        Error::IoError(err)
454    }
455}
456
457pub const QLOG_VERSION: &str = "0.3";
458
459pub type Bytes = String;
460pub type StatelessResetToken = Bytes;
461
462/// A specialized [`Result`] type for quiche qlog operations.
463///
464/// This type is used throughout the public API for any operation that
465/// can produce an error.
466///
467/// [`Result`]: https://doc.rust-lang.org/std/result/enum.Result.html
468pub type Result<T> = std::result::Result<T, Error>;
469
470#[serde_with::skip_serializing_none]
471#[derive(Serialize, Deserialize, Clone)]
472pub struct Qlog {
473    pub qlog_version: String,
474    pub qlog_format: String,
475    pub title: Option<String>,
476    pub description: Option<String>,
477    pub summary: Option<String>,
478
479    pub traces: Vec<Trace>,
480}
481#[serde_with::skip_serializing_none]
482#[derive(Serialize, Deserialize, Clone, Debug)]
483pub struct QlogSeq {
484    pub qlog_version: String,
485    pub qlog_format: String,
486    pub title: Option<String>,
487    pub description: Option<String>,
488    pub summary: Option<String>,
489
490    pub trace: TraceSeq,
491}
492
493#[derive(Clone, Copy)]
494pub enum ImportanceLogLevel {
495    Core  = 0,
496    Base  = 1,
497    Extra = 2,
498}
499
500// We now commence data definitions heavily styled on the QLOG
501// schema definition. Data is serialized using serde.
502#[serde_with::skip_serializing_none]
503#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
504pub struct Trace {
505    pub vantage_point: VantagePoint,
506    pub title: Option<String>,
507    pub description: Option<String>,
508
509    pub configuration: Option<Configuration>,
510
511    pub common_fields: Option<CommonFields>,
512
513    pub events: Vec<Event>,
514}
515
516/// Helper functions for using a qlog [Trace].
517impl Trace {
518    /// Creates a new qlog [Trace]
519    pub fn new(
520        vantage_point: VantagePoint, title: Option<String>,
521        description: Option<String>, configuration: Option<Configuration>,
522        common_fields: Option<CommonFields>,
523    ) -> Self {
524        Trace {
525            vantage_point,
526            title,
527            description,
528            configuration,
529            common_fields,
530            events: Vec::new(),
531        }
532    }
533
534    /// Append an [Event] to a [Trace]
535    pub fn push_event(&mut self, event: Event) {
536        self.events.push(event);
537    }
538}
539
540#[serde_with::skip_serializing_none]
541#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
542pub struct TraceSeq {
543    pub vantage_point: VantagePoint,
544    pub title: Option<String>,
545    pub description: Option<String>,
546
547    pub configuration: Option<Configuration>,
548
549    pub common_fields: Option<CommonFields>,
550}
551
552/// Helper functions for using a qlog [TraceSeq].
553impl TraceSeq {
554    /// Creates a new qlog [TraceSeq]
555    pub fn new(
556        vantage_point: VantagePoint, title: Option<String>,
557        description: Option<String>, configuration: Option<Configuration>,
558        common_fields: Option<CommonFields>,
559    ) -> Self {
560        TraceSeq {
561            vantage_point,
562            title,
563            description,
564            configuration,
565            common_fields,
566        }
567    }
568}
569
570#[serde_with::skip_serializing_none]
571#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
572pub struct VantagePoint {
573    pub name: Option<String>,
574
575    #[serde(rename = "type")]
576    pub ty: VantagePointType,
577
578    pub flow: Option<VantagePointType>,
579}
580
581#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
582#[serde(rename_all = "snake_case")]
583pub enum VantagePointType {
584    Client,
585    Server,
586    Network,
587    Unknown,
588}
589
590#[serde_with::skip_serializing_none]
591#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
592pub struct Configuration {
593    pub time_offset: Option<f64>,
594
595    pub original_uris: Option<Vec<String>>,
596    // TODO: additionalUserSpecifiedProperty
597}
598
599impl Default for Configuration {
600    fn default() -> Self {
601        Configuration {
602            time_offset: Some(0.0),
603            original_uris: None,
604        }
605    }
606}
607
608#[serde_with::skip_serializing_none]
609#[derive(Serialize, Deserialize, Clone, Default, PartialEq, Debug)]
610pub struct CommonFields {
611    pub group_id: Option<String>,
612    pub protocol_type: Option<Vec<String>>,
613
614    pub reference_time: Option<f64>,
615    pub time_format: Option<String>,
616    // TODO: additionalUserSpecifiedProperty
617}
618
619#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
620#[serde(rename_all = "snake_case")]
621pub enum TokenType {
622    Retry,
623    Resumption,
624}
625
626#[serde_with::skip_serializing_none]
627#[derive(Clone, Serialize, Deserialize, PartialEq, Eq, Debug)]
628pub struct Token {
629    #[serde(rename(serialize = "type"))]
630    pub ty: Option<TokenType>,
631
632    pub details: Option<String>,
633
634    pub raw: Option<events::RawInfo>,
635}
636
637pub struct HexSlice<'a>(&'a [u8]);
638
639impl<'a> HexSlice<'a> {
640    pub fn new<T>(data: &'a T) -> HexSlice<'a>
641    where
642        T: ?Sized + AsRef<[u8]> + 'a,
643    {
644        HexSlice(data.as_ref())
645    }
646
647    pub fn maybe_string<T>(data: Option<&'a T>) -> Option<String>
648    where
649        T: ?Sized + AsRef<[u8]> + 'a,
650    {
651        data.map(|d| format!("{}", HexSlice::new(d)))
652    }
653}
654
655impl std::fmt::Display for HexSlice<'_> {
656    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
657        for byte in self.0 {
658            write!(f, "{byte:02x}")?;
659        }
660        Ok(())
661    }
662}
663
664#[doc(hidden)]
665pub mod testing {
666    use super::*;
667    use crate::events::quic::PacketType;
668
669    pub fn make_pkt_hdr(packet_type: PacketType) -> PacketHeader {
670        let scid = [0x7e, 0x37, 0xe4, 0xdc, 0xc6, 0x68, 0x2d, 0xa8];
671        let dcid = [0x36, 0xce, 0x10, 0x4e, 0xee, 0x50, 0x10, 0x1c];
672
673        // Some(1251),
674        // Some(1224),
675
676        PacketHeader::new(
677            packet_type,
678            Some(0),
679            None,
680            None,
681            None,
682            Some(0x0000_0001),
683            Some(&scid),
684            Some(&dcid),
685        )
686    }
687
688    pub fn make_trace() -> Trace {
689        Trace::new(
690            VantagePoint {
691                name: None,
692                ty: VantagePointType::Server,
693                flow: None,
694            },
695            Some("Quiche qlog trace".to_string()),
696            Some("Quiche qlog trace description".to_string()),
697            Some(Configuration {
698                time_offset: Some(0.0),
699                original_uris: None,
700            }),
701            None,
702        )
703    }
704
705    pub fn make_trace_seq() -> TraceSeq {
706        TraceSeq::new(
707            VantagePoint {
708                name: None,
709                ty: VantagePointType::Server,
710                flow: None,
711            },
712            Some("Quiche qlog trace".to_string()),
713            Some("Quiche qlog trace description".to_string()),
714            Some(Configuration {
715                time_offset: Some(0.0),
716                original_uris: None,
717            }),
718            None,
719        )
720    }
721}
722
723#[cfg(test)]
724mod tests {
725    use super::*;
726    use crate::events::quic::PacketSent;
727    use crate::events::quic::PacketType;
728    use crate::events::quic::QuicFrame;
729    use crate::events::EventData;
730    use crate::events::RawInfo;
731    use testing::*;
732
733    #[test]
734    fn packet_sent_event_no_frames() {
735        let log_string = r#"{
736  "time": 0.0,
737  "name": "transport:packet_sent",
738  "data": {
739    "header": {
740      "packet_type": "initial",
741      "packet_number": 0,
742      "version": "1",
743      "scil": 8,
744      "dcil": 8,
745      "scid": "7e37e4dcc6682da8",
746      "dcid": "36ce104eee50101c"
747    },
748    "raw": {
749      "length": 1251,
750      "payload_length": 1224
751    }
752  }
753}"#;
754
755        let pkt_hdr = make_pkt_hdr(PacketType::Initial);
756        let ev_data = EventData::PacketSent(PacketSent {
757            header: pkt_hdr,
758            raw: Some(RawInfo {
759                length: Some(1251),
760                payload_length: Some(1224),
761                data: None,
762            }),
763            ..Default::default()
764        });
765
766        let ev = Event::with_time(0.0, ev_data);
767
768        assert_eq!(serde_json::to_string_pretty(&ev).unwrap(), log_string);
769    }
770
771    #[test]
772    fn packet_sent_event_some_frames() {
773        let log_string = r#"{
774  "time": 0.0,
775  "name": "transport:packet_sent",
776  "data": {
777    "header": {
778      "packet_type": "initial",
779      "packet_number": 0,
780      "version": "1",
781      "scil": 8,
782      "dcil": 8,
783      "scid": "7e37e4dcc6682da8",
784      "dcid": "36ce104eee50101c"
785    },
786    "raw": {
787      "length": 1251,
788      "payload_length": 1224
789    },
790    "frames": [
791      {
792        "frame_type": "padding",
793        "payload_length": 1234
794      },
795      {
796        "frame_type": "ping"
797      },
798      {
799        "frame_type": "stream",
800        "stream_id": 0,
801        "offset": 0,
802        "length": 100,
803        "fin": true
804      }
805    ]
806  }
807}"#;
808
809        let pkt_hdr = make_pkt_hdr(PacketType::Initial);
810
811        let frames = vec![
812            QuicFrame::Padding {
813                payload_length: 1234,
814                length: None,
815            },
816            QuicFrame::Ping {
817                payload_length: None,
818                length: None,
819            },
820            QuicFrame::Stream {
821                stream_id: 0,
822                offset: 0,
823                length: 100,
824                fin: Some(true),
825                raw: None,
826            },
827        ];
828
829        let ev_data = EventData::PacketSent(PacketSent {
830            header: pkt_hdr,
831            frames: Some(frames.into()),
832            raw: Some(RawInfo {
833                length: Some(1251),
834                payload_length: Some(1224),
835                data: None,
836            }),
837            ..Default::default()
838        });
839
840        let ev = Event::with_time(0.0, ev_data);
841        assert_eq!(serde_json::to_string_pretty(&ev).unwrap(), log_string);
842    }
843
844    #[test]
845    fn trace_no_events() {
846        let log_string = r#"{
847  "vantage_point": {
848    "type": "server"
849  },
850  "title": "Quiche qlog trace",
851  "description": "Quiche qlog trace description",
852  "configuration": {
853    "time_offset": 0.0
854  },
855  "events": []
856}"#;
857
858        let trace = make_trace();
859
860        let serialized = serde_json::to_string_pretty(&trace).unwrap();
861        assert_eq!(serialized, log_string);
862
863        let deserialized: Trace = serde_json::from_str(&serialized).unwrap();
864        assert_eq!(deserialized, trace);
865    }
866
867    #[test]
868    fn trace_seq_no_events() {
869        let log_string = r#"{
870  "vantage_point": {
871    "type": "server"
872  },
873  "title": "Quiche qlog trace",
874  "description": "Quiche qlog trace description",
875  "configuration": {
876    "time_offset": 0.0
877  }
878}"#;
879
880        let trace = make_trace_seq();
881
882        let serialized = serde_json::to_string_pretty(&trace).unwrap();
883        assert_eq!(serialized, log_string);
884
885        let deserialized: TraceSeq = serde_json::from_str(&serialized).unwrap();
886        assert_eq!(deserialized, trace);
887    }
888
889    #[test]
890    fn trace_single_transport_event() {
891        let log_string = r#"{
892  "vantage_point": {
893    "type": "server"
894  },
895  "title": "Quiche qlog trace",
896  "description": "Quiche qlog trace description",
897  "configuration": {
898    "time_offset": 0.0
899  },
900  "events": [
901    {
902      "time": 0.0,
903      "name": "transport:packet_sent",
904      "data": {
905        "header": {
906          "packet_type": "initial",
907          "packet_number": 0,
908          "version": "1",
909          "scil": 8,
910          "dcil": 8,
911          "scid": "7e37e4dcc6682da8",
912          "dcid": "36ce104eee50101c"
913        },
914        "raw": {
915          "length": 1251,
916          "payload_length": 1224
917        },
918        "frames": [
919          {
920            "frame_type": "stream",
921            "stream_id": 0,
922            "offset": 0,
923            "length": 100,
924            "fin": true
925          }
926        ]
927      }
928    }
929  ]
930}"#;
931
932        let mut trace = make_trace();
933
934        let pkt_hdr = make_pkt_hdr(PacketType::Initial);
935
936        let frames = vec![QuicFrame::Stream {
937            stream_id: 0,
938            offset: 0,
939            length: 100,
940            fin: Some(true),
941            raw: None,
942        }];
943        let event_data = EventData::PacketSent(PacketSent {
944            header: pkt_hdr,
945            frames: Some(frames.into()),
946            raw: Some(RawInfo {
947                length: Some(1251),
948                payload_length: Some(1224),
949                data: None,
950            }),
951            ..Default::default()
952        });
953
954        let ev = Event::with_time(0.0, event_data);
955
956        trace.push_event(ev);
957
958        let serialized = serde_json::to_string_pretty(&trace).unwrap();
959        assert_eq!(serialized, log_string);
960
961        let deserialized: Trace = serde_json::from_str(&serialized).unwrap();
962        assert_eq!(deserialized, trace);
963    }
964}
965
966pub mod events;
967pub mod reader;
968pub mod streamer;