Revision control
Copy as Markdown
Other Tools
// Copyright (C) 2026, Cloudflare, Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
use super::*;
use crate::events::quic::MetricsUpdated;
use crate::events::quic::PacketSent;
use crate::events::quic::PacketType;
use crate::events::quic::QuicFrame;
use crate::events::EventData;
use crate::events::ExData;
use crate::events::RawInfo;
use crate::Event;
#[test]
fn packet_sent_event_no_frames() {
let log_string = r#"{
"time": 0.0,
"name": "transport:packet_sent",
"data": {
"header": {
"packet_type": "initial",
"packet_number": 0,
"version": "1",
"scil": 8,
"dcil": 8,
"scid": "7e37e4dcc6682da8",
"dcid": "36ce104eee50101c"
},
"raw": {
"length": 1251,
"payload_length": 1224
}
}
}"#;
let pkt_hdr = make_pkt_hdr(PacketType::Initial);
let ev_data = EventData::PacketSent(PacketSent {
header: pkt_hdr,
raw: Some(RawInfo {
length: Some(1251),
payload_length: Some(1224),
data: None,
}),
..Default::default()
});
let ev = Event::with_time(0.0, ev_data);
assert_eq!(serde_json::to_string_pretty(&ev).unwrap(), log_string);
}
#[test]
fn packet_sent_event_some_frames() {
let log_string = r#"{
"time": 0.0,
"name": "transport:packet_sent",
"data": {
"header": {
"packet_type": "initial",
"packet_number": 0,
"version": "1",
"scil": 8,
"dcil": 8,
"scid": "7e37e4dcc6682da8",
"dcid": "36ce104eee50101c"
},
"raw": {
"length": 1251,
"payload_length": 1224
},
"frames": [
{
"frame_type": "padding",
"payload_length": 1234
},
{
"frame_type": "ping"
},
{
"frame_type": "stream",
"stream_id": 0,
"offset": 0,
"length": 100,
"fin": true
}
]
}
}"#;
let pkt_hdr = make_pkt_hdr(PacketType::Initial);
let frames = vec![
QuicFrame::Padding {
payload_length: 1234,
length: None,
},
QuicFrame::Ping {
payload_length: None,
length: None,
},
QuicFrame::Stream {
stream_id: 0,
offset: 0,
length: 100,
fin: Some(true),
raw: None,
},
];
let ev_data = EventData::PacketSent(PacketSent {
header: pkt_hdr,
frames: Some(frames.into()),
raw: Some(RawInfo {
length: Some(1251),
payload_length: Some(1224),
data: None,
}),
..Default::default()
});
let ev = Event::with_time(0.0, ev_data);
assert_eq!(serde_json::to_string_pretty(&ev).unwrap(), log_string);
}
// Test constants for MetricsUpdated tests
const MIN_RTT: f32 = 10.0;
const SMOOTHED_RTT: f32 = 15.0;
const CONGESTION_WINDOW: u64 = 12000;
const PACING_RATE: u64 = 500000;
const DELIVERY_RATE: u64 = 1000000;
const COLLISION_VALUE: f32 = 999.0;
#[test]
fn packet_header() {
let pkt_hdr = make_pkt_hdr(PacketType::Initial);
let log_string = r#"{
"packet_type": "initial",
"packet_number": 0,
"version": "1",
"scil": 8,
"dcil": 8,
"scid": "7e37e4dcc6682da8",
"dcid": "36ce104eee50101c"
}"#;
assert_eq!(serde_json::to_string_pretty(&pkt_hdr).unwrap(), log_string);
}
#[test]
fn metrics_updated_with_ex_data() {
// Test that ex_data fields are flattened into the same object
let ex_data = ExData::from([(
"delivery_rate".to_string(),
serde_json::json!(DELIVERY_RATE),
)]);
let metrics = MetricsUpdated {
min_rtt: Some(MIN_RTT),
congestion_window: Some(CONGESTION_WINDOW),
ex_data,
..Default::default()
};
let json = serde_json::to_value(&metrics).unwrap();
// Verify standard fields are present
assert_eq!(json["min_rtt"], MIN_RTT);
assert_eq!(json["congestion_window"], CONGESTION_WINDOW);
// Verify ex_data field is flattened (not nested under "ex_data")
assert_eq!(json["delivery_rate"], DELIVERY_RATE);
assert!(json.get("ex_data").is_none(), "ex_data should be flattened");
}
#[test]
fn metrics_updated_ex_data_collision() {
// Test collision: same field set via struct AND ex_data.
// With serde's preserve_order feature and ex_data at the top of the
// struct, standard fields are serialized last and take precedence.
let ex_data = ExData::from([(
"min_rtt".to_string(),
serde_json::json!(COLLISION_VALUE),
)]);
let metrics = MetricsUpdated {
min_rtt: Some(MIN_RTT), // struct field value
ex_data, // ex_data also has min_rtt
..Default::default()
};
let json = serde_json::to_value(&metrics).unwrap();
// Standard field wins in collision - ex_data cannot overwrite standard
// fields, which prevents accidental data corruption.
assert_eq!(json["min_rtt"], MIN_RTT);
}
#[test]
fn metrics_updated_round_trip() {
// Test serialization -> deserialization round-trip
let ex_data = ExData::from([(
"delivery_rate".to_string(),
serde_json::json!(DELIVERY_RATE),
)]);
let original = MetricsUpdated {
min_rtt: Some(MIN_RTT),
smoothed_rtt: Some(SMOOTHED_RTT),
congestion_window: Some(CONGESTION_WINDOW),
pacing_rate: Some(PACING_RATE),
ex_data,
..Default::default()
};
let json_str = serde_json::to_string(&original).unwrap();
let deserialized: MetricsUpdated = serde_json::from_str(&json_str).unwrap();
// Standard fields round-trip correctly
assert_eq!(deserialized.min_rtt, original.min_rtt);
assert_eq!(deserialized.smoothed_rtt, original.smoothed_rtt);
assert_eq!(deserialized.congestion_window, original.congestion_window);
assert_eq!(deserialized.pacing_rate, original.pacing_rate);
// ex_data fields round-trip correctly
assert_eq!(
deserialized.ex_data.get("delivery_rate"),
Some(&serde_json::json!(DELIVERY_RATE))
);
}
#[test]
fn metrics_updated_no_ex_data() {
// Test that ex_data is not present when not used
let metrics = MetricsUpdated {
min_rtt: Some(MIN_RTT),
congestion_window: Some(CONGESTION_WINDOW),
..Default::default()
};
let json = serde_json::to_value(&metrics).unwrap();
// Verify standard fields are present
assert_eq!(json["min_rtt"], MIN_RTT);
assert_eq!(json["congestion_window"], CONGESTION_WINDOW);
// Verify ex_data is not present
assert!(
json.get("ex_data").is_none(),
"ex_data should not be present"
);
}