1use std::collections::HashMap;
2
3use chrono::{DateTime, FixedOffset};
4use serde::Serialize;
5use serde_with::skip_serializing_none;
6
7use crate::util::{is_empty_or_none, PathId, GroupId};
8
9#[cfg(feature = "moq-transfork")]
10use crate::moq_transfork::data::MOQ_VERSION_STRING;
11
12#[cfg(feature = "quic-10")]
13use crate::quic_10::data::QUIC_10_VERSION_STRING;
14
15#[derive(Serialize)]
16pub struct QlogFileSeq {
17 #[serde(flatten)]
18 log_file_details: LogFile,
19 trace: TraceSeq
20}
21
22impl QlogFileSeq {
23 pub fn new(log_file_details: LogFile, trace: TraceSeq) -> QlogFileSeq {
24 QlogFileSeq { log_file_details, trace }
25 }
26}
27
28#[skip_serializing_none]
29#[derive(Serialize)]
30pub struct LogFile {
31 file_schema: String,
33 serialization_format: String,
35 title: Option<String>,
36 description: Option<String>
37}
38
39impl LogFile {
40 pub fn new(title: Option<String>, description: Option<String>) -> LogFile {
43 LogFile {
44 file_schema: "urn:ietf:params:qlog:file:sequential".to_string(),
45 serialization_format: "application/qlog+json-seq".to_string(),
46 title,
47 description
48 }
49 }
50}
51
52#[skip_serializing_none]
53#[derive(Serialize)]
54pub struct TraceSeq {
55 title: Option<String>,
56 description: Option<String>,
57 common_fields: Option<CommonFields>,
58 vantage_point: Option<VantagePoint>,
59 event_schemas: Vec<String>
61}
62
63impl TraceSeq {
64 pub fn new(title: Option<String>, description: Option<String>, common_fields: Option<CommonFields>, vantage_point: Option<VantagePoint>) -> TraceSeq {
65 #[allow(unused_mut)]
66 let mut event_schemas: Vec<String> = Vec::default();
67
68 #[cfg(feature = "moq-transfork")]
69 event_schemas.push(format!("urn:ietf:params:qlog:events:{MOQ_VERSION_STRING}"));
70
71 #[cfg(feature = "quic-10")]
72 event_schemas.push(format!("urn:ietf:params:qlog:events:{QUIC_10_VERSION_STRING}"));
73
74 TraceSeq {
75 title,
76 description,
77 common_fields,
78 vantage_point,
79 event_schemas
80 }
81 }
82}
83
84#[skip_serializing_none]
85#[derive(Serialize)]
86pub struct CommonFields {
87 #[serde(skip_serializing_if = "is_empty_or_none")]
88 path: Option<PathId>,
89 time_format: Option<TimeFormat>,
90 reference_time: Option<ReferenceTime>,
91 group_id: Option<GroupId>,
92 #[serde(flatten)] custom_fields: HashMap<String, String>
94}
95
96impl CommonFields {
97 pub fn new(path: Option<PathId>, time_format: Option<TimeFormat>, reference_time: Option<ReferenceTime>, group_id: Option<GroupId>, custom_fields: Option<HashMap<String, String>>) -> CommonFields {
98 let custom_fields = custom_fields.unwrap_or_default();
99
100 CommonFields { path, time_format, reference_time, group_id, custom_fields }
101 }
102}
103
104impl Default for CommonFields {
105 fn default() -> Self {
106 Self {
107 path: Some("".to_string()),
108 time_format: Some(TimeFormat::default()),
109 reference_time: Some(ReferenceTime::default()),
110 group_id: None,
111 custom_fields: HashMap::new()
112 }
113 }
114}
115
116#[derive(Default, Serialize)]
117#[serde(rename_all = "snake_case")]
118pub enum TimeFormat {
119 #[default]
121 RelativeToEpoch,
122 RelativeToPreviousEvent
124}
125
126#[skip_serializing_none]
127#[derive(Default, Serialize)]
128pub struct ReferenceTime {
129 clock_type: ClockType,
130 epoch: Epoch,
131 wall_clock_time: Option<DateTime<FixedOffset>>
132}
133
134impl ReferenceTime {
135 pub fn new(clock_type: Option<ClockType>, epoch: Option<Epoch>, wall_clock_time: Option<DateTime<FixedOffset>>) -> ReferenceTime {
139 let clock_type = clock_type.unwrap_or_default();
140 let epoch = epoch.unwrap_or_default();
141
142 if clock_type == ClockType::Monotonic && epoch != Epoch::Unknown {
143 panic!("When using the 'monotonic' clock type, the epoch field must have the value 'unknown'");
144 }
145
146 ReferenceTime { clock_type, epoch, wall_clock_time }
147 }
148}
149
150#[derive(Default, PartialEq, Eq, Serialize)]
151#[serde(rename_all = "snake_case")]
152pub enum ClockType {
153 #[default]
154 System,
155 Monotonic,
156 Other(String)
157}
158
159#[derive(PartialEq, Eq, Serialize)]
160#[serde(rename_all = "snake_case", untagged)]
161pub enum Epoch {
162 Rfc3339DateTime(DateTime<FixedOffset>),
163 Unknown
164}
165
166impl Default for Epoch {
167 fn default() -> Self {
168 Self::Rfc3339DateTime(DateTime::parse_from_rfc3339("1970-01-01T00:00:00.000Z").unwrap())
169 }
170}
171
172#[skip_serializing_none]
174#[derive(Serialize)]
175pub struct VantagePoint {
176 name: Option<String>,
177 #[serde(rename = "type")]
179 vp_type: VantagePointType,
180 flow: Option<VantagePointType>
182}
183
184impl VantagePoint {
185 pub fn new(name: Option<String>, vp_type: VantagePointType, flow: Option<VantagePointType>) -> VantagePoint {
186 if vp_type == VantagePointType::Network && flow.is_none() {
187 panic!("The 'flow' field is required if the type is 'network'");
188 }
189
190 VantagePoint { name, vp_type, flow }
191 }
192}
193
194#[derive(PartialEq, Eq, Serialize)]
195#[serde(rename_all = "snake_case")]
196pub enum VantagePointType {
197 Client,
199 Server,
201 Network,
203 Unknown
204}