cc: BBR congestion control
Adding a new congestion control module `bbr`, implementing
BBR Congestion Control algorithm as specified in
draft-cardwell-iccrg-bbr-congestion-control-00 (bbr version 1).
Added files:
- `bbr/mod.rs`: congestion control modules hooks.
- `bbr/init.rs`: initialization functions.
- `bbr/pacing.rs`: pacing functions.
- `bbr/per_ack.rs`: ACK processing functions.
- `bbr/per_transmit.rs`: per-transmit functions.
Notable changes:
- In `on_packets_acked()` hook, run `bbr_update_model_and_state()`
per ACK'ed packets for most of state changes and run
`bbr_update_control_parameters()` at the end for updating control
parameters (e.g. pacing rate, cwnd) for ACK received.
- `has_custom_pacing()` now returns true: pacing rate will be
updated during ACK processing.
- No Hystart/PRR will be used even enabled.
diff --git a/quiche/include/quiche.h b/quiche/include/quiche.h
index 25318f9..9dedd03 100644
--- a/quiche/include/quiche.h
+++ b/quiche/include/quiche.h
@@ -203,6 +203,7 @@
enum quiche_cc_algorithm {
QUICHE_CC_RENO = 0,
QUICHE_CC_CUBIC = 1,
+ QUICHE_CC_BBR = 2,
};
// Sets the congestion control algorithm used.
diff --git a/quiche/src/minmax.rs b/quiche/src/minmax.rs
index 8d81c28..274d138 100644
--- a/quiche/src/minmax.rs
+++ b/quiche/src/minmax.rs
@@ -108,7 +108,7 @@
}
/// Updates the max estimate based on the given measurement, and returns it.
- pub fn _running_max(&mut self, win: Duration, time: Instant, meas: T) -> T {
+ pub fn running_max(&mut self, win: Duration, time: Instant, meas: T) -> T {
let val = MinmaxSample { time, value: meas };
let delta_time = time.duration_since(self.estimate[2].time);
@@ -269,13 +269,13 @@
assert_eq!(rtt_max, rtt_24);
time += Duration::from_millis(250);
- rtt_max = f._running_max(win, time, rtt_25);
+ rtt_max = f.running_max(win, time, rtt_25);
assert_eq!(rtt_max, rtt_25);
assert_eq!(f.estimate[1].value, rtt_25);
assert_eq!(f.estimate[2].value, rtt_25);
time += Duration::from_millis(600);
- rtt_max = f._running_max(win, time, rtt_24);
+ rtt_max = f.running_max(win, time, rtt_24);
assert_eq!(rtt_max, rtt_24);
assert_eq!(f.estimate[1].value, rtt_24);
assert_eq!(f.estimate[2].value, rtt_24);
@@ -293,13 +293,13 @@
assert_eq!(bw_max, bw_200);
time += Duration::from_millis(5000);
- bw_max = f._running_max(win, time, bw_500);
+ bw_max = f.running_max(win, time, bw_500);
assert_eq!(bw_max, bw_500);
assert_eq!(f.estimate[1].value, bw_500);
assert_eq!(f.estimate[2].value, bw_500);
time += Duration::from_millis(600);
- bw_max = f._running_max(win, time, bw_200);
+ bw_max = f.running_max(win, time, bw_200);
assert_eq!(bw_max, bw_200);
assert_eq!(f.estimate[1].value, bw_200);
assert_eq!(f.estimate[2].value, bw_200);
@@ -383,19 +383,19 @@
assert_eq!(rtt_max, rtt_25);
time += Duration::from_millis(300);
- rtt_max = f._running_max(win, time, rtt_24);
+ rtt_max = f.running_max(win, time, rtt_24);
assert_eq!(rtt_max, rtt_25);
assert_eq!(f.estimate[1].value, rtt_24);
assert_eq!(f.estimate[2].value, rtt_24);
time += Duration::from_millis(300);
- rtt_max = f._running_max(win, time, rtt_23);
+ rtt_max = f.running_max(win, time, rtt_23);
assert_eq!(rtt_max, rtt_25);
assert_eq!(f.estimate[1].value, rtt_24);
assert_eq!(f.estimate[2].value, rtt_23);
time += Duration::from_millis(300);
- rtt_max = f._running_max(win, time, rtt_26);
+ rtt_max = f.running_max(win, time, rtt_26);
assert_eq!(rtt_max, rtt_26);
assert_eq!(f.estimate[1].value, rtt_26);
assert_eq!(f.estimate[2].value, rtt_26);
@@ -415,19 +415,19 @@
assert_eq!(bw_max, bw_500);
time += Duration::from_millis(300);
- bw_max = f._running_max(win, time, bw_400);
+ bw_max = f.running_max(win, time, bw_400);
assert_eq!(bw_max, bw_500);
assert_eq!(f.estimate[1].value, bw_400);
assert_eq!(f.estimate[2].value, bw_400);
time += Duration::from_millis(300);
- bw_max = f._running_max(win, time, bw_300);
+ bw_max = f.running_max(win, time, bw_300);
assert_eq!(bw_max, bw_500);
assert_eq!(f.estimate[1].value, bw_400);
assert_eq!(f.estimate[2].value, bw_300);
time += Duration::from_millis(300);
- bw_max = f._running_max(win, time, bw_600);
+ bw_max = f.running_max(win, time, bw_600);
assert_eq!(bw_max, bw_600);
assert_eq!(f.estimate[1].value, bw_600);
assert_eq!(f.estimate[2].value, bw_600);
diff --git a/quiche/src/recovery/bbr/init.rs b/quiche/src/recovery/bbr/init.rs
new file mode 100644
index 0000000..6a3f7ba
--- /dev/null
+++ b/quiche/src/recovery/bbr/init.rs
@@ -0,0 +1,93 @@
+// Copyright (C) 2022, Cloudflare, Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+use super::*;
+use crate::recovery::Recovery;
+
+use std::time::Duration;
+use std::time::Instant;
+
+// BBR Functions at Initialization.
+//
+
+// 4.3.1. Initialization Steps
+pub fn bbr_init(r: &mut Recovery) {
+ let rtt = r.rtt();
+ let bbr = &mut r.bbr_state;
+
+ bbr.rtprop = rtt;
+ bbr.rtprop_stamp = Instant::now();
+ bbr.next_round_delivered = r.delivery_rate.delivered();
+
+ r.send_quantum = r.max_datagram_size;
+
+ bbr_init_round_counting(r);
+ bbr_init_full_pipe(r);
+ bbr_init_pacing_rate(r);
+ bbr_enter_startup(r);
+}
+
+// 4.1.1.3. Tracking Time for the BBR.BtlBw Max Filter
+fn bbr_init_round_counting(r: &mut Recovery) {
+ let bbr = &mut r.bbr_state;
+
+ bbr.next_round_delivered = 0;
+ bbr.round_start = false;
+ bbr.round_count = 0;
+}
+
+// 4.2.1. Pacing Rate
+fn bbr_init_pacing_rate(r: &mut Recovery) {
+ let bbr = &mut r.bbr_state;
+
+ let srtt = r
+ .smoothed_rtt
+ .unwrap_or_else(|| Duration::from_millis(1))
+ .as_secs_f64();
+
+ // At init, cwnd is initcwnd.
+ let nominal_bandwidth = r.congestion_window as f64 / srtt;
+
+ bbr.pacing_rate = (bbr.pacing_gain * nominal_bandwidth) as u64;
+}
+
+// 4.3.2.1. Startup Dynamics
+pub fn bbr_enter_startup(r: &mut Recovery) {
+ let bbr = &mut r.bbr_state;
+
+ bbr.state = BBRStateMachine::Startup;
+ bbr.pacing_gain = BBR_HIGH_GAIN;
+ bbr.cwnd_gain = BBR_HIGH_GAIN;
+}
+
+// 4.3.2.2. Estimating When Startup has Filled the Pipe
+fn bbr_init_full_pipe(r: &mut Recovery) {
+ let bbr = &mut r.bbr_state;
+
+ bbr.filled_pipe = false;
+ bbr.full_bw = 0;
+ bbr.full_bw_count = 0;
+}
diff --git a/quiche/src/recovery/bbr/mod.rs b/quiche/src/recovery/bbr/mod.rs
new file mode 100644
index 0000000..2168086
--- /dev/null
+++ b/quiche/src/recovery/bbr/mod.rs
@@ -0,0 +1,830 @@
+// Copyright (C) 2022, Cloudflare, Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+//! BBR Congestion Control
+//!
+//! This implementation is based on the following draft:
+//! <https://tools.ietf.org/html/draft-cardwell-iccrg-bbr-congestion-control-00>
+
+use crate::minmax::Minmax;
+use crate::packet;
+use crate::recovery::*;
+
+use std::time::Duration;
+use std::time::Instant;
+
+pub static BBR: CongestionControlOps = CongestionControlOps {
+ on_init,
+ on_packet_sent,
+ on_packets_acked,
+ congestion_event,
+ collapse_cwnd,
+ checkpoint,
+ rollback,
+ has_custom_pacing,
+ debug_fmt,
+};
+
+/// A constant specifying the length of the BBR.BtlBw max filter window for
+/// BBR.BtlBwFilter, BtlBwFilterLen is 10 packet-timed round trips.
+const BTLBW_FILTER_LEN: Duration = Duration::from_secs(10);
+
+/// A constant specifying the minimum time interval between ProbeRTT states: 10
+/// secs.
+const PROBE_RTT_INTERVAL: Duration = Duration::from_secs(10);
+
+/// A constant specifying the length of the RTProp min filter window.
+const RTPROP_FILTER_LEN: Duration = PROBE_RTT_INTERVAL;
+
+/// A constant specifying the minimum gain value that will allow the sending
+/// rate to double each round (2/ln(2) ~= 2.89), used in Startup mode for both
+/// BBR.pacing_gain and BBR.cwnd_gain.
+const BBR_HIGH_GAIN: f64 = 2.89;
+
+/// The minimal cwnd value BBR tries to target using: 4 packets, or 4 * SMSS
+const BBR_MIN_PIPE_CWND_PKTS: usize = 4;
+
+/// The number of phases in the BBR ProbeBW gain cycle: 8.
+const BBR_GAIN_CYCLE_LEN: usize = 8;
+
+/// A constant specifying the minimum duration for which ProbeRTT state holds
+/// inflight to BBRMinPipeCwnd or fewer packets: 200 ms.
+const PROBE_RTT_DURATION: Duration = Duration::from_millis(200);
+
+/// Pacing Gain Cycle.
+const PACING_GAIN_CYCLE: [f64; BBR_GAIN_CYCLE_LEN] =
+ [5.0 / 4.0, 3.0 / 4.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0];
+
+/// A constant to check BBR.BtlBW is still growing.
+const BTLBW_GROWTH_TARGET: f64 = 1.25;
+
+/// BBR Internal State Machine.
+#[derive(Debug, PartialEq, Eq)]
+enum BBRStateMachine {
+ Startup,
+ Drain,
+ ProbeBW,
+ ProbeRTT,
+}
+
+/// BBR Specific State Variables.
+pub struct State {
+ // The current state of a BBR flow in the BBR state machine.
+ state: BBRStateMachine,
+
+ // The current pacing rate for a BBR flow, which controls inter-packet
+ // spacing.
+ pacing_rate: u64,
+
+ // BBR's estimated bottleneck bandwidth available to the transport flow,
+ // estimated from the maximum delivery rate sample in a sliding window.
+ btlbw: u64,
+
+ // The max filter used to estimate BBR.BtlBw.
+ btlbwfilter: Minmax<u64>,
+
+ // BBR's estimated two-way round-trip propagation delay of the path,
+ // estimated from the windowed minimum recent round-trip delay sample.
+ rtprop: Duration,
+
+ // The wall clock time at which the current BBR.RTProp sample was obtained.
+ rtprop_stamp: Instant,
+
+ // A boolean recording whether the BBR.RTprop has expired and is due for a
+ // refresh with an application idle period or a transition into ProbeRTT
+ // state.
+ rtprop_expired: bool,
+
+ // The dynamic gain factor used to scale BBR.BtlBw to produce
+ // BBR.pacing_rate.
+ pacing_gain: f64,
+
+ // The dynamic gain factor used to scale the estimated BDP to produce a
+ // congestion window (cwnd).
+ cwnd_gain: f64,
+
+ // A boolean that records whether BBR estimates that it has ever fully
+ // utilized its available bandwidth ("filled the pipe").
+ filled_pipe: bool,
+
+ // Count of packet-timed round trips elapsed so far.
+ round_count: u64,
+
+ // A boolean that BBR sets to true once per packet-timed round trip,
+ // on ACKs that advance BBR.round_count.
+ round_start: bool,
+
+ // packet.delivered value denoting the end of a packet-timed round trip.
+ next_round_delivered: usize,
+
+ // Timestamp when ProbeRTT state ends.
+ probe_rtt_done_stamp: Option<Instant>,
+
+ // Checking if a roundtrip in ProbeRTT state ends.
+ probe_rtt_round_done: bool,
+
+ // Checking if in the packet conservation mode during recovery.
+ packet_conservation: bool,
+
+ // Saved cwnd before loss recovery.
+ prior_cwnd: usize,
+
+ // Checking if restarting from idle.
+ idle_restart: bool,
+
+ // Baseline level delivery rate for full pipe estimator.
+ full_bw: u64,
+
+ // The number of round for full pipe estimator without much growth.
+ full_bw_count: usize,
+
+ // Last time cycle_index is updated.
+ cycle_stamp: Instant,
+
+ // Current index of pacing_gain_cycle[].
+ cycle_index: usize,
+
+ // The upper bound on the volume of data BBR allows in flight.
+ target_cwnd: usize,
+
+ // Whether in the recovery episode.
+ in_recovery: bool,
+
+ // Start time of the connection.
+ start_time: Instant,
+
+ // Newly marked lost data size in bytes.
+ newly_lost_bytes: usize,
+
+ // Newly acked data size in bytes.
+ newly_acked_bytes: usize,
+
+ // bytes_in_flight before processing this ACK.
+ prior_bytes_in_flight: usize,
+}
+
+impl State {
+ pub fn new() -> Self {
+ let now = Instant::now();
+
+ State {
+ state: BBRStateMachine::Startup,
+
+ pacing_rate: 0,
+
+ btlbw: 0,
+
+ btlbwfilter: Minmax::new(0),
+
+ rtprop: Duration::ZERO,
+
+ rtprop_stamp: now,
+
+ rtprop_expired: false,
+
+ pacing_gain: 0.0,
+
+ cwnd_gain: 0.0,
+
+ filled_pipe: false,
+
+ round_count: 0,
+
+ round_start: false,
+
+ next_round_delivered: 0,
+
+ probe_rtt_done_stamp: None,
+
+ probe_rtt_round_done: false,
+
+ packet_conservation: false,
+
+ prior_cwnd: 0,
+
+ idle_restart: false,
+
+ full_bw: 0,
+
+ full_bw_count: 0,
+
+ cycle_stamp: now,
+
+ cycle_index: 0,
+
+ target_cwnd: 0,
+
+ in_recovery: false,
+
+ start_time: now,
+
+ newly_lost_bytes: 0,
+
+ newly_acked_bytes: 0,
+
+ prior_bytes_in_flight: 0,
+ }
+ }
+}
+
+// When entering the recovery episode.
+fn bbr_enter_recovery(r: &mut Recovery, now: Instant) {
+ r.bbr_state.prior_cwnd = per_ack::bbr_save_cwnd(r);
+
+ r.congestion_window = r.bytes_in_flight +
+ r.bbr_state.newly_acked_bytes.max(r.max_datagram_size);
+ r.congestion_recovery_start_time = Some(now);
+
+ r.bbr_state.packet_conservation = true;
+ r.bbr_state.in_recovery = true;
+
+ // Start round now.
+ r.bbr_state.next_round_delivered = r.delivery_rate.delivered();
+}
+
+// When exiting the recovery episode.
+fn bbr_exit_recovery(r: &mut Recovery) {
+ r.congestion_recovery_start_time = None;
+
+ r.bbr_state.packet_conservation = false;
+ r.bbr_state.in_recovery = false;
+
+ per_ack::bbr_restore_cwnd(r);
+}
+
+// Congestion Control Hooks.
+//
+fn on_init(r: &mut Recovery) {
+ init::bbr_init(r);
+}
+
+fn on_packet_sent(r: &mut Recovery, sent_bytes: usize, _now: Instant) {
+ r.bytes_in_flight += sent_bytes;
+
+ per_transmit::bbr_on_transmit(r);
+}
+
+fn on_packets_acked(
+ r: &mut Recovery, packets: &[Acked], _epoch: packet::Epoch, now: Instant,
+) {
+ r.bbr_state.newly_acked_bytes = packets.iter().fold(0, |acked_bytes, p| {
+ r.bbr_state.prior_bytes_in_flight = r.bytes_in_flight;
+
+ per_ack::bbr_update_model_and_state(r, p, now);
+
+ r.bytes_in_flight = r.bytes_in_flight.saturating_sub(p.size);
+
+ acked_bytes + p.size
+ });
+
+ if let Some(pkt) = packets.last() {
+ if !r.in_congestion_recovery(pkt.time_sent) {
+ // Upon exiting loss recovery.
+ bbr_exit_recovery(r);
+ }
+ }
+
+ per_ack::bbr_update_control_parameters(r, now);
+
+ r.bbr_state.newly_lost_bytes = 0;
+}
+
+fn congestion_event(
+ r: &mut Recovery, lost_bytes: usize, time_sent: Instant,
+ _epoch: packet::Epoch, now: Instant,
+) {
+ r.bbr_state.newly_lost_bytes = lost_bytes;
+
+ // Upon entering Fast Recovery.
+ if !r.in_congestion_recovery(time_sent) {
+ // Upon entering Fast Recovery.
+ bbr_enter_recovery(r, now);
+ }
+}
+
+fn collapse_cwnd(r: &mut Recovery) {
+ r.bbr_state.prior_cwnd = per_ack::bbr_save_cwnd(r);
+
+ reno::collapse_cwnd(r);
+}
+
+fn checkpoint(_r: &mut Recovery) {}
+
+fn rollback(_r: &mut Recovery) -> bool {
+ false
+}
+
+fn has_custom_pacing() -> bool {
+ true
+}
+
+fn debug_fmt(r: &Recovery, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ let bbr = &r.bbr_state;
+
+ write!(
+ f,
+ "bbr={{ state={:?} btlbw={} rtprop={:?} pacing_rate={} pacing_gain={} cwnd_gain={} target_cwnd={} send_quantum={} filled_pipe={} round_count={} }}",
+ bbr.state, bbr.btlbw, bbr.rtprop, bbr.pacing_rate, bbr.pacing_gain, bbr.cwnd_gain, bbr.target_cwnd, r.send_quantum(), bbr.filled_pipe, bbr.round_count
+ )
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ use crate::recovery;
+
+ #[test]
+ fn bbr_init() {
+ let mut cfg = crate::Config::new(crate::PROTOCOL_VERSION).unwrap();
+ cfg.set_cc_algorithm(recovery::CongestionControlAlgorithm::BBR);
+
+ let mut r = Recovery::new(&cfg);
+
+ // on_init() is called in Connection::new(), so it need to be
+ // called manually here.
+ r.on_init();
+
+ assert_eq!(r.cwnd(), r.max_datagram_size * INITIAL_WINDOW_PACKETS);
+ assert_eq!(r.bytes_in_flight, 0);
+
+ assert_eq!(r.bbr_state.state, BBRStateMachine::Startup);
+ }
+
+ #[test]
+ fn bbr_send() {
+ let mut cfg = crate::Config::new(crate::PROTOCOL_VERSION).unwrap();
+ cfg.set_cc_algorithm(recovery::CongestionControlAlgorithm::BBR);
+
+ let mut r = Recovery::new(&cfg);
+ let now = Instant::now();
+
+ r.on_init();
+ r.on_packet_sent_cc(1000, now);
+
+ assert_eq!(r.bytes_in_flight, 1000);
+ }
+
+ #[test]
+ fn bbr_startup() {
+ let mut cfg = crate::Config::new(crate::PROTOCOL_VERSION).unwrap();
+ cfg.set_cc_algorithm(recovery::CongestionControlAlgorithm::BBR);
+
+ let mut r = Recovery::new(&cfg);
+ let now = Instant::now();
+ let mss = r.max_datagram_size;
+
+ r.on_init();
+
+ // Send 5 packets.
+ for pn in 0..5 {
+ let pkt = Sent {
+ pkt_num: pn,
+ frames: vec![],
+ time_sent: now,
+ time_acked: None,
+ time_lost: None,
+ size: mss,
+ ack_eliciting: true,
+ in_flight: true,
+ delivered: 0,
+ delivered_time: now,
+ first_sent_time: now,
+ is_app_limited: false,
+ has_data: false,
+ };
+
+ r.on_packet_sent(
+ pkt,
+ packet::EPOCH_APPLICATION,
+ HandshakeStatus::default(),
+ now,
+ "",
+ );
+ }
+
+ let rtt = Duration::from_millis(50);
+ let now = now + rtt;
+ let cwnd_prev = r.cwnd();
+
+ let mut acked = ranges::RangeSet::default();
+ acked.insert(0..5);
+
+ assert_eq!(
+ r.on_ack_received(
+ &acked,
+ 25,
+ packet::EPOCH_APPLICATION,
+ HandshakeStatus::default(),
+ now,
+ "",
+ ),
+ Ok(()),
+ );
+
+ assert_eq!(r.bbr_state.state, BBRStateMachine::Startup);
+ assert_eq!(r.cwnd(), cwnd_prev + mss * 5);
+ assert_eq!(r.bytes_in_flight, 0);
+ assert_eq!(
+ r.delivery_rate(),
+ ((mss * 5) as f64 / rtt.as_secs_f64()) as u64
+ );
+ assert_eq!(r.bbr_state.btlbw, r.delivery_rate());
+ }
+
+ #[test]
+ fn bbr_congestion_event() {
+ let mut cfg = crate::Config::new(crate::PROTOCOL_VERSION).unwrap();
+ cfg.set_cc_algorithm(recovery::CongestionControlAlgorithm::BBR);
+
+ let mut r = Recovery::new(&cfg);
+ let now = Instant::now();
+ let mss = r.max_datagram_size;
+
+ r.on_init();
+
+ // Send 5 packets.
+ for pn in 0..5 {
+ let pkt = Sent {
+ pkt_num: pn,
+ frames: vec![],
+ time_sent: now,
+ time_acked: None,
+ time_lost: None,
+ size: mss,
+ ack_eliciting: true,
+ in_flight: true,
+ delivered: 0,
+ delivered_time: now,
+ first_sent_time: now,
+ is_app_limited: false,
+ has_data: false,
+ };
+
+ r.on_packet_sent(
+ pkt,
+ packet::EPOCH_APPLICATION,
+ HandshakeStatus::default(),
+ now,
+ "",
+ );
+ }
+
+ let rtt = Duration::from_millis(50);
+ let now = now + rtt;
+
+ // Make a packet loss to trigger a congestion event.
+ let mut acked = ranges::RangeSet::default();
+ acked.insert(4..5);
+
+ assert_eq!(
+ r.on_ack_received(
+ &acked,
+ 25,
+ packet::EPOCH_APPLICATION,
+ HandshakeStatus::default(),
+ now,
+ "",
+ ),
+ Ok(()),
+ );
+
+ // Sent: 0, 1, 2, 3, 4, Acked 4
+ assert_eq!(r.cwnd(), mss * 4);
+ // Lost: 0, 1, Stil in flight: 2, 3
+ assert_eq!(r.bytes_in_flight, mss * 2);
+ }
+
+ #[test]
+ fn bbr_drain() {
+ let mut cfg = crate::Config::new(crate::PROTOCOL_VERSION).unwrap();
+ cfg.set_cc_algorithm(recovery::CongestionControlAlgorithm::BBR);
+
+ let mut r = Recovery::new(&cfg);
+ let now = Instant::now();
+ let mss = r.max_datagram_size;
+
+ r.on_init();
+
+ let mut pn = 0;
+
+ // Stop right before filled_pipe=true.
+ for _ in 0..3 {
+ let pkt = Sent {
+ pkt_num: pn,
+ frames: vec![],
+ time_sent: now,
+ time_acked: None,
+ time_lost: None,
+ size: mss,
+ ack_eliciting: true,
+ in_flight: true,
+ delivered: r.delivery_rate.delivered(),
+ delivered_time: now,
+ first_sent_time: now,
+ is_app_limited: false,
+ has_data: false,
+ };
+
+ r.on_packet_sent(
+ pkt,
+ packet::EPOCH_APPLICATION,
+ HandshakeStatus::default(),
+ now,
+ "",
+ );
+
+ pn += 1;
+
+ let rtt = Duration::from_millis(50);
+
+ let now = now + rtt;
+
+ let mut acked = ranges::RangeSet::default();
+ acked.insert(0..pn);
+
+ assert_eq!(
+ r.on_ack_received(
+ &acked,
+ 25,
+ packet::EPOCH_APPLICATION,
+ HandshakeStatus::default(),
+ now,
+ "",
+ ),
+ Ok(()),
+ );
+ }
+
+ // Stop at right before filled_pipe=true.
+ for _ in 0..5 {
+ let pkt = Sent {
+ pkt_num: pn,
+ frames: vec![],
+ time_sent: now,
+ time_acked: None,
+ time_lost: None,
+ size: mss,
+ ack_eliciting: true,
+ in_flight: true,
+ delivered: r.delivery_rate.delivered(),
+ delivered_time: now,
+ first_sent_time: now,
+ is_app_limited: false,
+ has_data: false,
+ };
+
+ r.on_packet_sent(
+ pkt,
+ packet::EPOCH_APPLICATION,
+ HandshakeStatus::default(),
+ now,
+ "",
+ );
+
+ pn += 1;
+ }
+
+ let rtt = Duration::from_millis(50);
+ let now = now + rtt;
+
+ let mut acked = ranges::RangeSet::default();
+
+ // We sent 5 packets, but ack only one, to stay
+ // in Drain state.
+ acked.insert(0..pn - 4);
+
+ assert_eq!(
+ r.on_ack_received(
+ &acked,
+ 25,
+ packet::EPOCH_APPLICATION,
+ HandshakeStatus::default(),
+ now,
+ "",
+ ),
+ Ok(()),
+ );
+
+ // Now we are in Drain state.
+ assert_eq!(r.bbr_state.filled_pipe, true);
+ assert_eq!(r.bbr_state.state, BBRStateMachine::Drain);
+ assert!(r.bbr_state.pacing_gain < 1.0);
+ }
+
+ #[test]
+ fn bbr_probe_bw() {
+ let mut cfg = crate::Config::new(crate::PROTOCOL_VERSION).unwrap();
+ cfg.set_cc_algorithm(recovery::CongestionControlAlgorithm::BBR);
+
+ let mut r = Recovery::new(&cfg);
+ let now = Instant::now();
+ let mss = r.max_datagram_size;
+
+ r.on_init();
+
+ let mut pn = 0;
+
+ // At 4th roundtrip, filled_pipe=true and switch to Drain,
+ // but move to ProbeBW immediately because bytes_in_flight is
+ // smaller than BBRInFlight(1).
+ for _ in 0..4 {
+ let pkt = Sent {
+ pkt_num: pn,
+ frames: vec![],
+ time_sent: now,
+ time_acked: None,
+ time_lost: None,
+ size: mss,
+ ack_eliciting: true,
+ in_flight: true,
+ delivered: r.delivery_rate.delivered(),
+ delivered_time: now,
+ first_sent_time: now,
+ is_app_limited: false,
+ has_data: false,
+ };
+
+ r.on_packet_sent(
+ pkt,
+ packet::EPOCH_APPLICATION,
+ HandshakeStatus::default(),
+ now,
+ "",
+ );
+
+ pn += 1;
+
+ let rtt = Duration::from_millis(50);
+ let now = now + rtt;
+
+ let mut acked = ranges::RangeSet::default();
+ acked.insert(0..pn);
+
+ assert_eq!(
+ r.on_ack_received(
+ &acked,
+ 25,
+ packet::EPOCH_APPLICATION,
+ HandshakeStatus::default(),
+ now,
+ "",
+ ),
+ Ok(()),
+ );
+ }
+
+ // Now we are in ProbeBW state.
+ assert_eq!(r.bbr_state.filled_pipe, true);
+ assert_eq!(r.bbr_state.state, BBRStateMachine::ProbeBW);
+
+ // In the first ProbeBW cycle, pacing_gain should be >= 1.0.
+ assert!(r.bbr_state.pacing_gain >= 1.0);
+ }
+
+ #[test]
+ fn bbr_probe_rtt() {
+ let mut cfg = crate::Config::new(crate::PROTOCOL_VERSION).unwrap();
+ cfg.set_cc_algorithm(recovery::CongestionControlAlgorithm::BBR);
+
+ let mut r = Recovery::new(&cfg);
+ let now = Instant::now();
+ let mss = r.max_datagram_size;
+
+ r.on_init();
+
+ let mut pn = 0;
+
+ // At 4th roundtrip, filled_pipe=true and switch to Drain,
+ // but move to ProbeBW immediately because bytes_in_flight is
+ // smaller than BBRInFlight(1).
+ for _ in 0..4 {
+ let pkt = Sent {
+ pkt_num: pn,
+ frames: vec![],
+ time_sent: now,
+ time_acked: None,
+ time_lost: None,
+ size: mss,
+ ack_eliciting: true,
+ in_flight: true,
+ delivered: r.delivery_rate.delivered(),
+ delivered_time: now,
+ first_sent_time: now,
+ is_app_limited: false,
+ has_data: false,
+ };
+
+ r.on_packet_sent(
+ pkt,
+ packet::EPOCH_APPLICATION,
+ HandshakeStatus::default(),
+ now,
+ "",
+ );
+
+ pn += 1;
+
+ let rtt = Duration::from_millis(50);
+ let now = now + rtt;
+
+ let mut acked = ranges::RangeSet::default();
+ acked.insert(0..pn);
+
+ assert_eq!(
+ r.on_ack_received(
+ &acked,
+ 25,
+ packet::EPOCH_APPLICATION,
+ HandshakeStatus::default(),
+ now,
+ "",
+ ),
+ Ok(()),
+ );
+ }
+
+ // Now we are in ProbeBW state.
+ assert_eq!(r.bbr_state.state, BBRStateMachine::ProbeBW);
+
+ // After RTPROP_FILTER_LEN (10s), switch to ProbeRTT.
+ let now = now + RTPROP_FILTER_LEN;
+
+ let pkt = Sent {
+ pkt_num: pn,
+ frames: vec![],
+ time_sent: now,
+ time_acked: None,
+ time_lost: None,
+ size: mss,
+ ack_eliciting: true,
+ in_flight: true,
+ delivered: r.delivery_rate.delivered(),
+ delivered_time: now,
+ first_sent_time: now,
+ is_app_limited: false,
+ has_data: false,
+ };
+
+ r.on_packet_sent(
+ pkt,
+ packet::EPOCH_APPLICATION,
+ HandshakeStatus::default(),
+ now,
+ "",
+ );
+
+ pn += 1;
+
+ // Don't update rtprop by giving larger rtt than before.
+ // If rtprop is updated, rtprop expiry check is reset.
+ let rtt = Duration::from_millis(100);
+ let now = now + rtt;
+
+ let mut acked = ranges::RangeSet::default();
+ acked.insert(0..pn);
+
+ assert_eq!(
+ r.on_ack_received(
+ &acked,
+ 25,
+ packet::EPOCH_APPLICATION,
+ HandshakeStatus::default(),
+ now,
+ "",
+ ),
+ Ok(()),
+ );
+
+ assert_eq!(r.bbr_state.state, BBRStateMachine::ProbeRTT);
+ assert_eq!(r.bbr_state.pacing_gain, 1.0);
+ }
+}
+
+mod init;
+mod pacing;
+mod per_ack;
+mod per_transmit;
diff --git a/quiche/src/recovery/bbr/pacing.rs b/quiche/src/recovery/bbr/pacing.rs
new file mode 100644
index 0000000..e5e21dd
--- /dev/null
+++ b/quiche/src/recovery/bbr/pacing.rs
@@ -0,0 +1,43 @@
+// Copyright (C) 2022, Cloudflare, Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+use crate::recovery::Recovery;
+
+// BBR Transmit Packet Pacing Functions
+//
+
+// 4.2.1. Pacing Rate
+pub fn bbr_set_pacing_rate_with_gain(r: &mut Recovery, pacing_gain: f64) {
+ let rate = (pacing_gain * r.bbr_state.btlbw as f64) as u64;
+
+ if r.bbr_state.filled_pipe || rate > r.bbr_state.pacing_rate {
+ r.bbr_state.pacing_rate = rate;
+ }
+}
+
+pub fn bbr_set_pacing_rate(r: &mut Recovery) {
+ bbr_set_pacing_rate_with_gain(r, r.bbr_state.pacing_gain);
+}
diff --git a/quiche/src/recovery/bbr/per_ack.rs b/quiche/src/recovery/bbr/per_ack.rs
new file mode 100644
index 0000000..6fe5651
--- /dev/null
+++ b/quiche/src/recovery/bbr/per_ack.rs
@@ -0,0 +1,380 @@
+// Copyright (C) 2022, Cloudflare, Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+use super::*;
+use crate::rand;
+use crate::recovery;
+
+use std::cmp;
+use std::time::Instant;
+
+/// 1.2Mbps in bytes/sec
+const PACING_RATE_1_2MBPS: u64 = 1200 * 1000 / 8;
+
+/// 24Mbps in bytes/sec
+const PACING_RATE_24MBPS: u64 = 24 * 1000 * 1000 / 8;
+
+/// The minimal cwnd value BBR tries to target, in bytes
+#[inline]
+fn bbr_min_pipe_cwnd(r: &mut Recovery) -> usize {
+ BBR_MIN_PIPE_CWND_PKTS * r.max_datagram_size
+}
+
+// BBR Functions when ACK is received.
+//
+pub fn bbr_update_model_and_state(
+ r: &mut Recovery, packet: &Acked, now: Instant,
+) {
+ bbr_update_btlbw(r, packet);
+ bbr_check_cycle_phase(r, now);
+ bbr_check_full_pipe(r);
+ bbr_check_drain(r, now);
+ bbr_update_rtprop(r, now);
+ bbr_check_probe_rtt(r, now);
+}
+
+pub fn bbr_update_control_parameters(r: &mut Recovery, now: Instant) {
+ pacing::bbr_set_pacing_rate(r);
+ bbr_set_send_quantum(r);
+
+ // Set outgoing packet pacing rate
+ // It is called here because send_quantum may be updated too.
+ r.set_pacing_rate(r.bbr_state.pacing_rate, now);
+
+ bbr_set_cwnd(r);
+}
+
+// BBR Functions while processing ACKs.
+//
+
+// 4.1.1.5. Updating the BBR.BtlBw Max Filter
+fn bbr_update_btlbw(r: &mut Recovery, packet: &Acked) {
+ bbr_update_round(r, packet);
+
+ if r.delivery_rate() >= r.bbr_state.btlbw ||
+ !r.delivery_rate.sample_is_app_limited()
+ {
+ // Since minmax filter is based on time,
+ // start_time + (round_count as seconds) is used instead.
+ r.bbr_state.btlbw = r.bbr_state.btlbwfilter.running_max(
+ BTLBW_FILTER_LEN,
+ r.bbr_state.start_time + Duration::from_secs(r.bbr_state.round_count),
+ r.delivery_rate(),
+ );
+ }
+}
+
+// 4.1.1.3 Tracking Time for the BBR.BtlBw Max Filter
+fn bbr_update_round(r: &mut Recovery, packet: &Acked) {
+ let bbr = &mut r.bbr_state;
+
+ if packet.delivered >= bbr.next_round_delivered {
+ bbr.next_round_delivered = r.delivery_rate.delivered();
+ bbr.round_count += 1;
+ bbr.round_start = true;
+ bbr.packet_conservation = false;
+ } else {
+ bbr.round_start = false;
+ }
+}
+
+// 4.1.2.3. Updating the BBR.RTprop Min Filter
+fn bbr_update_rtprop(r: &mut Recovery, now: Instant) {
+ let bbr = &mut r.bbr_state;
+ let rs_rtt = r.delivery_rate.sample_rtt();
+
+ bbr.rtprop_expired = now > bbr.rtprop_stamp + RTPROP_FILTER_LEN;
+
+ if !rs_rtt.is_zero() && (rs_rtt <= bbr.rtprop || bbr.rtprop_expired) {
+ bbr.rtprop = rs_rtt;
+ bbr.rtprop_stamp = now;
+ }
+}
+
+// 4.2.2 Send Quantum
+fn bbr_set_send_quantum(r: &mut Recovery) {
+ let rate = r.bbr_state.pacing_rate;
+
+ r.send_quantum = match rate {
+ rate if rate < PACING_RATE_1_2MBPS => r.max_datagram_size,
+
+ rate if rate < PACING_RATE_24MBPS => 2 * r.max_datagram_size,
+
+ _ => cmp::min((rate / 1000_u64) as usize, 64 * 1024),
+ }
+}
+
+// 4.2.3.2 Target cwnd
+fn bbr_inflight(r: &mut Recovery, gain: f64) -> usize {
+ let bbr = &mut r.bbr_state;
+
+ if bbr.rtprop == Duration::MAX {
+ return r.max_datagram_size * INITIAL_WINDOW_PACKETS;
+ }
+
+ let quanta = 3 * r.send_quantum;
+ let estimated_bdp = bbr.btlbw as f64 * bbr.rtprop.as_secs_f64();
+
+ (gain * estimated_bdp) as usize + quanta
+}
+
+fn bbr_update_target_cwnd(r: &mut Recovery) {
+ r.bbr_state.target_cwnd = bbr_inflight(r, r.bbr_state.cwnd_gain);
+}
+
+// 4.2.3.4 Modulating cwnd in Loss Recovery
+pub fn bbr_save_cwnd(r: &mut Recovery) -> usize {
+ if !r.bbr_state.in_recovery && r.bbr_state.state != BBRStateMachine::ProbeRTT
+ {
+ r.congestion_window
+ } else {
+ r.congestion_window.max(r.bbr_state.prior_cwnd)
+ }
+}
+
+pub fn bbr_restore_cwnd(r: &mut Recovery) {
+ r.congestion_window = r.congestion_window.max(r.bbr_state.prior_cwnd);
+}
+
+fn bbr_modulate_cwnd_for_recovery(r: &mut Recovery) {
+ let acked_bytes = r.bbr_state.newly_acked_bytes;
+ let lost_bytes = r.bbr_state.newly_lost_bytes;
+
+ if lost_bytes > 0 {
+ // QUIC mininum cwnd is 2 x MSS.
+ r.congestion_window = r
+ .congestion_window
+ .saturating_sub(lost_bytes)
+ .max(r.max_datagram_size * recovery::MINIMUM_WINDOW_PACKETS);
+ }
+
+ if r.bbr_state.packet_conservation {
+ r.congestion_window =
+ r.congestion_window.max(r.bytes_in_flight + acked_bytes);
+ }
+}
+
+// 4.2.3.5 Modulating cwnd in ProbeRTT
+fn bbr_modulate_cwnd_for_probe_rtt(r: &mut Recovery) {
+ if r.bbr_state.state == BBRStateMachine::ProbeRTT {
+ r.congestion_window = r.congestion_window.min(bbr_min_pipe_cwnd(r))
+ }
+}
+
+// 4.2.3.6 Core cwnd Adjustment Mechanism
+fn bbr_set_cwnd(r: &mut Recovery) {
+ let acked_bytes = r.bbr_state.newly_acked_bytes;
+
+ bbr_update_target_cwnd(r);
+ bbr_modulate_cwnd_for_recovery(r);
+
+ if !r.bbr_state.packet_conservation {
+ if r.bbr_state.filled_pipe {
+ r.congestion_window = cmp::min(
+ r.congestion_window + acked_bytes,
+ r.bbr_state.target_cwnd,
+ )
+ } else if r.congestion_window < r.bbr_state.target_cwnd ||
+ r.delivery_rate.delivered() <
+ r.max_datagram_size * INITIAL_WINDOW_PACKETS
+ {
+ r.congestion_window += acked_bytes;
+ }
+
+ r.congestion_window = r.congestion_window.max(bbr_min_pipe_cwnd(r))
+ }
+
+ bbr_modulate_cwnd_for_probe_rtt(r);
+}
+
+// 4.3.2.2. Estimating When Startup has Filled the Pipe
+fn bbr_check_full_pipe(r: &mut Recovery) {
+ // No need to check for a full pipe now.
+ if r.bbr_state.filled_pipe ||
+ !r.bbr_state.round_start ||
+ r.delivery_rate.sample_is_app_limited()
+ {
+ return;
+ }
+
+ // BBR.BtlBw still growing?
+ if r.bbr_state.btlbw >=
+ (r.bbr_state.full_bw as f64 * BTLBW_GROWTH_TARGET) as u64
+ {
+ // record new baseline level
+ r.bbr_state.full_bw = r.bbr_state.btlbw;
+ r.bbr_state.full_bw_count = 0;
+ return;
+ }
+
+ // another round w/o much growth
+ r.bbr_state.full_bw_count += 1;
+
+ if r.bbr_state.full_bw_count >= 3 {
+ r.bbr_state.filled_pipe = true;
+ }
+}
+
+// 4.3.3. Drain
+fn bbr_enter_drain(r: &mut Recovery) {
+ let bbr = &mut r.bbr_state;
+
+ bbr.state = BBRStateMachine::Drain;
+
+ // pace slowly
+ bbr.pacing_gain = 1.0 / BBR_HIGH_GAIN;
+
+ // maintain cwnd
+ bbr.cwnd_gain = BBR_HIGH_GAIN;
+}
+
+fn bbr_check_drain(r: &mut Recovery, now: Instant) {
+ if r.bbr_state.state == BBRStateMachine::Startup && r.bbr_state.filled_pipe {
+ bbr_enter_drain(r);
+ }
+
+ if r.bbr_state.state == BBRStateMachine::Drain &&
+ r.bytes_in_flight <= bbr_inflight(r, 1.0)
+ {
+ // we estimate queue is drained
+ bbr_enter_probe_bw(r, now);
+ }
+}
+
+// 4.3.4.3. Gain Cycling Algorithm
+fn bbr_enter_probe_bw(r: &mut Recovery, now: Instant) {
+ let bbr = &mut r.bbr_state;
+
+ bbr.state = BBRStateMachine::ProbeBW;
+ bbr.pacing_gain = 1.0;
+ bbr.cwnd_gain = 2.0;
+
+ // cycle_index will be one of (1, 2, 3, 4, 5, 6, 7). Since
+ // bbr_advance_cycle_phase() is called right next and it will
+ // increase cycle_index by 1, the actual cycle_index in the
+ // beginning of ProbeBW will be one of (2, 3, 4, 5, 6, 7, 0)
+ // to avoid index 1 (pacing_gain=3/4). See 4.3.4.2 for details.
+ bbr.cycle_index = BBR_GAIN_CYCLE_LEN -
+ 1 -
+ (rand::rand_u64_uniform(BBR_GAIN_CYCLE_LEN as u64 - 1) as usize);
+
+ bbr_advance_cycle_phase(r, now);
+}
+
+fn bbr_check_cycle_phase(r: &mut Recovery, now: Instant) {
+ let bbr = &mut r.bbr_state;
+
+ if bbr.state == BBRStateMachine::ProbeBW && bbr_is_next_cycle_phase(r, now) {
+ bbr_advance_cycle_phase(r, now);
+ }
+}
+
+fn bbr_advance_cycle_phase(r: &mut Recovery, now: Instant) {
+ let bbr = &mut r.bbr_state;
+
+ bbr.cycle_stamp = now;
+ bbr.cycle_index = (bbr.cycle_index + 1) % BBR_GAIN_CYCLE_LEN;
+ bbr.pacing_gain = PACING_GAIN_CYCLE[bbr.cycle_index];
+}
+
+fn bbr_is_next_cycle_phase(r: &mut Recovery, now: Instant) -> bool {
+ let bbr = &mut r.bbr_state;
+ let lost_bytes = bbr.newly_lost_bytes;
+ let pacing_gain = bbr.pacing_gain;
+ let prior_in_flight = bbr.prior_bytes_in_flight;
+
+ let is_full_length = (now - bbr.cycle_stamp) > bbr.rtprop;
+
+ // pacing_gain == 1.0
+ if (pacing_gain - 1.0).abs() < f64::EPSILON {
+ return is_full_length;
+ }
+
+ if pacing_gain > 1.0 {
+ return is_full_length &&
+ (lost_bytes > 0 ||
+ prior_in_flight >= bbr_inflight(r, pacing_gain));
+ }
+
+ is_full_length || prior_in_flight <= bbr_inflight(r, 1.0)
+}
+
+// 4.3.5. ProbeRTT
+fn bbr_check_probe_rtt(r: &mut Recovery, now: Instant) {
+ if r.bbr_state.state != BBRStateMachine::ProbeRTT &&
+ r.bbr_state.rtprop_expired &&
+ !r.bbr_state.idle_restart
+ {
+ bbr_enter_probe_rtt(r);
+
+ r.bbr_state.prior_cwnd = bbr_save_cwnd(r);
+ r.bbr_state.probe_rtt_done_stamp = None;
+ }
+
+ if r.bbr_state.state == BBRStateMachine::ProbeRTT {
+ bbr_handle_probe_rtt(r, now);
+ }
+
+ r.bbr_state.idle_restart = false;
+}
+
+fn bbr_enter_probe_rtt(r: &mut Recovery) {
+ let bbr = &mut r.bbr_state;
+
+ bbr.state = BBRStateMachine::ProbeRTT;
+ bbr.pacing_gain = 1.0;
+ bbr.cwnd_gain = 1.0;
+}
+
+fn bbr_handle_probe_rtt(r: &mut Recovery, now: Instant) {
+ // Ignore low rate samples during ProbeRTT.
+ r.delivery_rate.update_app_limited(true);
+
+ if let Some(probe_rtt_done_stamp) = r.bbr_state.probe_rtt_done_stamp {
+ if r.bbr_state.round_start {
+ r.bbr_state.probe_rtt_round_done = true;
+ }
+
+ if r.bbr_state.probe_rtt_round_done && now > probe_rtt_done_stamp {
+ r.bbr_state.rtprop_stamp = now;
+
+ bbr_restore_cwnd(r);
+ bbr_exit_probe_rtt(r, now);
+ }
+ } else if r.bytes_in_flight <= bbr_min_pipe_cwnd(r) {
+ r.bbr_state.probe_rtt_done_stamp = Some(now + PROBE_RTT_DURATION);
+ r.bbr_state.probe_rtt_round_done = false;
+ r.bbr_state.next_round_delivered = r.delivery_rate.delivered();
+ }
+}
+
+fn bbr_exit_probe_rtt(r: &mut Recovery, now: Instant) {
+ if r.bbr_state.filled_pipe {
+ bbr_enter_probe_bw(r, now);
+ } else {
+ init::bbr_enter_startup(r);
+ }
+}
diff --git a/quiche/src/recovery/bbr/per_transmit.rs b/quiche/src/recovery/bbr/per_transmit.rs
new file mode 100644
index 0000000..f454387
--- /dev/null
+++ b/quiche/src/recovery/bbr/per_transmit.rs
@@ -0,0 +1,46 @@
+// Copyright (C) 2022, Cloudflare, Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+use super::*;
+
+use crate::recovery::Recovery;
+
+// BBR Functions when trasmitting packets.
+//
+pub fn bbr_on_transmit(r: &mut Recovery) {
+ bbr_handle_restart_from_idle(r);
+}
+
+// 4.3.4.4. Restarting From Idle
+fn bbr_handle_restart_from_idle(r: &mut Recovery) {
+ if r.bytes_in_flight == 0 && r.delivery_rate.app_limited() {
+ r.bbr_state.idle_restart = true;
+
+ if r.bbr_state.state == BBRStateMachine::ProbeBW {
+ pacing::bbr_set_pacing_rate_with_gain(r, 1.0);
+ }
+ }
+}
diff --git a/quiche/src/recovery/delivery_rate.rs b/quiche/src/recovery/delivery_rate.rs
index dcc3e48..37c2aaf 100644
--- a/quiche/src/recovery/delivery_rate.rs
+++ b/quiche/src/recovery/delivery_rate.rs
@@ -162,7 +162,7 @@
self.end_of_app_limited != 0
}
- pub fn _delivered(&self) -> usize {
+ pub fn delivered(&self) -> usize {
self.delivered
}
@@ -170,11 +170,11 @@
self.rate_sample.delivery_rate
}
- pub fn _sample_rtt(&self) -> Duration {
+ pub fn sample_rtt(&self) -> Duration {
self.rate_sample.rtt
}
- pub fn _sample_is_app_limited(&self) -> bool {
+ pub fn sample_is_app_limited(&self) -> bool {
self.rate_sample.is_app_limited
}
}
@@ -264,7 +264,7 @@
r.delivery_rate.generate_rate_sample(rtt);
// Bytes acked so far.
- assert_eq!(r.delivery_rate._delivered(), 2400);
+ assert_eq!(r.delivery_rate.delivered(), 2400);
// Estimated delivery rate = (1200 x 2) / 0.05s = 48000.
assert_eq!(r.delivery_rate(), 48000);
@@ -306,7 +306,7 @@
}
assert_eq!(r.app_limited(), false);
- assert_eq!(r.delivery_rate._sample_is_app_limited(), false);
+ assert_eq!(r.delivery_rate.sample_is_app_limited(), false);
}
#[test]
@@ -363,9 +363,8 @@
);
assert_eq!(r.app_limited(), true);
-
// Rate sample is not app limited (all acked).
- assert_eq!(r.delivery_rate._sample_is_app_limited(), false);
- assert_eq!(r.delivery_rate._sample_rtt(), rtt);
+ assert_eq!(r.delivery_rate.sample_is_app_limited(), false);
+ assert_eq!(r.delivery_rate.sample_rtt(), rtt);
}
}
diff --git a/quiche/src/recovery/mod.rs b/quiche/src/recovery/mod.rs
index a753422..4011fe3 100644
--- a/quiche/src/recovery/mod.rs
+++ b/quiche/src/recovery/mod.rs
@@ -156,6 +156,9 @@
// The maximum size of a data aggregate scheduled and
// transmitted together.
send_quantum: usize,
+
+ // BBR state.
+ bbr_state: bbr::State,
}
impl Recovery {
@@ -249,6 +252,8 @@
#[cfg(feature = "qlog")]
qlog_metrics: QlogMetrics::default(),
+
+ bbr_state: bbr::State::new(),
}
}
@@ -977,6 +982,8 @@
Reno = 0,
/// CUBIC congestion control algorithm (default). `cubic` in a string form.
CUBIC = 1,
+ /// BBR congestion control algorithm. `bbr` in a string form.
+ BBR = 2,
}
impl FromStr for CongestionControlAlgorithm {
@@ -989,6 +996,7 @@
match name {
"reno" => Ok(CongestionControlAlgorithm::Reno),
"cubic" => Ok(CongestionControlAlgorithm::CUBIC),
+ "bbr" => Ok(CongestionControlAlgorithm::BBR),
_ => Err(crate::Error::CongestionControl),
}
@@ -1032,6 +1040,7 @@
match algo {
CongestionControlAlgorithm::Reno => &reno::RENO,
CongestionControlAlgorithm::CUBIC => &cubic::CUBIC,
+ CongestionControlAlgorithm::BBR => &bbr::BBR,
}
}
}
@@ -2045,6 +2054,7 @@
}
}
+mod bbr;
mod cubic;
mod delivery_rate;
mod hystart;