quiche/recovery/congestion/bbr2/
per_ack.rs

1// Copyright (C) 2022, Cloudflare, Inc.
2// All rights reserved.
3//
4// Redistribution and use in source and binary forms, with or without
5// modification, are permitted provided that the following conditions are
6// met:
7//
8//     * Redistributions of source code must retain the above copyright notice,
9//       this list of conditions and the following disclaimer.
10//
11//     * Redistributions in binary form must reproduce the above copyright
12//       notice, this list of conditions and the following disclaimer in the
13//       documentation and/or other materials provided with the distribution.
14//
15// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
16// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
17// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
19// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
22// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
23// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
24// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
27use std::cmp;
28
29use super::*;
30use crate::rand;
31use crate::recovery::MINIMUM_WINDOW_PACKETS;
32
33/// 1.2Mbps in bytes/sec
34const PACING_RATE_1_2MBPS: u64 = 1200 * 1000 / 8;
35
36/// The minimal cwnd value BBR2 tries to target, in bytes
37#[inline]
38fn bbr2_min_pipe_cwnd(r: &mut Congestion) -> usize {
39    MIN_PIPE_CWND_PKTS * r.max_datagram_size
40}
41
42// BBR2 Functions when ACK is received.
43//
44pub fn bbr2_update_model_and_state(
45    r: &mut Congestion, packet: &Acked, in_flight: usize, now: Instant,
46) {
47    per_loss::bbr2_update_latest_delivery_signals(r);
48    per_loss::bbr2_update_congestion_signals(r, packet);
49    bbr2_update_ack_aggregation(r, packet, now);
50    bbr2_check_startup_done(r);
51    bbr2_check_drain(r, in_flight, now);
52    bbr2_update_probe_bw_cycle_phase(r, in_flight, now);
53    bbr2_update_min_rtt(r, now);
54    bbr2_check_probe_rtt(r, in_flight, now);
55    per_loss::bbr2_advance_latest_delivery_signals(r);
56    per_loss::bbr2_bound_bw_for_model(r);
57}
58
59pub fn bbr2_update_control_parameters(
60    r: &mut Congestion, in_flight: usize, now: Instant,
61) {
62    pacing::bbr2_set_pacing_rate(r);
63    bbr2_set_send_quantum(r);
64
65    // Set outgoing packet pacing rate
66    // It is called here because send_quantum may be updated too.
67    r.set_pacing_rate(r.bbr2_state.pacing_rate, now);
68
69    bbr2_set_cwnd(r, in_flight);
70}
71
72// BBR2 Functions while processing ACKs.
73//
74
75// 4.3.1.1.  Startup Dynamics
76fn bbr2_check_startup_done(r: &mut Congestion) {
77    bbr2_check_startup_full_bandwidth(r);
78    bbr2_check_startup_high_loss(r);
79
80    if r.bbr2_state.state == BBR2StateMachine::Startup && r.bbr2_state.filled_pipe
81    {
82        bbr2_enter_drain(r);
83    }
84}
85
86// 4.3.1.2.  Exiting Startup Based on Bandwidth Plateau
87fn bbr2_check_startup_full_bandwidth(r: &mut Congestion) {
88    if r.bbr2_state.filled_pipe ||
89        !r.bbr2_state.round_start ||
90        r.delivery_rate.sample_is_app_limited()
91    {
92        // No need to check for a full pipe now.
93        return;
94    }
95
96    // Still growing?
97    if r.bbr2_state.max_bw >=
98        (r.bbr2_state.full_bw as f64 * MAX_BW_GROWTH_THRESHOLD) as u64
99    {
100        // Record new baseline level
101        r.bbr2_state.full_bw = r.bbr2_state.max_bw;
102        r.bbr2_state.full_bw_count = 0;
103        return;
104    }
105
106    // Another round w/o much growth
107    r.bbr2_state.full_bw_count += 1;
108
109    if r.bbr2_state.full_bw_count >= MAX_BW_COUNT {
110        r.bbr2_state.filled_pipe = true;
111    }
112}
113
114// 4.3.1.3.  Exiting Startup Based on Packet Loss
115fn bbr2_check_startup_high_loss(r: &mut Congestion) {
116    // TODO: this is not implemented (not in the draft)
117    if r.bbr2_state.loss_round_start &&
118        r.bbr2_state.in_recovery &&
119        r.bbr2_state.loss_events_in_round >= FULL_LOSS_COUNT as usize &&
120        per_loss::bbr2_is_inflight_too_high(r)
121    {
122        bbr2_handle_queue_too_high_in_startup(r);
123    }
124    if r.bbr2_state.loss_round_start {
125        r.bbr2_state.loss_events_in_round = 0
126    }
127}
128
129fn bbr2_handle_queue_too_high_in_startup(r: &mut Congestion) {
130    r.bbr2_state.filled_pipe = true;
131    r.bbr2_state.inflight_hi = bbr2_inflight(r, r.bbr2_state.max_bw, 1.0);
132}
133
134// 4.3.2.  Drain
135fn bbr2_enter_drain(r: &mut Congestion) {
136    let bbr = &mut r.bbr2_state;
137
138    bbr.state = BBR2StateMachine::Drain;
139
140    // pace slowly
141    bbr.pacing_gain = PACING_GAIN / STARTUP_CWND_GAIN;
142
143    // maintain cwnd
144    bbr.cwnd_gain = STARTUP_CWND_GAIN;
145}
146
147fn bbr2_check_drain(r: &mut Congestion, in_flight: usize, now: Instant) {
148    if r.bbr2_state.state == BBR2StateMachine::Drain &&
149        in_flight <= bbr2_inflight(r, r.bbr2_state.max_bw, 1.0)
150    {
151        // BBR estimates the queue was drained
152        bbr2_enter_probe_bw(r, now);
153    }
154}
155
156// 4.3.3.  ProbeBW
157// 4.3.3.5.3.  Design Considerations for Choosing Constant Parameters
158fn bbr2_check_time_to_probe_bw(r: &mut Congestion, now: Instant) -> bool {
159    // Is it time to transition from DOWN or CRUISE to REFILL?
160    if bbr2_has_elapsed_in_phase(r, r.bbr2_state.bw_probe_wait, now) ||
161        bbr2_is_reno_coexistence_probe_time(r)
162    {
163        bbr2_start_probe_bw_refill(r);
164
165        return true;
166    }
167
168    false
169}
170
171// Randomized decision about how long to wait until
172// probing for bandwidth, using round count and wall clock.
173fn bbr2_pick_probe_wait(r: &mut Congestion) {
174    let bbr = &mut r.bbr2_state;
175
176    // Decide random round-trip bound for wait
177    bbr.rounds_since_probe = rand::rand_u8() as usize % 2;
178
179    // Decide the random wall clock bound for wait
180    bbr.bw_probe_wait = Duration::from_secs_f64(
181        2.0 + rand::rand_u64_uniform(1000000) as f64 / 1000000.0,
182    );
183}
184
185fn bbr2_is_reno_coexistence_probe_time(r: &mut Congestion) -> bool {
186    let reno_rounds = bbr2_target_inflight(r);
187    let rounds = reno_rounds.min(63);
188
189    r.bbr2_state.rounds_since_probe >= rounds
190}
191
192// How much data do we want in flight?
193// Our estimated BDP, unless congestion cut cwnd.
194pub fn bbr2_target_inflight(r: &mut Congestion) -> usize {
195    r.bbr2_state.bdp.min(r.congestion_window)
196}
197
198// 4.3.3.6.  ProbeBW Algorithm Details
199fn bbr2_enter_probe_bw(r: &mut Congestion, now: Instant) {
200    bbr2_start_probe_bw_down(r, now);
201}
202
203pub fn bbr2_start_probe_bw_down(r: &mut Congestion, now: Instant) {
204    per_loss::bbr2_reset_congestion_signals(r);
205
206    // not growing inflight_hi
207    r.bbr2_state.probe_up_cnt = usize::MAX;
208
209    bbr2_pick_probe_wait(r);
210
211    // start wall clock
212    r.bbr2_state.cycle_stamp = now;
213    r.bbr2_state.ack_phase = BBR2AckPhase::ProbeStopping;
214
215    bbr2_start_round(r);
216
217    r.bbr2_state.state = BBR2StateMachine::ProbeBWDOWN;
218    r.bbr2_state.pacing_gain = PROBE_DOWN_PACING_GAIN;
219    r.bbr2_state.cwnd_gain = CWND_GAIN
220}
221
222fn bbr2_start_probe_bw_cruise(r: &mut Congestion) {
223    let bbr = &mut r.bbr2_state;
224
225    bbr.state = BBR2StateMachine::ProbeBWCRUISE;
226    bbr.pacing_gain = PACING_GAIN;
227    bbr.cwnd_gain = CWND_GAIN;
228}
229
230fn bbr2_start_probe_bw_refill(r: &mut Congestion) {
231    per_loss::bbr2_reset_lower_bounds(r);
232
233    r.bbr2_state.bw_probe_up_rounds = 0;
234    r.bbr2_state.bw_probe_up_acks = 0;
235    r.bbr2_state.ack_phase = BBR2AckPhase::Refilling;
236
237    bbr2_start_round(r);
238
239    r.bbr2_state.state = BBR2StateMachine::ProbeBWREFILL;
240    r.bbr2_state.pacing_gain = PACING_GAIN;
241    r.bbr2_state.cwnd_gain = CWND_GAIN;
242}
243
244fn bbr2_start_probe_bw_up(r: &mut Congestion, now: Instant) {
245    r.bbr2_state.ack_phase = BBR2AckPhase::ProbeStarting;
246
247    bbr2_start_round(r);
248
249    // Start wall clock.
250    r.bbr2_state.cycle_stamp = now;
251    r.bbr2_state.state = BBR2StateMachine::ProbeBWUP;
252    r.bbr2_state.pacing_gain = PROBE_UP_PACING_GAIN;
253    r.bbr2_state.cwnd_gain = CWND_GAIN;
254
255    bbr2_raise_inflight_hi_slope(r);
256}
257
258// The core state machine logic for ProbeBW
259fn bbr2_update_probe_bw_cycle_phase(
260    r: &mut Congestion, in_flight: usize, now: Instant,
261) {
262    if !r.bbr2_state.filled_pipe {
263        // only handling steady-state behavior here
264        return;
265    }
266
267    bbr2_adapt_upper_bounds(r, now);
268
269    if !bbr2_is_in_a_probe_bw_state(r) {
270        // only handling ProbeBW states here
271        return;
272    }
273
274    match r.bbr2_state.state {
275        BBR2StateMachine::ProbeBWDOWN => {
276            if bbr2_check_time_to_probe_bw(r, now) {
277                // Already decided state transition.
278                return;
279            }
280
281            if bbr2_check_time_to_cruise(r, in_flight) {
282                bbr2_start_probe_bw_cruise(r);
283            }
284        },
285
286        BBR2StateMachine::ProbeBWCRUISE => {
287            bbr2_check_time_to_probe_bw(r, now);
288        },
289
290        BBR2StateMachine::ProbeBWREFILL => {
291            // After one round of REFILL, start UP.
292            if r.bbr2_state.round_start {
293                r.bbr2_state.bw_probe_samples = true;
294
295                bbr2_start_probe_bw_up(r, now);
296            }
297        },
298
299        BBR2StateMachine::ProbeBWUP => {
300            if bbr2_has_elapsed_in_phase(r, r.bbr2_state.min_rtt, now) &&
301                in_flight > bbr2_inflight(r, r.bbr2_state.max_bw, 1.25)
302            {
303                bbr2_start_probe_bw_down(r, now);
304            }
305        },
306
307        _ => (),
308    }
309}
310
311pub fn bbr2_is_in_a_probe_bw_state(r: &mut Congestion) -> bool {
312    let state = r.bbr2_state.state;
313
314    state == BBR2StateMachine::ProbeBWDOWN ||
315        state == BBR2StateMachine::ProbeBWCRUISE ||
316        state == BBR2StateMachine::ProbeBWREFILL ||
317        state == BBR2StateMachine::ProbeBWUP
318}
319
320fn bbr2_check_time_to_cruise(r: &mut Congestion, in_flight: usize) -> bool {
321    if in_flight > bbr2_inflight_with_headroom(r) {
322        // Not enough headroom.
323        return false;
324    }
325
326    if in_flight <= bbr2_inflight(r, r.bbr2_state.max_bw, 1.0) {
327        // inflight <= estimated BDP
328        return true;
329    }
330
331    false
332}
333
334fn bbr2_has_elapsed_in_phase(
335    r: &mut Congestion, interval: Duration, now: Instant,
336) -> bool {
337    now > r.bbr2_state.cycle_stamp + interval
338}
339
340// Return a volume of data that tries to leave free
341// headroom in the bottleneck buffer or link for
342// other flows, for fairness convergence and lower
343// RTTs and loss
344fn bbr2_inflight_with_headroom(r: &mut Congestion) -> usize {
345    let bbr = &mut r.bbr2_state;
346
347    if bbr.inflight_hi == usize::MAX {
348        return usize::MAX;
349    }
350
351    let headroom = ((HEADROOM * bbr.inflight_hi as f64) as usize).max(1);
352
353    bbr.inflight_hi
354        .saturating_sub(headroom)
355        .max(bbr2_min_pipe_cwnd(r))
356}
357
358// Raise inflight_hi slope if appropriate.
359fn bbr2_raise_inflight_hi_slope(r: &mut Congestion) {
360    let bbr = &mut r.bbr2_state;
361
362    let growth_this_round = (1 << bbr.bw_probe_up_rounds) * r.max_datagram_size;
363
364    bbr.bw_probe_up_rounds = (bbr.bw_probe_up_rounds + 1).min(30);
365    bbr.probe_up_cnt = (r.congestion_window / growth_this_round).max(1);
366}
367
368// Increase inflight_hi if appropriate.
369fn bbr2_probe_inflight_hi_upward(r: &mut Congestion) {
370    if r.app_limited || r.congestion_window < r.bbr2_state.inflight_hi {
371        // Not fully using inflight_hi, so don't grow it.
372        return;
373    }
374
375    let bbr = &mut r.bbr2_state;
376
377    // bw_probe_up_acks is a packet count.
378    bbr.bw_probe_up_acks += 1;
379
380    if bbr.bw_probe_up_acks >= bbr.probe_up_cnt {
381        let delta = bbr.bw_probe_up_acks / bbr.probe_up_cnt;
382
383        bbr.bw_probe_up_acks -= delta * bbr.probe_up_cnt;
384
385        bbr.inflight_hi += delta * r.max_datagram_size;
386    }
387
388    if bbr.round_start {
389        bbr2_raise_inflight_hi_slope(r);
390    }
391}
392
393// Track ACK state and update bbr.max_bw window and
394// bbr.inflight_hi and bbr.bw_hi.
395fn bbr2_adapt_upper_bounds(r: &mut Congestion, now: Instant) {
396    if r.bbr2_state.ack_phase == BBR2AckPhase::ProbeStarting &&
397        r.bbr2_state.round_start
398    {
399        // Starting to get bw probing samples.
400        r.bbr2_state.ack_phase = BBR2AckPhase::ProbeFeedback;
401    }
402
403    if r.bbr2_state.ack_phase == BBR2AckPhase::ProbeStopping &&
404        r.bbr2_state.round_start
405    {
406        r.bbr2_state.bw_probe_samples = false;
407        r.bbr2_state.ack_phase = BBR2AckPhase::Init;
408
409        // End of samples from bw probing phase.
410        if bbr2_is_in_a_probe_bw_state(r) &&
411            !r.delivery_rate.sample_is_app_limited()
412        {
413            bbr2_advance_max_bw_filter(r);
414        }
415    }
416
417    if !per_loss::bbr2_check_inflight_too_high(r, now) {
418        // Loss rate is safe. Adjust upper bounds upward.
419        if r.bbr2_state.inflight_hi == usize::MAX ||
420            r.bbr2_state.bw_hi == u64::MAX
421        {
422            // No upper bounds to raise.
423            return;
424        }
425
426        if r.bbr2_state.tx_in_flight > r.bbr2_state.inflight_hi {
427            r.bbr2_state.inflight_hi = r.bbr2_state.tx_in_flight;
428        }
429
430        // TODO: what's rs.bw???
431        let delivery_rate = r.delivery_rate().to_bytes_per_second();
432        if delivery_rate > r.bbr2_state.bw_hi {
433            r.bbr2_state.bw_hi = delivery_rate;
434        }
435
436        if r.bbr2_state.state == BBR2StateMachine::ProbeBWUP {
437            bbr2_probe_inflight_hi_upward(r);
438        }
439    }
440}
441
442// 4.3.4. ProbeRTT
443// 4.3.4.4.  ProbeRTT Logic
444fn bbr2_update_min_rtt(r: &mut Congestion, now: Instant) {
445    let bbr = &mut r.bbr2_state;
446
447    bbr.probe_rtt_expired = now > bbr.probe_rtt_min_stamp + PROBE_RTT_INTERVAL;
448
449    let rs_rtt = r.delivery_rate.sample_rtt();
450
451    if !rs_rtt.is_zero() &&
452        (rs_rtt < bbr.probe_rtt_min_delay || bbr.probe_rtt_expired)
453    {
454        bbr.probe_rtt_min_delay = rs_rtt;
455        bbr.probe_rtt_min_stamp = now;
456    }
457
458    let min_rtt_expired =
459        now > bbr.min_rtt_stamp + rs_rtt.saturating_mul(MIN_RTT_FILTER_LEN);
460
461    // To do: Figure out Probe RTT logic
462    // if bbr.probe_rtt_min_delay < bbr.min_rtt ||  bbr.min_rtt == INITIAL_RTT ||
463    // min_rtt_expired {
464    if bbr.min_rtt == rtt::INITIAL_RTT || min_rtt_expired {
465        // bbr.min_rtt = bbr.probe_rtt_min_delay;
466        // bbr.min_rtt_stamp = bbr.probe_rtt_min_stamp;
467        bbr.min_rtt = rs_rtt;
468        bbr.min_rtt_stamp = now;
469    }
470}
471
472fn bbr2_check_probe_rtt(r: &mut Congestion, in_flight: usize, now: Instant) {
473    if r.bbr2_state.state != BBR2StateMachine::ProbeRTT &&
474        r.bbr2_state.probe_rtt_expired &&
475        !r.bbr2_state.idle_restart
476    {
477        bbr2_enter_probe_rtt(r);
478
479        r.bbr2_state.prior_cwnd = per_ack::bbr2_save_cwnd(r);
480        r.bbr2_state.probe_rtt_done_stamp = None;
481        r.bbr2_state.ack_phase = BBR2AckPhase::ProbeStopping;
482
483        bbr2_start_round(r);
484    }
485
486    if r.bbr2_state.state == BBR2StateMachine::ProbeRTT {
487        bbr2_handle_probe_rtt(r, in_flight, now);
488    }
489
490    if r.delivery_rate.sample_delivered() > 0 {
491        r.bbr2_state.idle_restart = false;
492    }
493}
494
495fn bbr2_enter_probe_rtt(r: &mut Congestion) {
496    let bbr = &mut r.bbr2_state;
497
498    bbr.state = BBR2StateMachine::ProbeRTT;
499    bbr.pacing_gain = PACING_GAIN;
500    bbr.cwnd_gain = PROBE_RTT_CWND_GAIN;
501}
502
503fn bbr2_handle_probe_rtt(r: &mut Congestion, in_flight: usize, now: Instant) {
504    // Ignore low rate samples during ProbeRTT.
505    r.delivery_rate.update_app_limited(true);
506
507    if r.bbr2_state.probe_rtt_done_stamp.is_some() {
508        if r.bbr2_state.round_start {
509            r.bbr2_state.probe_rtt_round_done = true;
510        }
511
512        if r.bbr2_state.probe_rtt_round_done {
513            bbr2_check_probe_rtt_done(r, now);
514        }
515    } else if in_flight <= bbr2_probe_rtt_cwnd(r) {
516        // Wait for at least ProbeRTTDuration to elapse.
517        r.bbr2_state.probe_rtt_done_stamp = Some(now + PROBE_RTT_DURATION);
518
519        // Wait for at lease one round to elapse.
520        r.bbr2_state.probe_rtt_round_done = false;
521
522        bbr2_start_round(r);
523    }
524}
525
526pub fn bbr2_check_probe_rtt_done(r: &mut Congestion, now: Instant) {
527    let bbr = &mut r.bbr2_state;
528
529    if let Some(probe_rtt_done_stamp) = bbr.probe_rtt_done_stamp {
530        if now > probe_rtt_done_stamp {
531            // Schedule next ProbeRTT.
532            bbr.probe_rtt_min_stamp = now;
533
534            bbr2_restore_cwnd(r);
535            bbr2_exit_probe_rtt(r, now);
536        }
537    }
538}
539
540// 4.3.4.5.  Exiting ProbeRTT
541fn bbr2_exit_probe_rtt(r: &mut Congestion, now: Instant) {
542    per_loss::bbr2_reset_lower_bounds(r);
543
544    if r.bbr2_state.filled_pipe {
545        bbr2_start_probe_bw_down(r, now);
546        bbr2_start_probe_bw_cruise(r);
547    } else {
548        init::bbr2_enter_startup(r);
549    }
550}
551
552// 4.5.1.  BBR.round_count: Tracking Packet-Timed Round Trips
553fn bbr2_update_round(r: &mut Congestion, packet: &Acked) {
554    if packet.delivered >= r.bbr2_state.next_round_delivered {
555        bbr2_start_round(r);
556
557        r.bbr2_state.round_count += 1;
558        r.bbr2_state.rounds_since_probe += 1;
559        r.bbr2_state.round_start = true;
560    } else {
561        r.bbr2_state.round_start = false;
562    }
563}
564
565fn bbr2_start_round(r: &mut Congestion) {
566    r.bbr2_state.next_round_delivered = r.delivery_rate.delivered();
567}
568
569// 4.5.2.4.  Updating the BBR.max_bw Max Filter
570pub fn bbr2_update_max_bw(r: &mut Congestion, packet: &Acked) {
571    bbr2_update_round(r, packet);
572
573    if r.delivery_rate().to_bytes_per_second() >= r.bbr2_state.max_bw ||
574        !r.delivery_rate.sample_is_app_limited()
575    {
576        let max_bw_filter_len = r
577            .delivery_rate
578            .sample_rtt()
579            .saturating_mul(MIN_RTT_FILTER_LEN);
580
581        r.bbr2_state.max_bw = r.bbr2_state.max_bw_filter.running_max(
582            max_bw_filter_len,
583            r.bbr2_state.start_time +
584                Duration::from_secs(r.bbr2_state.cycle_count),
585            r.delivery_rate().to_bytes_per_second(),
586        );
587    }
588}
589
590// 4.5.2.5.  Tracking Time for the BBR.max_bw Max Filter
591fn bbr2_advance_max_bw_filter(r: &mut Congestion) {
592    r.bbr2_state.cycle_count += 1;
593}
594
595// 4.5.4.  BBR.offload_budget
596fn bbr2_update_offload_budget(r: &mut Congestion) {
597    r.bbr2_state.offload_budget = 3 * r.send_quantum;
598}
599
600// 4.5.5.  BBR.extra_acked
601fn bbr2_update_ack_aggregation(r: &mut Congestion, packet: &Acked, now: Instant) {
602    let bbr = &mut r.bbr2_state;
603
604    // Find excess ACKed beyond expected amount over this interval.
605    let interval = now - bbr.extra_acked_interval_start;
606    let mut expected_delivered =
607        (bbr.bw as f64 * interval.as_secs_f64()) as usize;
608
609    // Reset interval if ACK rate is below expected rate.
610    if bbr.extra_acked_delivered <= expected_delivered {
611        bbr.extra_acked_delivered = 0;
612        bbr.extra_acked_interval_start = now;
613        expected_delivered = 0;
614    }
615
616    bbr.extra_acked_delivered += packet.size;
617
618    let extra = bbr.extra_acked_delivered.saturating_sub(expected_delivered);
619    let extra = extra.min(r.congestion_window);
620
621    let extra_acked_filter_len = r
622        .delivery_rate
623        .sample_rtt()
624        .saturating_mul(MIN_RTT_FILTER_LEN);
625
626    bbr.extra_acked = bbr.extra_acked_filter.running_max(
627        extra_acked_filter_len,
628        bbr.start_time + Duration::from_secs(bbr.round_count),
629        extra,
630    );
631}
632
633// 4.6.3.  Send Quantum: BBR.send_quantum
634fn bbr2_set_send_quantum(r: &mut Congestion) {
635    let bbr = &mut r.bbr2_state;
636
637    let rate = bbr.pacing_rate;
638    let floor = if rate < PACING_RATE_1_2MBPS {
639        r.max_datagram_size
640    } else {
641        2 * r.max_datagram_size
642    };
643
644    r.send_quantum = cmp::min((rate / 1000_u64) as usize, 64 * 1024); // Assumes send buffer is limited to 64KB
645    r.send_quantum = r.send_quantum.max(floor);
646}
647
648// 4.6.4.1.  Initial cwnd
649// 4.6.4.2.  Computing BBR.max_inflight
650fn bbr2_bdp_multiple(r: &mut Congestion, bw: u64, gain: f64) -> usize {
651    let bbr = &mut r.bbr2_state;
652
653    if bbr.min_rtt == Duration::MAX {
654        // No valid RTT samples yet.
655        return r.max_datagram_size * r.initial_congestion_window_packets;
656    }
657
658    bbr.bdp = (bw as f64 * bbr.min_rtt.as_secs_f64()) as usize;
659
660    (gain * bbr.bdp as f64) as usize
661}
662
663fn bbr2_quantization_budget(r: &mut Congestion, inflight: usize) -> usize {
664    bbr2_update_offload_budget(r);
665
666    let inflight = inflight.max(r.bbr2_state.offload_budget);
667    let inflight = inflight.max(bbr2_min_pipe_cwnd(r));
668
669    // TODO: cycle_idx is unused
670    if r.bbr2_state.state == BBR2StateMachine::ProbeBWUP {
671        return inflight + 2 * r.max_datagram_size;
672    }
673
674    inflight
675}
676
677fn bbr2_inflight(r: &mut Congestion, bw: u64, gain: f64) -> usize {
678    let inflight = bbr2_bdp_multiple(r, bw, gain);
679
680    bbr2_quantization_budget(r, inflight)
681}
682
683fn bbr2_update_max_inflight(r: &mut Congestion) {
684    // TODO: not implemented (not in the draft)
685    // bbr2_update_aggregation_budget(r);
686
687    let inflight =
688        bbr2_bdp_multiple(r, r.bbr2_state.max_bw, r.bbr2_state.cwnd_gain);
689    let inflight = inflight + r.bbr2_state.extra_acked;
690
691    r.bbr2_state.max_inflight = bbr2_quantization_budget(r, inflight);
692}
693
694// 4.6.4.4.  Modulating cwnd in Loss Recovery
695pub fn bbr2_save_cwnd(r: &mut Congestion) -> usize {
696    if !r.bbr2_state.in_recovery &&
697        r.bbr2_state.state != BBR2StateMachine::ProbeRTT
698    {
699        r.congestion_window
700    } else {
701        r.congestion_window.max(r.bbr2_state.prior_cwnd)
702    }
703}
704
705pub fn bbr2_restore_cwnd(r: &mut Congestion) {
706    r.congestion_window = r.congestion_window.max(r.bbr2_state.prior_cwnd);
707}
708
709fn bbr2_modulate_cwnd_for_recovery(r: &mut Congestion, in_flight: usize) {
710    let acked_bytes = r.bbr2_state.newly_acked_bytes;
711    let lost_bytes = r.bbr2_state.newly_lost_bytes;
712
713    if lost_bytes > 0 {
714        // QUIC mininum cwnd is 2 x MSS.
715        r.congestion_window = r
716            .congestion_window
717            .saturating_sub(lost_bytes)
718            .max(r.max_datagram_size * MINIMUM_WINDOW_PACKETS);
719    }
720
721    if r.bbr2_state.packet_conservation {
722        r.congestion_window = r.congestion_window.max(in_flight + acked_bytes);
723    }
724}
725
726// 4.6.4.5.  Modulating cwnd in ProbeRTT
727fn bbr2_probe_rtt_cwnd(r: &mut Congestion) -> usize {
728    let probe_rtt_cwnd =
729        bbr2_bdp_multiple(r, r.bbr2_state.bw, PROBE_RTT_CWND_GAIN);
730
731    probe_rtt_cwnd.max(bbr2_min_pipe_cwnd(r))
732}
733
734fn bbr2_bound_cwnd_for_probe_rtt(r: &mut Congestion) {
735    if r.bbr2_state.state == BBR2StateMachine::ProbeRTT {
736        r.congestion_window = r.congestion_window.min(bbr2_probe_rtt_cwnd(r));
737    }
738}
739
740// 4.6.4.6.  Core cwnd Adjustment Mechanism
741fn bbr2_set_cwnd(r: &mut Congestion, in_flight: usize) {
742    let acked_bytes = r.bbr2_state.newly_acked_bytes;
743
744    bbr2_update_max_inflight(r);
745    bbr2_modulate_cwnd_for_recovery(r, in_flight);
746
747    if !r.bbr2_state.packet_conservation {
748        if r.bbr2_state.filled_pipe {
749            r.congestion_window = cmp::min(
750                r.congestion_window + acked_bytes,
751                r.bbr2_state.max_inflight,
752            )
753        } else if r.congestion_window < r.bbr2_state.max_inflight ||
754            r.delivery_rate.delivered() <
755                r.max_datagram_size * r.initial_congestion_window_packets
756        {
757            r.congestion_window += acked_bytes;
758        }
759
760        r.congestion_window = r.congestion_window.max(bbr2_min_pipe_cwnd(r))
761    }
762
763    bbr2_bound_cwnd_for_probe_rtt(r);
764    bbr2_bound_cwnd_for_model(r);
765}
766
767// 4.6.4.7.  Bounding cwnd Based on Recent Congestion
768fn bbr2_bound_cwnd_for_model(r: &mut Congestion) {
769    let mut cap = usize::MAX;
770
771    if bbr2_is_in_a_probe_bw_state(r) &&
772        r.bbr2_state.state != BBR2StateMachine::ProbeBWCRUISE
773    {
774        cap = r.bbr2_state.inflight_hi;
775    } else if r.bbr2_state.state == BBR2StateMachine::ProbeRTT ||
776        r.bbr2_state.state == BBR2StateMachine::ProbeBWCRUISE
777    {
778        cap = bbr2_inflight_with_headroom(r);
779    }
780
781    // Apply inflight_lo (possibly infinite).
782    cap = cap.min(r.bbr2_state.inflight_lo);
783    cap = cap.max(bbr2_min_pipe_cwnd(r));
784
785    r.congestion_window = r.congestion_window.min(cap);
786}