use super::*;
use crate::{
event,
packet::number::PacketNumberSpace,
path,
recovery::congestion_controller::PathPublisher,
time::{Clock, NoopClock},
};
use core::time::Duration;
#[macro_export]
macro_rules! assert_delta {
($x:expr, $y:expr, $d:expr) => {
assert!(
($x - $y).abs() < $d,
"assertion failed: `({:?} - {:?}).abs() < {:?})`",
$x,
$y,
$d
);
};
}
fn bytes_to_packets(bytes: f32, max_datagram_size: u16) -> f32 {
bytes / max_datagram_size as f32
}
#[test]
fn w_cubic() {
let max_datagram_size = 1200;
let mut cubic = Cubic::new(max_datagram_size);
cubic.multiplicative_decrease(2_764_800.0);
assert_delta!(
cubic.w_max,
bytes_to_packets(2_764_800.0, max_datagram_size),
0.001
);
let mut t = Duration::from_secs(0);
assert_delta!(cubic.w_max * BETA_CUBIC, cubic.w_cubic(t), 0.001);
assert_eq!(cubic.k, Duration::from_secs(12));
t = Duration::from_secs(15);
assert_delta!(cubic.w_cubic(t), 2314.8, 0.001);
t = Duration::from_secs(10);
assert_delta!(cubic.w_cubic(t), 2300.8, 0.001);
}
#[test]
fn w_est() {
let max_datagram_size = 1200;
let mut cubic = Cubic::new(max_datagram_size);
cubic.w_max = 100.0;
let t = Duration::from_secs(6);
let rtt = Duration::from_millis(300);
assert_delta!(cubic.w_est(t, rtt), 80.5882, 0.001);
}
#[allow(clippy::float_cmp)]
#[test]
fn multiplicative_decrease() {
let max_datagram_size = 1200.0;
let mut cubic = Cubic::new(max_datagram_size as u16);
cubic.w_max = bytes_to_packets(10000.0, max_datagram_size as u16);
assert_eq!(
cubic.multiplicative_decrease(100_000.0),
(100_000.0 * BETA_CUBIC)
);
assert_delta!(cubic.w_last_max, cubic.w_max, 0.001);
assert_delta!(cubic.w_max, 100_000.0 / max_datagram_size, 0.001);
assert_eq!(
cubic.multiplicative_decrease(80000.0),
(80000.0 * BETA_CUBIC)
);
assert_delta!(cubic.w_last_max, 80000.0 / max_datagram_size, 0.001);
assert_delta!(cubic.w_max, 80000.0 * 0.85 / max_datagram_size, 0.001);
assert_eq!(0.7, BETA_CUBIC);
}
#[test]
fn is_congestion_limited() {
let max_datagram_size = 1000;
let mut cc = CubicCongestionController::new(max_datagram_size);
cc.congestion_window = 1000.0;
cc.bytes_in_flight = BytesInFlight::new(100);
assert!(cc.is_congestion_limited());
cc.congestion_window = 1100.0;
assert!(!cc.is_congestion_limited());
cc.bytes_in_flight = BytesInFlight::new(2000);
assert!(cc.is_congestion_limited());
}
#[test]
fn is_congestion_window_under_utilized() {
let max_datagram_size = 1200;
let mut cc = CubicCongestionController::new(max_datagram_size);
cc.congestion_window = 12000.0;
cc.bytes_in_flight = BytesInFlight::new(5999);
cc.state = SlowStart;
assert!(cc.is_congestion_window_under_utilized());
cc.bytes_in_flight = BytesInFlight::new(6000);
assert!(!cc.is_congestion_window_under_utilized());
cc.state = State::congestion_avoidance(NoopClock.get_time());
assert!(cc.is_congestion_window_under_utilized());
cc.bytes_in_flight = BytesInFlight::new(8399);
assert!(cc.is_congestion_window_under_utilized());
cc.bytes_in_flight = BytesInFlight::new(8400);
assert!(!cc.is_congestion_window_under_utilized());
}
#[test]
fn initial_window() {
let mut max_datagram_size = 1200;
assert_eq!(
(max_datagram_size * 10) as u32,
CubicCongestionController::initial_window(max_datagram_size)
);
max_datagram_size = 2000;
assert_eq!(
14720,
CubicCongestionController::initial_window(max_datagram_size)
);
max_datagram_size = 8000;
assert_eq!(
(max_datagram_size * 2) as u32,
CubicCongestionController::initial_window(max_datagram_size)
);
}
#[test]
fn minimum_window_equals_two_times_max_datagram_size() {
let max_datagram_size = 1200;
let cc = CubicCongestionController::new(max_datagram_size);
assert_delta!(
(2 * max_datagram_size) as f32,
cc.cubic.minimum_window(),
0.001
);
}
#[test]
fn on_packet_sent() {
let mut cc = CubicCongestionController::new(1000);
let mut publisher = event::testing::Publisher::snapshot();
let mut publisher = PathPublisher::new(&mut publisher, path::Id::test_id());
let mut rtt_estimator = RttEstimator::default();
let now = NoopClock.get_time();
cc.congestion_window = 100_000.0;
cc.on_packet_sent(
now + Duration::from_secs(10),
1,
None,
&rtt_estimator,
&mut publisher,
);
assert_eq!(cc.bytes_in_flight, 1);
rtt_estimator.update_rtt(
Duration::from_millis(0),
Duration::from_millis(100),
now,
true,
PacketNumberSpace::ApplicationData,
);
cc.on_rtt_update(now, now, &rtt_estimator, &mut publisher);
assert!(cc.state.is_slow_start());
rtt_estimator.update_rtt(
Duration::from_millis(0),
Duration::from_millis(200),
now,
true,
PacketNumberSpace::ApplicationData,
);
cc.on_packet_sent(
now + Duration::from_secs(20),
1,
None,
&rtt_estimator,
&mut publisher,
);
assert_eq!(cc.bytes_in_flight, 2);
assert!(cc.state.is_slow_start());
for _i in 1..=8 {
cc.on_rtt_update(
now + Duration::from_secs(10),
now + Duration::from_secs(10),
&rtt_estimator,
&mut publisher,
);
}
assert!(!cc.state.is_slow_start());
assert_delta!(cc.slow_start.threshold, 100_000.0, 0.001);
}
#[test]
fn on_packet_sent_application_limited() {
let mut cc = CubicCongestionController::new(1000);
let mut publisher = event::testing::Publisher::snapshot();
let mut publisher = PathPublisher::new(&mut publisher, path::Id::test_id());
let rtt_estimator = RttEstimator::default();
let now = NoopClock.get_time();
cc.congestion_window = 100_000.0;
cc.bytes_in_flight = BytesInFlight::new(92_500);
cc.state = SlowStart;
cc.on_packet_sent(now, 1000, Some(true), &rtt_estimator, &mut publisher);
assert_eq!(cc.bytes_in_flight, 93_500);
assert_eq!(cc.time_of_last_sent_packet, Some(now));
cc.state = State::congestion_avoidance(now + Duration::from_secs(10));
assert!(!cc.under_utilized);
cc.on_packet_sent(
now + Duration::from_secs(15),
1000,
Some(true),
&rtt_estimator,
&mut publisher,
);
assert_eq!(cc.bytes_in_flight, 94_500);
assert_eq!(
cc.time_of_last_sent_packet,
Some(now + Duration::from_secs(15))
);
assert!(cc.under_utilized);
while cc.bytes_in_flight < cc.congestion_window() {
cc.on_packet_sent(
now + Duration::from_secs(20),
1000,
Some(true),
&rtt_estimator,
&mut publisher,
);
}
assert!(!cc.under_utilized);
}
#[test]
fn on_packet_sent_none_application_limited() {
let mut cc = CubicCongestionController::new(1000);
let mut publisher = event::testing::Publisher::snapshot();
let mut publisher = PathPublisher::new(&mut publisher, path::Id::test_id());
let rtt_estimator = RttEstimator::default();
let now = NoopClock.get_time();
cc.congestion_window = 100_000.0;
cc.bytes_in_flight = BytesInFlight::new(92_500);
cc.state = SlowStart;
cc.on_packet_sent(now, 1000, None, &rtt_estimator, &mut publisher);
assert_eq!(cc.bytes_in_flight, 93_500);
assert_eq!(cc.time_of_last_sent_packet, Some(now));
cc.state = State::congestion_avoidance(now + Duration::from_secs(10));
assert!(!cc.under_utilized);
cc.on_packet_sent(
now + Duration::from_secs(15),
1000,
None,
&rtt_estimator,
&mut publisher,
);
assert_eq!(cc.bytes_in_flight, 94_500);
assert_eq!(
cc.time_of_last_sent_packet,
Some(now + Duration::from_secs(15))
);
assert!(cc.under_utilized);
while cc.bytes_in_flight < cc.congestion_window() {
cc.on_packet_sent(
now + Duration::from_secs(20),
1000,
None,
&rtt_estimator,
&mut publisher,
);
}
assert!(!cc.under_utilized);
}
#[test]
fn on_packet_sent_fast_retransmission() {
let mut cc = CubicCongestionController::new(1000);
let mut publisher = event::testing::Publisher::snapshot();
let mut publisher = PathPublisher::new(&mut publisher, path::Id::test_id());
let rtt_estimator = RttEstimator::default();
let now = NoopClock.get_time();
cc.congestion_window = 100_000.0;
cc.bytes_in_flight = BytesInFlight::new(99900);
cc.state = Recovery(now, RequiresTransmission);
cc.on_packet_sent(
now + Duration::from_secs(10),
100,
None,
&rtt_estimator,
&mut publisher,
);
assert_eq!(cc.state, Recovery(now, Idle));
}
#[test]
fn congestion_avoidance_after_idle_period() {
let mut cc = CubicCongestionController::new(1000);
let mut publisher = event::testing::Publisher::snapshot();
let mut publisher = PathPublisher::new(&mut publisher, path::Id::test_id());
let now = NoopClock.get_time();
let rtt_estimator = &RttEstimator::default();
let random = &mut random::testing::Generator::default();
cc.congestion_window = 6000.0;
cc.bytes_in_flight = BytesInFlight::new(0);
cc.state = SlowStart;
cc.on_packet_sent(now, 1000, Some(true), rtt_estimator, &mut publisher);
assert_eq!(cc.bytes_in_flight, 1000);
cc.cubic.w_max = 6.0;
cc.state = State::congestion_avoidance(now + Duration::from_secs(10));
cc.on_packet_sent(
now + Duration::from_secs(15),
1000,
Some(true),
rtt_estimator,
&mut publisher,
);
assert!(cc.is_congestion_window_under_utilized());
assert_eq!(cc.bytes_in_flight, 2000);
cc.on_ack(
now,
1000,
(),
rtt_estimator,
random,
now + Duration::from_secs(16),
&mut publisher,
);
assert_eq!(
cc.state,
CongestionAvoidance(CongestionAvoidanceTiming {
start_time: now + Duration::from_secs(10),
window_increase_time: now + Duration::from_secs(10),
app_limited_time: Some(now + Duration::from_secs(16)),
})
);
assert_eq!(cc.bytes_in_flight, 1000);
while cc.bytes_in_flight < cc.congestion_window() {
cc.on_packet_sent(
now + Duration::from_secs(20),
1000,
Some(false),
rtt_estimator,
&mut publisher,
);
}
assert!(!cc.is_congestion_window_under_utilized());
cc.on_ack(
now,
1000,
(),
rtt_estimator,
random,
now + Duration::from_secs(25),
&mut publisher,
);
assert_eq!(
cc.state,
CongestionAvoidance(CongestionAvoidanceTiming {
start_time: now + Duration::from_secs(16),
window_increase_time: now + Duration::from_secs(25),
app_limited_time: None,
})
);
if let CongestionAvoidance(timing) = cc.state {
assert_eq!(
Duration::from_secs(9),
timing.t(now + Duration::from_secs(25))
);
}
}
#[test]
fn congestion_avoidance_after_fast_convergence() {
let max_datagram_size = 1200;
let mut cc = CubicCongestionController::new(max_datagram_size);
let mut publisher = event::testing::Publisher::snapshot();
let mut publisher = PathPublisher::new(&mut publisher, path::Id::test_id());
let now = NoopClock.get_time();
let random = &mut random::testing::Generator::default();
cc.bytes_in_flight = BytesInFlight::new(100);
cc.congestion_window = 80_000.0;
cc.cubic.w_last_max = bytes_to_packets(100_000.0, max_datagram_size);
cc.on_packet_lost(100, (), false, false, random, now, &mut publisher);
assert_delta!(cc.congestion_window, 80_000.0 * BETA_CUBIC, 0.001);
assert_delta!(
cc.cubic.w_last_max,
80000.0 / max_datagram_size as f32,
0.001
);
assert_delta!(
cc.cubic.w_max,
80000.0 * 0.85 / max_datagram_size as f32,
0.001
);
let prev_cwnd = cc.congestion_window;
cc.congestion_avoidance(
Duration::from_millis(10),
Duration::from_millis(100),
100,
f32::MAX,
);
assert!(cc.congestion_window > prev_cwnd);
}
#[test]
fn congestion_avoidance_after_rtt_improvement() {
let max_datagram_size = 1200;
let mut cc = CubicCongestionController::new(max_datagram_size);
cc.bytes_in_flight = BytesInFlight::new(100);
cc.congestion_window = 80_000.0;
cc.cubic.w_max = cc.congestion_window / 1200.0;
cc.congestion_avoidance(
Duration::from_millis(10),
Duration::from_millis(750),
100,
f32::MAX,
);
let prev_cwnd = cc.congestion_window;
assert!(
cc.cubic.w_cubic(Duration::from_secs(0)) < bytes_to_packets(prev_cwnd, max_datagram_size)
);
cc.congestion_avoidance(
Duration::from_millis(20),
Duration::from_millis(10),
100,
f32::MAX,
);
assert_delta!(cc.congestion_window, prev_cwnd, 0.001);
}
#[test]
fn congestion_avoidance_with_small_min_rtt() {
let max_datagram_size = 1200;
let mut cc = CubicCongestionController::new(max_datagram_size);
cc.bytes_in_flight = BytesInFlight::new(100);
cc.congestion_window = 80_000.0;
cc.cubic.w_max = cc.congestion_window / 1200.0;
cc.congestion_avoidance(
Duration::from_millis(100),
Duration::from_millis(1),
100,
f32::MAX,
);
assert_delta!(cc.congestion_window, 80_050.0, 0.001);
}
#[test]
fn congestion_avoidance_max_cwnd() {
let max_datagram_size = 1200;
let mut cc = CubicCongestionController::new(max_datagram_size);
cc.bytes_in_flight = BytesInFlight::new(100);
cc.congestion_window = 80_000.0;
cc.cubic.w_max = bytes_to_packets(100_000.0, max_datagram_size);
cc.congestion_avoidance(
Duration::from_millis(300),
Duration::from_millis(100),
1200,
80_100.0,
);
assert_delta!(cc.congestion_window, 80_100.0, 0.001);
}
#[test]
fn on_packet_lost() {
let mut cc = CubicCongestionController::new(1000);
let mut publisher = event::testing::Publisher::snapshot();
let mut publisher = PathPublisher::new(&mut publisher, path::Id::test_id());
let now = NoopClock.get_time();
let random = &mut random::testing::Generator::default();
cc.congestion_window = 100_000.0;
cc.bytes_in_flight = BytesInFlight::new(100_000);
cc.state = SlowStart;
cc.on_packet_lost(
100,
(),
false,
false,
random,
now + Duration::from_secs(10),
&mut publisher,
);
assert_eq!(cc.bytes_in_flight, 100_000u32 - 100);
assert_eq!(
cc.state,
Recovery(now + Duration::from_secs(10), RequiresTransmission)
);
assert_delta!(cc.congestion_window, 100_000.0 * BETA_CUBIC, 0.001);
assert_delta!(cc.slow_start.threshold, 100_000.0 * BETA_CUBIC, 0.001);
}
#[test]
fn on_packet_lost_below_minimum_window() {
let mut cc = CubicCongestionController::new(1000);
let mut publisher = event::testing::Publisher::snapshot();
let mut publisher = PathPublisher::new(&mut publisher, path::Id::test_id());
let now = NoopClock.get_time();
let random = &mut random::testing::Generator::default();
cc.congestion_window = cc.cubic.minimum_window();
cc.bytes_in_flight = BytesInFlight::new(cc.congestion_window());
cc.state = State::congestion_avoidance(now);
cc.on_packet_lost(
100,
(),
false,
false,
random,
now + Duration::from_secs(10),
&mut publisher,
);
assert_delta!(cc.congestion_window, cc.cubic.minimum_window(), 0.001);
}
#[test]
fn on_packet_lost_already_in_recovery() {
let mut cc = CubicCongestionController::new(1000);
let mut publisher = event::testing::Publisher::snapshot();
let mut publisher = PathPublisher::new(&mut publisher, path::Id::test_id());
let now = NoopClock.get_time();
let random = &mut random::testing::Generator::default();
cc.congestion_window = 10000.0;
cc.bytes_in_flight = BytesInFlight::new(1000);
cc.state = Recovery(now, Idle);
cc.on_packet_lost(50, (), false, false, random, now, &mut publisher);
cc.on_packet_lost(50, (), false, false, random, now, &mut publisher);
assert_delta!(cc.congestion_window, 10000.0, 0.001);
}
#[test]
fn on_packet_lost_persistent_congestion() {
let mut cc = CubicCongestionController::new(1000);
let mut publisher = event::testing::Publisher::snapshot();
let mut publisher = PathPublisher::new(&mut publisher, path::Id::test_id());
let now = NoopClock.get_time();
let random = &mut random::testing::Generator::default();
cc.congestion_window = 10000.0;
cc.bytes_in_flight = BytesInFlight::new(1000);
cc.state = Recovery(now, Idle);
cc.on_packet_lost(100, (), true, false, random, now, &mut publisher);
assert!(cc.state.is_slow_start());
assert_eq!(cc.state, SlowStart);
assert_delta!(cc.congestion_window, cc.cubic.minimum_window(), 0.001);
assert_delta!(cc.cubic.w_max, 0.0, 0.001);
assert_delta!(cc.cubic.w_last_max, 0.0, 0.001);
assert_eq!(cc.cubic.k, Duration::from_millis(0));
}
#[test]
fn on_mtu_update_increase() {
let mut mtu = 5000;
let cwnd_in_packets = 100_000f32;
let cwnd_in_bytes = cwnd_in_packets / mtu as f32;
let mut cc = CubicCongestionController::new(mtu);
let mut publisher = event::testing::Publisher::snapshot();
let mut publisher = PathPublisher::new(&mut publisher, path::Id::test_id());
cc.congestion_window = cwnd_in_packets;
mtu = 10000;
cc.on_mtu_update(mtu, &mut publisher);
assert_eq!(cc.max_datagram_size, mtu);
assert_eq!(cc.cubic.max_datagram_size, mtu);
assert_delta!(cc.congestion_window, 200_000.0, 0.001);
assert_delta!(cc.congestion_window / mtu as f32, cwnd_in_bytes, 0.001);
}
#[test]
fn on_packet_discarded() {
let mut cc = CubicCongestionController::new(5000);
let mut publisher = event::testing::Publisher::snapshot();
let mut publisher = PathPublisher::new(&mut publisher, path::Id::test_id());
cc.bytes_in_flight = BytesInFlight::new(10000);
cc.on_packet_discarded(1000, &mut publisher);
assert_eq!(cc.bytes_in_flight, 10000 - 1000);
let now = NoopClock.get_time();
cc.state = Recovery(now, FastRetransmission::RequiresTransmission);
cc.on_packet_discarded(1000, &mut publisher);
assert_eq!(Recovery(now, FastRetransmission::Idle), cc.state);
}
#[test]
fn on_packet_ack_limited() {
let mut cc = CubicCongestionController::new(5000);
let mut publisher = event::testing::Publisher::snapshot();
let mut publisher = PathPublisher::new(&mut publisher, path::Id::test_id());
let now = NoopClock.get_time();
let random = &mut random::testing::Generator::default();
cc.congestion_window = 100_000.0;
cc.bytes_in_flight = BytesInFlight::new(10000);
cc.under_utilized = true;
cc.state = SlowStart;
cc.on_ack(
now,
1,
(),
&RttEstimator::default(),
random,
now,
&mut publisher,
);
assert_delta!(cc.congestion_window, 100_000.0, 0.001);
cc.state = State::congestion_avoidance(now);
cc.on_ack(
now,
1,
(),
&RttEstimator::default(),
random,
now,
&mut publisher,
);
assert_delta!(cc.congestion_window, 100_000.0, 0.001);
}
#[test]
#[should_panic]
fn on_packet_ack_timestamp_regression() {
let mut cc = CubicCongestionController::new(5000);
let mut publisher = event::testing::Publisher::snapshot();
let mut publisher = PathPublisher::new(&mut publisher, path::Id::test_id());
let now = NoopClock.get_time() + Duration::from_secs(1);
let rtt_estimator = RttEstimator::default();
let random = &mut random::testing::Generator::default();
cc.congestion_window = 100_000.0;
cc.bytes_in_flight = BytesInFlight::new(10000);
cc.under_utilized = true;
cc.state = State::congestion_avoidance(now);
cc.on_ack(now, 1, (), &rtt_estimator, random, now, &mut publisher);
assert_eq!(
State::CongestionAvoidance(CongestionAvoidanceTiming {
start_time: now,
window_increase_time: now,
app_limited_time: Some(now),
}),
cc.state
);
cc.on_ack(
now,
1,
(),
&rtt_estimator,
random,
now - Duration::from_secs(1),
&mut publisher,
);
}
#[test]
fn on_packet_ack_utilized_then_under_utilized() {
let mut cc = CubicCongestionController::new(5000);
let mut publisher = event::testing::Publisher::snapshot();
let mut publisher = PathPublisher::new(&mut publisher, path::Id::test_id());
let now = NoopClock.get_time();
let mut rtt_estimator = RttEstimator::default();
let random = &mut random::testing::Generator::default();
rtt_estimator.update_rtt(
Duration::from_secs(0),
Duration::from_millis(200),
now,
true,
PacketNumberSpace::ApplicationData,
);
cc.congestion_window = 100_000.0;
cc.state = SlowStart;
cc.on_packet_sent(now, 60_000, Some(true), &rtt_estimator, &mut publisher);
cc.on_ack(now, 10_000, (), &rtt_estimator, random, now, &mut publisher);
let cwnd = cc.congestion_window();
assert!(!cc.under_utilized);
assert!(cwnd > 100_000);
assert!(cc.is_congestion_window_under_utilized());
cc.on_ack(
now,
1200,
(),
&rtt_estimator,
random,
now + Duration::from_millis(100),
&mut publisher,
);
assert!(cc.congestion_window() > cwnd);
cc.on_ack(
now,
40_000,
(),
&rtt_estimator,
random,
now + Duration::from_millis(100),
&mut publisher,
);
assert_eq!(60_000 * 2, cc.congestion_window());
let cwnd = cc.congestion_window();
cc.on_packet_sent(now, 1200, Some(true), &rtt_estimator, &mut publisher);
assert!(cc.under_utilized);
cc.on_ack(
now,
1200,
(),
&rtt_estimator,
random,
now + Duration::from_millis(201),
&mut publisher,
);
assert_eq!(cc.congestion_window(), cwnd);
}
#[test]
fn on_packet_ack_congestion_avoidance_max_cwnd() {
let mut cc = CubicCongestionController::new(5000);
let mut publisher = event::testing::Publisher::snapshot();
let mut publisher = PathPublisher::new(&mut publisher, path::Id::test_id());
let now = NoopClock.get_time();
let mut rtt_estimator = RttEstimator::default();
let random = &mut random::testing::Generator::default();
rtt_estimator.update_rtt(
Duration::from_secs(0),
Duration::from_millis(200),
now,
true,
PacketNumberSpace::ApplicationData,
);
cc.congestion_window = 89_000.0;
cc.state = State::congestion_avoidance(now);
cc.cubic.w_max = 100_000.0;
cc.on_packet_sent(now, 60_000, Some(false), &rtt_estimator, &mut publisher);
cc.on_ack(now, 60_000, (), &rtt_estimator, random, now, &mut publisher);
assert_eq!(90_000, cc.congestion_window());
}
#[test]
fn on_packet_ack_recovery_to_congestion_avoidance() {
let mut cc = CubicCongestionController::new(5000);
let mut publisher = event::testing::Publisher::snapshot();
let mut publisher = PathPublisher::new(&mut publisher, path::Id::test_id());
let now = NoopClock.get_time();
let random = &mut random::testing::Generator::default();
cc.cubic.w_max = bytes_to_packets(25000.0, 5000);
cc.state = Recovery(now, Idle);
cc.bytes_in_flight = BytesInFlight::new(25000);
cc.under_utilized = false;
cc.on_ack(
now + Duration::from_millis(1),
1,
(),
&RttEstimator::default(),
random,
now + Duration::from_millis(2),
&mut publisher,
);
assert_eq!(
cc.state,
State::congestion_avoidance(now + Duration::from_millis(2))
);
}
#[test]
fn on_packet_ack_slow_start_to_congestion_avoidance() {
let mut cc = CubicCongestionController::new(5000);
let mut publisher = event::testing::Publisher::snapshot();
let mut publisher = PathPublisher::new(&mut publisher, path::Id::test_id());
let now = NoopClock.get_time();
let random = &mut random::testing::Generator::default();
cc.state = SlowStart;
cc.congestion_window = 10000.0;
cc.bytes_in_flight = BytesInFlight::new(10000);
cc.slow_start.threshold = 10050.0;
cc.under_utilized = false;
cc.on_ack(
now,
100,
(),
&RttEstimator::default(),
random,
now + Duration::from_millis(2),
&mut publisher,
);
assert_delta!(cc.congestion_window, 10100.0, 0.001);
assert_delta!(
cc.packets_to_bytes(cc.cubic.w_max),
cc.congestion_window,
0.001
);
assert_eq!(cc.cubic.k, Duration::from_secs(0));
assert_eq!(
cc.state,
State::congestion_avoidance(now + Duration::from_millis(2))
);
}
#[test]
fn on_packet_ack_recovery() {
let mut cc = CubicCongestionController::new(5000);
let mut publisher = event::testing::Publisher::snapshot();
let mut publisher = PathPublisher::new(&mut publisher, path::Id::test_id());
let now = NoopClock.get_time();
let random = &mut random::testing::Generator::default();
cc.state = Recovery(now, Idle);
cc.congestion_window = 10000.0;
cc.bytes_in_flight = BytesInFlight::new(10000);
cc.on_ack(
now,
100,
(),
&RttEstimator::default(),
random,
now + Duration::from_millis(2),
&mut publisher,
);
assert_delta!(cc.congestion_window, 10000.0, 0.001);
assert_eq!(cc.state, Recovery(now, Idle));
}
#[test]
fn on_packet_ack_congestion_avoidance() {
let max_datagram_size = 5000;
let mut cc = CubicCongestionController::new(max_datagram_size);
let mut cc2 = CubicCongestionController::new(max_datagram_size);
let mut publisher = event::testing::Publisher::snapshot();
let mut publisher = PathPublisher::new(&mut publisher, path::Id::test_id());
let now = NoopClock.get_time();
let random = &mut random::testing::Generator::default();
cc.state = State::congestion_avoidance(now + Duration::from_millis(3300));
cc.congestion_window = 10000.0;
cc.bytes_in_flight = BytesInFlight::new(10000);
cc.cubic.w_max = bytes_to_packets(10000.0, max_datagram_size);
cc.under_utilized = false;
cc2.congestion_window = 10000.0;
cc2.bytes_in_flight = BytesInFlight::new(10000);
cc2.cubic.w_max = bytes_to_packets(10000.0, max_datagram_size);
let mut rtt_estimator = RttEstimator::default();
rtt_estimator.update_rtt(
Duration::from_secs(0),
Duration::from_millis(275),
now,
true,
PacketNumberSpace::ApplicationData,
);
cc.on_ack(
now,
1000,
(),
&rtt_estimator,
random,
now + Duration::from_millis(4750),
&mut publisher,
);
let t = Duration::from_millis(4750) - Duration::from_millis(3300);
let rtt = rtt_estimator.min_rtt();
cc2.congestion_avoidance(t, rtt, 1000, f32::MAX);
assert_delta!(cc.congestion_window, cc2.congestion_window, 0.001);
}
#[test]
fn on_packet_ack_congestion_avoidance_tcp_friendly_region() {
let mut cc = CubicCongestionController::new(5000);
cc.congestion_window = 10000.0;
cc.cubic.w_max = 2.5;
cc.cubic.k = Duration::from_secs_f32(2.823);
let t = Duration::from_millis(300);
let rtt = Duration::from_millis(250);
cc.congestion_avoidance(t, rtt, 5000, f32::MAX);
assert!(cc.cubic.w_cubic(t) < cc.cubic.w_est(t, rtt));
assert_delta!(cc.congestion_window, cc.cubic.w_est(t, rtt) * 5000.0, 0.001);
}
#[test]
fn on_packet_ack_congestion_avoidance_concave_region() {
let max_datagram_size = 1200;
let mut cc = CubicCongestionController::new(max_datagram_size as u16);
cc.congestion_window = 2_400_000.0;
cc.cubic.w_max = 2304.0;
cc.cubic.k = Duration::from_secs(12);
let t = Duration::from_millis(9800);
let rtt = Duration::from_millis(200);
cc.congestion_avoidance(t, rtt, 1000, f32::MAX);
assert!(cc.cubic.w_cubic(t) > cc.cubic.w_est(t, rtt));
assert_delta!(cc.congestion_window, 2_400_180.5, 0.001);
}
#[test]
fn on_packet_ack_congestion_avoidance_convex_region() {
let max_datagram_size = 1200;
let mut cc = CubicCongestionController::new(max_datagram_size);
cc.congestion_window = 3_600_000.0;
cc.cubic.w_max = 2304.0;
cc.cubic.k = Duration::from_secs(12);
let t = Duration::from_millis(25800);
let rtt = Duration::from_millis(200);
cc.congestion_avoidance(t, rtt, 1000, f32::MAX);
assert!(cc.cubic.w_cubic(t) > cc.cubic.w_est(t, rtt));
assert_eq!(cc.congestion_window(), 3_600_160);
}
#[test]
fn on_packet_ack_congestion_avoidance_too_large_increase() {
let max_datagram_size = 1200;
let mut cc = CubicCongestionController::new(max_datagram_size);
cc.congestion_window = 3_600_000.0;
cc.cubic.w_max = bytes_to_packets(2_764_800.0, max_datagram_size);
let t = Duration::from_millis(125_800);
let rtt = Duration::from_millis(200);
cc.congestion_avoidance(t, rtt, 1000, f32::MAX);
assert!(cc.cubic.w_cubic(t) > cc.cubic.w_est(t, rtt));
assert_delta!(cc.congestion_window, 3_600_000.0 + 1000.0 / 2.0, 0.001);
}