use std::time::Instant;
use serde::Deserialize;
use serde::Serialize;
use self::events::Event;
use self::events::EventData;
use self::events::EventImportance;
use self::events::PacketHeader;
use crate::Error;
use crate::Result;
pub const QLOG_VERSION: &str = "0.4";
pub const JSON_SEQ_FORMAT: &str = "JSON-SEQ";
pub const JSON_SEQ_RS: &[u8] = b"\x1e";
#[serde_with::skip_serializing_none]
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct QlogFileSeq {
pub qlog_format: String,
pub qlog_version: String,
pub title: Option<String>,
pub description: Option<String>,
pub trace: TraceSeq,
}
#[serde_with::skip_serializing_none]
#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
pub struct TraceSeq {
pub title: Option<String>,
pub description: Option<String>,
pub common_fields: Option<CommonFields>,
pub vantage_point: VantagePoint,
}
impl TraceSeq {
pub fn new(
title: Option<String>,
description: Option<String>,
common_fields: Option<CommonFields>,
vantage_point: VantagePoint,
) -> Self {
TraceSeq {
title,
description,
common_fields,
vantage_point,
}
}
}
#[serde_with::skip_serializing_none]
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
pub struct VantagePoint {
pub name: Option<String>,
pub r#type: VantagePointType,
pub flow: Option<VantagePointType>,
}
impl VantagePoint {
pub fn new(name: Option<String>, is_server: bool) -> VantagePoint {
let vp_type = if is_server {
VantagePointType::Server
} else {
VantagePointType::Client
};
Self {
name,
r#type: vp_type,
flow: None,
}
}
}
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
#[serde(rename_all = "snake_case")]
pub enum VantagePointType {
Client,
Server,
Network,
Unknown,
}
#[serde_with::skip_serializing_none]
#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
pub struct Configuration {
pub time_offset: Option<f64>,
pub original_uris: Option<Vec<String>>,
}
impl Default for Configuration {
fn default() -> Self {
Configuration {
time_offset: Some(0.0),
original_uris: None,
}
}
}
#[serde_with::skip_serializing_none]
#[derive(Serialize, Deserialize, Clone, Default, PartialEq, Debug)]
pub struct CommonFields {
pub time_format: Option<String>,
pub reference_time: Option<f64>,
pub protocol_type: Option<Vec<String>>,
pub group_id: Option<String>,
}
pub struct QlogWriter {
qlog: QlogFileSeq,
level: EventImportance,
writer: Box<dyn std::io::Write + Send + Sync>,
ready: bool,
start_time: std::time::Instant,
}
impl QlogWriter {
#[allow(clippy::too_many_arguments)]
pub fn new(
title: Option<String>,
description: Option<String>,
trace: TraceSeq,
level: EventImportance,
writer: Box<dyn std::io::Write + Send + Sync>,
start_time: std::time::Instant,
) -> Self {
let qlog = QlogFileSeq {
qlog_format: JSON_SEQ_FORMAT.to_string(),
qlog_version: QLOG_VERSION.to_string(),
title,
description,
trace,
};
QlogWriter {
qlog,
level,
writer,
ready: false,
start_time,
}
}
pub fn start(&mut self) -> Result<()> {
if self.ready {
return Err(Error::Done);
}
self.writer.as_mut().write_all(JSON_SEQ_RS)?;
serde_json::to_writer(self.writer.as_mut(), &self.qlog).map_err(|_| Error::Done)?;
self.writer.as_mut().write_all(b"\n")?;
self.ready = true;
Ok(())
}
pub fn flush(&mut self) -> Result<()> {
if !self.ready {
return Err(Error::InvalidState("expect ready state".into()));
}
self.writer.as_mut().flush()?;
Ok(())
}
pub fn add_event(&mut self, event: Event) -> Result<()> {
self.check(event.importance())?;
self.writer.as_mut().write_all(JSON_SEQ_RS)?;
serde_json::to_writer(self.writer.as_mut(), &event).map_err(|_| Error::Done)?;
self.writer.as_mut().write_all(b"\n")?;
Ok(())
}
pub fn add_event_data(&mut self, time: Instant, event_data: EventData) -> Result<()> {
let event = Event::new(self.relative_time(time), event_data);
self.add_event(event)
}
fn check(&self, ei: EventImportance) -> Result<()> {
if !self.ready {
return Err(Error::InvalidState("not ready".into()));
}
if !ei.is_contained_in(&self.level) {
return Err(Error::Done);
}
Ok(())
}
pub fn relative_time(&self, time: Instant) -> f32 {
let duration = time.duration_since(self.start_time);
duration.as_secs_f32() * 1000.0
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::qlog::events::tests::new_test_pkt_hdr;
use crate::qlog::events::ConnectionState;
use crate::qlog::events::EventData;
use crate::qlog::events::PacketType;
use crate::qlog::events::QuicFrame;
use crate::qlog::events::QuicPacketSent;
use crate::qlog::events::RawInfo;
pub fn new_test_trace_seq() -> TraceSeq {
TraceSeq::new(
Some("qlog trace".to_string()),
Some("qlog trace description".to_string()),
None,
VantagePoint {
name: None,
r#type: VantagePointType::Server,
flow: None,
},
)
}
#[test]
fn qlog_writer_operations() -> Result<()> {
let writer = std::io::Cursor::new(Vec::<u8>::new());
let mut qlog_writer = QlogWriter::new(
Some("title".to_string()),
Some("description".to_string()),
new_test_trace_seq(),
EventImportance::Base,
Box::new(writer),
std::time::Instant::now(),
);
let event1 = Event::new(
0.0,
EventData::ConnectivityConnectionStateUpdated {
old: None,
new: ConnectionState::HandshakeCompleted,
},
);
assert!(qlog_writer.add_event(event1.clone()).is_err());
qlog_writer.start()?;
assert_eq!(qlog_writer.start(), Err(Error::Done));
qlog_writer.add_event(event1)?;
let event2 = EventData::RecoveryMarkedForRetransmit {
frames: vec![QuicFrame::Ping],
};
assert_eq!(
qlog_writer.add_event_data(Instant::now(), event2),
Err(Error::Done)
);
let event3 = Event::new(
0.0,
EventData::ConnectivityConnectionStateUpdated {
old: None,
new: ConnectionState::HandshakeConfirmed,
},
);
qlog_writer.add_event(event3)?;
qlog_writer.flush()?;
let w: &Box<std::io::Cursor<Vec<u8>>> = unsafe { std::mem::transmute(&qlog_writer.writer) };
let log = std::str::from_utf8(w.as_ref().get_ref()).unwrap();
assert_eq!(
log,
format!(
"\u{1e}{}\n\u{1e}{}\n\u{1e}{}\n",
r#"{"qlog_format":"JSON-SEQ","qlog_version":"0.4","title":"title","description":"description","trace":{"title":"qlog trace","description":"qlog trace description","vantage_point":{"type":"server"}}}"#,
r#"{"time":0.0,"name":"connectivity:connection_state_updated","data":{"new":"handshake_completed"}}"#,
r#"{"time":0.0,"name":"connectivity:connection_state_updated","data":{"new":"handshake_confirmed"}}"#,
)
);
Ok(())
}
}
pub mod events;