1use std::cmp;
35
36use std::time::Duration;
37use std::time::Instant;
38
39use super::rtt::RttStats;
40use super::Acked;
41use super::Sent;
42
43use super::reno;
44use super::Congestion;
45use super::CongestionControlOps;
46use crate::recovery::MINIMUM_WINDOW_PACKETS;
47
48pub(crate) static CUBIC: CongestionControlOps = CongestionControlOps {
49 on_init,
50 on_packet_sent,
51 on_packets_acked,
52 congestion_event,
53 checkpoint,
54 rollback,
55 #[cfg(feature = "qlog")]
56 state_str,
57 debug_fmt,
58};
59
60const BETA_CUBIC: f64 = 0.7;
64
65const C: f64 = 0.4;
66
67const ROLLBACK_THRESHOLD_PERCENT: usize = 20;
70
71const MIN_ROLLBACK_THRESHOLD: usize = 2;
73
74const ALPHA_AIMD: f64 = 3.0 * (1.0 - BETA_CUBIC) / (1.0 + BETA_CUBIC);
76
77#[derive(Debug, Default)]
82pub struct State {
83 k: f64,
84
85 w_max: f64,
86
87 w_est: f64,
88
89 alpha_aimd: f64,
90
91 last_sent_time: Option<Instant>,
93
94 last_ack_time: Option<Instant>,
99
100 cwnd_inc: usize,
102
103 prior: PriorState,
105}
106
107#[derive(Debug, Default)]
111struct PriorState {
112 congestion_window: usize,
113
114 ssthresh: usize,
115
116 w_max: f64,
117
118 k: f64,
119
120 epoch_start: Option<Instant>,
121
122 lost_count: usize,
123}
124
125impl State {
131 fn cubic_k(&self, cwnd: usize, max_datagram_size: usize) -> f64 {
133 let w_max = self.w_max / max_datagram_size as f64;
134 let cwnd = cwnd as f64 / max_datagram_size as f64;
135
136 libm::cbrt((w_max - cwnd) / C)
137 }
138
139 fn w_cubic(&self, t: Duration, max_datagram_size: usize) -> f64 {
141 let w_max = self.w_max / max_datagram_size as f64;
142
143 (C * (t.as_secs_f64() - self.k).powi(3) + w_max) *
144 max_datagram_size as f64
145 }
146
147 fn w_est_inc(
149 &self, acked: usize, cwnd: usize, max_datagram_size: usize,
150 ) -> f64 {
151 self.alpha_aimd * (acked as f64 / cwnd as f64) * max_datagram_size as f64
152 }
153}
154
155fn on_init(_r: &mut Congestion) {}
156
157fn on_packet_sent(
158 r: &mut Congestion, sent_bytes: usize, bytes_in_flight: usize, now: Instant,
159) {
160 reno::on_packet_sent(r, sent_bytes, bytes_in_flight, now);
161
162 if sent_bytes == 0 && r.enable_cubic_idle_restart_fix {
166 return;
167 }
168
169 let cubic = &mut r.cubic_state;
173
174 if bytes_in_flight == 0 {
175 if let Some(recovery_start_time) = r.congestion_recovery_start_time {
176 let idle_start = if r.enable_cubic_idle_restart_fix {
182 cmp::max(cubic.last_ack_time, cubic.last_sent_time)
183 } else {
184 cubic.last_sent_time
185 };
186
187 if let Some(idle_start) = idle_start {
188 if idle_start < now {
189 let delta = now - idle_start;
190 r.congestion_recovery_start_time =
191 Some(recovery_start_time + delta);
192 }
193 }
194 }
195 }
196
197 cubic.last_sent_time = Some(now);
198}
199
200fn on_packets_acked(
201 r: &mut Congestion, bytes_in_flight: usize, packets: &mut Vec<Acked>,
202 now: Instant, rtt_stats: &RttStats,
203) {
204 r.cubic_state.last_ack_time = Some(now);
205
206 for pkt in packets.drain(..) {
207 on_packet_acked(r, bytes_in_flight, &pkt, now, rtt_stats);
208 }
209}
210
211fn on_packet_acked(
212 r: &mut Congestion, bytes_in_flight: usize, packet: &Acked, now: Instant,
213 rtt_stats: &RttStats,
214) {
215 let in_congestion_recovery = r.in_congestion_recovery(packet.time_sent);
216
217 if in_congestion_recovery {
218 r.prr.on_packet_acked(
219 packet.size,
220 bytes_in_flight,
221 r.ssthresh.get(),
222 r.max_datagram_size,
223 );
224
225 return;
226 }
227
228 if r.app_limited {
229 return;
230 }
231
232 if r.congestion_recovery_start_time.is_some() {
239 let new_lost = r.lost_count - r.cubic_state.prior.lost_count;
240
241 let rollback_threshold = (r.congestion_window / r.max_datagram_size) *
242 ROLLBACK_THRESHOLD_PERCENT /
243 100;
244
245 let rollback_threshold = rollback_threshold.max(MIN_ROLLBACK_THRESHOLD);
246
247 if new_lost < rollback_threshold {
248 let did_rollback = rollback(r);
249 if did_rollback {
250 return;
251 }
252 }
253 }
254
255 if r.congestion_window < r.ssthresh.get() {
256 r.bytes_acked_sl += packet.size;
259
260 if r.bytes_acked_sl >= r.max_datagram_size {
261 if r.hystart.in_css() {
262 r.congestion_window +=
263 r.hystart.css_cwnd_inc(r.max_datagram_size);
264 } else {
265 r.congestion_window += r.max_datagram_size;
266 }
267
268 r.bytes_acked_sl -= r.max_datagram_size;
269 }
270
271 if r.hystart.on_packet_acked(packet, rtt_stats.latest_rtt, now) {
272 r.ssthresh.update(r.congestion_window, true);
274 }
275 } else {
276 let ca_start_time;
278
279 if r.hystart.in_css() {
281 ca_start_time = r.hystart.css_start_time().unwrap();
282
283 if r.cubic_state.w_max == 0.0 {
285 r.cubic_state.w_max = r.congestion_window as f64;
286 r.cubic_state.k = 0.0;
287
288 r.cubic_state.w_est = r.congestion_window as f64;
289 r.cubic_state.alpha_aimd = ALPHA_AIMD;
290 }
291 } else {
292 match r.congestion_recovery_start_time {
293 Some(t) => ca_start_time = t,
294 None => {
295 ca_start_time = now;
298 r.congestion_recovery_start_time = Some(now);
299
300 r.cubic_state.w_max = r.congestion_window as f64;
301 r.cubic_state.k = 0.0;
302
303 r.cubic_state.w_est = r.congestion_window as f64;
304 r.cubic_state.alpha_aimd = ALPHA_AIMD;
305 },
306 }
307 }
308
309 let t = now.saturating_duration_since(ca_start_time);
310
311 let target = r
313 .cubic_state
314 .w_cubic(t + *rtt_stats.min_rtt, r.max_datagram_size);
315
316 let target = f64::max(target, r.congestion_window as f64);
318 let target = f64::min(target, r.congestion_window as f64 * 1.5);
319
320 let w_est_inc = r.cubic_state.w_est_inc(
322 packet.size,
323 r.congestion_window,
324 r.max_datagram_size,
325 );
326 r.cubic_state.w_est += w_est_inc;
327
328 if r.cubic_state.w_est >= r.cubic_state.w_max {
329 r.cubic_state.alpha_aimd = 1.0;
330 }
331
332 let mut cubic_cwnd = r.congestion_window;
333
334 if r.cubic_state.w_cubic(t, r.max_datagram_size) < r.cubic_state.w_est {
335 cubic_cwnd = cmp::max(cubic_cwnd, r.cubic_state.w_est as usize);
337 } else {
338 let cubic_inc =
340 r.max_datagram_size * (target as usize - cubic_cwnd) / cubic_cwnd;
341
342 cubic_cwnd += cubic_inc;
343 }
344
345 r.cubic_state.cwnd_inc += cubic_cwnd - r.congestion_window;
347
348 if r.cubic_state.cwnd_inc >= r.max_datagram_size {
349 r.congestion_window += r.max_datagram_size;
350 r.cubic_state.cwnd_inc -= r.max_datagram_size;
351 }
352 }
353}
354
355fn congestion_event(
356 r: &mut Congestion, bytes_in_flight: usize, _lost_bytes: usize,
357 largest_lost_pkt: &Sent, now: Instant,
358) {
359 let time_sent = largest_lost_pkt.time_sent;
360 let in_congestion_recovery = r.in_congestion_recovery(time_sent);
361
362 if !in_congestion_recovery {
365 r.congestion_recovery_start_time = Some(now);
366
367 if (r.congestion_window as f64) < r.cubic_state.w_max {
369 r.cubic_state.w_max =
370 r.congestion_window as f64 * (1.0 + BETA_CUBIC) / 2.0;
371 } else {
372 r.cubic_state.w_max = r.congestion_window as f64;
373 }
374
375 let ssthresh = (r.congestion_window as f64 * BETA_CUBIC) as usize;
376 let ssthresh =
377 cmp::max(ssthresh, r.max_datagram_size * MINIMUM_WINDOW_PACKETS);
378 r.ssthresh.update(ssthresh, r.hystart.in_css());
379 r.congestion_window = ssthresh;
380
381 r.cubic_state.k = if r.cubic_state.w_max < r.congestion_window as f64 {
382 0.0
383 } else {
384 r.cubic_state
385 .cubic_k(r.congestion_window, r.max_datagram_size)
386 };
387
388 r.cubic_state.cwnd_inc =
389 (r.cubic_state.cwnd_inc as f64 * BETA_CUBIC) as usize;
390
391 r.cubic_state.w_est = r.congestion_window as f64;
392 r.cubic_state.alpha_aimd = ALPHA_AIMD;
393
394 if r.hystart.in_css() {
395 r.hystart.congestion_event();
396 }
397
398 r.prr.congestion_event(bytes_in_flight);
399 }
400}
401
402fn checkpoint(r: &mut Congestion) {
403 r.cubic_state.prior.congestion_window = r.congestion_window;
404 r.cubic_state.prior.ssthresh = r.ssthresh.get();
405 r.cubic_state.prior.w_max = r.cubic_state.w_max;
406 r.cubic_state.prior.k = r.cubic_state.k;
407 r.cubic_state.prior.epoch_start = r.congestion_recovery_start_time;
408 r.cubic_state.prior.lost_count = r.lost_count;
409}
410
411fn rollback(r: &mut Congestion) -> bool {
412 if r.cubic_state.prior.congestion_window < r.cubic_state.prior.ssthresh {
414 return false;
415 }
416
417 if r.congestion_window >= r.cubic_state.prior.congestion_window {
418 return false;
419 }
420
421 r.congestion_window = r.cubic_state.prior.congestion_window;
422 r.ssthresh.update(r.cubic_state.prior.ssthresh, false);
423 r.cubic_state.w_max = r.cubic_state.prior.w_max;
424 r.cubic_state.k = r.cubic_state.prior.k;
425 r.congestion_recovery_start_time = r.cubic_state.prior.epoch_start;
426
427 true
428}
429
430#[cfg(feature = "qlog")]
431fn state_str(r: &Congestion, now: Instant) -> &'static str {
432 reno::state_str(r, now)
433}
434
435fn debug_fmt(r: &Congestion, f: &mut std::fmt::Formatter) -> std::fmt::Result {
436 write!(
437 f,
438 "cubic={{ k={} w_max={} }} ",
439 r.cubic_state.k, r.cubic_state.w_max
440 )
441}
442
443#[cfg(test)]
444mod tests {
445 use super::*;
446
447 use crate::CongestionControlAlgorithm;
448
449 use crate::recovery::congestion::hystart;
450 use crate::recovery::congestion::recovery::LegacyRecovery;
451 use crate::recovery::congestion::test_sender::TestSender;
452 use crate::recovery::RecoveryOps;
453
454 fn test_sender() -> TestSender {
455 TestSender::new(CongestionControlAlgorithm::CUBIC, false)
456 }
457
458 fn hystart_test_sender() -> TestSender {
459 TestSender::new(CongestionControlAlgorithm::CUBIC, true)
460 }
461
462 #[test]
463 fn cubic_init() {
464 let mut cfg = crate::Config::new(crate::PROTOCOL_VERSION).unwrap();
465 cfg.set_cc_algorithm(CongestionControlAlgorithm::CUBIC);
466
467 let r = LegacyRecovery::new(&cfg);
468
469 assert!(r.cwnd() > 0);
470 assert_eq!(r.bytes_in_flight(), 0);
471 }
472
473 #[test]
474 fn cubic_slow_start() {
475 let mut sender = test_sender();
476 let size = sender.max_datagram_size;
477
478 for _ in 0..sender.initial_congestion_window_packets {
480 sender.send_packet(size);
481 }
482
483 let cwnd_prev = sender.congestion_window;
484
485 sender.ack_n_packets(1, size);
486
487 assert_eq!(sender.congestion_window, cwnd_prev + size);
489 }
490
491 #[test]
492 fn cubic_slow_start_multi_acks() {
493 let mut sender = test_sender();
494 let size = sender.max_datagram_size;
495
496 for _ in 0..sender.initial_congestion_window_packets {
498 sender.send_packet(size);
499 }
500
501 let cwnd_prev = sender.congestion_window;
502
503 sender.ack_n_packets(3, size);
504
505 assert_eq!(sender.congestion_window, cwnd_prev + size * 3);
507 }
508
509 #[test]
510 fn cubic_congestion_event() {
511 let mut sender = test_sender();
512 let size = sender.max_datagram_size;
513
514 sender.send_packet(size);
515
516 let cwnd_prev = sender.congestion_window;
517
518 sender.lose_n_packets(1, size, None);
519
520 assert_eq!(
523 cwnd_prev as f64 * BETA_CUBIC,
524 sender.congestion_window as f64
525 );
526 }
527
528 #[test]
529 fn cubic_congestion_avoidance() {
530 let mut sender = test_sender();
531 let size = sender.max_datagram_size;
532
533 let prev_cwnd = sender.congestion_window;
534
535 for _ in 0..sender.initial_congestion_window_packets {
537 sender.send_packet(size);
538 }
539
540 let rtt = Duration::from_millis(100);
541
542 sender.advance_time(rtt);
543 sender.lose_n_packets(1, size, None);
544
545 let cur_cwnd = (prev_cwnd as f64 * BETA_CUBIC) as usize;
547 assert_eq!(sender.congestion_window, cur_cwnd);
548
549 sender.update_rtt(rtt);
551 sender.advance_time(rtt);
553
554 sender.ack_n_packets(sender.initial_congestion_window_packets - 2, size);
555 assert_eq!(sender.congestion_window, cur_cwnd);
557
558 for _ in 0..8 {
559 sender.send_packet(size);
560 }
561 sender.advance_time(rtt);
562
563 sender.ack_n_packets(1, size);
564 assert_eq!(sender.congestion_window, cur_cwnd);
566
567 for _ in 0..6 {
570 sender.ack_n_packets(1, size);
571 sender.advance_time(rtt);
572 }
573
574 assert_eq!(sender.congestion_window, cur_cwnd + size);
575 }
576
577 #[test]
578 fn cubic_hystart_css_to_ss() {
579 let mut sender = hystart_test_sender();
580 let size = sender.max_datagram_size;
581
582 let n_rtt_sample = hystart::N_RTT_SAMPLE;
584
585 let rtt_1st = Duration::from_millis(50);
586
587 let next_rnd = sender.next_pkt + n_rtt_sample as u64 - 1;
588 sender.hystart.start_round(next_rnd);
589 for _ in 0..n_rtt_sample {
591 sender.send_packet(size);
592 }
593 sender.update_app_limited(false);
594
595 sender.advance_time(rtt_1st);
597 sender.update_rtt(rtt_1st);
598 sender.ack_n_packets(n_rtt_sample, size);
599
600 assert!(sender.hystart.css_start_time().is_none());
602
603 let mut rtt_2nd = Duration::from_millis(100);
605
606 sender.advance_time(rtt_2nd);
607
608 let next_rnd = sender.next_pkt + n_rtt_sample as u64 - 1;
609 sender.hystart.start_round(next_rnd);
610 for _ in 0..n_rtt_sample {
612 sender.send_packet(size);
613 }
614 sender.update_app_limited(false);
615
616 let mut cwnd_prev = sender.congestion_window();
619
620 for _ in 0..n_rtt_sample {
621 cwnd_prev = sender.congestion_window();
622 sender.update_rtt(rtt_2nd);
623 sender.ack_n_packets(1, size);
624 rtt_2nd += rtt_2nd.saturating_add(Duration::from_millis(4));
626 }
627
628 assert!(sender.hystart.css_start_time().is_some());
630 assert_eq!(sender.congestion_window(), cwnd_prev + size);
631
632 let rtt_3rd = Duration::from_millis(80);
635 sender.advance_time(rtt_3rd);
636 cwnd_prev = sender.congestion_window();
637
638 let next_rnd = sender.next_pkt + n_rtt_sample as u64 - 1;
639 sender.hystart.start_round(next_rnd);
640 for _ in 0..n_rtt_sample {
642 sender.send_packet(size);
643 }
644 sender.update_app_limited(false);
645
646 sender.update_rtt(rtt_3rd);
649 sender.ack_n_packets(n_rtt_sample, size);
650
651 assert!(sender.hystart.css_start_time().is_none());
653 assert_eq!(
654 sender.congestion_window(),
655 cwnd_prev +
656 size / hystart::CSS_GROWTH_DIVISOR * hystart::N_RTT_SAMPLE
657 );
658 }
659
660 #[test]
661 fn cubic_hystart_css_to_ca() {
662 let mut sender = hystart_test_sender();
663 let size = sender.max_datagram_size;
664
665 let n_rtt_sample = hystart::N_RTT_SAMPLE;
667
668 let rtt_1st = Duration::from_millis(50);
669
670 let next_rnd = sender.next_pkt + n_rtt_sample as u64 - 1;
671 sender.hystart.start_round(next_rnd);
672 for _ in 0..n_rtt_sample {
674 sender.send_packet(size);
675 }
676 sender.update_app_limited(false);
677
678 sender.advance_time(rtt_1st);
680 sender.update_rtt(rtt_1st);
681 sender.ack_n_packets(n_rtt_sample, size);
682
683 assert!(sender.hystart.css_start_time().is_none());
685
686 let mut rtt_2nd = Duration::from_millis(100);
688 sender.advance_time(rtt_2nd);
689 let next_rnd = sender.next_pkt + n_rtt_sample as u64 - 1;
691 sender.hystart.start_round(next_rnd);
692 for _ in 0..n_rtt_sample {
693 sender.send_packet(size);
694 }
695 sender.update_app_limited(false);
696
697 let mut cwnd_prev = sender.congestion_window();
700
701 for _ in 0..n_rtt_sample {
702 cwnd_prev = sender.congestion_window();
703 sender.update_rtt(rtt_2nd);
704 sender.ack_n_packets(1, size);
705 rtt_2nd += rtt_2nd.saturating_add(Duration::from_millis(4));
707 }
708
709 assert!(sender.hystart.css_start_time().is_some());
711 assert_eq!(sender.congestion_window(), cwnd_prev + size);
712
713 let rtt_css = Duration::from_millis(100);
715 sender.advance_time(rtt_css);
716
717 for _ in 0..hystart::CSS_ROUNDS {
718 let next_rnd = sender.next_pkt + n_rtt_sample as u64 - 1;
720 sender.hystart.start_round(next_rnd);
721 for _ in 0..n_rtt_sample {
722 sender.send_packet(size);
723 }
724 sender.update_app_limited(false);
725
726 sender.update_rtt(rtt_css);
728 sender.ack_n_packets(n_rtt_sample, size);
729 }
730 assert_eq!(sender.congestion_window(), sender.ssthresh.get());
732 }
733
734 #[test]
735 fn cubic_spurious_congestion_event() {
736 let mut sender = test_sender();
737 let size = sender.max_datagram_size;
738
739 let prev_cwnd = sender.congestion_window();
740
741 for _ in 0..sender.initial_congestion_window_packets {
743 sender.send_packet(size);
744 }
745 sender.lose_n_packets(1, size, None);
746
747 let cur_cwnd = (prev_cwnd as f64 * BETA_CUBIC) as usize;
749 assert_eq!(sender.congestion_window(), cur_cwnd);
750
751 let rtt = Duration::from_millis(100);
753 sender.update_rtt(rtt);
754
755 let acked = Acked {
756 pkt_num: 0,
757 time_sent: sender.time + rtt,
759 size,
760 delivered: 0,
761 delivered_time: sender.time,
762 first_sent_time: sender.time,
763 is_app_limited: false,
764 rtt: Duration::ZERO,
765 };
766
767 sender.inject_ack(acked, sender.time + rtt + Duration::from_millis(5));
769
770 assert_eq!(sender.congestion_window(), cur_cwnd);
772
773 sender.advance_time(rtt);
774
775 let prev_cwnd = sender.congestion_window();
776
777 sender.lose_n_packets(1, size, Some(sender.time));
778
779 let cur_cwnd = (cur_cwnd as f64 * BETA_CUBIC) as usize;
781 assert_eq!(sender.congestion_window(), cur_cwnd);
782
783 sender.advance_time(rtt + Duration::from_millis(5));
784
785 let acked = Acked {
786 pkt_num: 0,
787 time_sent: sender.time + rtt,
789 size,
790 delivered: 0,
791 delivered_time: sender.time,
792 first_sent_time: sender.time,
793 is_app_limited: false,
794 rtt: Duration::ZERO,
795 };
796
797 sender.inject_ack(acked, sender.time + rtt + Duration::from_millis(5));
799
800 assert_eq!(sender.congestion_window(), prev_cwnd);
802 }
803
804 #[test]
805 fn cubic_fast_convergence() {
806 let mut sender = test_sender();
807 let size = sender.max_datagram_size;
808
809 let prev_cwnd = sender.congestion_window;
810
811 for _ in 0..sender.initial_congestion_window_packets {
813 sender.send_packet(size);
814 }
815
816 sender.lose_n_packets(1, size, None);
818
819 let cur_cwnd = (prev_cwnd as f64 * BETA_CUBIC) as usize;
821 assert_eq!(sender.congestion_window, cur_cwnd);
822
823 let rtt = Duration::from_millis(100);
825 sender.update_rtt(rtt);
826 sender.advance_time(rtt);
828
829 sender.ack_n_packets(sender.initial_congestion_window_packets - 2, size);
832
833 for _ in 0..7 {
837 sender.send_packet(size);
838 }
839 sender.ack_n_packets(1, size);
841 sender.advance_time(rtt);
842 for _ in 0..6 {
843 sender.ack_n_packets(1, size);
844 }
845
846 assert_eq!(sender.congestion_window, cur_cwnd + size);
847
848 let prev_cwnd = sender.congestion_window;
849
850 sender.lose_n_packets(1, size, None);
854
855 let cur_cwnd = (prev_cwnd as f64 * BETA_CUBIC) as usize;
857 assert_eq!(sender.congestion_window, cur_cwnd);
858
859 assert_eq!(
861 sender.cubic_state.w_max,
862 prev_cwnd as f64 * (1.0 + BETA_CUBIC) / 2.0
863 );
864 }
865
866 #[test]
867 fn cubic_perpetual_recovery_trap() {
868 let mut sender = test_sender();
873 let size = sender.max_datagram_size;
874 let rtt = Duration::from_millis(100);
875
876 for _ in 0..sender.initial_congestion_window_packets {
878 sender.send_packet(size);
879 }
880
881 sender.update_rtt(rtt);
882 sender.advance_time(rtt);
883
884 let initial_cwnd = sender.congestion_window;
886 sender.lose_n_packets(1, size, None);
887 let post_loss_cwnd = sender.congestion_window;
888 assert_eq!(post_loss_cwnd, (initial_cwnd as f64 * BETA_CUBIC) as usize);
889 let initial_recovery_start_time =
890 sender.congestion_recovery_start_time.unwrap();
891 assert_eq!(initial_recovery_start_time, sender.time);
892
893 sender.ack_n_packets(sender.initial_congestion_window_packets - 1, size);
895 assert_eq!(sender.bytes_in_flight, 0);
896
897 let packets_per_burst = cmp::max(1, sender.congestion_window / size);
902
903 for _ in 0..20 {
904 for _ in 0..packets_per_burst {
905 sender.send_packet(size);
906 }
907
908 sender.advance_time(rtt);
909 sender.ack_n_packets(packets_per_burst, size);
910 assert_eq!(sender.bytes_in_flight, 0);
911 }
912
913 assert!(
915 sender.congestion_window > post_loss_cwnd,
916 "cwnd stuck at {} (post-loss {}): perpetual recovery trap",
917 sender.congestion_window,
918 post_loss_cwnd,
919 );
920 assert_eq!(
922 sender.congestion_recovery_start_time.unwrap() -
923 initial_recovery_start_time,
924 Duration::ZERO,
925 "epoch should not have shifted without idle gap",
926 );
927 }
928
929 #[test]
930 fn cubic_genuine_idle_epoch_shift() {
931 let mut sender = test_sender();
935 let test_start = sender.time;
936 let size = sender.max_datagram_size;
937 let rtt = Duration::from_millis(100);
938
939 for _ in 0..sender.initial_congestion_window_packets {
941 sender.send_packet(size);
942 }
943 sender.update_rtt(rtt);
944 sender.advance_time(rtt);
945 sender.lose_n_packets(1, size, None);
946
947 let initial_recovery_start =
948 sender.congestion_recovery_start_time.unwrap();
949 assert_eq!(
950 initial_recovery_start,
951 test_start + rtt,
952 "Recovery start is set",
953 );
954
955 let post_loss_cwnd = sender.congestion_window;
956
957 sender.ack_n_packets(sender.initial_congestion_window_packets - 1, size);
959
960 let idle_duration = Duration::from_secs(5);
963 sender.advance_time(idle_duration);
964
965 let packets_per_burst = cmp::max(1, sender.congestion_window / size);
967
968 for _ in 0..packets_per_burst {
969 sender.send_packet(size);
970 }
971 sender.advance_time(rtt);
972 sender.ack_n_packets(packets_per_burst, size);
973
974 assert_eq!(sender.congestion_window, post_loss_cwnd);
977
978 let recovery_start = sender.congestion_recovery_start_time.unwrap();
981 assert!(
982 recovery_start >
983 sender.cubic_state.last_sent_time.unwrap() - idle_duration,
984 "epoch should have been shifted forward by idle period",
985 );
986 assert_eq!(
987 recovery_start - sender.cubic_state.last_sent_time.unwrap(),
988 Duration::ZERO,
989 "epoch should have been shifted forward by idle period",
990 );
991 assert_eq!(
992 recovery_start - initial_recovery_start,
993 idle_duration,
994 "Recovery start should have moved forward by the idle duration",
995 );
996 }
997
998 #[test]
999 fn cubic_zero_bytes_sent_no_epoch_shift() {
1000 let mut sender = test_sender();
1003 let size = sender.max_datagram_size;
1004 let rtt = Duration::from_millis(100);
1005
1006 for _ in 0..sender.initial_congestion_window_packets {
1008 sender.send_packet(size);
1009 }
1010 sender.update_rtt(rtt);
1011 sender.advance_time(rtt);
1012 sender.lose_n_packets(1, size, None);
1013
1014 sender.ack_n_packets(sender.initial_congestion_window_packets - 1, size);
1016 assert_eq!(sender.bytes_in_flight, 0);
1017
1018 let recovery_start_before = sender.congestion_recovery_start_time;
1020 let last_sent_before = sender.cubic_state.last_sent_time;
1021
1022 sender.advance_time(Duration::from_millis(500));
1024
1025 sender.send_packet(0);
1027
1028 assert_eq!(
1030 sender.congestion_recovery_start_time, recovery_start_before,
1031 "recovery_start_time must not shift on zero-byte send",
1032 );
1033 assert_eq!(
1034 sender.cubic_state.last_sent_time, last_sent_before,
1035 "last_sent_time must not update on zero-byte send",
1036 );
1037 }
1038}