Network System 0.1.1
High-performance modular networking library for scalable client-server applications
Loading...
Searching...
No Matches
loss_detector.cpp
Go to the documentation of this file.
1// BSD 3-Clause License
2// Copyright (c) 2024, 🍀☀🌕🌥 🌊
3// See the LICENSE file in the project root for full license information.
4
6
7#include <algorithm>
8#include <cmath>
9
11{
12
14 : rtt_(rtt)
15 , ecn_tracker_{}
16 , spaces_{}
17 , pto_count_{0}
18 , handshake_confirmed_{false}
19 , loss_detection_timer_{}
20 , timer_armed_{false}
21{
22}
23
24auto loss_detector::space_index(encryption_level level) const noexcept -> size_t
25{
26 // Map encryption levels to packet number spaces:
27 // Initial -> 0, Handshake -> 1, Zero-RTT/Application -> 2
28 switch (level)
29 {
31 return 0;
33 return 1;
36 return 2;
37 default:
38 return 2;
39 }
40}
41
42auto loss_detector::on_packet_sent(const sent_packet& packet) -> void
43{
44 auto idx = space_index(packet.level);
45 auto& space = spaces_[idx];
46
47 space.sent_packets[packet.packet_number] = packet;
48
49 if (packet.in_flight)
50 {
51 space.bytes_in_flight += packet.sent_bytes;
52 }
53
54 if (packet.ack_eliciting)
55 {
56 space.time_of_last_ack_eliciting = packet.sent_time;
57 }
58
59 set_loss_detection_timer();
60}
61
63 encryption_level level,
64 std::chrono::steady_clock::time_point recv_time)
66{
68 auto idx = space_index(level);
69 auto& space = spaces_[idx];
70
71 auto largest_newly_acked = ack.largest_acknowledged;
72
73 if (!space.largest_acked_set || largest_newly_acked > space.largest_acked)
74 {
75 space.largest_acked = largest_newly_acked;
76 space.largest_acked_set = true;
77 }
78
79 // Process the ACK ranges to find newly acknowledged packets
80 // First range starts at largest_acknowledged and goes back ack_range_count
81 uint64_t current_pn = ack.largest_acknowledged;
82 size_t first_range_count = ack.ranges.empty() ? 0 : ack.ranges[0].length;
83
84 // Process first ack block (largest_acknowledged down to largest - first_range)
85 for (uint64_t i = 0; i <= first_range_count && current_pn > 0; ++i)
86 {
87 auto it = space.sent_packets.find(current_pn - i);
88 if (it != space.sent_packets.end())
89 {
90 auto& pkt = it->second;
91 if (pkt.in_flight)
92 {
93 space.bytes_in_flight -= pkt.sent_bytes;
94 }
95 result.acked_packets.push_back(std::move(pkt));
96 space.sent_packets.erase(it);
97 }
98 }
99
100 // Also check largest_acknowledged itself
101 {
102 auto it = space.sent_packets.find(ack.largest_acknowledged);
103 if (it != space.sent_packets.end())
104 {
105 auto& pkt = it->second;
106 if (pkt.in_flight)
107 {
108 space.bytes_in_flight -= pkt.sent_bytes;
109 }
110 result.acked_packets.push_back(std::move(pkt));
111 space.sent_packets.erase(it);
112 }
113 }
114
115 // Process additional ACK ranges
116 current_pn = ack.largest_acknowledged;
117 if (!ack.ranges.empty())
118 {
119 current_pn -= ack.ranges[0].length + 1; // After first range
120 }
121
122 for (size_t r = 1; r < ack.ranges.size(); ++r)
123 {
124 // Skip the gap
125 auto gap = ack.ranges[r].gap;
126 if (current_pn >= gap + 2)
127 {
128 current_pn -= gap + 2;
129 }
130 else
131 {
132 break;
133 }
134
135 // Process this range
136 auto range_len = ack.ranges[r].length;
137 for (uint64_t i = 0; i <= range_len && current_pn > 0; ++i)
138 {
139 auto it = space.sent_packets.find(current_pn - i);
140 if (it != space.sent_packets.end())
141 {
142 auto& pkt = it->second;
143 if (pkt.in_flight)
144 {
145 space.bytes_in_flight -= pkt.sent_bytes;
146 }
147 result.acked_packets.push_back(std::move(pkt));
148 space.sent_packets.erase(it);
149 }
150 }
151 if (current_pn >= range_len + 1)
152 {
153 current_pn -= range_len + 1;
154 }
155 else
156 {
157 break;
158 }
159 }
160
161 // Update RTT if we got a sample from the largest acknowledged packet
162 if (!result.acked_packets.empty())
163 {
164 // Find the largest acked packet to compute RTT
165 auto largest_it = std::find_if(
166 result.acked_packets.begin(),
167 result.acked_packets.end(),
168 [largest_newly_acked](const sent_packet& p) {
169 return p.packet_number == largest_newly_acked;
170 });
171
172 if (largest_it != result.acked_packets.end())
173 {
174 auto latest_rtt = std::chrono::duration_cast<std::chrono::microseconds>(
175 recv_time - largest_it->sent_time);
176 auto ack_delay_us = std::chrono::microseconds{
177 static_cast<int64_t>(ack.ack_delay)}; // Encoded value
178 rtt_.update(latest_rtt, ack_delay_us, handshake_confirmed_);
179 }
180
181 // Reset PTO count since we got an ACK
182 pto_count_ = 0;
183 }
184
185 // Detect lost packets
186 auto lost = detect_lost_packets(level, recv_time);
187 if (!lost.empty())
188 {
190 result.lost_packets = std::move(lost);
191 }
192
193 // Process ECN counts if present (RFC 9000 Section 13.4, RFC 9002 Section 7.1)
194 if (ack.ecn && !result.acked_packets.empty())
195 {
196 // Find the earliest sent time among acked packets for congestion recovery tracking
197 auto earliest_sent_time = result.acked_packets[0].sent_time;
198 for (const auto& pkt : result.acked_packets)
199 {
200 if (pkt.sent_time < earliest_sent_time)
201 {
202 earliest_sent_time = pkt.sent_time;
203 }
204 }
205
206 auto ecn_signal = ecn_tracker_.process_ecn_counts(
207 *ack.ecn,
208 result.acked_packets.size(),
209 earliest_sent_time);
210
211 result.ecn_signal = ecn_signal;
212 if (ecn_signal == ecn_result::congestion_signal)
213 {
214 result.ecn_congestion_sent_time = ecn_tracker_.last_congestion_sent_time();
215 }
216 }
217
218 set_loss_detection_timer();
219
220 return result;
221}
222
224 std::chrono::steady_clock::time_point now)
225 -> std::vector<sent_packet>
226{
227 auto idx = space_index(level);
228 auto& space = spaces_[idx];
229 std::vector<sent_packet> lost;
230
231 if (!space.largest_acked_set)
232 {
233 return lost;
234 }
235
236 // Calculate loss delay (RFC 9002 Section 6.1.2)
237 auto smoothed = rtt_.smoothed_rtt();
238 auto min_rtt = rtt_.min_rtt();
239 if (min_rtt == std::chrono::microseconds::max())
240 {
241 min_rtt = smoothed;
242 }
243 auto max_rtt = std::max(smoothed, min_rtt);
244 auto loss_delay_us = static_cast<int64_t>(
245 kTimeThreshold * static_cast<double>(max_rtt.count()));
246 auto granularity_us = std::chrono::duration_cast<std::chrono::microseconds>(
247 kGranularity).count();
248 auto loss_delay = std::chrono::microseconds{
249 std::max(loss_delay_us, granularity_us)};
250
251 auto lost_send_time = now - loss_delay;
252
253 // Reset loss time for this space
254 space.loss_time = std::chrono::steady_clock::time_point{};
255
256 for (auto it = space.sent_packets.begin(); it != space.sent_packets.end();)
257 {
258 auto& [pn, packet] = *it;
259
260 if (pn > space.largest_acked)
261 {
262 ++it;
263 continue;
264 }
265
266 // Check if packet is lost by packet threshold or time threshold
267 bool time_lost = packet.sent_time <= lost_send_time;
268 bool reorder_lost = (space.largest_acked >= pn + kPacketThreshold);
269
270 if (time_lost || reorder_lost)
271 {
272 if (packet.in_flight)
273 {
274 space.bytes_in_flight -= packet.sent_bytes;
275 }
276 lost.push_back(std::move(packet));
277 it = space.sent_packets.erase(it);
278 }
279 else
280 {
281 // This packet might be lost later by time threshold
282 auto potential_loss_time = packet.sent_time + loss_delay;
283 if (space.loss_time == std::chrono::steady_clock::time_point{} ||
284 potential_loss_time < space.loss_time)
285 {
286 space.loss_time = potential_loss_time;
287 }
288 ++it;
289 }
290 }
291
292 return lost;
293}
294
296 -> std::optional<std::chrono::steady_clock::time_point>
297{
298 if (!timer_armed_)
299 {
300 return std::nullopt;
301 }
303}
304
306{
308 auto now = std::chrono::steady_clock::now();
309
310 auto [loss_time, loss_level] = get_loss_time_and_space();
311 if (loss_time != std::chrono::steady_clock::time_point{} && loss_time <= now)
312 {
313 // Time threshold loss detection
314 auto lost = detect_lost_packets(loss_level, now);
315 if (!lost.empty())
316 {
318 result.lost_packets = std::move(lost);
319 }
320 }
321 else
322 {
323 // PTO timeout
325 ++pto_count_;
326 }
327
328 set_loss_detection_timer();
329
330 return result;
331}
332
334 -> std::pair<std::chrono::steady_clock::time_point, encryption_level>
335{
336 auto earliest_loss_time = std::chrono::steady_clock::time_point::max();
337 auto earliest_level = encryption_level::initial;
338
339 for (size_t i = 0; i < spaces_.size(); ++i)
340 {
341 auto& space = spaces_[i];
342 if (space.loss_time != std::chrono::steady_clock::time_point{} &&
343 space.loss_time < earliest_loss_time)
344 {
345 earliest_loss_time = space.loss_time;
346 switch (i)
347 {
348 case 0:
349 earliest_level = encryption_level::initial;
350 break;
351 case 1:
352 earliest_level = encryption_level::handshake;
353 break;
354 case 2:
355 earliest_level = encryption_level::application;
356 break;
357 }
358 }
359 }
360
361 if (earliest_loss_time == std::chrono::steady_clock::time_point::max())
362 {
363 earliest_loss_time = std::chrono::steady_clock::time_point{};
364 }
365
366 return {earliest_loss_time, earliest_level};
367}
368
370 -> std::pair<std::chrono::steady_clock::time_point, encryption_level>
371{
372 auto pto_duration = rtt_.pto() * (1 << pto_count_); // Exponential backoff
373 auto earliest_pto_time = std::chrono::steady_clock::time_point::max();
374 auto earliest_level = encryption_level::application;
375
376 for (size_t i = 0; i < spaces_.size(); ++i)
377 {
378 auto& space = spaces_[i];
379
380 // Skip spaces with no ack-eliciting packets
381 bool has_ack_eliciting = false;
382 for (const auto& [pn, pkt] : space.sent_packets)
383 {
384 if (pkt.ack_eliciting)
385 {
386 has_ack_eliciting = true;
387 break;
388 }
389 }
390
391 if (!has_ack_eliciting)
392 {
393 continue;
394 }
395
396 // Don't include application space if handshake not confirmed
397 if (i == 2 && !handshake_confirmed_)
398 {
399 continue;
400 }
401
402 auto pto_time = space.time_of_last_ack_eliciting + pto_duration;
403 if (pto_time < earliest_pto_time)
404 {
405 earliest_pto_time = pto_time;
406 switch (i)
407 {
408 case 0:
409 earliest_level = encryption_level::initial;
410 break;
411 case 1:
412 earliest_level = encryption_level::handshake;
413 break;
414 case 2:
415 earliest_level = encryption_level::application;
416 break;
417 }
418 }
419 }
420
421 if (earliest_pto_time == std::chrono::steady_clock::time_point::max())
422 {
423 earliest_pto_time = std::chrono::steady_clock::time_point{};
424 }
425
426 return {earliest_pto_time, earliest_level};
427}
428
430{
431 auto [loss_time, loss_level] = get_loss_time_and_space();
432
433 if (loss_time != std::chrono::steady_clock::time_point{})
434 {
435 // Time threshold loss detection timer
436 loss_detection_timer_ = loss_time;
437 timer_armed_ = true;
438 return;
439 }
440
441 // Check if there are any ack-eliciting packets in flight
442 bool any_ack_eliciting = false;
443 for (const auto& space : spaces_)
444 {
445 for (const auto& [pn, pkt] : space.sent_packets)
446 {
447 if (pkt.ack_eliciting)
448 {
449 any_ack_eliciting = true;
450 break;
451 }
452 }
453 if (any_ack_eliciting)
454 {
455 break;
456 }
457 }
458
459 if (!any_ack_eliciting)
460 {
461 timer_armed_ = false;
462 return;
463 }
464
465 auto [pto_time, pto_level] = get_pto_time_and_space();
466 if (pto_time != std::chrono::steady_clock::time_point{})
467 {
468 loss_detection_timer_ = pto_time;
469 timer_armed_ = true;
470 }
471 else
472 {
473 timer_armed_ = false;
474 }
475}
476
477auto loss_detector::largest_acked(encryption_level level) const noexcept -> uint64_t
478{
479 auto idx = space_index(level);
480 return spaces_[idx].largest_acked;
481}
482
484{
485 auto idx = space_index(level);
486 return !spaces_[idx].sent_packets.empty();
487}
488
490{
491 auto idx = space_index(level);
492 return spaces_[idx].bytes_in_flight;
493}
494
496{
497 size_t total = 0;
498 for (const auto& space : spaces_)
499 {
500 total += space.bytes_in_flight;
501 }
502 return total;
503}
504
506{
507 auto idx = space_index(level);
508 spaces_[idx] = space_state{};
509 set_loss_detection_timer();
510}
511
512} // namespace kcenon::network::protocols::quic
auto has_unacked_packets(encryption_level level) const -> bool
Check if there are any unacked packets in the given space.
bool handshake_confirmed_
True if the handshake is confirmed.
auto on_timeout() -> loss_detection_result
Handle timeout expiry (RFC 9002 Section 6.2)
auto total_bytes_in_flight() const -> size_t
Get total bytes in flight across all spaces.
auto space_index(encryption_level level) const noexcept -> size_t
Get space index from encryption level.
loss_detector(rtt_estimator &rtt)
Constructor.
std::chrono::steady_clock::time_point loss_detection_timer_
Scheduled loss detection timeout.
auto bytes_in_flight(encryption_level level) const -> size_t
Get bytes in flight for a packet number space.
auto set_loss_detection_timer() -> void
Set loss detection timer (RFC 9002 Section 6.2)
auto discard_space(encryption_level level) -> void
Discard packet number space (e.g., after handshake keys discarded)
auto on_packet_sent(const sent_packet &packet) -> void
Record a sent packet.
auto get_pto_time_and_space() const -> std::pair< std::chrono::steady_clock::time_point, encryption_level >
Get PTO time and space (RFC 9002 Appendix A.8)
auto on_ack_received(const ack_frame &ack, encryption_level level, std::chrono::steady_clock::time_point recv_time) -> loss_detection_result
Process received ACK frame (RFC 9002 Section 6)
rtt_estimator & rtt_
Reference to RTT estimator.
bool timer_armed_
True if loss detection timer is armed.
auto detect_lost_packets(encryption_level level, std::chrono::steady_clock::time_point now) -> std::vector< sent_packet >
Detect lost packets (RFC 9002 Section 6.1)
auto next_timeout() const -> std::optional< std::chrono::steady_clock::time_point >
Get the time of the next scheduled timeout.
uint32_t pto_count_
Number of times PTO has expired without receiving an ACK.
auto get_loss_time_and_space() const -> std::pair< std::chrono::steady_clock::time_point, encryption_level >
Get loss time and space (RFC 9002 Appendix A.8)
std::array< space_state, 3 > spaces_
Per packet-number-space state (Initial, Handshake, Application)
auto largest_acked(encryption_level level) const noexcept -> uint64_t
Get the largest acknowledged packet number for a space.
RTT estimation for QUIC (RFC 9002 Section 5)
auto pto() const noexcept -> std::chrono::microseconds
Calculate probe timeout duration (RFC 9002 Section 6.2.1)
encryption_level
QUIC encryption levels (RFC 9001 Section 4)
Definition keys.h:54
@ application
1-RTT application data encryption
@ initial
Initial encryption (derived from DCID)
@ congestion_signal
ECN-CE increased (congestion experienced)
ACK frame (RFC 9000 Section 19.3)
loss_detection_event event
Event that occurred.
ecn_result ecn_signal
ECN signal from ACK_ECN frame processing.
std::chrono::steady_clock::time_point ecn_congestion_sent_time
Sent time of the packet that triggered ECN congestion signal (used for congestion recovery tracking)
std::vector< sent_packet > acked_packets
Packets that were acknowledged.
std::vector< sent_packet > lost_packets
Packets that were declared lost.
Per packet-number-space state (RFC 9002 Appendix A.1)
Information about a sent packet for loss detection (RFC 9002 Section A.1.1)