1use std::any::Any;
2use std::cmp;
3use std::sync::Arc;
4
5use super::{BASE_DATAGRAM_SIZE, Controller, ControllerFactory};
6use crate::connection::RttEstimator;
7use crate::{Duration, Instant};
8
9const BETA_CUBIC: f64 = 0.7;
13
14const C: f64 = 0.4;
15
16#[derive(Debug, Default, Clone)]
21pub(super) struct State {
22 k: f64,
24
25 w_max: f64,
27
28 cwnd_inc: u64,
30
31 window: u64,
33
34 ssthresh: u64,
39
40 recovery_start_time: Option<Instant>,
43}
44
45impl State {
51 fn cubic_k(&self, max_datagram_size: u64) -> f64 {
53 let w_max = self.w_max / max_datagram_size as f64;
54 (w_max * (1.0 - BETA_CUBIC) / C).cbrt()
55 }
56
57 fn w_cubic(&self, t: Duration, max_datagram_size: u64) -> f64 {
59 let w_max = self.w_max / max_datagram_size as f64;
60
61 (C * (t.as_secs_f64() - self.k).powi(3) + w_max) * max_datagram_size as f64
62 }
63
64 fn w_est(&self, t: Duration, rtt: Duration, max_datagram_size: u64) -> f64 {
67 let w_max = self.w_max / max_datagram_size as f64;
68 (w_max * BETA_CUBIC
69 + 3.0 * (1.0 - BETA_CUBIC) / (1.0 + BETA_CUBIC) * t.as_secs_f64() / rtt.as_secs_f64())
70 * max_datagram_size as f64
71 }
72}
73
74#[derive(Debug, Clone)]
76pub struct Cubic {
77 config: Arc<CubicConfig>,
78 current_mtu: u64,
79 state: State,
80 pre_congestion_state: Option<State>,
82}
83
84impl Cubic {
85 pub fn new(config: Arc<CubicConfig>, _now: Instant, current_mtu: u16) -> Self {
87 Self {
88 state: State {
89 window: config.initial_window,
90 ssthresh: u64::MAX,
91 ..Default::default()
92 },
93 current_mtu: current_mtu as u64,
94 pre_congestion_state: None,
95 config,
96 }
97 }
98
99 fn minimum_window(&self) -> u64 {
100 2 * self.current_mtu
101 }
102}
103
104impl Controller for Cubic {
105 fn on_ack(
106 &mut self,
107 now: Instant,
108 sent: Instant,
109 bytes: u64,
110 _pn: u64,
111 app_limited: bool,
112 rtt: &RttEstimator,
113 ) {
114 if app_limited
115 || self
116 .state
117 .recovery_start_time
118 .map(|recovery_start_time| sent <= recovery_start_time)
119 .unwrap_or(false)
120 {
121 return;
122 }
123
124 if self.state.window < self.state.ssthresh {
125 self.state.window += bytes;
127 } else {
128 let ca_start_time;
130
131 match self.state.recovery_start_time {
132 Some(t) => ca_start_time = t,
133 None => {
134 ca_start_time = now;
137 self.state.recovery_start_time = Some(now);
138
139 self.state.w_max = self.state.window as f64;
140 self.state.k = 0.0;
141 }
142 }
143
144 let t = now - ca_start_time;
145
146 let w_cubic = self.state.w_cubic(t + rtt.get(), self.current_mtu);
148
149 let w_est = self.state.w_est(t, rtt.get(), self.current_mtu);
151
152 let mut cubic_cwnd = self.state.window;
153
154 if w_cubic < w_est {
155 cubic_cwnd = cmp::max(cubic_cwnd, w_est as u64);
157 } else if cubic_cwnd < w_cubic as u64 {
158 let cubic_inc =
160 (w_cubic - cubic_cwnd as f64) / cubic_cwnd as f64 * self.current_mtu as f64;
161
162 cubic_cwnd += cubic_inc as u64;
163 }
164
165 self.state.cwnd_inc = self
168 .state
169 .cwnd_inc
170 .saturating_add(cubic_cwnd - self.state.window);
171
172 if self.state.cwnd_inc >= self.current_mtu {
178 self.state.window += self.current_mtu;
179 self.state.cwnd_inc -= self.current_mtu;
180 }
181 }
182 }
183
184 fn on_congestion_event(
185 &mut self,
186 now: Instant,
187 sent: Instant,
188 is_persistent_congestion: bool,
189 is_ecn: bool,
190 _lost_bytes: u64,
191 _largest_lost_pn: u64,
192 ) {
193 if self
194 .state
195 .recovery_start_time
196 .map(|recovery_start_time| sent <= recovery_start_time)
197 .unwrap_or(false)
198 {
199 return;
200 }
201
202 if !is_ecn {
204 self.pre_congestion_state = Some(self.state.clone());
205 }
206
207 self.state.recovery_start_time = Some(now);
208 let window = self.state.window as f64;
209
210 if window < self.state.w_max {
215 self.state.w_max = window * (1.0 + BETA_CUBIC) / 2.0;
216 } else {
217 self.state.w_max = window;
218 }
219
220 self.state.ssthresh = cmp::max((window * BETA_CUBIC) as u64, self.minimum_window());
221 self.state.window = self.state.ssthresh;
222 self.state.k = self.state.cubic_k(self.current_mtu);
223
224 self.state.cwnd_inc = (self.state.cwnd_inc as f64 * BETA_CUBIC) as u64;
225
226 if is_persistent_congestion {
227 self.state.recovery_start_time = None;
228 self.state.w_max = self.state.window as f64;
229
230 self.state.ssthresh = cmp::max(
232 (self.state.window as f64 * BETA_CUBIC) as u64,
233 self.minimum_window(),
234 );
235
236 self.state.cwnd_inc = 0;
237
238 self.state.window = self.minimum_window();
239 }
240 }
241
242 fn on_spurious_congestion_event(&mut self) {
243 if let Some(prior_state) = self.pre_congestion_state.take()
244 && self.state.window < prior_state.window
245 {
246 self.state = prior_state;
247 }
248 }
249
250 fn on_mtu_update(&mut self, new_mtu: u16) {
251 self.current_mtu = new_mtu as u64;
252 self.state.window = self.state.window.max(self.minimum_window());
253 }
254
255 fn window(&self) -> u64 {
256 self.state.window
257 }
258
259 fn metrics(&self) -> super::ControllerMetrics {
260 super::ControllerMetrics {
261 congestion_window: self.window(),
262 ssthresh: Some(self.state.ssthresh),
263 pacing_rate: None,
264 send_quantum: None,
265 }
266 }
267
268 fn clone_box(&self) -> Box<dyn Controller> {
269 Box::new(self.clone())
270 }
271
272 fn initial_window(&self) -> u64 {
273 self.config.initial_window
274 }
275
276 fn into_any(self: Box<Self>) -> Box<dyn Any> {
277 self
278 }
279}
280
281#[derive(Debug, Clone)]
283pub struct CubicConfig {
284 initial_window: u64,
285}
286
287impl CubicConfig {
288 pub fn initial_window(&mut self, value: u64) -> &mut Self {
292 self.initial_window = value;
293 self
294 }
295}
296
297impl Default for CubicConfig {
298 fn default() -> Self {
299 Self {
300 initial_window: 14720.clamp(2 * BASE_DATAGRAM_SIZE, 10 * BASE_DATAGRAM_SIZE),
301 }
302 }
303}
304
305impl ControllerFactory for CubicConfig {
306 fn build(self: Arc<Self>, now: Instant, current_mtu: u16) -> Box<dyn Controller> {
307 Box::new(Cubic::new(self, now, current_mtu))
308 }
309}
310#[cfg(test)]
311mod tests {
312 use super::*;
313
314 #[test]
315 fn fast_convergence_reduces_w_max_without_double_reducing_window() {
316 let now = Instant::now();
317 let config = Arc::new(CubicConfig::default());
318 let mut cubic = Cubic::new(config, now, BASE_DATAGRAM_SIZE as u16);
319 let window = 8 * BASE_DATAGRAM_SIZE;
320
321 cubic.state.window = window;
322 cubic.state.ssthresh = window;
323 cubic.state.w_max = 12.0 * BASE_DATAGRAM_SIZE as f64;
324
325 cubic.on_congestion_event(now, now + Duration::from_millis(1), false, false, 0, 100);
326
327 assert_eq!(cubic.state.w_max, window as f64 * (1.0 + BETA_CUBIC) / 2.0);
328 assert_eq!(cubic.state.ssthresh, (window as f64 * BETA_CUBIC) as u64);
329 assert_eq!(cubic.state.window, cubic.state.ssthresh);
330 }
331
332 #[test]
333 fn congestion_avoidance_preserves_excess_cwnd_increment() {
334 let now = Instant::now();
335 let rtt = RttEstimator::new(Duration::from_millis(100));
336 let config = Arc::new(CubicConfig::default());
337 let mut cubic = Cubic::new(config, now, BASE_DATAGRAM_SIZE as u16);
338
339 cubic.state.ssthresh = cubic.state.window;
341 cubic.state.recovery_start_time = Some(now);
342 cubic.state.w_max = cubic.state.window as f64;
343
344 cubic.state.cwnd_inc = 2 * BASE_DATAGRAM_SIZE + 1;
347 let window = cubic.state.window;
348
349 cubic.on_ack(
350 now,
351 now + Duration::from_millis(1),
352 BASE_DATAGRAM_SIZE,
353 0,
354 false,
355 &rtt,
356 );
357
358 assert_eq!(cubic.state.window, window + BASE_DATAGRAM_SIZE);
361 assert_eq!(cubic.state.cwnd_inc, BASE_DATAGRAM_SIZE + 1);
362 }
363}