quiche/recovery/congestion/bbr2/
mod.rs

1// Redistribution and use in source and binary forms, with or without
2// modification, are permitted provided that the following conditions are
3// met:
4//
5//     * Redistributions of source code must retain the above copyright notice,
6//       this list of conditions and the following disclaimer.
7//
8//     * Redistributions in binary form must reproduce the above copyright
9//       notice, this list of conditions and the following disclaimer in the
10//       documentation and/or other materials provided with the distribution.
11//
12// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
13// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
14// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
15// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
16// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
17// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
18// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
19// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
20// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
21// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
22// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23
24//! BBR v2 Congestion Control
25//!
26//! This implementation is based on the following draft:
27//! <https://tools.ietf.org/html/draft-cardwell-iccrg-bbr-congestion-control-02>
28
29use super::*;
30use crate::minmax::Minmax;
31
32use std::time::Duration;
33use std::time::Instant;
34
35use super::CongestionControlOps;
36
37pub(crate) static BBR2: CongestionControlOps = CongestionControlOps {
38    on_init,
39    on_packet_sent,
40    on_packets_acked,
41    congestion_event,
42    checkpoint,
43    rollback,
44    has_custom_pacing,
45    #[cfg(feature = "qlog")]
46    state_str,
47    debug_fmt,
48};
49
50/// The static discount factor of 1% used to scale BBR.bw to produce
51/// BBR.pacing_rate.
52const PACING_MARGIN_PERCENT: f64 = 0.01;
53
54/// A constant specifying the minimum gain value
55/// for calculating the pacing rate that will allow the sending rate to
56/// double each round (4*ln(2) ~=2.77 ) BBRStartupPacingGain; used in
57/// Startup mode for BBR.pacing_gain.
58const STARTUP_PACING_GAIN: f64 = 2.77;
59
60/// A constant specifying the pacing gain value for Probe Down mode.
61const PROBE_DOWN_PACING_GAIN: f64 = 3_f64 / 4_f64;
62
63/// A constant specifying the pacing gain value for Probe Up mode.
64const PROBE_UP_PACING_GAIN: f64 = 5_f64 / 4_f64;
65
66/// A constant specifying the pacing gain value for Probe Refill, Probe RTT,
67/// Cruise mode.
68const PACING_GAIN: f64 = 1.0;
69
70/// A constant specifying the minimum gain value for the cwnd in the Startup
71/// phase
72const STARTUP_CWND_GAIN: f64 = 2.77;
73
74/// A constant specifying the minimum gain value for
75/// calculating the cwnd that will allow the sending rate to double each
76/// round (2.0); used in Probe and Drain mode for BBR.cwnd_gain.
77const CWND_GAIN: f64 = 2.0;
78
79/// The maximum tolerated per-round-trip packet loss rate
80/// when probing for bandwidth (the default is 2%).
81const LOSS_THRESH: f64 = 0.02;
82
83/// Exit startup if the number of loss marking events is >=FULL_LOSS_COUNT
84const FULL_LOSS_COUNT: u32 = 8;
85
86/// The default multiplicative decrease to make upon each round
87/// trip during which the connection detects packet loss (the value is
88/// 0.7).
89const BETA: f64 = 0.7;
90
91/// The multiplicative factor to apply to BBR.inflight_hi
92/// when attempting to leave free headroom in the path (e.g. free space
93/// in the bottleneck buffer or free time slots in the bottleneck link)
94/// that can be used by cross traffic (the value is 0.85).
95const HEADROOM: f64 = 0.85;
96
97/// The minimal cwnd value BBR targets, to allow
98/// pipelining with TCP endpoints that follow an "ACK every other packet"
99/// delayed-ACK policy: 4 * SMSS.
100const MIN_PIPE_CWND_PKTS: usize = 4;
101
102// To do: Tune window for expiry of Max BW measurement
103// The filter window length for BBR.MaxBwFilter = 2 (representing up to 2
104// ProbeBW cycles, the current cycle and the previous full cycle).
105// const MAX_BW_FILTER_LEN: Duration = Duration::from_secs(2);
106
107// To do: Tune window for expiry of ACK aggregation measurement
108// The window length of the BBR.ExtraACKedFilter max filter window: 10 (in
109// units of packet-timed round trips).
110// const EXTRA_ACKED_FILTER_LEN: Duration = Duration::from_secs(10);
111
112/// A constant specifying the length of the BBR.min_rtt min filter window,
113/// MinRTTFilterLen is 10 secs.
114const MIN_RTT_FILTER_LEN: u32 = 1;
115
116/// A constant specifying the gain value for calculating the cwnd during
117/// ProbeRTT: 0.5 (meaning that ProbeRTT attempts to reduce in-flight data to
118/// 50% of the estimated BDP).
119const PROBE_RTT_CWND_GAIN: f64 = 0.5;
120
121/// A constant specifying the minimum duration for which ProbeRTT state holds
122/// inflight to BBRMinPipeCwnd or fewer packets: 200 ms.
123const PROBE_RTT_DURATION: Duration = Duration::from_millis(200);
124
125/// ProbeRTTInterval: A constant specifying the minimum time interval between
126/// ProbeRTT states. To do: investigate probe duration. Set arbitrarily high for
127/// now.
128const PROBE_RTT_INTERVAL: Duration = Duration::from_secs(86400);
129
130/// Threshold for checking a full bandwidth growth during Startup.
131const MAX_BW_GROWTH_THRESHOLD: f64 = 1.25;
132
133/// Threshold for determining maximum bandwidth of network during Startup.
134const MAX_BW_COUNT: usize = 3;
135
136/// BBR2 Internal State Machine.
137#[derive(Debug, PartialEq, Eq, Copy, Clone)]
138enum BBR2StateMachine {
139    Startup,
140    Drain,
141    ProbeBWDOWN,
142    ProbeBWCRUISE,
143    ProbeBWREFILL,
144    ProbeBWUP,
145    ProbeRTT,
146}
147
148impl From<BBR2StateMachine> for &'static str {
149    fn from(state: BBR2StateMachine) -> &'static str {
150        match state {
151            BBR2StateMachine::Startup => "bbr_startup",
152            BBR2StateMachine::Drain => "bbr_drain",
153            BBR2StateMachine::ProbeBWDOWN => "bbr_probe_bw_down",
154            BBR2StateMachine::ProbeBWCRUISE => "bbr_probe_bw_cruise",
155            BBR2StateMachine::ProbeBWREFILL => "bbr_probe_bw_refill",
156            BBR2StateMachine::ProbeBWUP => "bbr_probe_bw_up",
157            BBR2StateMachine::ProbeRTT => "bbr_probe_rtt",
158        }
159    }
160}
161
162/// BBR2 Ack Phases.
163#[derive(Debug, PartialEq, Eq)]
164enum BBR2AckPhase {
165    Init,
166    ProbeFeedback,
167    ProbeStarting,
168    ProbeStopping,
169    Refilling,
170}
171
172/// BBR2 Specific State Variables.
173pub struct State {
174    // 2.3.  Per-ACK Rate Sample State
175    // It's stored in rate sample but we keep in BBR state here.
176
177    // The volume of data that was estimated to be in
178    // flight at the time of the transmission of the packet that has just
179    // been ACKed.
180    tx_in_flight: usize,
181
182    // The volume of data that was declared lost between the
183    // transmission and acknowledgement of the packet that has just been
184    // ACKed.
185    lost: usize,
186
187    // The volume of data cumulatively or selectively acknowledged upon the ACK
188    // that was just received.  (This quantity is referred to as "DeliveredData"
189    // in [RFC6937].)
190    newly_acked_bytes: usize,
191
192    // The volume of data newly marked lost upon the ACK that was just received.
193    newly_lost_bytes: usize,
194
195    // 2.4.  Output Control Parameters
196    // The current pacing rate for a BBR2 flow, which controls inter-packet
197    // spacing.
198    pacing_rate: u64,
199
200    // Save initial pacing rate so we can update when more reliable bytes
201    // delivered and RTT samples are available
202    init_pacing_rate: u64,
203
204    // 2.5.  Pacing State and Parameters
205    // The dynamic gain factor used to scale BBR.bw to
206    // produce BBR.pacing_rate.
207    pacing_gain: f64,
208
209    // 2.6.  cwnd State and Parameters
210    // The dynamic gain factor used to scale the estimated BDP to produce a
211    // congestion window (cwnd).
212    cwnd_gain: f64,
213
214    // A boolean indicating whether BBR is currently using packet conservation
215    // dynamics to bound cwnd.
216    packet_conservation: bool,
217
218    // 2.7.  General Algorithm State
219    // The current state of a BBR2 flow in the BBR2 state machine.
220    state: BBR2StateMachine,
221
222    // Count of packet-timed round trips elapsed so far.
223    round_count: u64,
224
225    // A boolean that BBR2 sets to true once per packet-timed round trip,
226    // on ACKs that advance BBR2.round_count.
227    round_start: bool,
228
229    // packet.delivered value denoting the end of a packet-timed round trip.
230    next_round_delivered: usize,
231
232    // A boolean that is true if and only if a connection is restarting after
233    // being idle.
234    idle_restart: bool,
235
236    // 2.9.1.  Data Rate Network Path Model Parameters
237    // The windowed maximum recent bandwidth sample - obtained using the BBR
238    // delivery rate sampling algorithm
239    // [draft-cheng-iccrg-delivery-rate-estimation] - measured during the current
240    // or previous bandwidth probing cycle (or during Startup, if the flow is
241    // still in that state).  (Part of the long-term model.)
242    max_bw: u64,
243
244    // The long-term maximum sending bandwidth that the algorithm estimates will
245    // produce acceptable queue pressure, based on signals in the current or
246    // previous bandwidth probing cycle, as measured by loss.  (Part of the
247    // long-term model.)
248    bw_hi: u64,
249
250    // The short-term maximum sending bandwidth that the algorithm estimates is
251    // safe for matching the current network path delivery rate, based on any
252    // loss signals in the current bandwidth probing cycle.  This is generally
253    // lower than max_bw or bw_hi (thus the name).  (Part of the short-term
254    // model.)
255    bw_lo: u64,
256
257    // The maximum sending bandwidth that the algorithm estimates is appropriate
258    // for matching the current network path delivery rate, given all available
259    // signals in the model, at any time scale.  It is the min() of max_bw,
260    // bw_hi, and bw_lo.
261    bw: u64,
262
263    // 2.9.2.  Data Volume Network Path Model Parameters
264    // The windowed minimum round-trip time sample measured over the last
265    // MinRTTFilterLen = 10 seconds.  This attempts to estimate the two-way
266    // propagation delay of the network path when all connections sharing a
267    // bottleneck are using BBR, but also allows BBR to estimate the value
268    // required for a bdp estimate that allows full throughput if there are
269    // legacy loss-based Reno or CUBIC flows sharing the bottleneck.
270    min_rtt: Duration,
271
272    // The estimate of the network path's BDP (Bandwidth-Delay Product), computed
273    // as: BBR.bdp = BBR.bw * BBR.min_rtt.
274    bdp: usize,
275
276    // A volume of data that is the estimate of the recent degree of aggregation
277    // in the network path.
278    extra_acked: usize,
279
280    // The estimate of the minimum volume of data necessary to achieve full
281    // throughput when using sender (TSO/GSO) and receiver (LRO, GRO) host
282    // offload mechanisms.
283    offload_budget: usize,
284
285    // The estimate of the volume of in-flight data required to fully utilize the
286    // bottleneck bandwidth available to the flow, based on the BDP estimate
287    // (BBR.bdp), the aggregation estimate (BBR.extra_acked), the offload budget
288    // (BBR.offload_budget), and BBRMinPipeCwnd.
289    max_inflight: usize,
290
291    // Analogous to BBR.bw_hi, the long-term maximum volume of in-flight data
292    // that the algorithm estimates will produce acceptable queue pressure, based
293    // on signals in the current or previous bandwidth probing cycle, as measured
294    // by loss.  That is, if a flow is probing for bandwidth, and observes that
295    // sending a particular volume of in-flight data causes a loss rate higher
296    // than the loss rate objective, it sets inflight_hi to that volume of data.
297    // (Part of the long-term model.)
298    inflight_hi: usize,
299
300    // Analogous to BBR.bw_lo, the short-term maximum volume of in-flight data
301    // that the algorithm estimates is safe for matching the current network path
302    // delivery process, based on any loss signals in the current bandwidth
303    // probing cycle.  This is generally lower than max_inflight or inflight_hi
304    // (thus the name).  (Part of the short-term model.)
305    inflight_lo: usize,
306
307    // 2.10.  State for Responding to Congestion
308    // a 1-round-trip max of delivered bandwidth (rs.delivery_rate).
309    bw_latest: u64,
310
311    // a 1-round-trip max of delivered volume of data (rs.delivered).
312    inflight_latest: usize,
313
314    // 2.11.  Estimating BBR.max_bw
315    // The filter for tracking the maximum recent rs.delivery_rate sample, for
316    // estimating BBR.max_bw.
317    max_bw_filter: Minmax<u64>,
318
319    // The virtual time used by the BBR.max_bw filter window.  Note that
320    // BBR.cycle_count only needs to be tracked with a single bit, since the
321    // BBR.MaxBwFilter only needs to track samples from two time slots: the
322    // previous ProbeBW cycle and the current ProbeBW cycle.
323    cycle_count: u64,
324
325    // 2.12.  Estimating BBR.extra_acked
326    // the start of the time interval for estimating the excess amount of data
327    // acknowledged due to aggregation effects.
328    extra_acked_interval_start: Instant,
329
330    // the volume of data marked as delivered since
331    // BBR.extra_acked_interval_start.
332    extra_acked_delivered: usize,
333
334    // BBR.ExtraACKedFilter: the max filter tracking the recent maximum degree of
335    // aggregation in the path.
336    extra_acked_filter: Minmax<usize>,
337
338    // 2.13.  Startup Parameters and State
339    // A boolean that records whether BBR estimates that it has ever fully
340    // utilized its available bandwidth ("filled the pipe").
341    filled_pipe: bool,
342
343    // A recent baseline BBR.max_bw to estimate if BBR has "filled the pipe" in
344    // Startup.
345    full_bw: u64,
346
347    // The number of non-app-limited round trips without large increases in
348    // BBR.full_bw.
349    full_bw_count: usize,
350
351    // 2.14.1.  Parameters for Estimating BBR.min_rtt
352    // The wall clock time at which the current BBR.min_rtt sample was obtained.
353    min_rtt_stamp: Instant,
354
355    // 2.14.2.  Parameters for Scheduling ProbeRTT
356    // The minimum RTT sample recorded in the last ProbeRTTInterval.
357    probe_rtt_min_delay: Duration,
358
359    // The wall clock time at which the current BBR.probe_rtt_min_delay sample
360    // was obtained.
361    probe_rtt_min_stamp: Instant,
362
363    // A boolean recording whether the BBR.probe_rtt_min_delay has expired and is
364    // due for a refresh with an application idle period or a transition into
365    // ProbeRTT state.
366    probe_rtt_expired: bool,
367
368    // Others
369    // A state indicating we are in the recovery.
370    in_recovery: bool,
371
372    // Start time of the connection.
373    start_time: Instant,
374
375    // Saved cwnd before loss recovery.
376    prior_cwnd: usize,
377
378    // Whether we have a bandwidth probe samples.
379    bw_probe_samples: bool,
380
381    // Others
382    probe_up_cnt: usize,
383
384    prior_bytes_in_flight: usize,
385
386    probe_rtt_done_stamp: Option<Instant>,
387
388    probe_rtt_round_done: bool,
389
390    bw_probe_wait: Duration,
391
392    rounds_since_probe: usize,
393
394    cycle_stamp: Instant,
395
396    ack_phase: BBR2AckPhase,
397
398    bw_probe_up_rounds: usize,
399
400    bw_probe_up_acks: usize,
401
402    loss_round_start: bool,
403
404    loss_round_delivered: usize,
405
406    loss_in_round: bool,
407
408    loss_events_in_round: usize,
409}
410
411impl State {
412    pub fn new() -> Self {
413        let now = Instant::now();
414
415        State {
416            tx_in_flight: 0,
417
418            lost: 0,
419
420            newly_acked_bytes: 0,
421
422            newly_lost_bytes: 0,
423
424            pacing_rate: 0,
425
426            init_pacing_rate: 0,
427
428            pacing_gain: 0.0,
429
430            cwnd_gain: 0.0,
431
432            packet_conservation: false,
433
434            state: BBR2StateMachine::Startup,
435
436            round_count: 0,
437
438            round_start: false,
439
440            next_round_delivered: 0,
441
442            idle_restart: false,
443
444            max_bw: 0,
445
446            bw_hi: u64::MAX,
447
448            bw_lo: u64::MAX,
449
450            bw: 0,
451
452            min_rtt: Duration::MAX,
453
454            bdp: 0,
455
456            extra_acked: 0,
457
458            offload_budget: 0,
459
460            max_inflight: 0,
461
462            inflight_hi: usize::MAX,
463
464            inflight_lo: usize::MAX,
465
466            bw_latest: 0,
467
468            inflight_latest: 0,
469
470            max_bw_filter: Minmax::new(0),
471
472            cycle_count: 0,
473
474            extra_acked_interval_start: now,
475
476            extra_acked_delivered: 0,
477
478            extra_acked_filter: Minmax::new(0),
479
480            filled_pipe: false,
481
482            full_bw: 0,
483
484            full_bw_count: 0,
485
486            min_rtt_stamp: now,
487
488            probe_rtt_min_delay: Duration::MAX,
489
490            probe_rtt_min_stamp: now,
491
492            probe_rtt_expired: false,
493
494            in_recovery: false,
495
496            start_time: now,
497
498            prior_cwnd: 0,
499
500            bw_probe_samples: false,
501
502            probe_up_cnt: 0,
503
504            prior_bytes_in_flight: 0,
505
506            probe_rtt_done_stamp: None,
507
508            probe_rtt_round_done: false,
509
510            bw_probe_wait: Duration::ZERO,
511
512            rounds_since_probe: 0,
513
514            cycle_stamp: now,
515
516            ack_phase: BBR2AckPhase::Init,
517
518            bw_probe_up_rounds: 0,
519
520            bw_probe_up_acks: 0,
521
522            loss_round_start: false,
523
524            loss_round_delivered: 0,
525
526            loss_in_round: false,
527
528            loss_events_in_round: 0,
529        }
530    }
531}
532
533// When entering the recovery episode.
534fn bbr2_enter_recovery(r: &mut Congestion, in_flight: usize, now: Instant) {
535    r.bbr2_state.prior_cwnd = per_ack::bbr2_save_cwnd(r);
536
537    r.congestion_window =
538        in_flight + r.bbr2_state.newly_acked_bytes.max(r.max_datagram_size);
539    r.congestion_recovery_start_time = Some(now);
540
541    r.bbr2_state.packet_conservation = true;
542    r.bbr2_state.in_recovery = true;
543
544    // Start round now.
545    r.bbr2_state.next_round_delivered = r.delivery_rate.delivered();
546}
547
548// When exiting the recovery episode.
549fn bbr2_exit_recovery(r: &mut Congestion) {
550    r.congestion_recovery_start_time = None;
551
552    r.bbr2_state.packet_conservation = false;
553    r.bbr2_state.in_recovery = false;
554
555    per_ack::bbr2_restore_cwnd(r);
556}
557
558// Congestion Control Hooks.
559//
560fn on_init(r: &mut Congestion) {
561    init::bbr2_init(r);
562}
563
564fn on_packet_sent(
565    r: &mut Congestion, _sent_bytes: usize, bytes_in_flight: usize, now: Instant,
566) {
567    per_transmit::bbr2_on_transmit(r, bytes_in_flight, now);
568}
569
570fn on_packets_acked(
571    r: &mut Congestion, bytes_in_flight: usize, packets: &mut Vec<Acked>,
572    now: Instant, _rtt_stats: &RttStats,
573) {
574    r.bbr2_state.newly_acked_bytes = 0;
575
576    let time_sent = packets.last().map(|pkt| pkt.time_sent);
577
578    r.bbr2_state.prior_bytes_in_flight = bytes_in_flight;
579    let mut bytes_in_flight = bytes_in_flight;
580
581    for p in packets.drain(..) {
582        per_ack::bbr2_update_model_and_state(r, &p, bytes_in_flight, now);
583
584        r.bbr2_state.prior_bytes_in_flight = bytes_in_flight;
585        bytes_in_flight -= p.size;
586
587        r.bbr2_state.newly_acked_bytes += p.size;
588    }
589
590    if let Some(ts) = time_sent {
591        if !r.in_congestion_recovery(ts) {
592            // Upon exiting loss recovery.
593            bbr2_exit_recovery(r);
594        }
595    }
596
597    per_ack::bbr2_update_control_parameters(r, bytes_in_flight, now);
598
599    r.bbr2_state.newly_lost_bytes = 0;
600}
601
602fn congestion_event(
603    r: &mut Congestion, bytes_in_flight: usize, lost_bytes: usize,
604    largest_lost_pkt: &Sent, now: Instant,
605) {
606    r.bbr2_state.newly_lost_bytes = lost_bytes;
607
608    per_loss::bbr2_update_on_loss(r, largest_lost_pkt, lost_bytes, now);
609
610    // Upon entering Fast Recovery.
611    if !r.in_congestion_recovery(largest_lost_pkt.time_sent) {
612        // Upon entering Fast Recovery.
613        bbr2_enter_recovery(r, bytes_in_flight - lost_bytes, now);
614    }
615}
616
617fn checkpoint(_r: &mut Congestion) {}
618
619fn rollback(_r: &mut Congestion) -> bool {
620    false
621}
622
623fn has_custom_pacing() -> bool {
624    true
625}
626
627// rate -> kbit/sec. if inf, return -1
628fn rate_kbps(rate: u64) -> isize {
629    if rate == u64::MAX {
630        -1
631    } else {
632        (rate * 8 / 1000) as isize
633    }
634}
635
636#[cfg(feature = "qlog")]
637fn state_str(r: &Congestion, _now: Instant) -> &'static str {
638    r.bbr2_state.state.into()
639}
640
641fn debug_fmt(r: &Congestion, f: &mut std::fmt::Formatter) -> std::fmt::Result {
642    let bbr = &r.bbr2_state;
643
644    write!(f, "bbr2={{ ")?;
645    write!(
646        f,
647        "state={:?} in_recovery={} ack_phase={:?} filled_pipe={} full_bw_count={} loss_events_in_round={} ",
648        bbr.state, bbr.in_recovery, bbr.ack_phase, bbr.filled_pipe, bbr.full_bw_count, bbr.loss_events_in_round
649    )?;
650    write!(
651        f,
652        "send_quantum={} extra_acked={} min_rtt={:?} round_start={} ",
653        r.send_quantum, bbr.extra_acked, bbr.min_rtt, bbr.round_start
654    )?;
655    write!(
656        f,
657        "max_bw={}kbps bw_lo={}kbps bw={}kbps bw_hi={}kbps full_bw={}kbps ",
658        rate_kbps(bbr.max_bw),
659        rate_kbps(bbr.bw_lo),
660        rate_kbps(bbr.bw),
661        rate_kbps(bbr.bw_hi),
662        rate_kbps(bbr.full_bw)
663    )?;
664    write!(
665        f,
666        "inflight_lo={} inflight_hi={} max_inflight={} ",
667        bbr.inflight_lo, bbr.inflight_hi, bbr.max_inflight
668    )?;
669    write!(
670        f,
671        "probe_up_cnt={} bw_probe_samples={} ",
672        bbr.probe_up_cnt, bbr.bw_probe_samples
673    )?;
674    write!(f, "}}")
675}
676
677// TODO: write more tests
678#[cfg(test)]
679mod tests {
680    use super::*;
681
682    use smallvec::smallvec;
683
684    use crate::packet;
685    use crate::ranges;
686    use crate::recovery::congestion::recovery::LegacyRecovery;
687    use crate::recovery::HandshakeStatus;
688    use crate::recovery::RecoveryOps;
689    use crate::CongestionControlAlgorithm;
690    use crate::OnAckReceivedOutcome;
691
692    #[test]
693    fn bbr_init() {
694        let mut cfg = crate::Config::new(crate::PROTOCOL_VERSION).unwrap();
695        cfg.set_cc_algorithm(CongestionControlAlgorithm::BBR2);
696
697        let r = LegacyRecovery::new(&cfg);
698
699        // on_init() is called in Connection::new(), so it need to be
700        // called manually here.
701
702        assert_eq!(
703            r.cwnd(),
704            r.max_datagram_size * r.congestion.initial_congestion_window_packets
705        );
706        assert_eq!(r.bytes_in_flight(), 0);
707
708        assert_eq!(r.congestion.bbr2_state.state, BBR2StateMachine::Startup);
709    }
710
711    #[test]
712    fn bbr2_startup() {
713        let mut cfg = crate::Config::new(crate::PROTOCOL_VERSION).unwrap();
714        cfg.set_cc_algorithm(CongestionControlAlgorithm::BBR2);
715
716        let mut r = LegacyRecovery::new(&cfg);
717        let now = Instant::now();
718        let mss = r.max_datagram_size;
719
720        // Send 5 packets.
721        for pn in 0..5 {
722            let pkt = Sent {
723                pkt_num: pn,
724                frames: smallvec![],
725                time_sent: now,
726                time_acked: None,
727                time_lost: None,
728                size: mss,
729                ack_eliciting: true,
730                in_flight: true,
731                delivered: 0,
732                delivered_time: now,
733                first_sent_time: now,
734                is_app_limited: false,
735                tx_in_flight: 0,
736                lost: 0,
737                has_data: false,
738                is_pmtud_probe: false,
739            };
740
741            r.on_packet_sent(
742                pkt,
743                packet::Epoch::Application,
744                HandshakeStatus::default(),
745                now,
746                "",
747            );
748        }
749
750        let rtt = Duration::from_millis(50);
751        let now = now + rtt;
752        let cwnd_prev = r.cwnd();
753
754        let mut acked = ranges::RangeSet::default();
755        acked.insert(0..5);
756
757        assert_eq!(
758            r.on_ack_received(
759                &acked,
760                25,
761                packet::Epoch::Application,
762                HandshakeStatus::default(),
763                now,
764                None,
765                "",
766            )
767            .unwrap(),
768            OnAckReceivedOutcome {
769                lost_packets: 0,
770                lost_bytes: 0,
771                acked_bytes: mss * 5,
772                spurious_losses: 0,
773            }
774        );
775
776        assert_eq!(r.congestion.bbr2_state.state, BBR2StateMachine::Startup);
777        assert_eq!(r.cwnd(), cwnd_prev + mss * 5);
778        assert_eq!(r.bytes_in_flight(), 0);
779        assert_eq!(
780            r.delivery_rate().to_bytes_per_second(),
781            ((mss * 5) as f64 / rtt.as_secs_f64()) as u64
782        );
783        assert_eq!(
784            r.congestion.bbr2_state.full_bw,
785            r.delivery_rate().to_bytes_per_second()
786        );
787    }
788
789    #[test]
790    fn bbr2_congestion_event() {
791        let mut cfg = crate::Config::new(crate::PROTOCOL_VERSION).unwrap();
792        cfg.set_cc_algorithm(CongestionControlAlgorithm::BBR2);
793
794        let mut r = LegacyRecovery::new(&cfg);
795        let now = Instant::now();
796        let mss = r.max_datagram_size;
797
798        // Send 5 packets.
799        for pn in 0..5 {
800            let pkt = Sent {
801                pkt_num: pn,
802                frames: smallvec![],
803                time_sent: now,
804                time_acked: None,
805                time_lost: None,
806                size: mss,
807                ack_eliciting: true,
808                in_flight: true,
809                delivered: 0,
810                delivered_time: now,
811                first_sent_time: now,
812                is_app_limited: false,
813                tx_in_flight: 0,
814                lost: 0,
815                has_data: false,
816                is_pmtud_probe: false,
817            };
818
819            r.on_packet_sent(
820                pkt,
821                packet::Epoch::Application,
822                HandshakeStatus::default(),
823                now,
824                "",
825            );
826        }
827
828        let rtt = Duration::from_millis(50);
829        let now = now + rtt;
830
831        // Make a packet loss to trigger a congestion event.
832        let mut acked = ranges::RangeSet::default();
833        acked.insert(4..5);
834
835        // 2 acked, 2 x MSS lost.
836        assert_eq!(
837            r.on_ack_received(
838                &acked,
839                25,
840                packet::Epoch::Application,
841                HandshakeStatus::default(),
842                now,
843                None,
844                "",
845            )
846            .unwrap(),
847            OnAckReceivedOutcome {
848                lost_packets: 2,
849                lost_bytes: 2 * mss,
850                acked_bytes: mss,
851                spurious_losses: 0,
852            }
853        );
854
855        assert!(r.congestion.bbr2_state.in_recovery);
856
857        // Still in flight: 2, 3.
858        assert_eq!(r.bytes_in_flight(), mss * 2);
859
860        assert_eq!(r.congestion.bbr2_state.newly_acked_bytes, mss);
861
862        assert_eq!(r.cwnd(), mss * 3);
863    }
864
865    #[test]
866    fn bbr2_probe_bw() {
867        let mut cfg = crate::Config::new(crate::PROTOCOL_VERSION).unwrap();
868        cfg.set_cc_algorithm(CongestionControlAlgorithm::BBR2);
869
870        let mut r = LegacyRecovery::new(&cfg);
871        let now = Instant::now();
872        let mss = r.max_datagram_size;
873
874        let mut pn = 0;
875
876        // Stop right before filled_pipe=true.
877        for _ in 0..3 {
878            let pkt = Sent {
879                pkt_num: pn,
880                frames: smallvec![],
881                time_sent: now,
882                time_acked: None,
883                time_lost: None,
884                size: mss,
885                ack_eliciting: true,
886                in_flight: true,
887                delivered: r.congestion.delivery_rate.delivered(),
888                delivered_time: now,
889                first_sent_time: now,
890                is_app_limited: false,
891                tx_in_flight: 0,
892                lost: 0,
893                has_data: false,
894                is_pmtud_probe: false,
895            };
896
897            r.on_packet_sent(
898                pkt,
899                packet::Epoch::Application,
900                HandshakeStatus::default(),
901                now,
902                "",
903            );
904
905            pn += 1;
906
907            let rtt = Duration::from_millis(50);
908
909            let now = now + rtt;
910
911            let mut acked = ranges::RangeSet::default();
912            acked.insert(0..pn);
913
914            assert_eq!(
915                r.on_ack_received(
916                    &acked,
917                    25,
918                    packet::Epoch::Application,
919                    HandshakeStatus::default(),
920                    now,
921                    None,
922                    "",
923                )
924                .unwrap(),
925                OnAckReceivedOutcome {
926                    lost_packets: 0,
927                    lost_bytes: 0,
928                    acked_bytes: mss,
929                    spurious_losses: 0,
930                }
931            );
932        }
933
934        // Stop at right before filled_pipe=true.
935        for _ in 0..5 {
936            let pkt = Sent {
937                pkt_num: pn,
938                frames: smallvec![],
939                time_sent: now,
940                time_acked: None,
941                time_lost: None,
942                size: mss,
943                ack_eliciting: true,
944                in_flight: true,
945                delivered: r.congestion.delivery_rate.delivered(),
946                delivered_time: now,
947                first_sent_time: now,
948                is_app_limited: false,
949                tx_in_flight: 0,
950                lost: 0,
951                has_data: false,
952                is_pmtud_probe: false,
953            };
954
955            r.on_packet_sent(
956                pkt,
957                packet::Epoch::Application,
958                HandshakeStatus::default(),
959                now,
960                "",
961            );
962
963            pn += 1;
964        }
965
966        let rtt = Duration::from_millis(50);
967        let now = now + rtt;
968
969        let mut acked = ranges::RangeSet::default();
970
971        // We sent 5 packets, but ack only one, so stay
972        // in Drain state.
973        acked.insert(0..pn - 4);
974
975        assert_eq!(
976            r.on_ack_received(
977                &acked,
978                25,
979                packet::Epoch::Application,
980                HandshakeStatus::default(),
981                now,
982                None,
983                "",
984            )
985            .unwrap(),
986            OnAckReceivedOutcome {
987                lost_packets: 0,
988                lost_bytes: 0,
989                acked_bytes: mss,
990                spurious_losses: 0,
991            }
992        );
993
994        assert_eq!(r.congestion.bbr2_state.state, BBR2StateMachine::Drain);
995        assert!(r.congestion.bbr2_state.filled_pipe);
996        assert!(r.congestion.bbr2_state.pacing_gain < 1.0);
997    }
998
999    #[test]
1000    fn bbr2_probe_rtt() {
1001        let mut cfg = crate::Config::new(crate::PROTOCOL_VERSION).unwrap();
1002        cfg.set_cc_algorithm(CongestionControlAlgorithm::BBR2);
1003
1004        let mut r = LegacyRecovery::new(&cfg);
1005        let now = Instant::now();
1006        let mss = r.max_datagram_size;
1007
1008        let mut pn = 0;
1009
1010        // At 4th roundtrip, filled_pipe=true and switch to Drain,
1011        // but move to ProbeBW immediately because bytes_in_flight is
1012        // smaller than BBRInFlight(1).
1013        for _ in 0..4 {
1014            let pkt = Sent {
1015                pkt_num: pn,
1016                frames: smallvec![],
1017                time_sent: now,
1018                time_acked: None,
1019                time_lost: None,
1020                size: mss,
1021                ack_eliciting: true,
1022                in_flight: true,
1023                delivered: r.congestion.delivery_rate.delivered(),
1024                delivered_time: now,
1025                first_sent_time: now,
1026                is_app_limited: false,
1027                tx_in_flight: 0,
1028                lost: 0,
1029                has_data: false,
1030                is_pmtud_probe: false,
1031            };
1032
1033            r.on_packet_sent(
1034                pkt,
1035                packet::Epoch::Application,
1036                HandshakeStatus::default(),
1037                now,
1038                "",
1039            );
1040
1041            pn += 1;
1042
1043            let rtt = Duration::from_millis(50);
1044            let now = now + rtt;
1045
1046            let mut acked = ranges::RangeSet::default();
1047            acked.insert(0..pn);
1048
1049            assert_eq!(
1050                r.on_ack_received(
1051                    &acked,
1052                    25,
1053                    packet::Epoch::Application,
1054                    HandshakeStatus::default(),
1055                    now,
1056                    None,
1057                    "",
1058                )
1059                .unwrap(),
1060                OnAckReceivedOutcome {
1061                    lost_packets: 0,
1062                    lost_bytes: 0,
1063                    acked_bytes: mss,
1064                    spurious_losses: 0,
1065                }
1066            );
1067        }
1068
1069        // Now we are in ProbeBW state.
1070        assert_eq!(
1071            r.congestion.bbr2_state.state,
1072            BBR2StateMachine::ProbeBWCRUISE
1073        );
1074
1075        // After RTPROP_FILTER_LEN (10s), switch to ProbeRTT.
1076        let now = now + PROBE_RTT_INTERVAL;
1077
1078        let pkt = Sent {
1079            pkt_num: pn,
1080            frames: smallvec![],
1081            time_sent: now,
1082            time_acked: None,
1083            time_lost: None,
1084            size: mss,
1085            ack_eliciting: true,
1086            in_flight: true,
1087            delivered: r.congestion.delivery_rate.delivered(),
1088            delivered_time: now,
1089            first_sent_time: now,
1090            is_app_limited: false,
1091            tx_in_flight: 0,
1092            lost: 0,
1093            has_data: false,
1094            is_pmtud_probe: false,
1095        };
1096
1097        r.on_packet_sent(
1098            pkt,
1099            packet::Epoch::Application,
1100            HandshakeStatus::default(),
1101            now,
1102            "",
1103        );
1104
1105        pn += 1;
1106
1107        // Don't update rtprop by giving larger rtt than before.
1108        // If rtprop is updated, rtprop expiry check is reset.
1109        let rtt = Duration::from_millis(100);
1110        let now = now + rtt;
1111
1112        let mut acked = ranges::RangeSet::default();
1113        acked.insert(0..pn);
1114
1115        assert_eq!(
1116            r.on_ack_received(
1117                &acked,
1118                25,
1119                packet::Epoch::Application,
1120                HandshakeStatus::default(),
1121                now,
1122                None,
1123                "",
1124            )
1125            .unwrap(),
1126            OnAckReceivedOutcome {
1127                lost_packets: 0,
1128                lost_bytes: 0,
1129                acked_bytes: mss,
1130                spurious_losses: 0,
1131            }
1132        );
1133
1134        assert_eq!(r.congestion.bbr2_state.state, BBR2StateMachine::ProbeRTT);
1135        assert_eq!(r.congestion.bbr2_state.pacing_gain, 1.0);
1136    }
1137}
1138
1139mod init;
1140mod pacing;
1141mod per_ack;
1142mod per_loss;
1143mod per_transmit;