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