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