noq_proto/congestion/
new_reno.rs

1use std::any::Any;
2use std::sync::Arc;
3
4use super::{BASE_DATAGRAM_SIZE, Controller, ControllerFactory};
5use crate::Instant;
6use crate::connection::RttEstimator;
7
8/// A simple, standard congestion controller
9#[derive(Debug, Clone)]
10pub struct NewReno {
11    config: Arc<NewRenoConfig>,
12    current_mtu: u64,
13    /// Maximum number of bytes in flight that may be sent.
14    window: u64,
15    /// Slow start threshold in bytes. When the congestion window is below ssthresh, the mode is
16    /// slow start and the window grows by the number of bytes acknowledged.
17    ssthresh: u64,
18    /// The time when QUIC first detects a loss, causing it to enter recovery. When a packet sent
19    /// after this time is acknowledged, QUIC exits recovery.
20    recovery_start_time: Instant,
21    /// Bytes which had been acked by the peer since leaving slow start
22    bytes_acked: u64,
23}
24
25impl NewReno {
26    /// Construct a state using the given `config` and current time `now`
27    pub fn new(config: Arc<NewRenoConfig>, now: Instant, current_mtu: u16) -> Self {
28        Self {
29            window: config.initial_window,
30            ssthresh: u64::MAX,
31            recovery_start_time: now,
32            current_mtu: current_mtu as u64,
33            config,
34            bytes_acked: 0,
35        }
36    }
37
38    fn minimum_window(&self) -> u64 {
39        2 * self.current_mtu
40    }
41}
42
43impl Controller for NewReno {
44    fn on_ack(
45        &mut self,
46        _now: Instant,
47        sent: Instant,
48        bytes: u64,
49        _pn: u64,
50        app_limited: bool,
51        _rtt: &RttEstimator,
52    ) {
53        if app_limited || sent <= self.recovery_start_time {
54            return;
55        }
56
57        if self.window < self.ssthresh {
58            // Slow start
59            self.window += bytes;
60
61            if self.window >= self.ssthresh {
62                // Exiting slow start
63                // Initialize `bytes_acked` for congestion avoidance. The idea
64                // here is that any bytes over `sshthresh` will already be counted
65                // towards the congestion avoidance phase - independent of when
66                // how close to `sshthresh` the `window` was when switching states,
67                // and independent of datagram sizes.
68                self.bytes_acked = self.window - self.ssthresh;
69            }
70        } else {
71            // Congestion avoidance
72            // This implementation uses the method which does not require
73            // floating point math, which also increases the window by 1 datagram
74            // for every round trip.
75            // This mechanism is called Appropriate Byte Counting in
76            // https://tools.ietf.org/html/rfc3465
77            self.bytes_acked += bytes;
78
79            if self.bytes_acked >= self.window {
80                self.bytes_acked -= self.window;
81                self.window += self.current_mtu;
82            }
83        }
84    }
85
86    fn on_congestion_event(
87        &mut self,
88        now: Instant,
89        sent: Instant,
90        is_persistent_congestion: bool,
91        _is_ecn: bool,
92        _lost_bytes: u64,
93        _largest_lost_pn: u64,
94    ) {
95        if sent <= self.recovery_start_time {
96            return;
97        }
98
99        self.recovery_start_time = now;
100        self.window = (self.window as f32 * self.config.loss_reduction_factor) as u64;
101        self.window = self.window.max(self.minimum_window());
102        self.ssthresh = self.window;
103
104        if is_persistent_congestion {
105            self.window = self.minimum_window();
106        }
107    }
108
109    fn on_mtu_update(&mut self, new_mtu: u16) {
110        self.current_mtu = new_mtu as u64;
111        self.window = self.window.max(self.minimum_window());
112    }
113
114    fn window(&self) -> u64 {
115        self.window
116    }
117
118    fn metrics(&self) -> super::ControllerMetrics {
119        super::ControllerMetrics {
120            congestion_window: self.window(),
121            ssthresh: Some(self.ssthresh),
122            pacing_rate: None,
123            send_quantum: None,
124        }
125    }
126
127    fn clone_box(&self) -> Box<dyn Controller> {
128        Box::new(self.clone())
129    }
130
131    fn initial_window(&self) -> u64 {
132        self.config.initial_window
133    }
134
135    fn into_any(self: Box<Self>) -> Box<dyn Any> {
136        self
137    }
138}
139
140/// Configuration for the `NewReno` congestion controller
141#[derive(Debug, Clone)]
142pub struct NewRenoConfig {
143    initial_window: u64,
144    loss_reduction_factor: f32,
145}
146
147impl NewRenoConfig {
148    /// Default limit on the amount of outstanding data in bytes.
149    ///
150    /// Recommended value: `min(10 * max_datagram_size, max(2 * max_datagram_size, 14720))`
151    pub fn initial_window(&mut self, value: u64) -> &mut Self {
152        self.initial_window = value;
153        self
154    }
155
156    /// Reduction in congestion window when a new loss event is detected.
157    pub fn loss_reduction_factor(&mut self, value: f32) -> &mut Self {
158        self.loss_reduction_factor = value;
159        self
160    }
161}
162
163impl Default for NewRenoConfig {
164    fn default() -> Self {
165        Self {
166            initial_window: 14720.clamp(2 * BASE_DATAGRAM_SIZE, 10 * BASE_DATAGRAM_SIZE),
167            loss_reduction_factor: 0.5,
168        }
169    }
170}
171
172impl ControllerFactory for NewRenoConfig {
173    fn build(self: Arc<Self>, now: Instant, current_mtu: u16) -> Box<dyn Controller> {
174        Box::new(NewReno::new(self, now, current_mtu))
175    }
176}