quiche/recovery/congestion/
reno.rs1use std::cmp;
32use std::time::Instant;
33
34use super::rtt::RttStats;
35use super::Acked;
36use super::Sent;
37
38use super::Congestion;
39use super::CongestionControlOps;
40use crate::recovery::LOSS_REDUCTION_FACTOR;
41use crate::recovery::MINIMUM_WINDOW_PACKETS;
42
43pub(crate) static RENO: CongestionControlOps = CongestionControlOps {
44 on_init,
45 on_packet_sent,
46 on_packets_acked,
47 congestion_event,
48 checkpoint,
49 rollback,
50 #[cfg(feature = "qlog")]
51 state_str,
52 debug_fmt,
53};
54
55pub fn on_init(_r: &mut Congestion) {}
56
57pub fn on_packet_sent(
58 _r: &mut Congestion, _sent_bytes: usize, _bytes_in_flight: usize,
59 _now: Instant,
60) {
61}
62
63fn on_packets_acked(
64 r: &mut Congestion, _bytes_in_flight: usize, packets: &mut Vec<Acked>,
65 now: Instant, rtt_stats: &RttStats,
66) {
67 for pkt in packets.drain(..) {
68 on_packet_acked(r, &pkt, now, rtt_stats);
69 }
70}
71
72fn on_packet_acked(
73 r: &mut Congestion, packet: &Acked, now: Instant, rtt_stats: &RttStats,
74) {
75 if r.in_congestion_recovery(packet.time_sent) {
76 return;
77 }
78
79 if r.app_limited {
80 return;
81 }
82
83 if r.congestion_window < r.ssthresh.get() {
84 r.bytes_acked_sl += packet.size;
87
88 if r.hystart.in_css() {
89 r.congestion_window += r.hystart.css_cwnd_inc(r.max_datagram_size);
90 } else {
91 r.congestion_window += r.max_datagram_size;
92 }
93
94 if r.hystart.on_packet_acked(packet, rtt_stats.latest_rtt, now) {
95 r.ssthresh.update(r.congestion_window, true);
97 }
98 } else {
99 r.bytes_acked_ca += packet.size;
101
102 if r.bytes_acked_ca >= r.congestion_window {
103 r.bytes_acked_ca -= r.congestion_window;
104 r.congestion_window += r.max_datagram_size;
105 }
106 }
107}
108
109fn congestion_event(
110 r: &mut Congestion, _bytes_in_flight: usize, _lost_bytes: usize,
111 largest_lost_pkt: &Sent, now: Instant,
112) {
113 let time_sent = largest_lost_pkt.time_sent;
116
117 if !r.in_congestion_recovery(time_sent) {
118 r.congestion_recovery_start_time = Some(now);
119
120 r.congestion_window =
121 (r.congestion_window as f64 * LOSS_REDUCTION_FACTOR) as usize;
122
123 r.congestion_window = cmp::max(
124 r.congestion_window,
125 r.max_datagram_size * MINIMUM_WINDOW_PACKETS,
126 );
127
128 r.bytes_acked_ca =
129 (r.congestion_window as f64 * LOSS_REDUCTION_FACTOR) as usize;
130
131 r.ssthresh.update(r.congestion_window, r.hystart.in_css());
132
133 if r.hystart.in_css() {
134 r.hystart.congestion_event();
135 }
136 }
137}
138
139fn checkpoint(_r: &mut Congestion) {}
140
141fn rollback(_r: &mut Congestion) -> bool {
142 true
143}
144
145#[cfg(feature = "qlog")]
146pub fn state_str(r: &Congestion, now: Instant) -> &'static str {
147 if r.hystart.in_css() {
148 "conservative_slow_start"
149 } else if r.congestion_window < r.ssthresh.get() {
150 "slow_start"
151 } else if r.in_congestion_recovery(now) {
152 "recovery"
153 } else {
154 "congestion_avoidance"
155 }
156}
157
158fn debug_fmt(_r: &Congestion, _f: &mut std::fmt::Formatter) -> std::fmt::Result {
159 Ok(())
160}
161
162#[cfg(test)]
163mod tests {
164 use crate::CongestionControlAlgorithm;
165
166 use super::*;
167
168 use crate::recovery::congestion::recovery::LegacyRecovery;
169 use crate::recovery::congestion::test_sender::TestSender;
170 use crate::recovery::RecoveryOps;
171
172 use std::time::Duration;
173
174 fn test_sender() -> TestSender {
175 TestSender::new(CongestionControlAlgorithm::Reno, false)
176 }
177
178 #[test]
179 fn reno_init() {
180 let mut cfg = crate::Config::new(crate::PROTOCOL_VERSION).unwrap();
181 cfg.set_cc_algorithm(CongestionControlAlgorithm::Reno);
182
183 let r = LegacyRecovery::new(&cfg);
184
185 assert!(r.cwnd() > 0);
186 assert_eq!(r.bytes_in_flight(), 0);
187 }
188
189 #[test]
190 fn reno_slow_start() {
191 let mut sender = test_sender();
192 let size = sender.max_datagram_size;
193
194 for _ in 0..sender.initial_congestion_window_packets {
196 sender.send_packet(size);
197 }
198
199 let cwnd_prev = sender.congestion_window;
200
201 sender.ack_n_packets(1, size);
202
203 assert_eq!(sender.congestion_window, cwnd_prev + size);
205 }
206
207 #[test]
208 fn reno_slow_start_multi_acks() {
209 let mut sender = test_sender();
210 let size = sender.max_datagram_size;
211
212 for _ in 0..sender.initial_congestion_window_packets {
214 sender.send_packet(size);
215 }
216
217 let cwnd_prev = sender.congestion_window;
218
219 sender.ack_n_packets(3, size);
220
221 assert_eq!(sender.congestion_window, cwnd_prev + size * 3);
223 }
224
225 #[test]
226 fn reno_congestion_event() {
227 let mut sender = test_sender();
228 let size = sender.max_datagram_size;
229
230 let prev_cwnd = sender.congestion_window;
231
232 sender.send_packet(size);
233 sender.lose_n_packets(1, size, None);
234
235 assert_eq!(prev_cwnd / 2, sender.congestion_window);
237 }
238
239 #[test]
240 fn reno_congestion_avoidance() {
241 let mut sender = test_sender();
242 let size = sender.max_datagram_size;
243
244 for _ in 0..14 {
246 sender.send_packet(size);
247 }
248
249 let rtt = Duration::from_millis(100);
250 let prev_cwnd = sender.congestion_window;
251
252 sender.advance_time(rtt);
253 sender.lose_n_packets(1, size, None);
254
255 let cur_cwnd = (prev_cwnd as f64 * LOSS_REDUCTION_FACTOR) as usize;
257 assert_eq!(sender.congestion_window, cur_cwnd);
258
259 sender.update_rtt(rtt);
260 sender.advance_time(2 * rtt);
261
262 sender.ack_n_packets(13, size);
263 assert_eq!(sender.congestion_window, cur_cwnd);
266
267 for _ in 0..7 {
268 sender.send_packet(size);
269 }
270 sender.advance_time(rtt);
271 sender.ack_n_packets(2, size);
272 assert_eq!(sender.congestion_window, cur_cwnd);
274
275 sender.ack_n_packets(1, size);
276 assert_eq!(sender.congestion_window, cur_cwnd + size);
278
279 for _ in 0..7 {
280 sender.send_packet(size);
281 }
282
283 sender.ack_n_packets(5, size);
285 assert_eq!(sender.congestion_window, cur_cwnd + size);
287 sender.ack_n_packets(1, size);
288 assert_eq!(sender.congestion_window, cur_cwnd + 2 * size);
290 }
291}