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;