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//! # Some("Example qlog trace".to_string()),
71//! # Some("Example qlog trace description".to_string()),
72//! # None,
73//! # Some(qlog::VantagePoint {
74//! # name: Some("Example client".to_string()),
75//! # ty: qlog::VantagePointType::Client,
76//! # flow: None,
77//! # }),
78//! # vec![qlog::events::QUIC_URI.to_string(), qlog::events::HTTP3_URI.to_string()],
79//! # );
80//! ```
81//!
82//! ### Adding events to a Trace
83//!
84//! Qlog [`Event`] objects are added to [`qlog::Trace.events`].
85//!
86//! The following example demonstrates how to log a qlog QUIC `packet_sent`
87//! event containing a single Crypto frame. It constructs the necessary elements
88//! of the [`Event`], then appends it to the trace with [`push_event()`].
89//!
90//! ```
91//! # let mut trace = qlog::Trace::new(
92//! # Some("Example qlog trace".to_string()),
93//! # Some("Example qlog trace description".to_string()),
94//! # None,
95//! # Some(qlog::VantagePoint {
96//! # name: Some("Example client".to_string()),
97//! # ty: qlog::VantagePointType::Client,
98//! # flow: None,
99//! # }),
100//! # vec![qlog::events::QUIC_URI.to_string(), qlog::events::HTTP3_URI.to_string()],
101//! # );
102//!
103//! let scid = [0x7e, 0x37, 0xe4, 0xdc, 0xc6, 0x68, 0x2d, 0xa8];
104//! let dcid = [0x36, 0xce, 0x10, 0x4e, 0xee, 0x50, 0x10, 0x1c];
105//!
106//! let pkt_hdr = qlog::events::quic::PacketHeader::new(
107//! qlog::events::quic::PacketType::Initial,
108//! Some(0), // packet_number
109//! None, // token
110//! None, // length
111//! Some(0x00000001), // version
112//! Some(&scid),
113//! Some(&dcid),
114//! );
115//!
116//! let frames = vec![qlog::events::quic::QuicFrame::Crypto {
117//! offset: 0,
118//! raw: None,
119//! }];
120//!
121//! let raw = qlog::events::RawInfo {
122//! length: Some(1251),
123//! payload_length: Some(1224),
124//! data: None,
125//! };
126//!
127//! let event_data =
128//! qlog::events::EventData::QuicPacketSent(qlog::events::quic::PacketSent {
129//! header: pkt_hdr,
130//! frames: Some(frames.into()),
131//! stateless_reset_token: None,
132//! supported_versions: None,
133//! raw: Some(raw),
134//! datagram_id: None,
135//! is_mtu_probe_packet: None,
136//! send_at_time: None,
137//! trigger: None,
138//! });
139//!
140//! trace.push_event(qlog::events::Event::with_time(0.0, event_data));
141//! ```
142//!
143//! ### Serializing
144//!
145//! The qlog crate has only been tested with `serde_json`, however
146//! other serializer targets might work.
147//!
148//! For example, serializing the trace created above:
149//!
150//! ```
151//! # let mut trace = qlog::Trace::new(
152//! # Some("Example qlog trace".to_string()),
153//! # Some("Example qlog trace description".to_string()),
154//! # None,
155//! # Some(qlog::VantagePoint {
156//! # name: Some("Example client".to_string()),
157//! # ty: qlog::VantagePointType::Client,
158//! # flow: None,
159//! # }),
160//! # vec![qlog::events::QUIC_URI.to_string(), qlog::events::HTTP3_URI.to_string()],
161//! # );
162//! serde_json::to_string_pretty(&trace).unwrap();
163//! ```
164//!
165//! which would generate the following:
166//!
167//! ```ignore
168//! {
169//! "vantage_point": {
170//! "name": "Example client",
171//! "type": "client"
172//! },
173//! "title": "Example qlog trace",
174//! "description": "Example qlog trace description",
175//! "configuration": {
176//! "time_offset": 0.0
177//! },
178//! "events": [
179//! {
180//! "time": 0.0,
181//! "name": "quic:packet_sent",
182//! "data": {
183//! "header": {
184//! "packet_type": "initial",
185//! "packet_number": 0,
186//! "version": "1",
187//! "scil": 8,
188//! "dcil": 8,
189//! "scid": "7e37e4dcc6682da8",
190//! "dcid": "36ce104eee50101c"
191//! },
192//! "raw": {
193//! "length": 1251,
194//! "payload_length": 1224
195//! },
196//! "frames": [
197//! {
198//! "frame_type": "crypto",
199//! "offset": 0,
200//! "length": 0
201//! }
202//! ]
203//! }
204//! }
205//! ]
206//! }
207//! ```
208//!
209//! ## Streaming Traces with JSON-SEQ
210//!
211//! To help support streaming serialization of qlogs,
212//! draft-ietf-quic-qlog-main-schema-01 introduced support for RFC 7464 JSON
213//! Text Sequences (JSON-SEQ). The qlog crate supports this format and provides
214//! utilities that aid streaming.
215//!
216//! A [`TraceSeq`] contains metadata such as the [`VantagePoint`] of capture and
217//! the [`Configuration`]. However, protocol event data is handled as separate
218//! lines containing a record separator character, a serialized [`Event`], and a
219//! newline.
220//!
221//! ### Creating a TraceSeq
222//!
223//! ```
224//! let mut trace = qlog::TraceSeq::new(
225//! Some("Example qlog trace".to_string()),
226//! Some("Example qlog trace description".to_string()),
227//! None,
228//! Some(qlog::VantagePoint {
229//! name: Some("Example client".to_string()),
230//! ty: qlog::VantagePointType::Client,
231//! flow: None,
232//! }),
233//! vec![
234//! qlog::events::QUIC_URI.to_string(),
235//! qlog::events::HTTP3_URI.to_string(),
236//! ],
237//! );
238//! ```
239//!
240//! Create an object with the [`Write`] trait:
241//!
242//! ```
243//! let mut file = std::fs::File::create("foo.sqlog").unwrap();
244//! ```
245//!
246//! Create a [`QlogStreamer`] and start serialization to foo.sqlog
247//! using [`start_log()`]:
248//!
249//! ```
250//! # let mut trace = qlog::TraceSeq::new(
251//! # Some("Example qlog trace".to_string()),
252//! # Some("Example qlog trace description".to_string()),
253//! # None,
254//! # Some(qlog::VantagePoint {
255//! # name: Some("Example client".to_string()),
256//! # ty: qlog::VantagePointType::Client,
257//! # flow: None,
258//! # }),
259//! # vec![qlog::events::QUIC_URI.to_string(), qlog::events::HTTP3_URI.to_string()],
260//! # );
261//! # let mut file = std::fs::File::create("foo.sqlog").unwrap();
262//! let mut streamer = qlog::streamer::QlogStreamer::new(
263//! Some("Example qlog".to_string()),
264//! Some("Example qlog description".to_string()),
265//! std::time::Instant::now(),
266//! trace,
267//! qlog::events::EventImportance::Base,
268//! qlog::streamer::EventTimePrecision::NanoSeconds,
269//! Box::new(file),
270//! );
271//!
272//! streamer.start_log().ok();
273//! ```
274//!
275//! ### Adding events
276//!
277//! Once logging has started you can stream events. Events
278//! are written in one step using one of [`add_event()`],
279//! [`add_event_with_instant()`], [`add_event_now()`],
280//! [`add_event_data_with_instant()`], or [`add_event_data_now()`] :
281//!
282//! ```
283//! # let mut trace = qlog::TraceSeq::new(
284//! # Some("Example qlog trace".to_string()),
285//! # Some("Example qlog trace description".to_string()),
286//! # None,
287//! # Some(qlog::VantagePoint {
288//! # name: Some("Example client".to_string()),
289//! # ty: qlog::VantagePointType::Client,
290//! # flow: None,
291//! # }),
292//! # vec![qlog::events::QUIC_URI.to_string(), qlog::events::HTTP3_URI.to_string()],
293//! # );
294//! # let mut file = std::fs::File::create("foo.qlog").unwrap();
295//! # let mut streamer = qlog::streamer::QlogStreamer::new(
296//! # Some("Example qlog".to_string()),
297//! # Some("Example qlog description".to_string()),
298//! # std::time::Instant::now(),
299//! # trace,
300//! # qlog::events::EventImportance::Base,
301//! # qlog::streamer::EventTimePrecision::NanoSeconds,
302//! # Box::new(file),
303//! # );
304//!
305//! let scid = [0x7e, 0x37, 0xe4, 0xdc, 0xc6, 0x68, 0x2d, 0xa8];
306//! let dcid = [0x36, 0xce, 0x10, 0x4e, 0xee, 0x50, 0x10, 0x1c];
307//!
308//! let pkt_hdr = qlog::events::quic::PacketHeader::with_type(
309//! qlog::events::quic::PacketType::OneRtt,
310//! Some(0),
311//! Some(0x00000001),
312//! Some(&scid),
313//! Some(&dcid),
314//! );
315//!
316//! let ping = qlog::events::quic::QuicFrame::Ping {
317//! raw: None,
318//! };
319//!
320//! let raw = qlog::events::RawInfo {
321//! length: None,
322//! payload_length:
323//! Some(1234), data: None
324//! };
325//! let padding = qlog::events::quic::QuicFrame::Padding {
326//! raw: Some(Box::new(raw)),
327//! };
328//!
329//! let event_data =
330//! qlog::events::EventData::QuicPacketSent(qlog::events::quic::PacketSent {
331//! header: pkt_hdr,
332//! frames: Some(vec![ping, padding].into()),
333//! stateless_reset_token: None,
334//! supported_versions: None,
335//! raw: None,
336//! datagram_id: None,
337//! is_mtu_probe_packet: None,
338//! send_at_time: None,
339//! trigger: None,
340//! });
341//!
342//! let event = qlog::events::Event::with_time(0.0, event_data);
343//!
344//! streamer.add_event(event).ok();
345//! ```
346//!
347//! Once all events have been written, the log
348//! can be finalized with [`finish_log()`]:
349//!
350//! ```
351//! # let mut trace = qlog::TraceSeq::new(
352//! # Some("Example qlog trace".to_string()),
353//! # Some("Example qlog trace description".to_string()),
354//! # None,
355//! # Some(qlog::VantagePoint {
356//! # name: Some("Example client".to_string()),
357//! # ty: qlog::VantagePointType::Client,
358//! # flow: None,
359//! # }),
360//! # vec![qlog::events::QUIC_URI.to_string(), qlog::events::HTTP3_URI.to_string()],
361//! # );
362//! # let mut file = std::fs::File::create("foo.qlog").unwrap();
363//! # let mut streamer = qlog::streamer::QlogStreamer::new(
364//! # Some("Example qlog".to_string()),
365//! # Some("Example qlog description".to_string()),
366//! # std::time::Instant::now(),
367//! # trace,
368//! # qlog::events::EventImportance::Base,
369//! # qlog::streamer::EventTimePrecision::NanoSeconds,
370//! # Box::new(file),
371//! # );
372//! streamer.finish_log().ok();
373//! ```
374//!
375//! ### Serializing
376//!
377//! Serialization to JSON occurs as methods on the [`QlogStreamer`]
378//! are called. No additional steps are required.
379//!
380//! [`Trace`]: struct.Trace.html
381//! [`TraceSeq`]: struct.TraceSeq.html
382//! [`VantagePoint`]: struct.VantagePoint.html
383//! [`Configuration`]: struct.Configuration.html
384//! [`qlog::Trace.events`]: struct.Trace.html#structfield.events
385//! [`push_event()`]: struct.Trace.html#method.push_event
386//! [`QlogStreamer`]: struct.QlogStreamer.html
387//! [`Write`]: https://doc.rust-lang.org/std/io/trait.Write.html
388//! [`start_log()`]: streamer/struct.QlogStreamer.html#method.start_log
389//! [`add_event()`]: streamer/struct.QlogStreamer.html#method.add_event
390//! [`add_event_with_instant()`]: streamer/struct.QlogStreamer.html#method.add_event_with_instant
391//! [`add_event_now()`]: streamer/struct.QlogStreamer.html#method.add_event_now
392//! [`add_event_data_with_instant()`]: streamer/struct.QlogStreamer.html#method.add_event_data_with_instant
393//! [`add_event_data_now()`]: streamer/struct.QlogStreamer.html#method.add_event_data_now
394//! [`finish_log()`]: streamer/struct.QlogStreamer.html#method.finish_log
395
396use std::time::SystemTime;
397
398use crate::events::quic::PacketHeader;
399use crate::events::Event;
400
401use serde::Deserialize;
402use serde::Serialize;
403
404/// A quiche qlog error.
405#[derive(Debug)]
406pub enum Error {
407 /// There is no more work to do.
408 Done,
409
410 /// The operation cannot be completed because it was attempted
411 /// in an invalid state.
412 InvalidState,
413
414 // Invalid Qlog format
415 InvalidFormat,
416
417 /// I/O error.
418 IoError(std::io::Error),
419}
420
421impl std::fmt::Display for Error {
422 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
423 write!(f, "{self:?}")
424 }
425}
426
427impl std::error::Error for Error {
428 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
429 None
430 }
431}
432
433impl std::convert::From<std::io::Error> for Error {
434 fn from(err: std::io::Error) -> Self {
435 Error::IoError(err)
436 }
437}
438
439pub const QLOGFILE_URI: &str = "urn:ietf:params:qlog:file:contained";
440pub const QLOGFILESEQ_URI: &str = "urn:ietf:params:qlog:file:sequential";
441
442/// File extension for an uncompressed qlog stream (JSON-SEQ).
443///
444/// Includes the leading dot so it can be concatenated as a suffix
445/// (`format!("{id}{SQLOG_EXT}")`) and matched as a path suffix
446/// (`name.ends_with(SQLOG_EXT)`).
447pub const SQLOG_EXT: &str = ".sqlog";
448
449/// File extension for a gzip-compressed qlog stream.
450pub const SQLOG_GZ_EXT: &str = ".sqlog.gz";
451
452/// File extension for a zstd-compressed qlog stream.
453pub const SQLOG_ZST_EXT: &str = ".sqlog.zst";
454
455pub type Bytes = String;
456pub type StatelessResetToken = Bytes;
457
458/// A specialized [`Result`] type for quiche qlog operations.
459///
460/// This type is used throughout the public API for any operation that
461/// can produce an error.
462///
463/// [`Result`]: https://doc.rust-lang.org/std/result/enum.Result.html
464pub type Result<T> = std::result::Result<T, Error>;
465
466#[serde_with::skip_serializing_none]
467#[derive(Serialize, Deserialize, Clone)]
468pub struct Qlog {
469 pub file_schema: String,
470 pub serialization_format: String,
471 pub title: Option<String>,
472 pub description: Option<String>,
473
474 pub traces: Vec<Trace>,
475}
476#[serde_with::skip_serializing_none]
477#[derive(Serialize, Deserialize, Clone, Debug)]
478pub struct QlogSeq {
479 pub file_schema: String,
480 pub serialization_format: String,
481 pub title: Option<String>,
482 pub description: Option<String>,
483
484 pub trace: TraceSeq,
485}
486
487#[derive(Clone, Copy)]
488pub enum ImportanceLogLevel {
489 Core = 0,
490 Base = 1,
491 Extra = 2,
492}
493
494// We now commence data definitions heavily styled on the QLOG
495// schema definition. Data is serialized using serde.
496#[serde_with::skip_serializing_none]
497#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
498pub struct Trace {
499 pub title: Option<String>,
500 pub description: Option<String>,
501 pub common_fields: Option<CommonFields>,
502 pub vantage_point: Option<VantagePoint>,
503 pub event_schemas: Vec<String>,
504
505 pub events: Vec<Event>,
506}
507
508/// Helper functions for using a qlog [Trace].
509impl Trace {
510 /// Creates a new qlog [Trace]
511 pub fn new(
512 title: Option<String>, description: Option<String>,
513 common_fields: Option<CommonFields>, vantage_point: Option<VantagePoint>,
514 event_schemas: Vec<String>,
515 ) -> Self {
516 Trace {
517 title,
518 description,
519 common_fields,
520 vantage_point,
521 event_schemas,
522 events: Vec::new(),
523 }
524 }
525
526 /// Append an [Event] to a [Trace]
527 pub fn push_event(&mut self, event: Event) {
528 self.events.push(event);
529 }
530}
531
532#[serde_with::skip_serializing_none]
533#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
534pub struct TraceSeq {
535 pub title: Option<String>,
536 pub description: Option<String>,
537 pub common_fields: Option<CommonFields>,
538 pub vantage_point: Option<VantagePoint>,
539 pub event_schemas: Vec<String>,
540}
541
542/// Helper functions for using a qlog [TraceSeq].
543impl TraceSeq {
544 /// Creates a new qlog [TraceSeq]
545 pub fn new(
546 title: Option<String>, description: Option<String>,
547 common_fields: Option<CommonFields>, vantage_point: Option<VantagePoint>,
548 event_schemas: Vec<String>,
549 ) -> Self {
550 TraceSeq {
551 title,
552 description,
553 common_fields,
554 vantage_point,
555 event_schemas,
556 }
557 }
558}
559
560#[serde_with::skip_serializing_none]
561#[derive(Serialize, Deserialize, Clone, Default, PartialEq, Eq, Debug)]
562pub struct VantagePoint {
563 pub name: Option<String>,
564
565 #[serde(rename = "type")]
566 pub ty: VantagePointType,
567
568 pub flow: Option<VantagePointType>,
569}
570
571#[derive(Serialize, Deserialize, Clone, Default, PartialEq, Eq, Debug)]
572#[serde(rename_all = "snake_case")]
573pub enum VantagePointType {
574 Client,
575 Server,
576 Network,
577 #[default]
578 Unknown,
579}
580
581#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug, Default)]
582#[serde(rename_all = "snake_case")]
583pub enum TimeFormat {
584 #[default]
585 RelativeToEpoch,
586 RelativeToPreviousEvent,
587}
588
589#[serde_with::skip_serializing_none]
590#[derive(Serialize, Deserialize, Clone, Default, PartialEq, Debug)]
591#[serde(rename_all = "snake_case")]
592pub struct ReferenceTime {
593 pub clock_type: String,
594 pub epoch: String,
595 pub wall_clock_time: Option<String>,
596}
597
598impl ReferenceTime {
599 /// Create a new `ReferenceTime` instance that uses a monotonic clock.
600 ///
601 /// If `wall_clock_time` is specified, it will be added as the optional
602 /// `wall_clock_time` field of `ReferenceTime`.
603 pub fn new_monotonic(wall_clock_time: Option<SystemTime>) -> Self {
604 let wall_clock_time =
605 wall_clock_time.map(|t| humantime::format_rfc3339(t).to_string());
606 ReferenceTime {
607 clock_type: "monotonic".to_string(),
608 // per draft-ietf-quic-qlog-main-schema-13 epoch must be "unknown"
609 // for monotonic clocks
610 epoch: "unknown".to_string(),
611 wall_clock_time,
612 }
613 }
614}
615
616#[serde_with::skip_serializing_none]
617#[derive(Serialize, Deserialize, Clone, Default, PartialEq, Debug)]
618pub struct CommonFields {
619 pub tuple: Option<String>,
620 pub group_id: Option<String>,
621 pub protocol_types: Option<Vec<String>>,
622
623 pub reference_time: ReferenceTime,
624 pub time_format: Option<TimeFormat>,
625}
626
627#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
628#[serde(rename_all = "snake_case")]
629pub enum TokenType {
630 Retry,
631 Resumption,
632}
633
634#[serde_with::skip_serializing_none]
635#[derive(Clone, Serialize, Deserialize, PartialEq, Eq, Debug)]
636pub struct Token {
637 #[serde(rename(serialize = "type"))]
638 pub ty: Option<TokenType>,
639
640 pub details: Option<String>,
641
642 pub raw: Option<events::RawInfo>,
643}
644
645pub struct HexSlice<'a>(&'a [u8]);
646
647impl<'a> HexSlice<'a> {
648 pub fn new<T>(data: &'a T) -> HexSlice<'a>
649 where
650 T: ?Sized + AsRef<[u8]> + 'a,
651 {
652 HexSlice(data.as_ref())
653 }
654
655 pub fn maybe_string<T>(data: Option<&'a T>) -> Option<String>
656 where
657 T: ?Sized + AsRef<[u8]> + 'a,
658 {
659 data.map(|d| format!("{}", HexSlice::new(d)))
660 }
661}
662
663impl std::fmt::Display for HexSlice<'_> {
664 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
665 for byte in self.0 {
666 write!(f, "{byte:02x}")?;
667 }
668 Ok(())
669 }
670}
671
672pub mod events;
673pub mod reader;
674pub mod streamer;
675#[doc(hidden)]
676pub mod testing;
677pub mod writer;
678
679#[cfg(test)]
680mod tests {
681 use std::time::Duration;
682 use std::time::UNIX_EPOCH;
683
684 use super::ReferenceTime;
685
686 #[test]
687 fn reference_time_new_monotonic_serialization() {
688 // 2024-01-15T10:30:00Z = 1705314600 seconds after UNIX epoch
689 let t = UNIX_EPOCH + Duration::from_secs(1_705_314_600);
690 let rt = ReferenceTime::new_monotonic(Some(t));
691 let map: serde_json::Map<String, serde_json::Value> =
692 serde_json::from_str(&serde_json::to_string(&rt).unwrap()).unwrap();
693
694 assert_eq!(map["clock_type"], "monotonic");
695 assert_eq!(map["epoch"], "unknown");
696 assert_eq!(map["wall_clock_time"], "2024-01-15T10:30:00Z");
697
698 let rt = ReferenceTime::new_monotonic(None);
699 let map: serde_json::Map<String, serde_json::Value> =
700 serde_json::from_str(&serde_json::to_string(&rt).unwrap()).unwrap();
701
702 assert_eq!(map["clock_type"], "monotonic");
703 assert_eq!(map["epoch"], "unknown");
704 assert!(!map.contains_key("wall_clock_time"));
705 }
706}