1#![allow(clippy::collapsible_match)]
28
29use std::collections::BTreeMap;
30use std::error::Error;
31use std::fs::File;
32use std::io::BufReader;
33use std::path::Path;
34
35use config::AppConfig;
36use datastore::Datastore;
37use datastore::NetlogSession;
38use log::debug;
39use log::error;
40
41use qlog::reader::QlogSeqReader;
42use qlog::Qlog;
43
44use serde::ser::Serialize;
45
46use crate::wirefilter::filter_sqlog_events;
47
48pub type QlogPointu64 = (f64, u64);
49pub type QlogPointRtt = (f64, f32);
50
51#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Copy, Clone)]
52pub enum PacketType {
53 Initial,
54 Handshake,
55 ZeroRtt,
56 OneRtt,
57 Retry,
58 VersionNegotiation,
59 Unknown,
60}
61
62impl PacketType {
63 pub fn from_qlog_packet_type(ty: &qlog::events::quic::PacketType) -> Self {
64 match ty {
65 qlog::events::quic::PacketType::Initial => PacketType::Initial,
66 qlog::events::quic::PacketType::Handshake => PacketType::Handshake,
67 qlog::events::quic::PacketType::ZeroRtt => PacketType::ZeroRtt,
68 qlog::events::quic::PacketType::OneRtt => PacketType::OneRtt,
69 qlog::events::quic::PacketType::Retry => PacketType::Retry,
70 qlog::events::quic::PacketType::VersionNegotiation =>
71 PacketType::VersionNegotiation,
72 qlog::events::quic::PacketType::StatelessReset => PacketType::Unknown,
73 qlog::events::quic::PacketType::Unknown => PacketType::Unknown,
74 }
75 }
76
77 pub fn from_netlog_packet_header(
78 header_format: &str, long_header_type: &Option<String>,
79 ) -> Self {
80 match header_format {
81 "IETF_QUIC_LONG_HEADER_PACKET" => match long_header_type {
82 Some(v) => match v.as_str() {
83 "INITIAL" => PacketType::Initial,
84 "HANDSHAKE" => PacketType::Handshake,
85 _ => PacketType::Unknown,
86 },
87
88 None => PacketType::Unknown,
89 },
90
91 "IETF_QUIC_SHORT_HEADER_PACKET" => PacketType::OneRtt,
92
93 _ => PacketType::Unknown,
94 }
95 }
96
97 pub fn from_netlog_encryption_level(encryption_level: &str) -> Self {
98 match encryption_level {
99 "ENCRYPTION_INITIAL" => PacketType::Initial,
100
101 "ENCRYPTION_HANDSHAKE" => PacketType::Handshake,
102
103 "ENCRYPTION_ZERO_RTT" => PacketType::ZeroRtt,
104
105 "ENCRYPTION_FORWARD_SECURE" => PacketType::OneRtt,
106
107 _ => PacketType::Unknown,
108 }
109 }
110}
111
112#[derive(Debug)]
113pub enum SerializationFormat {
114 QlogJson,
115 QlogJsonSeq,
116 NetlogJson,
117 Unknown,
118}
119
120impl SerializationFormat {
121 pub fn from_file_extension(extension: &str) -> Self {
122 match extension {
123 "qlog" => SerializationFormat::QlogJson,
124 "sqlog" | "gz" | "zst" => SerializationFormat::QlogJsonSeq,
125 "json" => Self::NetlogJson,
126 _ => SerializationFormat::Unknown,
127 }
128 }
129
130 pub fn from_filename(filename: &str) -> Self {
131 if filename.ends_with(".sqlog.gz") || filename.ends_with(".sqlog.zst") {
133 return SerializationFormat::QlogJsonSeq;
134 }
135 if filename.ends_with(".sqlog") {
136 return SerializationFormat::QlogJsonSeq;
137 }
138 if filename.ends_with(".qlog") {
139 return SerializationFormat::QlogJson;
140 }
141 if let Some(ext) = filename.rsplit('.').next() {
143 return Self::from_file_extension(ext);
144 }
145 SerializationFormat::Unknown
146 }
147}
148
149pub struct VantagePointTypeShim {
150 pub inner: qlog::VantagePointType,
151}
152
153impl Default for VantagePointTypeShim {
154 fn default() -> Self {
155 VantagePointTypeShim {
156 inner: qlog::VantagePointType::Unknown,
157 }
158 }
159}
160
161impl std::fmt::Display for VantagePointTypeShim {
162 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
163 write!(f, "{:?}", self.inner)
164 }
165}
166
167pub struct LogFileDetails {
168 pub file_schema: String,
169 pub serialization_format: String,
170 pub qlog_vantage_point_type: VantagePointTypeShim,
171 pub sessions: BTreeMap<i64, NetlogSession>,
172}
173
174pub enum RawLogEvents {
175 QlogJson { events: Vec<qlog::events::Event> },
176 QlogJsonSeq { events: Vec<qlog::reader::Event> },
177 Netlog,
178}
179
180pub struct LogFileData {
181 pub datastore: Datastore,
182 pub raw: RawLogEvents,
183}
184
185pub struct LogFileParseResult {
186 pub details: LogFileDetails,
187 pub data: Vec<LogFileData>,
188}
189
190pub fn parse_log_file(
191 config: &AppConfig,
192) -> Result<LogFileParseResult, Box<dyn Error>> {
193 match config.log_format {
194 SerializationFormat::QlogJson => {
195 println!("parsing qlog as JSON...");
196 let mark = std::time::Instant::now();
197 let qlog = read_qlog_from_file(config.file.clone())?;
198 let vp = qlog.traces[0].vantage_point.clone().unwrap_or_default().ty;
199
200 let details = LogFileDetails {
201 file_schema: qlog.file_schema.clone(),
202 serialization_format: qlog.serialization_format.clone(),
203 qlog_vantage_point_type: VantagePointTypeShim { inner: vp },
204 sessions: BTreeMap::new(),
205 };
206 debug!("\tcomplete in {:?}", std::time::Instant::now() - mark);
207
208 println!("populating datastore...");
209 let mark = std::time::Instant::now();
210 let datastore = Datastore::with_qlog_events(
212 &qlog.traces[0].events,
213 &details.qlog_vantage_point_type.inner,
214 !config.ignore_acks,
215 );
216 debug!("\tcomplete in {:?}", std::time::Instant::now() - mark);
217
218 let raw = RawLogEvents::QlogJson {
219 events: qlog.traces[0].events.clone(),
220 };
221
222 Ok(LogFileParseResult {
223 details,
224 data: vec![LogFileData { datastore, raw }],
225 })
226 },
227
228 SerializationFormat::QlogJsonSeq => {
229 println!("parsing qlog as JSON-SEQ...");
230 let mark = std::time::Instant::now();
231
232 let (qlog_reader, details) = qlog_seq_reader(config)?;
233
234 debug!("\tcomplete in {:?}", std::time::Instant::now() - mark);
235
236 println!("populating datastore...");
237 let mark = std::time::Instant::now();
238 let events: Vec<qlog::reader::Event> =
239 qlog_reader.into_iter().collect();
240 let datastore: Datastore = Datastore::with_sqlog_reader_events(
241 &events,
242 &details.qlog_vantage_point_type.inner,
243 !config.ignore_acks,
244 );
245 debug!("\tcomplete in {:?}", std::time::Instant::now() - mark);
246
247 let events = if let Some(filter) = &config.qlog_wirefilter {
248 filter_sqlog_events(events, filter)
249 } else {
250 events
251 };
252
253 let raw = RawLogEvents::QlogJsonSeq { events };
254
255 Ok(LogFileParseResult {
256 details,
257 data: vec![LogFileData { datastore, raw }],
258 })
259 },
260
261 SerializationFormat::NetlogJson => {
262 println!("setting up parsing file as netlog...");
263 let mark = std::time::Instant::now();
264
265 let file = std::fs::File::open(config.file.clone())?;
266 let mut reader = BufReader::new(file);
267
268 let constants = netlog_with_reader(&mut reader).unwrap();
269
270 debug!("\tcomplete in {:?}", std::time::Instant::now() - mark);
272
273 println!("parsing file ...");
274 println!("Filtering on hostnames: {:#?}", config.netlog_filter);
275 let mark = std::time::Instant::now();
276
277 let (data, sessions) = datastore::with_netlog_reader(
278 &mut reader,
279 config.netlog_filter.clone(),
280 &constants,
281 );
282 debug!("\tcomplete in {:?}", std::time::Instant::now() - mark);
283
284 let details = LogFileDetails {
285 file_schema: constants.log_format_version.to_string(),
286 serialization_format: "Chrome netlog".to_string(),
287 qlog_vantage_point_type: VantagePointTypeShim {
288 inner: qlog::VantagePointType::Client,
289 },
290 sessions,
291 };
292
293 Ok(LogFileParseResult { details, data })
294 },
295
296 _ => {
297 error!("Unknown log file format for {}", config.filename);
298 Err("total fail".into())
299 },
300 }
301}
302
303pub fn read_qlog_from_file<P: AsRef<Path>>(
304 path: P,
305) -> Result<Qlog, Box<dyn Error>> {
306 let file = File::open(path)?;
307 let reader = BufReader::new(file);
308
309 let qlog = serde_json::from_reader(reader)?;
310
311 Ok(qlog)
312}
313
314pub fn qlog_seq_reader(
315 config: &AppConfig,
316) -> Result<(QlogSeqReader<'static>, LogFileDetails), Box<dyn Error>> {
317 let qlog_reader =
318 QlogSeqReader::with_file(config.file.clone()).map_err(|e| {
319 std::io::Error::other(format!("problem reading file! {}", e))
320 })?;
321
322 let vp = qlog_reader
323 .qlog
324 .trace
325 .vantage_point
326 .clone()
327 .unwrap_or_default()
328 .ty;
329 let log_file_details = LogFileDetails {
330 file_schema: qlog_reader.qlog.file_schema.clone(),
331 serialization_format: qlog_reader.qlog.serialization_format.clone(),
332 qlog_vantage_point_type: VantagePointTypeShim { inner: vp },
333 sessions: BTreeMap::new(),
334 };
335
336 Ok((qlog_reader, log_file_details))
337}
338
339pub fn netlog_with_reader<R: std::io::BufRead>(
340 reader: &mut R,
341) -> Result<netlog::constants::Constants, Box<dyn Error>> {
342 let mut buf = Vec::<u8>::new();
347
348 let len = reader.read_until(b'\n', &mut buf).unwrap();
350
351 buf[len - 2] = b'}';
354
355 let res: Result<netlog::constants::ConstantsLine, serde_json::Error> =
356 serde_json::from_slice(&buf);
357
358 match res {
359 Ok(mut line) => {
360 line.constants.populate_id_keyed();
361
362 Ok(line.constants)
363 },
364
365 Err(e) => {
366 error!("Error deserializing: {}", e);
367
368 Err(e.into())
371 },
372 }
373}
374
375pub fn stringify_last<T>(src: &[T]) -> String
376where
377 T: std::fmt::Debug,
378{
379 if src.len() == 1 {
380 "n/a".to_string()
381 } else {
382 format!("{:?}", src.last().unwrap())
383 }
384}
385
386fn push_interp<X: Clone, Y: Clone>(collection: &mut Vec<(X, Y)>, value: (X, Y)) {
389 let prev = collection.last().cloned();
390 let new_time = value.0.clone();
391
392 if let Some((_, y)) = prev {
393 collection.push((new_time, y));
394 }
395
396 collection.push(value)
397}
398
399fn create_file_recursive(filename: &str) -> std::io::Result<File> {
400 let path = std::path::Path::new(filename);
401 if let Some(dir) = path.parent() {
402 std::fs::create_dir_all(dir)?;
403 }
404
405 File::create(filename)
406}
407
408pub fn category_and_type_from_name(name: &str) -> (String, String) {
409 let mut category = "".to_string();
410 let mut ty = "".to_string();
411
412 let split: Vec<&str> = name.split(':').collect();
413 if let Some(cat) = split.first() {
414 category = cat.to_string();
415 }
416 if let Some(t) = split.get(1) {
417 ty = t.to_string();
418 }
419
420 (category, ty)
421}
422pub fn category_and_type_from_event<T: Serialize>(ev: &T) -> (String, String) {
423 let name = serde_json::to_value(ev).unwrap()["name"]
424 .to_string()
425 .replace("\"", "");
426 category_and_type_from_name(&name)
427}
428
429pub mod config;
430pub mod datastore;
431pub mod plots;
432pub mod reports;
433pub mod request_stub;
434pub mod seriesstore;
435pub mod trackers;
436#[cfg(target_arch = "wasm32")]
437pub mod web;
438pub mod wirefilter;