Skip to main content

quinn_proto/congestion/bbr/
mod.rs

1use std::any::Any;
2use std::fmt::Debug;
3use std::sync::Arc;
4
5use rand::{RngExt, SeedableRng};
6use rand_pcg::Pcg32;
7
8use crate::congestion::ControllerMetrics;
9use crate::congestion::bbr::bw_estimation::BandwidthEstimation;
10use crate::congestion::bbr::min_max::MinMax;
11use crate::connection::RttEstimator;
12use crate::{Duration, Instant};
13
14use super::{BASE_DATAGRAM_SIZE, Controller, ControllerFactory};
15
16mod bw_estimation;
17mod min_max;
18
19/// Experimental! Use at your own risk.
20///
21/// Aims for reduced buffer bloat and improved performance over high bandwidth-delay product networks.
22/// Based on google's quiche implementation <https://source.chromium.org/chromium/chromium/src/+/master:net/third_party/quiche/src/quic/core/congestion_control/bbr_sender.cc>
23/// of BBR <https://datatracker.ietf.org/doc/html/draft-cardwell-iccrg-bbr-congestion-control>.
24/// More discussion and links at <https://groups.google.com/g/bbr-dev>.
25#[derive(Debug, Clone)]
26pub struct Bbr {
27    config: Arc<BbrConfig>,
28    current_mtu: u64,
29    max_bandwidth: BandwidthEstimation,
30    acked_bytes: u64,
31    mode: Mode,
32    loss_state: LossState,
33    recovery_state: RecoveryState,
34    recovery_window: u64,
35    is_at_full_bandwidth: bool,
36    pacing_gain: f32,
37    high_gain: f32,
38    drain_gain: f32,
39    cwnd_gain: f32,
40    high_cwnd_gain: f32,
41    last_cycle_start: Option<Instant>,
42    current_cycle_offset: u8,
43    init_cwnd: u64,
44    min_cwnd: u64,
45    prev_in_flight_count: u64,
46    exit_probe_rtt_at: Option<Instant>,
47    probe_rtt_last_started_at: Option<Instant>,
48    min_rtt: Duration,
49    exiting_quiescence: bool,
50    pacing_rate: u64,
51    max_acked_packet_number: u64,
52    max_sent_packet_number: u64,
53    end_recovery_at_packet_number: u64,
54    cwnd: u64,
55    current_round_trip_end_packet_number: u64,
56    round_count: u64,
57    bw_at_last_round: u64,
58    round_wo_bw_gain: u64,
59    ack_aggregation: AckAggregationState,
60    random_number_generator: Pcg32,
61}
62
63impl Bbr {
64    /// Construct a state using the given `config` and current time `now`
65    pub fn new(config: Arc<BbrConfig>, current_mtu: u16) -> Self {
66        let initial_window = config.initial_window;
67        Self {
68            config,
69            current_mtu: current_mtu as u64,
70            max_bandwidth: BandwidthEstimation::default(),
71            acked_bytes: 0,
72            mode: Mode::Startup,
73            loss_state: Default::default(),
74            recovery_state: RecoveryState::NotInRecovery,
75            recovery_window: 0,
76            is_at_full_bandwidth: false,
77            pacing_gain: K_DEFAULT_HIGH_GAIN,
78            high_gain: K_DEFAULT_HIGH_GAIN,
79            drain_gain: 1.0 / K_DEFAULT_HIGH_GAIN,
80            cwnd_gain: K_DEFAULT_HIGH_GAIN,
81            high_cwnd_gain: K_DEFAULT_HIGH_GAIN,
82            last_cycle_start: None,
83            current_cycle_offset: 0,
84            init_cwnd: initial_window,
85            min_cwnd: calculate_min_window(current_mtu as u64),
86            prev_in_flight_count: 0,
87            exit_probe_rtt_at: None,
88            probe_rtt_last_started_at: None,
89            min_rtt: Default::default(),
90            exiting_quiescence: false,
91            pacing_rate: 0,
92            max_acked_packet_number: 0,
93            max_sent_packet_number: 0,
94            end_recovery_at_packet_number: 0,
95            cwnd: initial_window,
96            current_round_trip_end_packet_number: 0,
97            round_count: 0,
98            bw_at_last_round: 0,
99            round_wo_bw_gain: 0,
100            ack_aggregation: AckAggregationState::default(),
101            random_number_generator: Pcg32::from_rng(&mut rand::rng()),
102        }
103    }
104
105    fn enter_startup_mode(&mut self) {
106        self.mode = Mode::Startup;
107        self.pacing_gain = self.high_gain;
108        self.cwnd_gain = self.high_cwnd_gain;
109    }
110
111    fn enter_probe_bandwidth_mode(&mut self, now: Instant) {
112        self.mode = Mode::ProbeBw;
113        self.cwnd_gain = K_DERIVED_HIGH_CWNDGAIN;
114        self.last_cycle_start = Some(now);
115        // Pick a random offset for the gain cycle out of {0, 2..7} range. 1 is
116        // excluded because in that case increased gain and decreased gain would not
117        // follow each other.
118        let mut rand_index = self
119            .random_number_generator
120            .random_range(0..K_PACING_GAIN.len() as u8 - 1);
121        if rand_index >= 1 {
122            rand_index += 1;
123        }
124        self.current_cycle_offset = rand_index;
125        self.pacing_gain = K_PACING_GAIN[rand_index as usize];
126    }
127
128    fn update_recovery_state(&mut self, is_round_start: bool) {
129        // Exit recovery when there are no losses for a round.
130        if self.loss_state.has_losses() {
131            self.end_recovery_at_packet_number = self.max_sent_packet_number;
132        }
133        match self.recovery_state {
134            // Enter conservation on the first loss.
135            RecoveryState::NotInRecovery if self.loss_state.has_losses() => {
136                self.recovery_state = RecoveryState::Conservation;
137                // This will cause the |recovery_window| to be set to the
138                // correct value in CalculateRecoveryWindow().
139                self.recovery_window = 0;
140                // Since the conservation phase is meant to be lasting for a whole
141                // round, extend the current round as if it were started right now.
142                self.current_round_trip_end_packet_number = self.max_sent_packet_number;
143            }
144            RecoveryState::Growth | RecoveryState::Conservation => {
145                if self.recovery_state == RecoveryState::Conservation && is_round_start {
146                    self.recovery_state = RecoveryState::Growth;
147                }
148                // Exit recovery if appropriate.
149                if !self.loss_state.has_losses()
150                    && self.max_acked_packet_number > self.end_recovery_at_packet_number
151                {
152                    self.recovery_state = RecoveryState::NotInRecovery;
153                }
154            }
155            _ => {}
156        }
157    }
158
159    fn update_gain_cycle_phase(&mut self, now: Instant, in_flight: u64) {
160        // In most cases, the cycle is advanced after an RTT passes.
161        let mut should_advance_gain_cycling = self
162            .last_cycle_start
163            .map(|last_cycle_start| now.duration_since(last_cycle_start) > self.min_rtt)
164            .unwrap_or(false);
165        // If the pacing gain is above 1.0, the connection is trying to probe the
166        // bandwidth by increasing the number of bytes in flight to at least
167        // pacing_gain * BDP.  Make sure that it actually reaches the target, as
168        // long as there are no losses suggesting that the buffers are not able to
169        // hold that much.
170        if self.pacing_gain > 1.0
171            && !self.loss_state.has_losses()
172            && self.prev_in_flight_count < self.get_target_cwnd(self.pacing_gain)
173        {
174            should_advance_gain_cycling = false;
175        }
176
177        // If pacing gain is below 1.0, the connection is trying to drain the extra
178        // queue which could have been incurred by probing prior to it.  If the
179        // number of bytes in flight falls down to the estimated BDP value earlier,
180        // conclude that the queue has been successfully drained and exit this cycle
181        // early.
182        if self.pacing_gain < 1.0 && in_flight <= self.get_target_cwnd(1.0) {
183            should_advance_gain_cycling = true;
184        }
185
186        if should_advance_gain_cycling {
187            self.current_cycle_offset = (self.current_cycle_offset + 1) % K_PACING_GAIN.len() as u8;
188            self.last_cycle_start = Some(now);
189            // Stay in low gain mode until the target BDP is hit.  Low gain mode
190            // will be exited immediately when the target BDP is achieved.
191            if DRAIN_TO_TARGET
192                && self.pacing_gain < 1.0
193                && (K_PACING_GAIN[self.current_cycle_offset as usize] - 1.0).abs() < f32::EPSILON
194                && in_flight > self.get_target_cwnd(1.0)
195            {
196                return;
197            }
198            self.pacing_gain = K_PACING_GAIN[self.current_cycle_offset as usize];
199        }
200    }
201
202    fn maybe_exit_startup_or_drain(&mut self, now: Instant, in_flight: u64) {
203        if self.mode == Mode::Startup && self.is_at_full_bandwidth {
204            self.mode = Mode::Drain;
205            self.pacing_gain = self.drain_gain;
206            self.cwnd_gain = self.high_cwnd_gain;
207        }
208        if self.mode == Mode::Drain && in_flight <= self.get_target_cwnd(1.0) {
209            self.enter_probe_bandwidth_mode(now);
210        }
211    }
212
213    fn is_min_rtt_expired(&self, now: Instant, app_limited: bool) -> bool {
214        !app_limited
215            && self
216                .probe_rtt_last_started_at
217                .map(|last| now.saturating_duration_since(last) > Duration::from_secs(10))
218                .unwrap_or(true)
219    }
220
221    fn maybe_enter_or_exit_probe_rtt(
222        &mut self,
223        now: Instant,
224        is_round_start: bool,
225        bytes_in_flight: u64,
226        app_limited: bool,
227    ) {
228        let min_rtt_expired = self.is_min_rtt_expired(now, app_limited);
229        if min_rtt_expired && !self.exiting_quiescence && self.mode != Mode::ProbeRtt {
230            self.mode = Mode::ProbeRtt;
231            self.pacing_gain = 1.0;
232            // Do not decide on the time to exit ProbeRtt until the
233            // |bytes_in_flight| is at the target small value.
234            self.exit_probe_rtt_at = None;
235            self.probe_rtt_last_started_at = Some(now);
236        }
237
238        if self.mode == Mode::ProbeRtt {
239            match self.exit_probe_rtt_at {
240                None => {
241                    // If the window has reached the appropriate size, schedule exiting
242                    // ProbeRtt.  The CWND during ProbeRtt is
243                    // kMinimumCongestionWindow, but we allow an extra packet since QUIC
244                    // checks CWND before sending a packet.
245                    if bytes_in_flight < self.get_probe_rtt_cwnd() + self.current_mtu {
246                        const K_PROBE_RTT_TIME: Duration = Duration::from_millis(200);
247                        self.exit_probe_rtt_at = Some(now + K_PROBE_RTT_TIME);
248                    }
249                }
250                Some(exit_time) if is_round_start && now >= exit_time => {
251                    if !self.is_at_full_bandwidth {
252                        self.enter_startup_mode();
253                    } else {
254                        self.enter_probe_bandwidth_mode(now);
255                    }
256                }
257                Some(_) => {}
258            }
259        }
260
261        self.exiting_quiescence = false;
262    }
263
264    fn get_target_cwnd(&self, gain: f32) -> u64 {
265        let bw = self.max_bandwidth.get_estimate();
266        let bdp = self.min_rtt.as_micros() as u64 * bw;
267        let bdpf = bdp as f64;
268        let cwnd = ((gain as f64 * bdpf) / 1_000_000f64) as u64;
269        // BDP estimate will be zero if no bandwidth samples are available yet.
270        if cwnd == 0 {
271            return self.init_cwnd;
272        }
273        cwnd.max(self.min_cwnd)
274    }
275
276    fn get_probe_rtt_cwnd(&self) -> u64 {
277        const K_MODERATE_PROBE_RTT_MULTIPLIER: f32 = 0.75;
278        if PROBE_RTT_BASED_ON_BDP {
279            return self.get_target_cwnd(K_MODERATE_PROBE_RTT_MULTIPLIER);
280        }
281        self.min_cwnd
282    }
283
284    fn calculate_pacing_rate(&mut self) {
285        let bw = self.max_bandwidth.get_estimate();
286        if bw == 0 {
287            return;
288        }
289        let target_rate = (bw as f64 * self.pacing_gain as f64) as u64;
290        if self.is_at_full_bandwidth {
291            self.pacing_rate = target_rate;
292            return;
293        }
294
295        // Pace at the rate of initial_window / RTT as soon as RTT measurements are
296        // available.
297        if self.pacing_rate == 0 && self.min_rtt.as_nanos() != 0 {
298            self.pacing_rate =
299                BandwidthEstimation::bw_from_delta(self.init_cwnd, self.min_rtt).unwrap();
300            return;
301        }
302
303        // Do not decrease the pacing rate during startup.
304        if self.pacing_rate < target_rate {
305            self.pacing_rate = target_rate;
306        }
307    }
308
309    fn calculate_cwnd(&mut self, bytes_acked: u64, excess_acked: u64) {
310        if self.mode == Mode::ProbeRtt {
311            return;
312        }
313        let mut target_window = self.get_target_cwnd(self.cwnd_gain);
314        if self.is_at_full_bandwidth {
315            // Add the max recently measured ack aggregation to CWND.
316            target_window += self.ack_aggregation.max_ack_height.get();
317        } else {
318            // Add the most recent excess acked.  Because CWND never decreases in
319            // STARTUP, this will automatically create a very localized max filter.
320            target_window += excess_acked;
321        }
322        // Instead of immediately setting the target CWND as the new one, BBR grows
323        // the CWND towards |target_window| by only increasing it |bytes_acked| at a
324        // time.
325        if self.is_at_full_bandwidth {
326            self.cwnd = target_window.min(self.cwnd + bytes_acked);
327        } else if (self.cwnd_gain < target_window as f32) || (self.acked_bytes < self.init_cwnd) {
328            // If the connection is not yet out of startup phase, do not decrease
329            // the window.
330            self.cwnd += bytes_acked;
331        }
332
333        // Enforce the limits on the congestion window.
334        if self.cwnd < self.min_cwnd {
335            self.cwnd = self.min_cwnd;
336        }
337    }
338
339    fn calculate_recovery_window(&mut self, bytes_acked: u64, bytes_lost: u64, in_flight: u64) {
340        if !self.recovery_state.in_recovery() {
341            return;
342        }
343        // Set up the initial recovery window.
344        if self.recovery_window == 0 {
345            self.recovery_window = self.min_cwnd.max(in_flight + bytes_acked);
346            return;
347        }
348
349        // Remove losses from the recovery window, while accounting for a potential
350        // integer underflow.
351        if self.recovery_window >= bytes_lost {
352            self.recovery_window -= bytes_lost;
353        } else {
354            // k_max_segment_size = current_mtu
355            self.recovery_window = self.current_mtu;
356        }
357        // In CONSERVATION mode, just subtracting losses is sufficient.  In GROWTH,
358        // release additional |bytes_acked| to achieve a slow-start-like behavior.
359        if self.recovery_state == RecoveryState::Growth {
360            self.recovery_window += bytes_acked;
361        }
362
363        // Sanity checks.  Ensure that we always allow to send at least an MSS or
364        // |bytes_acked| in response, whichever is larger.
365        self.recovery_window = self
366            .recovery_window
367            .max(in_flight + bytes_acked)
368            .max(self.min_cwnd);
369    }
370
371    /// <https://datatracker.ietf.org/doc/html/draft-cardwell-iccrg-bbr-congestion-control#section-4.3.2.2>
372    fn check_if_full_bw_reached(&mut self, app_limited: bool) {
373        if app_limited {
374            return;
375        }
376        let target = (self.bw_at_last_round as f64 * K_STARTUP_GROWTH_TARGET as f64) as u64;
377        let bw = self.max_bandwidth.get_estimate();
378        if bw >= target {
379            self.bw_at_last_round = bw;
380            self.round_wo_bw_gain = 0;
381            self.ack_aggregation.max_ack_height.reset();
382            return;
383        }
384
385        self.round_wo_bw_gain += 1;
386        if self.round_wo_bw_gain >= K_ROUND_TRIPS_WITHOUT_GROWTH_BEFORE_EXITING_STARTUP as u64
387            || (self.recovery_state.in_recovery())
388        {
389            self.is_at_full_bandwidth = true;
390        }
391    }
392}
393
394impl Controller for Bbr {
395    fn on_sent(&mut self, now: Instant, bytes: u64, last_packet_number: u64) {
396        self.max_sent_packet_number = last_packet_number;
397        self.max_bandwidth.on_sent(now, bytes);
398    }
399
400    fn on_ack(
401        &mut self,
402        now: Instant,
403        sent: Instant,
404        bytes: u64,
405        app_limited: bool,
406        rtt: &RttEstimator,
407    ) {
408        self.max_bandwidth
409            .on_ack(now, sent, bytes, self.round_count, app_limited);
410        self.acked_bytes += bytes;
411        if self.is_min_rtt_expired(now, app_limited) || self.min_rtt > rtt.min() {
412            self.min_rtt = rtt.min();
413        }
414    }
415
416    fn on_end_acks(
417        &mut self,
418        now: Instant,
419        in_flight: u64,
420        app_limited: bool,
421        largest_packet_num_acked: Option<u64>,
422    ) {
423        let bytes_acked = self.max_bandwidth.bytes_acked_this_window();
424        let excess_acked = self.ack_aggregation.update_ack_aggregation_bytes(
425            bytes_acked,
426            now,
427            self.round_count,
428            self.max_bandwidth.get_estimate(),
429        );
430        self.max_bandwidth.end_acks(self.round_count, app_limited);
431        if let Some(largest_acked_packet) = largest_packet_num_acked {
432            self.max_acked_packet_number = largest_acked_packet;
433        }
434
435        let mut is_round_start = false;
436        if bytes_acked > 0 {
437            is_round_start =
438                self.max_acked_packet_number > self.current_round_trip_end_packet_number;
439            if is_round_start {
440                self.current_round_trip_end_packet_number = self.max_sent_packet_number;
441                self.round_count += 1;
442            }
443        }
444
445        self.update_recovery_state(is_round_start);
446
447        if self.mode == Mode::ProbeBw {
448            self.update_gain_cycle_phase(now, in_flight);
449        }
450
451        if is_round_start && !self.is_at_full_bandwidth {
452            self.check_if_full_bw_reached(app_limited);
453        }
454
455        self.maybe_exit_startup_or_drain(now, in_flight);
456
457        self.maybe_enter_or_exit_probe_rtt(now, is_round_start, in_flight, app_limited);
458
459        // After the model is updated, recalculate the pacing rate and congestion window.
460        self.calculate_pacing_rate();
461        self.calculate_cwnd(bytes_acked, excess_acked);
462        self.calculate_recovery_window(bytes_acked, self.loss_state.lost_bytes, in_flight);
463
464        self.prev_in_flight_count = in_flight;
465        self.loss_state.reset();
466    }
467
468    fn on_congestion_event(
469        &mut self,
470        _now: Instant,
471        _sent: Instant,
472        _is_persistent_congestion: bool,
473        lost_bytes: u64,
474    ) {
475        self.loss_state.lost_bytes += lost_bytes;
476    }
477
478    fn on_mtu_update(&mut self, new_mtu: u16) {
479        self.current_mtu = new_mtu as u64;
480        self.min_cwnd = calculate_min_window(self.current_mtu);
481        self.init_cwnd = self.config.initial_window.max(self.min_cwnd);
482        self.cwnd = self.cwnd.max(self.min_cwnd);
483    }
484
485    fn window(&self) -> u64 {
486        if self.mode == Mode::ProbeRtt {
487            return self.get_probe_rtt_cwnd();
488        } else if self.recovery_state.in_recovery() && self.mode != Mode::Startup {
489            return self.cwnd.min(self.recovery_window);
490        }
491        self.cwnd
492    }
493
494    fn metrics(&self) -> ControllerMetrics {
495        ControllerMetrics {
496            congestion_window: self.window(),
497            ssthresh: None,
498            pacing_rate: Some(self.pacing_rate * 8),
499        }
500    }
501
502    fn clone_box(&self) -> Box<dyn Controller> {
503        Box::new(self.clone())
504    }
505
506    fn initial_window(&self) -> u64 {
507        self.config.initial_window
508    }
509
510    fn into_any(self: Box<Self>) -> Box<dyn Any> {
511        self
512    }
513}
514
515/// Configuration for the [`Bbr`] congestion controller
516#[derive(Debug, Clone)]
517pub struct BbrConfig {
518    initial_window: u64,
519}
520
521impl BbrConfig {
522    /// Default limit on the amount of outstanding data in bytes.
523    ///
524    /// Recommended value: `min(10 * max_datagram_size, max(2 * max_datagram_size, 14720))`
525    pub fn initial_window(&mut self, value: u64) -> &mut Self {
526        self.initial_window = value;
527        self
528    }
529}
530
531impl Default for BbrConfig {
532    fn default() -> Self {
533        Self {
534            initial_window: K_MAX_INITIAL_CONGESTION_WINDOW * BASE_DATAGRAM_SIZE,
535        }
536    }
537}
538
539impl ControllerFactory for BbrConfig {
540    fn build(self: Arc<Self>, _now: Instant, current_mtu: u16) -> Box<dyn Controller> {
541        Box::new(Bbr::new(self, current_mtu))
542    }
543}
544
545#[derive(Debug, Default, Copy, Clone)]
546struct AckAggregationState {
547    max_ack_height: MinMax,
548    aggregation_epoch_start_time: Option<Instant>,
549    aggregation_epoch_bytes: u64,
550}
551
552impl AckAggregationState {
553    fn update_ack_aggregation_bytes(
554        &mut self,
555        newly_acked_bytes: u64,
556        now: Instant,
557        round: u64,
558        max_bandwidth: u64,
559    ) -> u64 {
560        // Compute how many bytes are expected to be delivered, assuming max
561        // bandwidth is correct.
562        let expected_bytes_acked = max_bandwidth
563            * now
564                .saturating_duration_since(self.aggregation_epoch_start_time.unwrap_or(now))
565                .as_micros() as u64
566            / 1_000_000;
567
568        // Reset the current aggregation epoch as soon as the ack arrival rate is
569        // less than or equal to the max bandwidth.
570        if self.aggregation_epoch_bytes <= expected_bytes_acked {
571            // Reset to start measuring a new aggregation epoch.
572            self.aggregation_epoch_bytes = newly_acked_bytes;
573            self.aggregation_epoch_start_time = Some(now);
574            return 0;
575        }
576
577        // Compute how many extra bytes were delivered vs max bandwidth.
578        // Include the bytes most recently acknowledged to account for stretch acks.
579        self.aggregation_epoch_bytes += newly_acked_bytes;
580        let diff = self.aggregation_epoch_bytes - expected_bytes_acked;
581        self.max_ack_height.update_max(round, diff);
582        diff
583    }
584}
585
586#[derive(Debug, Clone, Copy, Eq, PartialEq)]
587enum Mode {
588    // Startup phase of the connection.
589    Startup,
590    // After achieving the highest possible bandwidth during the startup, lower
591    // the pacing rate in order to drain the queue.
592    Drain,
593    // Cruising mode.
594    ProbeBw,
595    // Temporarily slow down sending in order to empty the buffer and measure
596    // the real minimum RTT.
597    ProbeRtt,
598}
599
600// Indicates how the congestion control limits the amount of bytes in flight.
601#[derive(Debug, Clone, Copy, Eq, PartialEq)]
602enum RecoveryState {
603    // Do not limit.
604    NotInRecovery,
605    // Allow an extra outstanding byte for each byte acknowledged.
606    Conservation,
607    // Allow two extra outstanding bytes for each byte acknowledged (slow
608    // start).
609    Growth,
610}
611
612impl RecoveryState {
613    pub(super) fn in_recovery(&self) -> bool {
614        !matches!(self, Self::NotInRecovery)
615    }
616}
617
618#[derive(Debug, Clone, Default)]
619struct LossState {
620    lost_bytes: u64,
621}
622
623impl LossState {
624    pub(super) fn reset(&mut self) {
625        self.lost_bytes = 0;
626    }
627
628    pub(super) fn has_losses(&self) -> bool {
629        self.lost_bytes != 0
630    }
631}
632
633fn calculate_min_window(current_mtu: u64) -> u64 {
634    4 * current_mtu
635}
636
637// The gain used for the STARTUP, equal to 2/ln(2).
638const K_DEFAULT_HIGH_GAIN: f32 = 2.885;
639// The newly derived CWND gain for STARTUP, 2.
640const K_DERIVED_HIGH_CWNDGAIN: f32 = 2.0;
641// The cycle of gains used during the ProbeBw stage.
642const K_PACING_GAIN: [f32; 8] = [1.25, 0.75, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0];
643
644const K_STARTUP_GROWTH_TARGET: f32 = 1.25;
645const K_ROUND_TRIPS_WITHOUT_GROWTH_BEFORE_EXITING_STARTUP: u8 = 3;
646
647// Do not allow initial congestion window to be greater than 200 packets.
648const K_MAX_INITIAL_CONGESTION_WINDOW: u64 = 200;
649
650const PROBE_RTT_BASED_ON_BDP: bool = true;
651const DRAIN_TO_TARGET: bool = true;