quiche/recovery/congestion/bbr/
mod.rs

1// Copyright (C) 2022, Cloudflare, Inc.
2// All rights reserved.
3//
4// Redistribution and use in source and binary forms, with or without
5// modification, are permitted provided that the following conditions are
6// met:
7//
8//     * Redistributions of source code must retain the above copyright notice,
9//       this list of conditions and the following disclaimer.
10//
11//     * Redistributions in binary form must reproduce the above copyright
12//       notice, this list of conditions and the following disclaimer in the
13//       documentation and/or other materials provided with the distribution.
14//
15// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
16// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
17// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
19// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
22// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
23// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
24// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
27//! BBR Congestion Control
28//!
29//! This implementation is based on the following draft:
30//! <https://tools.ietf.org/html/draft-cardwell-iccrg-bbr-congestion-control-00>
31
32use super::*;
33use crate::minmax::Minmax;
34
35use std::time::Duration;
36
37use super::CongestionControlOps;
38
39pub(crate) static BBR: CongestionControlOps = CongestionControlOps {
40    on_init,
41    on_packet_sent,
42    on_packets_acked,
43    congestion_event,
44    checkpoint,
45    rollback,
46    has_custom_pacing,
47    #[cfg(feature = "qlog")]
48    state_str,
49    debug_fmt,
50};
51
52/// A constant specifying the length of the BBR.BtlBw max filter window for
53/// BBR.BtlBwFilter, BtlBwFilterLen is 10 packet-timed round trips.
54const BTLBW_FILTER_LEN: Duration = Duration::from_secs(10);
55
56/// A constant specifying the minimum time interval between ProbeRTT states: 10
57/// secs.
58const PROBE_RTT_INTERVAL: Duration = Duration::from_secs(10);
59
60/// A constant specifying the length of the RTProp min filter window.
61const RTPROP_FILTER_LEN: Duration = PROBE_RTT_INTERVAL;
62
63/// A constant specifying the minimum gain value that will allow the sending
64/// rate to double each round (2/ln(2) ~= 2.89), used in Startup mode for both
65/// BBR.pacing_gain and BBR.cwnd_gain.
66const BBR_HIGH_GAIN: f64 = 2.89;
67
68/// The minimal cwnd value BBR tries to target using: 4 packets, or 4 * SMSS
69const BBR_MIN_PIPE_CWND_PKTS: usize = 4;
70
71/// The number of phases in the BBR ProbeBW gain cycle: 8.
72const BBR_GAIN_CYCLE_LEN: usize = 8;
73
74/// A constant specifying the minimum duration for which ProbeRTT state holds
75/// inflight to BBRMinPipeCwnd or fewer packets: 200 ms.
76const PROBE_RTT_DURATION: Duration = Duration::from_millis(200);
77
78/// Pacing Gain Cycle.
79const PACING_GAIN_CYCLE: [f64; BBR_GAIN_CYCLE_LEN] =
80    [5.0 / 4.0, 3.0 / 4.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0];
81
82/// A constant to check BBR.BtlBW is still growing.
83const BTLBW_GROWTH_TARGET: f64 = 1.25;
84
85/// BBR Internal State Machine.
86#[derive(Clone, Copy, Debug, PartialEq, Eq)]
87enum BBRStateMachine {
88    Startup,
89    Drain,
90    ProbeBW,
91    ProbeRTT,
92}
93
94impl From<BBRStateMachine> for &'static str {
95    fn from(state: BBRStateMachine) -> &'static str {
96        match state {
97            BBRStateMachine::Startup => "bbr_startup",
98            BBRStateMachine::Drain => "bbr_drain",
99            BBRStateMachine::ProbeBW => "bbr_probe_bw",
100            BBRStateMachine::ProbeRTT => "bbr_probe_rtt",
101        }
102    }
103}
104
105/// BBR Specific State Variables.
106pub struct State {
107    // The current state of a BBR flow in the BBR state machine.
108    state: BBRStateMachine,
109
110    // The current pacing rate for a BBR flow, which controls inter-packet
111    // spacing.
112    pacing_rate: u64,
113
114    // BBR's estimated bottleneck bandwidth available to the transport flow,
115    // estimated from the maximum delivery rate sample in a sliding window.
116    btlbw: u64,
117
118    // The max filter used to estimate BBR.BtlBw.
119    btlbwfilter: Minmax<u64>,
120
121    // BBR's estimated two-way round-trip propagation delay of the path,
122    // estimated from the windowed minimum recent round-trip delay sample.
123    rtprop: Duration,
124
125    // The wall clock time at which the current BBR.RTProp sample was obtained.
126    rtprop_stamp: Instant,
127
128    // A boolean recording whether the BBR.RTprop has expired and is due for a
129    // refresh with an application idle period or a transition into ProbeRTT
130    // state.
131    rtprop_expired: bool,
132
133    // The dynamic gain factor used to scale BBR.BtlBw to produce
134    // BBR.pacing_rate.
135    pacing_gain: f64,
136
137    // The dynamic gain factor used to scale the estimated BDP to produce a
138    // congestion window (cwnd).
139    cwnd_gain: f64,
140
141    // A boolean that records whether BBR estimates that it has ever fully
142    // utilized its available bandwidth ("filled the pipe").
143    filled_pipe: bool,
144
145    // Count of packet-timed round trips elapsed so far.
146    round_count: u64,
147
148    // A boolean that BBR sets to true once per packet-timed round trip,
149    // on ACKs that advance BBR.round_count.
150    round_start: bool,
151
152    // packet.delivered value denoting the end of a packet-timed round trip.
153    next_round_delivered: usize,
154
155    // Timestamp when ProbeRTT state ends.
156    probe_rtt_done_stamp: Option<Instant>,
157
158    // Checking if a roundtrip in ProbeRTT state ends.
159    probe_rtt_round_done: bool,
160
161    // Checking if in the packet conservation mode during recovery.
162    packet_conservation: bool,
163
164    // Saved cwnd before loss recovery.
165    prior_cwnd: usize,
166
167    // Checking if restarting from idle.
168    idle_restart: bool,
169
170    // Baseline level delivery rate for full pipe estimator.
171    full_bw: u64,
172
173    // The number of round for full pipe estimator without much growth.
174    full_bw_count: usize,
175
176    // Last time cycle_index is updated.
177    cycle_stamp: Instant,
178
179    // Current index of pacing_gain_cycle[].
180    cycle_index: usize,
181
182    // The upper bound on the volume of data BBR allows in flight.
183    target_cwnd: usize,
184
185    // Whether in the recovery episode.
186    in_recovery: bool,
187
188    // Start time of the connection.
189    start_time: Instant,
190
191    // Newly marked lost data size in bytes.
192    newly_lost_bytes: usize,
193
194    // Newly acked data size in bytes.
195    newly_acked_bytes: usize,
196
197    // bytes_in_flight before processing this ACK.
198    prior_bytes_in_flight: usize,
199}
200
201impl State {
202    pub fn new() -> Self {
203        let now = Instant::now();
204
205        State {
206            state: BBRStateMachine::Startup,
207
208            pacing_rate: 0,
209
210            btlbw: 0,
211
212            btlbwfilter: Minmax::new(0),
213
214            rtprop: Duration::ZERO,
215
216            rtprop_stamp: now,
217
218            rtprop_expired: false,
219
220            pacing_gain: 0.0,
221
222            cwnd_gain: 0.0,
223
224            filled_pipe: false,
225
226            round_count: 0,
227
228            round_start: false,
229
230            next_round_delivered: 0,
231
232            probe_rtt_done_stamp: None,
233
234            probe_rtt_round_done: false,
235
236            packet_conservation: false,
237
238            prior_cwnd: 0,
239
240            idle_restart: false,
241
242            full_bw: 0,
243
244            full_bw_count: 0,
245
246            cycle_stamp: now,
247
248            cycle_index: 0,
249
250            target_cwnd: 0,
251
252            in_recovery: false,
253
254            start_time: now,
255
256            newly_lost_bytes: 0,
257
258            newly_acked_bytes: 0,
259
260            prior_bytes_in_flight: 0,
261        }
262    }
263}
264
265// When entering the recovery episode.
266fn bbr_enter_recovery(r: &mut Congestion, in_flight: usize, now: Instant) {
267    r.bbr_state.prior_cwnd = per_ack::bbr_save_cwnd(r);
268
269    r.congestion_window = in_flight.max(r.max_datagram_size);
270    r.congestion_recovery_start_time = Some(now);
271
272    r.bbr_state.packet_conservation = true;
273    r.bbr_state.in_recovery = true;
274
275    r.bbr_state.newly_lost_bytes = 0;
276
277    // Start round now.
278    r.bbr_state.next_round_delivered = r.delivery_rate.delivered();
279}
280
281// When exiting the recovery episode.
282fn bbr_exit_recovery(r: &mut Congestion) {
283    r.congestion_recovery_start_time = None;
284
285    r.bbr_state.packet_conservation = false;
286    r.bbr_state.in_recovery = false;
287
288    per_ack::bbr_restore_cwnd(r);
289}
290
291// Congestion Control Hooks.
292//
293fn on_init(r: &mut Congestion) {
294    init::bbr_init(r);
295}
296
297fn on_packet_sent(
298    r: &mut Congestion, _sent_bytes: usize, bytes_in_flight: usize, _now: Instant,
299) {
300    per_transmit::bbr_on_transmit(r, bytes_in_flight);
301}
302
303fn on_packets_acked(
304    r: &mut Congestion, bytes_in_flight: usize, packets: &mut Vec<Acked>,
305    now: Instant, _rtt_stats: &RttStats,
306) {
307    r.bbr_state.prior_bytes_in_flight = bytes_in_flight;
308
309    r.bbr_state.newly_acked_bytes =
310        packets.drain(..).fold(0, |acked_bytes, p| {
311            r.bbr_state.prior_bytes_in_flight -= p.size;
312
313            per_ack::bbr_update_model_and_state(r, &p, bytes_in_flight, now);
314
315            acked_bytes + p.size
316        });
317
318    if let Some(pkt) = packets.last() {
319        if !r.in_congestion_recovery(pkt.time_sent) && r.bbr_state.in_recovery {
320            // Upon exiting loss recovery.
321            bbr_exit_recovery(r);
322        }
323    }
324
325    per_ack::bbr_update_control_parameters(r, bytes_in_flight, now);
326
327    r.bbr_state.newly_lost_bytes = 0;
328}
329
330fn congestion_event(
331    r: &mut Congestion, bytes_in_flight: usize, lost_bytes: usize,
332    largest_lost_pkt: &Sent, now: Instant,
333) {
334    r.bbr_state.newly_lost_bytes = lost_bytes;
335
336    // Upon entering Fast Recovery.
337    if !r.in_congestion_recovery(largest_lost_pkt.time_sent) {
338        // Upon entering Fast Recovery.
339        bbr_enter_recovery(r, bytes_in_flight - lost_bytes, now);
340    }
341}
342
343fn checkpoint(_r: &mut Congestion) {}
344
345fn rollback(_r: &mut Congestion) -> bool {
346    false
347}
348
349fn has_custom_pacing() -> bool {
350    true
351}
352
353#[cfg(feature = "qlog")]
354fn state_str(r: &Congestion, _now: Instant) -> &'static str {
355    r.bbr_state.state.into()
356}
357
358fn debug_fmt(r: &Congestion, f: &mut std::fmt::Formatter) -> std::fmt::Result {
359    let bbr = &r.bbr_state;
360
361    write!(
362         f,
363         "bbr={{ state={:?} btlbw={} rtprop={:?} pacing_rate={} pacing_gain={} cwnd_gain={} target_cwnd={} send_quantum={} filled_pipe={} round_count={} }}",
364         bbr.state, bbr.btlbw, bbr.rtprop, bbr.pacing_rate, bbr.pacing_gain, bbr.cwnd_gain, bbr.target_cwnd, r.send_quantum(), bbr.filled_pipe, bbr.round_count
365    )
366}
367
368#[cfg(test)]
369mod tests {
370    use super::*;
371
372    use crate::packet;
373    use crate::ranges;
374    use crate::recovery::congestion::recovery::LegacyRecovery;
375    use crate::recovery::congestion::test_sender::TestSender;
376    use crate::recovery::HandshakeStatus;
377    use crate::recovery::RecoveryOps;
378    use crate::OnAckReceivedOutcome;
379
380    use smallvec::smallvec;
381
382    fn test_sender() -> TestSender {
383        TestSender::new(CongestionControlAlgorithm::BBR, false)
384    }
385
386    #[test]
387    fn bbr_init() {
388        let mut cfg = crate::Config::new(crate::PROTOCOL_VERSION).unwrap();
389        cfg.set_cc_algorithm(CongestionControlAlgorithm::BBR);
390
391        let r = LegacyRecovery::new(&cfg);
392
393        assert_eq!(
394            r.cwnd(),
395            r.max_datagram_size * cfg.initial_congestion_window_packets
396        );
397        assert_eq!(r.bytes_in_flight(), 0);
398
399        assert_eq!(r.congestion.bbr_state.state, BBRStateMachine::Startup);
400    }
401
402    #[test]
403    fn bbr_startup() {
404        let mut sender = test_sender();
405        let mss = sender.max_datagram_size;
406
407        let rtt = Duration::from_millis(50);
408        sender.update_rtt(rtt);
409        sender.advance_time(rtt);
410
411        // Send 5 packets.
412        for _ in 0..5 {
413            sender.send_packet(mss);
414        }
415
416        sender.advance_time(rtt);
417
418        let cwnd_prev = sender.congestion_window;
419
420        sender.ack_n_packets(5, mss);
421
422        assert_eq!(sender.bbr_state.state, BBRStateMachine::Startup);
423        assert_eq!(sender.congestion_window, cwnd_prev + mss * 5);
424        assert_eq!(sender.bytes_in_flight, 0);
425        assert_eq!(
426            sender.delivery_rate().to_bytes_per_second(),
427            ((mss * 5) as f64 / rtt.as_secs_f64()) as u64
428        );
429        assert_eq!(
430            sender.bbr_state.btlbw,
431            sender.delivery_rate().to_bytes_per_second()
432        );
433    }
434
435    #[test]
436    fn bbr_congestion_event() {
437        let mut cfg = crate::Config::new(crate::PROTOCOL_VERSION).unwrap();
438        cfg.set_cc_algorithm(CongestionControlAlgorithm::BBR);
439
440        let mut r = LegacyRecovery::new(&cfg);
441        let now = Instant::now();
442        let mss = r.max_datagram_size;
443
444        // Send 5 packets.
445        for pn in 0..5 {
446            let pkt = Sent {
447                pkt_num: pn,
448                frames: smallvec![],
449                time_sent: now,
450                time_acked: None,
451                time_lost: None,
452                size: mss,
453                ack_eliciting: true,
454                in_flight: true,
455                delivered: 0,
456                delivered_time: now,
457                first_sent_time: now,
458                is_app_limited: false,
459                tx_in_flight: 0,
460                lost: 0,
461                has_data: false,
462                is_pmtud_probe: false,
463            };
464
465            r.on_packet_sent(
466                pkt,
467                packet::Epoch::Application,
468                HandshakeStatus::default(),
469                now,
470                "",
471            );
472        }
473
474        let rtt = Duration::from_millis(50);
475        let now = now + rtt;
476
477        // Make a packet loss to trigger a congestion event.
478        let mut acked = ranges::RangeSet::default();
479        acked.insert(4..5);
480
481        // 1 acked, 2 x MSS lost.
482        assert_eq!(
483            r.on_ack_received(
484                &acked,
485                25,
486                packet::Epoch::Application,
487                HandshakeStatus::default(),
488                now,
489                None,
490                "",
491            )
492            .unwrap(),
493            OnAckReceivedOutcome {
494                lost_packets: 2,
495                lost_bytes: 2 * mss,
496                acked_bytes: mss,
497                spurious_losses: 0,
498            },
499        );
500
501        // Sent: 0, 1, 2, 3, 4, Acked 4.
502        assert_eq!(r.cwnd(), mss * 4);
503        // Stil in flight: 2, 3.
504        assert_eq!(r.bytes_in_flight(), mss * 2);
505    }
506
507    #[test]
508    fn bbr_drain() {
509        let mut cfg = crate::Config::new(crate::PROTOCOL_VERSION).unwrap();
510        cfg.set_cc_algorithm(CongestionControlAlgorithm::BBR);
511
512        let mut r = LegacyRecovery::new(&cfg);
513        let now = Instant::now();
514        let mss = r.max_datagram_size;
515
516        let mut pn = 0;
517
518        // Stop right before filled_pipe=true.
519        for _ in 0..3 {
520            let pkt = Sent {
521                pkt_num: pn,
522                frames: smallvec![],
523                time_sent: now,
524                time_acked: None,
525                time_lost: None,
526                size: mss,
527                ack_eliciting: true,
528                in_flight: true,
529                delivered: r.congestion.delivery_rate.delivered(),
530                delivered_time: now,
531                first_sent_time: now,
532                is_app_limited: false,
533                tx_in_flight: 0,
534                lost: 0,
535                has_data: false,
536                is_pmtud_probe: false,
537            };
538
539            r.on_packet_sent(
540                pkt,
541                packet::Epoch::Application,
542                HandshakeStatus::default(),
543                now,
544                "",
545            );
546
547            pn += 1;
548
549            let rtt = Duration::from_millis(50);
550
551            let now = now + rtt;
552
553            let mut acked = ranges::RangeSet::default();
554            acked.insert(0..pn);
555
556            assert_eq!(
557                r.on_ack_received(
558                    &acked,
559                    25,
560                    packet::Epoch::Application,
561                    HandshakeStatus::default(),
562                    now,
563                    None,
564                    "",
565                )
566                .unwrap(),
567                OnAckReceivedOutcome {
568                    lost_packets: 0,
569                    lost_bytes: 0,
570                    acked_bytes: mss,
571                    spurious_losses: 0,
572                },
573            );
574        }
575
576        for _ in 0..7 {
577            let pkt = Sent {
578                pkt_num: pn,
579                frames: smallvec![],
580                time_sent: now,
581                time_acked: None,
582                time_lost: None,
583                size: mss,
584                ack_eliciting: true,
585                in_flight: true,
586                delivered: r.congestion.delivery_rate.delivered(),
587                delivered_time: now,
588                first_sent_time: now,
589                is_app_limited: false,
590                tx_in_flight: 0,
591                lost: 0,
592                has_data: false,
593                is_pmtud_probe: false,
594            };
595
596            r.on_packet_sent(
597                pkt,
598                packet::Epoch::Application,
599                HandshakeStatus::default(),
600                now,
601                "",
602            );
603
604            pn += 1;
605        }
606
607        let rtt = Duration::from_millis(50);
608        let now = now + rtt;
609
610        let mut acked = ranges::RangeSet::default();
611
612        // We sent 7 packets, but ack only one, to stay in Drain state.
613        acked.insert(0..pn - 6);
614
615        assert_eq!(
616            r.on_ack_received(
617                &acked,
618                25,
619                packet::Epoch::Application,
620                HandshakeStatus::default(),
621                now,
622                None,
623                "",
624            )
625            .unwrap(),
626            OnAckReceivedOutcome {
627                lost_packets: 0,
628                lost_bytes: 0,
629                acked_bytes: mss,
630                spurious_losses: 0,
631            },
632        );
633
634        // Now we are in Drain state.
635        assert!(r.congestion.bbr_state.filled_pipe);
636        assert_eq!(r.congestion.bbr_state.state, BBRStateMachine::Drain);
637        assert!(r.congestion.bbr_state.pacing_gain < 1.0);
638    }
639
640    #[test]
641    fn bbr_probe_bw() {
642        let mut cfg = crate::Config::new(crate::PROTOCOL_VERSION).unwrap();
643        cfg.set_cc_algorithm(CongestionControlAlgorithm::BBR);
644
645        let mut r = LegacyRecovery::new(&cfg);
646        let now = Instant::now();
647        let mss = r.max_datagram_size;
648
649        // At 4th roundtrip, filled_pipe=true and switch to Drain,
650        // but move to ProbeBW immediately because bytes_in_flight is
651        // smaller than BBRInFlight(1).
652        for (pn, _) in (0..4).enumerate() {
653            let pkt = Sent {
654                pkt_num: pn as u64,
655                frames: smallvec![],
656                time_sent: now,
657                time_acked: None,
658                time_lost: None,
659                size: mss,
660                ack_eliciting: true,
661                in_flight: true,
662                delivered: r.congestion.delivery_rate.delivered(),
663                delivered_time: now,
664                first_sent_time: now,
665                is_app_limited: false,
666                tx_in_flight: 0,
667                lost: 0,
668                has_data: false,
669                is_pmtud_probe: false,
670            };
671
672            r.on_packet_sent(
673                pkt,
674                packet::Epoch::Application,
675                HandshakeStatus::default(),
676                now,
677                "",
678            );
679
680            let rtt = Duration::from_millis(50);
681            let now = now + rtt;
682
683            let mut acked = ranges::RangeSet::default();
684            acked.insert(0..pn as u64 + 1);
685
686            assert_eq!(
687                r.on_ack_received(
688                    &acked,
689                    25,
690                    packet::Epoch::Application,
691                    HandshakeStatus::default(),
692                    now,
693                    None,
694                    "",
695                )
696                .unwrap(),
697                OnAckReceivedOutcome {
698                    lost_packets: 0,
699                    lost_bytes: 0,
700                    acked_bytes: mss,
701                    spurious_losses: 0,
702                },
703            );
704        }
705
706        // Now we are in ProbeBW state.
707        assert!(r.congestion.bbr_state.filled_pipe);
708        assert_eq!(r.congestion.bbr_state.state, BBRStateMachine::ProbeBW);
709
710        // In the first ProbeBW cycle, pacing_gain should be >= 1.0.
711        assert!(r.congestion.bbr_state.pacing_gain >= 1.0);
712    }
713
714    #[test]
715    fn bbr_probe_rtt() {
716        let mut cfg = crate::Config::new(crate::PROTOCOL_VERSION).unwrap();
717        cfg.set_cc_algorithm(CongestionControlAlgorithm::BBR);
718
719        let mut r = LegacyRecovery::new(&cfg);
720        let now = Instant::now();
721        let mss = r.max_datagram_size;
722
723        let mut pn = 0;
724
725        // At 4th roundtrip, filled_pipe=true and switch to Drain,
726        // but move to ProbeBW immediately because bytes_in_flight is
727        // smaller than BBRInFlight(1).
728        for _ in 0..4 {
729            let pkt = Sent {
730                pkt_num: pn,
731                frames: smallvec![],
732                time_sent: now,
733                time_acked: None,
734                time_lost: None,
735                size: mss,
736                ack_eliciting: true,
737                in_flight: true,
738                delivered: r.congestion.delivery_rate.delivered(),
739                delivered_time: now,
740                first_sent_time: now,
741                is_app_limited: false,
742                tx_in_flight: 0,
743                lost: 0,
744                has_data: false,
745                is_pmtud_probe: false,
746            };
747
748            r.on_packet_sent(
749                pkt,
750                packet::Epoch::Application,
751                HandshakeStatus::default(),
752                now,
753                "",
754            );
755
756            pn += 1;
757
758            let rtt = Duration::from_millis(50);
759            let now = now + rtt;
760
761            let mut acked = ranges::RangeSet::default();
762            acked.insert(0..pn);
763
764            assert_eq!(
765                r.on_ack_received(
766                    &acked,
767                    25,
768                    packet::Epoch::Application,
769                    HandshakeStatus::default(),
770                    now,
771                    None,
772                    "",
773                )
774                .unwrap(),
775                OnAckReceivedOutcome {
776                    lost_packets: 0,
777                    lost_bytes: 0,
778                    acked_bytes: mss,
779                    spurious_losses: 0,
780                },
781            );
782        }
783
784        // Now we are in ProbeBW state.
785        assert_eq!(r.congestion.bbr_state.state, BBRStateMachine::ProbeBW);
786
787        // After RTPROP_FILTER_LEN (10s), switch to ProbeRTT.
788        let now = now + RTPROP_FILTER_LEN;
789
790        let pkt = Sent {
791            pkt_num: pn,
792            frames: smallvec![],
793            time_sent: now,
794            time_acked: None,
795            time_lost: None,
796            size: mss,
797            ack_eliciting: true,
798            in_flight: true,
799            delivered: r.congestion.delivery_rate.delivered(),
800            delivered_time: now,
801            first_sent_time: now,
802            is_app_limited: false,
803            tx_in_flight: 0,
804            lost: 0,
805            has_data: false,
806            is_pmtud_probe: false,
807        };
808
809        r.on_packet_sent(
810            pkt,
811            packet::Epoch::Application,
812            HandshakeStatus::default(),
813            now,
814            "",
815        );
816
817        pn += 1;
818
819        // Don't update rtprop by giving larger rtt than before.
820        // If rtprop is updated, rtprop expiry check is reset.
821        let rtt = Duration::from_millis(100);
822        let now = now + rtt;
823
824        let mut acked = ranges::RangeSet::default();
825        acked.insert(0..pn);
826
827        assert_eq!(
828            r.on_ack_received(
829                &acked,
830                25,
831                packet::Epoch::Application,
832                HandshakeStatus::default(),
833                now,
834                None,
835                "",
836            )
837            .unwrap(),
838            OnAckReceivedOutcome {
839                lost_packets: 0,
840                lost_bytes: 0,
841                acked_bytes: mss,
842                spurious_losses: 0,
843            },
844        );
845
846        assert_eq!(r.congestion.bbr_state.state, BBRStateMachine::ProbeRTT);
847        assert_eq!(r.congestion.bbr_state.pacing_gain, 1.0);
848    }
849}
850
851mod init;
852mod pacing;
853mod per_ack;
854mod per_transmit;