s2n_quic_core/recovery/
bbr.rs

1// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4use crate::{
5    counter::Counter,
6    event,
7    event::IntoEvent,
8    random,
9    recovery::{
10        bandwidth,
11        bandwidth::{Bandwidth, RateSample},
12        bbr::{
13            pacing::Pacer,
14            probe_bw::{CyclePhase, PROBE_BW_FULL_LOSS_COUNT},
15        },
16        congestion_controller,
17        congestion_controller::Publisher,
18        CongestionController, RttEstimator,
19    },
20    time::Timestamp,
21};
22use core::{
23    cmp::{max, min},
24    time::Duration,
25};
26use num_rational::Ratio;
27use num_traits::{CheckedMul, Inv, One};
28
29mod congestion;
30mod data_rate;
31mod data_volume;
32mod drain;
33mod ecn;
34mod full_pipe;
35mod pacing;
36mod probe_bw;
37mod probe_rtt;
38mod recovery;
39mod round;
40mod startup;
41mod windowed_filter;
42
43//= https://tools.ietf.org/id/draft-cardwell-iccrg-bbr-congestion-control-02#2.8
44//# The maximum tolerated per-round-trip packet loss rate when probing for bandwidth (the default is 2%).
45const LOSS_THRESH: Ratio<u32> = Ratio::new_raw(1, 50);
46
47//= https://tools.ietf.org/id/draft-cardwell-iccrg-bbr-congestion-control-02#2.8
48//# The default multiplicative decrease to make upon each round trip during which
49//# the connection detects packet loss (the value is 0.7)
50const BETA: Ratio<u64> = Ratio::new_raw(7, 10);
51
52//= https://tools.ietf.org/id/draft-cardwell-iccrg-bbr-congestion-control-02#2.8
53//# The multiplicative factor to apply to BBR.inflight_hi when attempting to leave free headroom in
54//# the path (e.g. free space in the bottleneck buffer or free time slots in the bottleneck link)
55//# that can be used by cross traffic (the value is 0.85).
56const HEADROOM: Ratio<u64> = Ratio::new_raw(85, 100);
57
58//= https://tools.ietf.org/id/draft-cardwell-iccrg-bbr-congestion-control-02#2.8
59//# The minimal cwnd value BBR targets, to allow pipelining with TCP endpoints
60//# that follow an "ACK every other packet" delayed-ACK policy: 4 * SMSS.
61const MIN_PIPE_CWND_PACKETS: u16 = 4;
62
63//= https://tools.ietf.org/id/draft-cardwell-iccrg-bbr-congestion-control-02#4.1.1
64//# The following state transition diagram summarizes the flow of control and the relationship between the different states:
65//#
66//#              |
67//#              V
68//#     +---> Startup  ------------+
69//#     |        |                 |
70//#     |        V                 |
71//#     |     Drain  --------------+
72//#     |        |                 |
73//#     |        V                 |
74//#     +---> ProbeBW_DOWN  -------+
75//#     | ^      |                 |
76//#     | |      V                 |
77//#     | |   ProbeBW_CRUISE ------+
78//#     | |      |                 |
79//#     | |      V                 |
80//#     | |   ProbeBW_REFILL  -----+
81//#     | |      |                 |
82//#     | |      V                 |
83//#     | |   ProbeBW_UP  ---------+
84//#     | |      |                 |
85//#     | +------+                 |
86//#     |                          |
87//#     +---- ProbeRTT <-----------+
88#[derive(Clone, Debug)]
89enum State {
90    Startup,
91    Drain,
92    ProbeBw(probe_bw::State),
93    ProbeRtt(probe_rtt::State),
94}
95
96impl State {
97    /// The dynamic gain factor used to scale BBR.bw to produce BBR.pacing_rate
98    fn pacing_gain(&self, app_settings: &ApplicationSettings) -> Ratio<u64> {
99        match self {
100            State::Startup => startup::PACING_GAIN,
101            State::Drain => drain::PACING_GAIN,
102            State::ProbeBw(probe_bw_state) => {
103                probe_bw_state.cycle_phase().pacing_gain(app_settings)
104            }
105            State::ProbeRtt(_) => probe_rtt::PACING_GAIN,
106        }
107    }
108
109    /// The dynamic gain factor used to scale the estimated BDP to produce a congestion window (cwnd)
110    fn cwnd_gain(&self, app_settings: &ApplicationSettings) -> Ratio<u64> {
111        let cwnd_gain = app_settings
112            .probe_bw_cwnd_gain()
113            .unwrap_or(probe_bw::CWND_GAIN);
114
115        match self {
116            State::Startup => startup::CWND_GAIN,
117            State::Drain => drain::CWND_GAIN,
118            State::ProbeBw(_) => cwnd_gain,
119            State::ProbeRtt(_) => probe_rtt::CWND_GAIN,
120        }
121    }
122
123    /// True if the current state is Startup
124    fn is_startup(&self) -> bool {
125        matches!(self, State::Startup)
126    }
127
128    /// True if the current state is Drain
129    fn is_drain(&self) -> bool {
130        matches!(self, State::Drain)
131    }
132
133    /// True if the current state is ProbeBw
134    fn is_probing_bw(&self) -> bool {
135        //= https://tools.ietf.org/id/draft-cardwell-iccrg-bbr-congestion-control-02#4.3.3.6
136        //# IsInAProbeBWState()
137        //#   state = BBR.state
138        //#   return (state == ProbeBW_DOWN or
139        //#           state == ProbeBW_CRUISE or
140        //#           state == ProbeBW_REFILL or
141        //#           state == ProbeBW_UP)
142        matches!(self, State::ProbeBw(_))
143    }
144
145    /// True if the current state is ProbeBw and the CyclePhase is `Up`
146    fn is_probing_bw_up(&self) -> bool {
147        if let State::ProbeBw(probe_bw_state) = self {
148            return probe_bw_state.cycle_phase() == CyclePhase::Up;
149        }
150        false
151    }
152
153    /// True if the current state is ProbeBw and the CyclePhase is `Cruise`
154    fn is_probing_bw_cruise(&self) -> bool {
155        if let State::ProbeBw(probe_bw_state) = self {
156            return probe_bw_state.cycle_phase() == CyclePhase::Cruise;
157        }
158        false
159    }
160
161    /// True if the current state is ProbeBw and the CyclePhase is `Refill`
162    fn is_probing_bw_refill(&self) -> bool {
163        if let State::ProbeBw(probe_bw_state) = self {
164            return probe_bw_state.cycle_phase() == CyclePhase::Refill;
165        }
166        false
167    }
168
169    /// True if the current state is ProbeRtt
170    fn is_probing_rtt(&self) -> bool {
171        matches!(self, State::ProbeRtt(_))
172    }
173
174    /// True if BBR is accelerating sending in order to probe for bandwidth
175    ///
176    /// Note: This is not the same as `is_probing_bw`, as states other than
177    ///       `State::ProbingBw` are also considered as probing for bandwidth
178    ///       and not every `ProbingBw` sub-state is actually probing.
179    ///
180    /// See https://github.com/google/bbr/blob/a23c4bb59e0c5a505fc0f5cc84c4d095a64ed361/net/ipv4/tcp_bbr2.c#L1348
181    fn is_probing_for_bandwidth(&self) -> bool {
182        self.is_startup() || self.is_probing_bw_up() || self.is_probing_bw_refill()
183    }
184
185    /// Transition to the given `new_state`
186    fn transition_to<Pub: Publisher>(&mut self, new_state: State, publisher: &mut Pub) {
187        if cfg!(debug_assertions) {
188            match &new_state {
189                // BBR is initialized in the Startup state, but may re-enter Startup after ProbeRtt
190                State::Startup => assert!(self.is_probing_rtt()),
191                State::Drain => assert!(self.is_startup()),
192                State::ProbeBw(_) => assert!(self.is_drain() || self.is_probing_rtt()),
193                State::ProbeRtt(_) => {} // ProbeRtt may be entered anytime
194            }
195        }
196
197        if !new_state.is_probing_bw() {
198            // ProbeBw::CyclePhase emits this metric for the ProbingBw state
199            publisher.on_bbr_state_changed(new_state.into_event());
200        }
201
202        *self = new_state;
203    }
204}
205
206impl IntoEvent<event::builder::BbrState> for &State {
207    #[inline]
208    fn into_event(self) -> event::builder::BbrState {
209        use event::builder::BbrState;
210        match self {
211            State::Startup => BbrState::Startup,
212            State::Drain => BbrState::Drain,
213            State::ProbeBw(probe_bw_state) => probe_bw_state.cycle_phase().into_event(),
214            State::ProbeRtt(_) => BbrState::ProbeRtt,
215        }
216    }
217}
218
219#[derive(Default, Debug, Clone, Copy)]
220pub struct ApplicationSettings {
221    initial_congestion_window: Option<u32>,
222    probe_bw_cwnd_gain: Option<u32>,
223    probe_bw_up_pacing_gain: Option<u32>,
224    loss_threshold: Option<u32>,
225}
226
227impl ApplicationSettings {
228    fn probe_bw_cwnd_gain(&self) -> Option<Ratio<u64>> {
229        self.probe_bw_cwnd_gain
230            .map(|cwnd_gain| Ratio::new_raw(cwnd_gain as u64, 100))
231    }
232
233    fn probe_bw_up_pacing_gain(&self) -> Option<Ratio<u64>> {
234        self.probe_bw_up_pacing_gain
235            .map(|pacing_gain| Ratio::new_raw(pacing_gain as u64, 100))
236    }
237
238    fn loss_threshold(&self) -> Option<Ratio<u32>> {
239        self.loss_threshold
240            .map(|loss_threshold| Ratio::new_raw(loss_threshold, 100))
241    }
242}
243
244/// A congestion controller that implements "Bottleneck Bandwidth and Round-trip propagation time"
245/// version 2 (BBRv2) as specified in <https://datatracker.ietf.org/doc/draft-cardwell-iccrg-bbr-congestion-control/>.
246///
247/// Based in part on the Chromium BBRv2 implementation, see <https://source.chromium.org/chromium/chromium/src/+/main:net/third_party/quiche/src/quic/core/congestion_control/bbr2_sender.cc>
248/// and the Linux Kernel TCP BBRv2 implementation, see <https://github.com/google/bbr/blob/v2alpha/net/ipv4/tcp_bbr2.c>
249#[derive(Debug, Clone)]
250pub struct BbrCongestionController {
251    state: State,
252    round_counter: round::Counter,
253    bw_estimator: bandwidth::Estimator,
254    full_pipe_estimator: full_pipe::Estimator,
255    //= https://www.rfc-editor.org/rfc/rfc9002#appendix-B.2
256    //# The sum of the size in bytes of all sent packets
257    //# that contain at least one ack-eliciting or PADDING frame and have
258    //# not been acknowledged or declared lost.  The size does not include
259    //# IP or UDP overhead, but does include the QUIC header and
260    //# Authenticated Encryption with Associated Data (AEAD) overhead.
261    //# Packets only containing ACK frames do not count toward
262    //# bytes_in_flight to ensure congestion control does not impede
263    //# congestion feedback.
264    bytes_in_flight: BytesInFlight,
265    cwnd: u32,
266    prior_cwnd: u32,
267    recovery_state: recovery::State,
268    congestion_state: congestion::State,
269    ecn_state: ecn::State,
270    data_rate_model: data_rate::Model,
271    data_volume_model: data_volume::Model,
272    max_datagram_size: u16,
273    /// A boolean that is true if and only if a connection is restarting after being idle
274    idle_restart: bool,
275    /// True if rate samples reflect bandwidth probing
276    bw_probe_samples: bool,
277    /// Controls the departure time and send quantum of packets
278    pacer: Pacer,
279    /// If true, we can attempt to avoid updating control parameters and/or model parameters
280    try_fast_path: bool,
281    //= https://tools.ietf.org/id/draft-cardwell-iccrg-bbr-congestion-control-02#2.1
282    //# True if the connection has fully utilized its cwnd at any point in the last packet-timed round trip.
283    cwnd_limited_in_round: bool,
284    app_settings: ApplicationSettings,
285}
286
287type BytesInFlight = Counter<u32>;
288
289impl CongestionController for BbrCongestionController {
290    type PacketInfo = bandwidth::PacketInfo;
291
292    #[inline]
293    fn congestion_window(&self) -> u32 {
294        self.cwnd
295    }
296
297    #[inline]
298    fn bytes_in_flight(&self) -> u32 {
299        *self.bytes_in_flight
300    }
301
302    #[inline]
303    fn is_congestion_limited(&self) -> bool {
304        let available_congestion_window = self
305            .congestion_window()
306            .saturating_sub(*self.bytes_in_flight);
307        available_congestion_window < self.max_datagram_size as u32
308    }
309
310    #[inline]
311    fn requires_fast_retransmission(&self) -> bool {
312        self.recovery_state.requires_fast_retransmission()
313    }
314
315    #[inline]
316    fn on_packet_sent<Pub: Publisher>(
317        &mut self,
318        time_sent: Timestamp,
319        sent_bytes: usize,
320        app_limited: Option<bool>,
321        rtt_estimator: &RttEstimator,
322        publisher: &mut Pub,
323    ) -> Self::PacketInfo {
324        let prior_bytes_in_flight = *self.bytes_in_flight;
325
326        if sent_bytes > 0 {
327            self.recovery_state.on_packet_sent();
328
329            //= https://tools.ietf.org/id/draft-cardwell-iccrg-bbr-congestion-control-02#4.2.2
330            //# BBROnTransmit():
331            //#   BBRHandleRestartFromIdle()
332            self.handle_restart_from_idle(time_sent, publisher);
333
334            self.bytes_in_flight
335                .try_add(sent_bytes)
336                .expect("sent_bytes should not exceed u32::MAX");
337            self.pacer
338                .on_packet_sent(time_sent, sent_bytes, rtt_estimator.smoothed_rtt());
339            self.cwnd_limited_in_round |= self.is_congestion_limited();
340        }
341
342        self.bw_estimator
343            .on_packet_sent(prior_bytes_in_flight, sent_bytes, app_limited, time_sent)
344    }
345
346    #[inline]
347    fn on_rtt_update<Pub: Publisher>(
348        &mut self,
349        _time_sent: Timestamp,
350        _now: Timestamp,
351        rtt_estimator: &RttEstimator,
352        publisher: &mut Pub,
353    ) {
354        if self.data_volume_model.min_rtt().is_none() {
355            // This is the first RTT estimate, so initialize the pacing rate to
356            // override the default initialized value with a more realistic value
357            self.pacer.initialize_pacing_rate(
358                self.cwnd,
359                rtt_estimator.smoothed_rtt(),
360                self.state.pacing_gain(&self.app_settings),
361                publisher,
362            );
363        }
364
365        // BBRUpdateMinRTT() called in `on_ack`
366    }
367
368    #[inline]
369    fn on_ack<Pub: Publisher>(
370        &mut self,
371        newest_acked_time_sent: Timestamp,
372        bytes_acknowledged: usize,
373        newest_acked_packet_info: Self::PacketInfo,
374        rtt_estimator: &RttEstimator,
375        random_generator: &mut dyn random::Generator,
376        ack_receive_time: Timestamp,
377        publisher: &mut Pub,
378    ) {
379        let is_cwnd_limited = self.is_congestion_limited();
380        self.bytes_in_flight
381            .try_sub(bytes_acknowledged)
382            .expect("bytes_acknowledged should not exceed u32::MAX");
383        self.bw_estimator.on_ack(
384            bytes_acknowledged,
385            newest_acked_time_sent,
386            newest_acked_packet_info,
387            ack_receive_time,
388            publisher,
389        );
390        self.round_counter.on_ack(
391            newest_acked_packet_info,
392            self.bw_estimator.delivered_bytes(),
393        );
394        self.recovery_state.on_ack(newest_acked_time_sent);
395        if self.round_counter.round_start() {
396            self.ecn_state
397                .on_round_start(self.bw_estimator.delivered_bytes(), self.max_datagram_size);
398            self.cwnd_limited_in_round = is_cwnd_limited;
399        }
400
401        //= https://tools.ietf.org/id/draft-cardwell-iccrg-bbr-congestion-control-02#4.2.3
402        //# On every ACK, the BBR algorithm executes the following BBRUpdateOnACK() steps in order
403        //# to update its network path model, update its state machine, and adjust its control
404        //# parameters to adapt to the updated model:
405        //# BBRUpdateOnACK():
406        //#   BBRUpdateModelAndState()
407        //#   BBRUpdateControlParameters()
408
409        //= https://tools.ietf.org/id/draft-cardwell-iccrg-bbr-congestion-control-02#4.2.3
410        //# BBRUpdateModelAndState():
411        //#   BBRUpdateLatestDeliverySignals()
412        //#   BBRUpdateCongestionSignals()
413        // implements BBRUpdateLatestDeliverySignals() and BBRUpdateCongestionSignals()
414
415        // Check if we need to update model parameters
416        let update_model = self.model_update_required();
417
418        if update_model {
419            self.update_latest_signals(newest_acked_packet_info);
420            //= https://tools.ietf.org/id/draft-cardwell-iccrg-bbr-congestion-control-02#4.2.3
421            //# BBRUpdateACKAggregation()
422            self.data_volume_model.update_ack_aggregation(
423                self.data_rate_model.bw(),
424                bytes_acknowledged,
425                self.cwnd,
426                self.round_counter.round_count(),
427                ack_receive_time,
428            );
429
430            //= https://tools.ietf.org/id/draft-cardwell-iccrg-bbr-congestion-control-02#4.2.3
431            //# BBRCheckStartupDone()
432            //# BBRCheckDrain()
433            self.check_startup_done(publisher);
434        }
435        self.check_drain_done(random_generator, ack_receive_time, publisher);
436
437        //= https://tools.ietf.org/id/draft-cardwell-iccrg-bbr-congestion-control-02#4.2.3
438        //# BBRUpdateProbeBWCyclePhase()
439        if self.full_pipe_estimator.filled_pipe() {
440            // BBRUpdateProbeBWCyclePhase() internally calls BBRAdaptUpperBounds() if BBR.filled_pipe == true
441            self.adapt_upper_bounds(
442                bytes_acknowledged,
443                random_generator,
444                ack_receive_time,
445                publisher,
446            );
447            if self.state.is_probing_bw() {
448                self.update_probe_bw_cycle_phase(random_generator, ack_receive_time, publisher);
449            }
450        }
451
452        //= https://tools.ietf.org/id/draft-cardwell-iccrg-bbr-congestion-control-02#4.2.3
453        //# BBRUpdateMinRTT()
454        //# BBRCheckProbeRTT()
455        //# BBRAdvanceLatestDeliverySignals()
456        //# BBRBoundBWForModel()
457        let prev_min_rtt = self.data_volume_model.min_rtt();
458        self.data_volume_model
459            .update_min_rtt(rtt_estimator.latest_rtt(), ack_receive_time);
460        self.check_probe_rtt(random_generator, ack_receive_time, publisher);
461
462        // Update control parameters if required
463        if self.control_update_required(update_model, prev_min_rtt) {
464            self.congestion_state
465                .advance(self.bw_estimator.rate_sample());
466            self.data_rate_model.bound_bw_for_model();
467
468            //= https://tools.ietf.org/id/draft-cardwell-iccrg-bbr-congestion-control-02#4.2.3
469            //# BBRUpdateControlParameters():
470            //#   BBRSetPacingRate()
471            //#   BBRSetSendQuantum()
472            //#   BBRSetCwnd()
473            self.pacer.set_pacing_rate(
474                self.data_rate_model.bw(),
475                self.state.pacing_gain(&self.app_settings),
476                self.full_pipe_estimator.filled_pipe(),
477                publisher,
478            );
479            self.pacer.set_send_quantum(self.max_datagram_size);
480            self.set_cwnd(bytes_acknowledged);
481        }
482    }
483
484    #[inline]
485    fn on_packet_lost<Pub: Publisher>(
486        &mut self,
487        lost_bytes: u32,
488        packet_info: Self::PacketInfo,
489        _persistent_congestion: bool,
490        new_loss_burst: bool,
491        random_generator: &mut dyn random::Generator,
492        timestamp: Timestamp,
493        publisher: &mut Pub,
494    ) {
495        debug_assert!(lost_bytes > 0);
496
497        self.bytes_in_flight -= lost_bytes;
498        self.bw_estimator.on_loss(lost_bytes as usize);
499        self.recovery_state.on_congestion_event(timestamp);
500        self.congestion_state
501            .on_packet_lost(self.bw_estimator.delivered_bytes(), new_loss_burst);
502
503        //= https://tools.ietf.org/id/draft-cardwell-iccrg-bbr-congestion-control-02#4.2.4
504        //# BBRUpdateOnLoss(packet):
505        //#   BBRHandleLostPacket(packet)
506        self.handle_lost_packet(
507            lost_bytes,
508            packet_info,
509            random_generator,
510            timestamp,
511            publisher,
512        );
513    }
514
515    #[inline]
516    fn on_explicit_congestion<Pub: Publisher>(
517        &mut self,
518        ce_count: u64,
519        event_time: Timestamp,
520        _publisher: &mut Pub,
521    ) {
522        self.bw_estimator.on_explicit_congestion(ce_count);
523        self.ecn_state.on_explicit_congestion(ce_count);
524        self.congestion_state.on_explicit_congestion();
525        self.recovery_state.on_congestion_event(event_time);
526    }
527
528    //= https://www.rfc-editor.org/rfc/rfc8899#section-3
529    //= type=exception
530    //= reason=See https://github.com/aws/s2n-quic/issues/959
531    //# An update to the PLPMTU (or MPS) MUST NOT increase the congestion
532    //# window measured in bytes [RFC4821].
533
534    //= https://www.rfc-editor.org/rfc/rfc9002#section-7.2
535    //# If the maximum datagram size is decreased in order to complete the
536    //# handshake, the congestion window SHOULD be set to the new initial
537    //# congestion window.
538    #[inline]
539    fn on_mtu_update<Pub: Publisher>(&mut self, max_datagram_size: u16, _publisher: &mut Pub) {
540        let old_max_datagram_size = self.max_datagram_size;
541        self.max_datagram_size = max_datagram_size;
542
543        let cwnd =
544            ((self.cwnd as f32 / old_max_datagram_size as f32) * max_datagram_size as f32) as u32;
545        let initial_window = Self::initial_window(max_datagram_size, &Default::default());
546
547        self.cwnd = max(cwnd, initial_window);
548    }
549
550    #[inline]
551    fn on_packet_discarded<Pub: Publisher>(&mut self, bytes_sent: usize, _publisher: &mut Pub) {
552        self.bytes_in_flight
553            .try_sub(bytes_sent)
554            .expect("bytes sent should not exceed u32::MAX");
555        self.bw_estimator.on_packet_discarded(bytes_sent);
556        self.recovery_state.on_packet_discarded();
557    }
558
559    #[inline]
560    fn earliest_departure_time(&self) -> Option<Timestamp> {
561        self.pacer.earliest_departure_time()
562    }
563
564    #[inline]
565    fn send_quantum(&self) -> Option<usize> {
566        Some(self.pacer.send_quantum())
567    }
568}
569
570impl BbrCongestionController {
571    /// Constructs a new `BbrCongestionController`
572    /// max_datagram_size is the current max_datagram_size, and is
573    /// expected to be 1200 when the congestion controller is created.
574    pub fn new(max_datagram_size: u16, app_settings: ApplicationSettings) -> Self {
575        //= https://tools.ietf.org/id/draft-cardwell-iccrg-bbr-congestion-control-02#4.2.1
576        //# BBROnInit():
577        //#   init_windowed_max_filter(filter=BBR.MaxBwFilter, value=0, time=0)
578        //#   BBR.min_rtt = SRTT ? SRTT : Inf
579        //#   BBR.min_rtt_stamp = Now()
580        //#   BBR.probe_rtt_done_stamp = 0
581        //#   BBR.probe_rtt_round_done = false
582        //#   BBR.prior_cwnd = 0
583        //#   BBR.idle_restart = false
584        //#   BBR.extra_acked_interval_start = Now()
585        //#   BBR.extra_acked_delivered = 0
586        //#   BBRResetCongestionSignals()
587        //#   BBRResetLowerBounds()
588        //#   BBRInitRoundCounting()
589        //#   BBRInitFullPipe()
590        //#   BBRInitPacingRate()
591        //#   BBREnterStartup()
592
593        // BBRResetCongestionSignals() is implemented by the default congestion::State
594        // BBRResetLowerBounds() is implemented by data_rate::Model::new() and data_volume::Model::new()
595        // BBRInitRoundCounting() is implemented by round::Counter::default()
596        // BBRInitFullPipe() is implemented by full_pipe::Estimator::default()
597
598        Self {
599            state: State::Startup,
600            round_counter: Default::default(),
601            bw_estimator: Default::default(),
602            full_pipe_estimator: Default::default(),
603            bytes_in_flight: Default::default(),
604            cwnd: Self::initial_window(max_datagram_size, &app_settings),
605            prior_cwnd: 0,
606            recovery_state: recovery::State::Recovered,
607            congestion_state: Default::default(),
608            ecn_state: Default::default(),
609            data_rate_model: data_rate::Model::new(),
610            // initialize extra_acked_interval_start and extra_acked_delivered
611            data_volume_model: data_volume::Model::new(),
612            max_datagram_size,
613            idle_restart: false,
614            bw_probe_samples: false,
615            pacer: Pacer::new(max_datagram_size, &app_settings),
616            try_fast_path: false,
617            cwnd_limited_in_round: false,
618            app_settings,
619        }
620    }
621
622    /// Returns the current pacing rate
623    #[inline]
624    pub fn pacing_rate(&self) -> Bandwidth {
625        self.pacer.pacing_rate()
626    }
627
628    /// The bandwidth-delay product
629    ///
630    /// Based on the current estimate of maximum sending bandwidth and minimum RTT
631    #[inline]
632    fn bdp(&self) -> u64 {
633        self.bdp_multiple(self.data_rate_model.bw(), Ratio::one())
634    }
635
636    /// Calculates a bandwidth-delay product using the supplied `Bandwidth` and `gain`
637    #[inline]
638    fn bdp_multiple(&self, bw: Bandwidth, gain: Ratio<u64>) -> u64 {
639        //= https://tools.ietf.org/id/draft-cardwell-iccrg-bbr-congestion-control-02#4.6.4.2
640        //# BBRBDPMultiple(gain):
641        //#   if (BBR.min_rtt == Inf)
642        //#       return InitialCwnd /* no valid RTT samples yet */
643        //#     BBR.bdp = BBR.bw * BBR.min_rtt
644        //#     return gain * BBR.bdp
645
646        if let Some(min_rtt) = self.data_volume_model.min_rtt() {
647            gain.checked_mul(&(bw * min_rtt).into())
648                .map_or(u64::MAX, |bdp| bdp.to_integer())
649        } else {
650            Self::initial_window(self.max_datagram_size, &self.app_settings).into()
651        }
652    }
653
654    /// How much data do we want in flight
655    ///
656    /// Based on the estimated BDP, unless congestion reduced the cwnd
657    #[inline]
658    fn target_inflight(&self) -> u32 {
659        //= https://tools.ietf.org/id/draft-cardwell-iccrg-bbr-congestion-control-02#4.3.3.5.3
660        //# BBRTargetInflight()
661        //#   return min(BBR.bdp, cwnd)
662
663        self.bdp().min(self.cwnd as u64) as u32
664    }
665
666    /// The estimate of the volume of in-flight data required to fully utilize the bottleneck
667    /// bandwidth available to the flow
668    ///
669    /// Based on the BDP estimate (BBR.bdp), the aggregation estimate (BBR.extra_acked), the
670    /// offload budget (BBR.offload_budget), and BBRMinPipeCwnd.
671    #[inline]
672    fn max_inflight(&self) -> u64 {
673        //= https://tools.ietf.org/id/draft-cardwell-iccrg-bbr-congestion-control-02#4.6.4.2
674        //# BBRUpdateMaxInflight()
675        //#   BBRUpdateAggregationBudget()
676        //#   inflight = BBRBDPMultiple(BBR.cwnd_gain)
677        //#   inflight += BBR.extra_acked
678        //#   BBR.max_inflight = BBRQuantizationBudget(inflight)
679
680        // max_inflight is calculated and returned from this function
681        // as needed, rather than maintained as a field
682
683        let bdp = self.bdp_multiple(
684            self.data_rate_model.bw(),
685            self.state.cwnd_gain(&self.app_settings),
686        );
687        let inflight = bdp.saturating_add(self.data_volume_model.extra_acked());
688        self.quantization_budget(inflight)
689    }
690
691    /// Inflight based on min RTT and the estimated bottleneck bandwidth
692    #[inline]
693    fn inflight(&self, bw: Bandwidth, gain: Ratio<u64>) -> u32 {
694        //= https://tools.ietf.org/id/draft-cardwell-iccrg-bbr-congestion-control-02#4.6.4.2
695        //# BBRInflight(gain)
696        //#   inflight = BBRBDPMultiple(gain)
697        //#   return BBRQuantizationBudget(inflight)
698
699        // BBRInflight is defined in the RFC with and without a Bandwidth input
700
701        let inflight = self.bdp_multiple(bw, gain);
702        self.quantization_budget(inflight)
703            .try_into()
704            .unwrap_or(u32::MAX)
705    }
706
707    /// The volume of data that tries to leave free headroom in the bottleneck buffer or link for
708    /// other flows, for fairness convergence and lower RTTs and loss
709    #[inline]
710    fn inflight_with_headroom(&self) -> u32 {
711        //= https://tools.ietf.org/id/draft-cardwell-iccrg-bbr-congestion-control-02#4.3.3.6
712        //# BBRInflightWithHeadroom()
713        //#   if (BBR.inflight_hi == Infinity)
714        //#     return Infinity
715        //#   headroom = max(1, BBRHeadroom * BBR.inflight_hi)
716        //#     return max(BBR.inflight_hi - headroom,
717        //#                BBRMinPipeCwnd)
718
719        if self.data_volume_model.inflight_hi() == u64::MAX {
720            return u32::MAX;
721        }
722
723        // The RFC pseudocode mistakenly subtracts headroom (representing 85% of inflight_hi)
724        // from inflight_hi, resulting a reduction to 15% of inflight_hi. Since the intention is
725        // to reduce inflight_hi to 85% of inflight_hi, we can just multiply by `HEADROOM`.
726        // See https://groups.google.com/g/bbr-dev/c/xmley7VkeoE/m/uXDlnxiuCgAJ
727        let inflight_with_headroom = (HEADROOM * self.data_volume_model.inflight_hi())
728            .to_integer()
729            .try_into()
730            .unwrap_or(u32::MAX);
731
732        inflight_with_headroom.max(Self::minimum_window(self.max_datagram_size))
733    }
734
735    /// Calculates the quantization budget
736    #[inline]
737    fn quantization_budget(&self, inflight: u64) -> u64 {
738        //= https://tools.ietf.org/id/draft-cardwell-iccrg-bbr-congestion-control-02#4.6.4.2
739        //# BBRQuantizationBudget(inflight)
740        //#   BBRUpdateOffloadBudget()
741        //#   inflight = max(inflight, BBR.offload_budget)
742        //#   inflight = max(inflight, BBRMinPipeCwnd)
743        //#   if (BBR.state == ProbeBW && BBR.cycle_idx == ProbeBW_UP)
744        //#     inflight += 2
745        //#   return inflight
746
747        //= https://tools.ietf.org/id/draft-cardwell-iccrg-bbr-congestion-control-02#4.5.4
748        //# BBRUpdateOffloadBudget():
749        //#   BBR.offload_budget = 3 * BBR.send_quantum
750        let offload_budget = 3 * self.pacer.send_quantum() as u64;
751
752        let mut inflight = inflight
753            .max(offload_budget)
754            .max(Self::minimum_window(self.max_datagram_size) as u64);
755
756        if self.state.is_probing_bw_up() {
757            inflight = inflight.saturating_add(2 * self.max_datagram_size as u64);
758        }
759
760        inflight
761    }
762
763    /// True if the amount of loss or ECN CE markings exceed the BBR thresholds
764    #[inline]
765    fn is_inflight_too_high(
766        rate_sample: RateSample,
767        max_datagram_size: u16,
768        loss_bursts: u8,
769        loss_burst_limit: u8,
770        app_settings: &ApplicationSettings,
771    ) -> bool {
772        if Self::is_loss_too_high(
773            rate_sample.lost_bytes,
774            rate_sample.bytes_in_flight,
775            loss_bursts,
776            loss_burst_limit,
777            app_settings,
778        ) {
779            return true;
780        }
781
782        if rate_sample.delivered_bytes > 0 {
783            let ecn_ce_ratio = ecn::ce_ratio(
784                rate_sample.ecn_ce_count,
785                rate_sample.delivered_bytes,
786                max_datagram_size,
787            );
788            return ecn::is_ce_too_high(ecn_ce_ratio);
789        }
790
791        false
792    }
793
794    /// True if the amount of `lost_bytes` exceeds the BBR loss threshold and the count of loss
795    /// bursts is greater than or equal to the loss burst limit
796    #[inline]
797    fn is_loss_too_high(
798        lost_bytes: u64,
799        bytes_inflight: u32,
800        loss_bursts: u8,
801        loss_burst_limit: u8,
802        app_settings: &ApplicationSettings,
803    ) -> bool {
804        //= https://tools.ietf.org/id/draft-cardwell-iccrg-bbr-congestion-control-02#4.5.6.2
805        //# IsInflightTooHigh()
806        //#   return (rs.lost > rs.tx_in_flight * BBRLossThresh)
807        loss_bursts >= loss_burst_limit
808            && lost_bytes > (Self::loss_thresh(app_settings) * bytes_inflight).to_integer() as u64
809    }
810
811    //= https://www.rfc-editor.org/rfc/rfc9002#section-7.2
812    //# Endpoints SHOULD use an initial congestion
813    //# window of ten times the maximum datagram size (max_datagram_size),
814    //# while limiting the window to the larger of 14,720 bytes or twice the
815    //# maximum datagram size.
816    #[inline]
817    fn initial_window(max_datagram_size: u16, app_settings: &ApplicationSettings) -> u32 {
818        const INITIAL_WINDOW_LIMIT: u32 = 14720;
819        let default = min(
820            10 * max_datagram_size as u32,
821            max(INITIAL_WINDOW_LIMIT, 2 * max_datagram_size as u32),
822        );
823        let initial_window = app_settings.initial_congestion_window.unwrap_or(default);
824
825        max(initial_window, Self::minimum_window(max_datagram_size))
826    }
827
828    /// The minimal cwnd value BBR targets
829    #[inline]
830    fn minimum_window(max_datagram_size: u16) -> u32 {
831        (MIN_PIPE_CWND_PACKETS * max_datagram_size) as u32
832    }
833
834    /// Updates the congestion window based on the latest model
835    #[inline]
836    fn set_cwnd(&mut self, newly_acked: usize) {
837        //= https://tools.ietf.org/id/draft-cardwell-iccrg-bbr-congestion-control-02#4.6.4.6
838        //# BBRSetCwnd():
839        //#   BBRUpdateMaxInflight()
840        //#   BBRModulateCwndForRecovery()
841        //#   if (!BBR.packet_conservation) {
842        //#     if (BBR.filled_pipe)
843        //#       cwnd = min(cwnd + rs.newly_acked, BBR.max_inflight)
844        //#     else if (cwnd < BBR.max_inflight || C.delivered < InitialCwnd)
845        //#       cwnd = cwnd + rs.newly_acked
846        //#     cwnd = max(cwnd, BBRMinPipeCwnd)
847        //#  }
848        //#  BBRBoundCwndForProbeRTT()
849        //#  BBRBoundCwndForModel()
850
851        //= https://tools.ietf.org/id/draft-cardwell-iccrg-bbr-congestion-control-02#4.6.4.6
852        //= type=exception
853        //= reason=https://github.com/aws/s2n-quic/issues/1511
854        //#   BBRModulateCwndForRecovery()
855
856        let max_inflight = self.max_inflight().try_into().unwrap_or(u32::MAX);
857        let initial_cwnd = Self::initial_window(self.max_datagram_size, &self.app_settings);
858        let mut cwnd = self.cwnd;
859
860        // Enable fast path if the cwnd has reached max_inflight
861        // Adapted from the Linux TCP BBRv2 implementation
862        // See https://github.com/google/bbr/blob/1a45fd4faf30229a3d3116de7bfe9d2f933d3562/net/ipv4/tcp_bbr2.c#L923
863        self.try_fast_path = false;
864
865        if self.full_pipe_estimator.filled_pipe() {
866            cwnd = cwnd.saturating_add(newly_acked as u32);
867            if cwnd >= max_inflight {
868                cwnd = max_inflight;
869                self.try_fast_path = true;
870            }
871        } else if cwnd < max_inflight
872            || self.bw_estimator.delivered_bytes() < 2 * initial_cwnd as u64
873        {
874            // cwnd has room to grow, or so little data has been delivered that max_inflight should not be used
875            // The Linux TCP BBRv2 implementation and Chromium BBRv2 implementation both use 2 * initial_cwnd here
876            // See https://github.com/google/bbr/blob/1ee29b79317a3028ed1fcd85cb46da009f45de00/net/ipv4/tcp_bbr2.c#L931
877            // and https://source.chromium.org/chromium/chromium/src/+/main:net/third_party/quiche/src/quiche/quic/core/congestion_control/bbr2_sender.cc;l=404;bpv=1;bpt=1
878            cwnd += newly_acked as u32;
879        } else {
880            self.try_fast_path = true;
881        }
882
883        //= https://tools.ietf.org/id/draft-cardwell-iccrg-bbr-congestion-control-02#4.6.4.5
884        //# BBRBoundCwndForProbeRTT():
885        //#   if (BBR.state == ProbeRTT)
886        //#     cwnd = min(cwnd, BBRProbeRTTCwnd())
887        if self.state.is_probing_rtt() {
888            cwnd = cwnd.min(self.probe_rtt_cwnd());
889        }
890
891        // Ensure the cwnd is at least the minimum window, and at most the bound defined by the model.
892        // This applies regardless of whether packet conservation is in place, as the pseudocode
893        // applies this clamping within BBRBoundCwndForModel(), which is called after all prior
894        // cwnd adjustments have been made.
895        self.cwnd = cwnd.clamp(
896            Self::minimum_window(self.max_datagram_size),
897            self.bound_cwnd_for_model(),
898        );
899    }
900
901    /// Returns the maximum congestion window bound by recent congestion
902    #[inline]
903    fn bound_cwnd_for_model(&self) -> u32 {
904        //= https://tools.ietf.org/id/draft-cardwell-iccrg-bbr-congestion-control-02#4.6.4.7
905        //# BBRBoundCwndForModel():
906        //#   cap = Infinity
907        //#   if (IsInAProbeBWState() and
908        //#       BBR.state != ProbeBW_CRUISE)
909        //#     cap = BBR.inflight_hi
910        //#   else if (BBR.state == ProbeRTT or
911        //#            BBR.state == ProbeBW_CRUISE)
912        //#     cap = BBRInflightWithHeadroom()
913        //#
914        //#   /* apply inflight_lo (possibly infinite): */
915        //#   cap = min(cap, BBR.inflight_lo)
916        //#   cap = max(cap, BBRMinPipeCwnd)
917        //#   cwnd = min(cwnd, cap)
918        let inflight_hi = self
919            .data_volume_model
920            .inflight_hi()
921            .try_into()
922            .unwrap_or(u32::MAX);
923        let inflight_lo = self
924            .data_volume_model
925            .inflight_lo()
926            .try_into()
927            .unwrap_or(u32::MAX);
928
929        let cap = if self.state.is_probing_bw() && !self.state.is_probing_bw_cruise() {
930            inflight_hi
931        } else if self.state.is_probing_rtt() || self.state.is_probing_bw_cruise() {
932            self.inflight_with_headroom()
933        } else {
934            u32::MAX
935        };
936
937        cap.min(inflight_lo)
938            .max(Self::minimum_window(self.max_datagram_size))
939    }
940
941    /// Saves the last-known good congestion window (the latest cwnd unmodulated by loss recovery or ProbeRTT)
942    #[inline]
943    fn save_cwnd(&mut self) {
944        //= https://tools.ietf.org/id/draft-cardwell-iccrg-bbr-congestion-control-02#4.6.4.4
945        //# BBRSaveCwnd()
946        //#   if (!InLossRecovery() and BBR.state != ProbeRTT)
947        //#     return cwnd
948        //#   else
949        //#     return max(BBR.prior_cwnd, cwnd)
950
951        // We don't save the cwnd when entering recovery, so we don't need to check the recovery state
952        debug_assert!(self.state.is_probing_rtt());
953
954        self.prior_cwnd = self.prior_cwnd.max(self.cwnd);
955    }
956
957    /// Restores the last-known good congestion window (the latest cwnd unmodulated by loss recovery or ProbeRTT)
958    #[inline]
959    fn restore_cwnd(&mut self) {
960        //= https://tools.ietf.org/id/draft-cardwell-iccrg-bbr-congestion-control-02#4.6.4.4
961        //# BBRRestoreCwnd()
962        //#   cwnd = max(cwnd, BBR.prior_cwnd)
963
964        debug_assert!(self.state.is_probing_rtt());
965
966        self.cwnd = self.cwnd.max(self.prior_cwnd);
967    }
968
969    #[inline]
970    fn handle_lost_packet<Pub: Publisher>(
971        &mut self,
972        lost_bytes: u32,
973        packet_info: <BbrCongestionController as CongestionController>::PacketInfo,
974        random_generator: &mut dyn random::Generator,
975        now: Timestamp,
976        publisher: &mut Pub,
977    ) {
978        //= https://tools.ietf.org/id/draft-cardwell-iccrg-bbr-congestion-control-02#4.5.6.2
979        //# if (!BBR.bw_probe_samples)
980        //#   return /* not a packet sent while probing bandwidth */
981        //# rs.tx_in_flight = packet.tx_in_flight /* inflight at transmit */
982        //# rs.lost = C.lost - packet.lost /* data lost since transmit */
983        //# rs.is_app_limited = packet.is_app_limited;
984        //# if (IsInflightTooHigh(rs))
985        //#   rs.tx_in_flight = BBRInflightHiFromLostPacket(rs, packet)
986        //#   BBRHandleInflightTooHigh(rs)
987
988        if !self.bw_probe_samples {
989            // not a packet sent while probing bandwidth
990            return;
991        }
992
993        let lost_since_transmit = (self.bw_estimator.lost_bytes() - packet_info.lost_bytes)
994            .try_into()
995            .unwrap_or(u32::MAX);
996
997        if Self::is_loss_too_high(
998            lost_since_transmit as u64,
999            packet_info.bytes_in_flight,
1000            self.congestion_state.loss_bursts_in_round(),
1001            PROBE_BW_FULL_LOSS_COUNT,
1002            &self.app_settings,
1003        ) {
1004            let inflight_hi_from_lost_packet = Self::inflight_hi_from_lost_packet(
1005                lost_bytes,
1006                lost_since_transmit,
1007                packet_info,
1008                &self.app_settings,
1009            );
1010            self.on_inflight_too_high(
1011                packet_info.is_app_limited,
1012                inflight_hi_from_lost_packet,
1013                random_generator,
1014                now,
1015                publisher,
1016            );
1017        }
1018    }
1019
1020    /// Returns the prefix of packet where losses exceeded `LOSS_THRESH`
1021    #[inline]
1022    fn inflight_hi_from_lost_packet(
1023        size: u32,
1024        lost_since_transmit: u32,
1025        packet_info: <BbrCongestionController as CongestionController>::PacketInfo,
1026        app_settings: &ApplicationSettings,
1027    ) -> u32 {
1028        let loss_thresh = Self::loss_thresh(app_settings);
1029        //= https://tools.ietf.org/id/draft-cardwell-iccrg-bbr-congestion-control-02#4.5.6.2
1030        //# BBRInflightHiFromLostPacket(rs, packet):
1031        //#   size = packet.size
1032        //#   /* What was in flight before this packet? */
1033        //#   inflight_prev = rs.tx_in_flight - size
1034        //#   /* What was lost before this packet? */
1035        //#   lost_prev = rs.lost - size
1036        //#   lost_prefix = (BBRLossThresh * inflight_prev - lost_prev) /
1037        //#                 (1 - BBRLossThresh)
1038        //#   /* At what inflight value did losses cross BBRLossThresh? */
1039        //#   inflight = inflight_prev + lost_prefix
1040        //#   return inflight
1041
1042        // The RFC passes a newly construct Rate Sample to BBRInflightHiFromLostPacket as
1043        // a means for holding tx_in_flight and lost_since_transmit. Instead, we pass
1044        // the required information directly.
1045
1046        // What was in flight before this packet?
1047        // Note: The TCP BBRv2 impl treats a negative inflight_prev as an error case
1048        // see https://github.com/aws/s2n-quic/issues/1456
1049        let inflight_prev = packet_info.bytes_in_flight.saturating_sub(size);
1050        // What was lost before this packet?
1051        let lost_prev = lost_since_transmit - size;
1052        // BBRLossThresh * inflight_prev - lost_prev
1053        let loss_budget = (loss_thresh * inflight_prev)
1054            .to_integer()
1055            .saturating_sub(lost_prev);
1056        // Multiply by the inverse of 1 - LOSS_THRESH instead of dividing
1057        let lost_prefix = ((Ratio::one() - loss_thresh).inv() * loss_budget).to_integer();
1058        // At what inflight value did losses cross BBRLossThresh?
1059        inflight_prev + lost_prefix
1060    }
1061
1062    #[inline]
1063    fn loss_thresh(app_settings: &ApplicationSettings) -> Ratio<u32> {
1064        app_settings.loss_threshold().unwrap_or(LOSS_THRESH)
1065    }
1066
1067    /// Handles when the connection resumes transmitting after an idle period
1068    #[inline]
1069    fn handle_restart_from_idle<Pub: Publisher>(&mut self, now: Timestamp, publisher: &mut Pub) {
1070        //= https://www.rfc-editor.org/rfc/rfc9002#section-7.8
1071        //# A sender MAY implement alternative mechanisms to update its congestion window
1072        //# after periods of underutilization, such as those proposed for TCP in [RFC7661].
1073
1074        //= https://tools.ietf.org/id/draft-cardwell-iccrg-bbr-congestion-control-02#4.4.3
1075        //# BBRHandleRestartFromIdle():
1076        //#   if (packets_in_flight == 0 and C.app_limited)
1077        //#     BBR.idle_restart = true
1078        //#        BBR.extra_acked_interval_start = Now()
1079        //#     if (IsInAProbeBWState())
1080        //#       BBRSetPacingRateWithGain(1)
1081
1082        if self.bytes_in_flight == 0 && self.bw_estimator.is_app_limited() {
1083            self.idle_restart = true;
1084            self.data_volume_model.set_extra_acked_interval_start(now);
1085            if self.state.is_probing_bw() {
1086                self.pacer.set_pacing_rate(
1087                    self.data_rate_model.bw(),
1088                    Ratio::one(),
1089                    self.full_pipe_estimator.filled_pipe(),
1090                    publisher,
1091                );
1092            }
1093        }
1094
1095        // As an optimization, we can check if the ProbeRtt may be exited here, see #1412 for details.
1096        // Without this optimization, ProbeRtt will be exited on the next received Ack.
1097
1098        //= https://tools.ietf.org/id/draft-cardwell-iccrg-bbr-congestion-control-02#4.4.3
1099        //= type=TODO
1100        //= tracking-issue=1412
1101        //#   else if (BBR.state == ProbeRTT)
1102        //#     BBRCheckProbeRTTDone()
1103
1104        //= https://tools.ietf.org/id/draft-cardwell-iccrg-bbr-congestion-control-02#4.4.2
1105        //= type=TODO
1106        //= tracking-issue=1412
1107        //# As an optimization, when restarting from idle BBR checks to see if the connection is in
1108        //# ProbeRTT and has met the exit conditions for ProbeRTT. If a connection goes idle during
1109        //# ProbeRTT then often it will have met those exit conditions by the time it restarts, so
1110        //# that the connection can restore the cwnd to its full value before it starts transmitting
1111        //# a new flight of data.
1112    }
1113
1114    /// Determines if the BBR model does not need to be updated
1115    ///
1116    /// Based on `bbr2_fast_path` in the Linux TCP BBRv2.
1117    /// See https://github.com/google/bbr/blob/1a45fd4faf30229a3d3116de7bfe9d2f933d3562/net/ipv4/tcp_bbr2.c#L2208
1118    #[inline]
1119    fn model_update_required(&self) -> bool {
1120        let rate_sample = self.bw_estimator.rate_sample();
1121
1122        // We can skip updating the model when app limited and there is no congestion,
1123        // and the bandwidth sample is less than the estimated maximum bandwidth
1124        !self.try_fast_path
1125            || !rate_sample.is_app_limited
1126            || rate_sample.delivery_rate() >= self.data_rate_model.max_bw()
1127            || self.congestion_state.loss_in_round()
1128            || self.congestion_state.ecn_in_round()
1129    }
1130
1131    /// Determines if the BBR control parameters do not need to be updated
1132    #[inline]
1133    fn control_update_required(&self, model_updated: bool, prev_min_rtt: Option<Duration>) -> bool {
1134        // We can skip updating the control parameters if we had skipped updating the model
1135        // and the BBR state and min rtt did not change. `try_fast_path` is set to false
1136        // when the BBR state is changed.
1137        !self.try_fast_path || model_updated || prev_min_rtt != self.data_volume_model.min_rtt()
1138    }
1139}
1140
1141#[non_exhaustive]
1142#[derive(Debug, Default)]
1143pub struct Endpoint {
1144    app_settings: ApplicationSettings,
1145}
1146
1147impl congestion_controller::Endpoint for Endpoint {
1148    type CongestionController = BbrCongestionController;
1149
1150    fn new_congestion_controller(
1151        &mut self,
1152        path_info: congestion_controller::PathInfo,
1153    ) -> Self::CongestionController {
1154        BbrCongestionController::new(path_info.max_datagram_size, self.app_settings)
1155    }
1156}
1157
1158pub mod builder {
1159    use super::{ApplicationSettings, Endpoint};
1160
1161    /// Build the congestion controller endpoint with application provided overrides
1162    #[derive(Debug, Default)]
1163    pub struct Builder {
1164        initial_congestion_window: Option<u32>,
1165        probe_bw_cwnd_gain: Option<u32>,
1166        probe_bw_up_pacing_gain: Option<u32>,
1167        loss_threshold: Option<u32>,
1168    }
1169
1170    impl Builder {
1171        /// Set the initial congestion window in bytes.
1172        pub fn with_initial_congestion_window(mut self, initial_congestion_window: u32) -> Self {
1173            self.initial_congestion_window = Some(initial_congestion_window);
1174            self
1175        }
1176
1177        #[cfg(feature = "unstable-congestion-controller")]
1178        /// The dynamic gain factor used to scale the estimated BDP to produce a
1179        /// congestion window (default: 2).
1180        ///
1181        /// The gain value is calculated as the ratio: `probe_bw_cwnd_gain / 100`
1182        pub fn with_probe_bw_cwnd_gain(mut self, probe_bw_cwnd_gain: u32) -> Self {
1183            self.probe_bw_cwnd_gain = Some(probe_bw_cwnd_gain);
1184            self
1185        }
1186
1187        #[cfg(feature = "unstable-congestion-controller")]
1188        /// Set the gain factor used during the ProbeBW_UP phase of the BBR
1189        /// algorithm (default: 5/4).
1190        ///
1191        /// The gain value is calculated as the ratio: `probe_bw_up_pacing_gain / 100`
1192        pub fn with_probe_bw_up_pacing_gain(mut self, probe_bw_up_pacing_gain: u32) -> Self {
1193            self.probe_bw_up_pacing_gain = Some(probe_bw_up_pacing_gain);
1194            self
1195        }
1196
1197        #[cfg(feature = "unstable-congestion-controller")]
1198        /// The maximum tolerated per-round-trip packet loss rate when probing
1199        /// for bandwidth (default: 1/50).
1200        ///
1201        /// The threshold value is calculated as the ratio: `loss_threshold / 100`
1202        pub fn with_loss_threshold(mut self, loss_threshold: u32) -> Self {
1203            self.loss_threshold = Some(loss_threshold);
1204            self
1205        }
1206
1207        pub fn build(self) -> Endpoint {
1208            let app_settings = ApplicationSettings {
1209                initial_congestion_window: self.initial_congestion_window,
1210                probe_bw_cwnd_gain: self.probe_bw_cwnd_gain,
1211                probe_bw_up_pacing_gain: self.probe_bw_up_pacing_gain,
1212                loss_threshold: self.loss_threshold,
1213            };
1214            Endpoint { app_settings }
1215        }
1216    }
1217}
1218
1219#[cfg(test)]
1220mod tests;