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;