18 , handshake_confirmed_{false}
19 , loss_detection_timer_{}
44 auto idx = space_index(packet.level);
45 auto& space = spaces_[idx];
47 space.sent_packets[packet.packet_number] = packet;
51 space.bytes_in_flight += packet.sent_bytes;
54 if (packet.ack_eliciting)
56 space.time_of_last_ack_eliciting = packet.sent_time;
59 set_loss_detection_timer();
64 std::chrono::steady_clock::time_point recv_time)
68 auto idx = space_index(level);
69 auto& space = spaces_[idx];
71 auto largest_newly_acked =
ack.largest_acknowledged;
73 if (!space.largest_acked_set || largest_newly_acked > space.largest_acked)
75 space.largest_acked = largest_newly_acked;
76 space.largest_acked_set =
true;
81 uint64_t current_pn =
ack.largest_acknowledged;
82 size_t first_range_count =
ack.ranges.empty() ? 0 :
ack.ranges[0].length;
85 for (uint64_t i = 0; i <= first_range_count && current_pn > 0; ++i)
87 auto it = space.sent_packets.find(current_pn - i);
88 if (it != space.sent_packets.end())
90 auto& pkt = it->second;
93 space.bytes_in_flight -= pkt.sent_bytes;
96 space.sent_packets.erase(it);
102 auto it = space.sent_packets.find(
ack.largest_acknowledged);
103 if (it != space.sent_packets.end())
105 auto& pkt = it->second;
108 space.bytes_in_flight -= pkt.sent_bytes;
111 space.sent_packets.erase(it);
116 current_pn =
ack.largest_acknowledged;
117 if (!
ack.ranges.empty())
119 current_pn -=
ack.ranges[0].length + 1;
122 for (
size_t r = 1; r <
ack.ranges.size(); ++r)
125 auto gap =
ack.ranges[r].gap;
126 if (current_pn >= gap + 2)
128 current_pn -= gap + 2;
136 auto range_len =
ack.ranges[r].length;
137 for (uint64_t i = 0; i <= range_len && current_pn > 0; ++i)
139 auto it = space.sent_packets.find(current_pn - i);
140 if (it != space.sent_packets.end())
142 auto& pkt = it->second;
145 space.bytes_in_flight -= pkt.sent_bytes;
148 space.sent_packets.erase(it);
151 if (current_pn >= range_len + 1)
153 current_pn -= range_len + 1;
165 auto largest_it = std::find_if(
169 return p.packet_number == largest_newly_acked;
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)};
178 rtt_.update(latest_rtt, ack_delay_us, handshake_confirmed_);
186 auto lost = detect_lost_packets(level, recv_time);
200 if (pkt.sent_time < earliest_sent_time)
202 earliest_sent_time = pkt.sent_time;
206 auto ecn_signal = ecn_tracker_.process_ecn_counts(
218 set_loss_detection_timer();
224 std::chrono::steady_clock::time_point now)
225 -> std::vector<sent_packet>
227 auto idx = space_index(level);
228 auto& space = spaces_[idx];
229 std::vector<sent_packet> lost;
231 if (!space.largest_acked_set)
237 auto smoothed = rtt_.smoothed_rtt();
238 auto min_rtt = rtt_.min_rtt();
239 if (min_rtt == std::chrono::microseconds::max())
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)};
251 auto lost_send_time = now - loss_delay;
254 space.loss_time = std::chrono::steady_clock::time_point{};
256 for (
auto it = space.sent_packets.begin(); it != space.sent_packets.end();)
258 auto& [pn, packet] = *it;
260 if (pn > space.largest_acked)
267 bool time_lost = packet.sent_time <= lost_send_time;
268 bool reorder_lost = (space.largest_acked >= pn + kPacketThreshold);
270 if (time_lost || reorder_lost)
272 if (packet.in_flight)
274 space.bytes_in_flight -= packet.sent_bytes;
276 lost.push_back(std::move(packet));
277 it = space.sent_packets.erase(it);
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)
286 space.loss_time = potential_loss_time;
296 -> std::optional<std::chrono::steady_clock::time_point>
308 auto now = std::chrono::steady_clock::now();
310 auto [loss_time, loss_level] = get_loss_time_and_space();
311 if (loss_time != std::chrono::steady_clock::time_point{} && loss_time <= now)
314 auto lost = detect_lost_packets(loss_level, now);
328 set_loss_detection_timer();
336 auto earliest_loss_time = std::chrono::steady_clock::time_point::max();
339 for (
size_t i = 0; i <
spaces_.size(); ++i)
342 if (space.loss_time != std::chrono::steady_clock::time_point{} &&
343 space.loss_time < earliest_loss_time)
345 earliest_loss_time = space.loss_time;
361 if (earliest_loss_time == std::chrono::steady_clock::time_point::max())
363 earliest_loss_time = std::chrono::steady_clock::time_point{};
366 return {earliest_loss_time, earliest_level};
373 auto earliest_pto_time = std::chrono::steady_clock::time_point::max();
376 for (
size_t i = 0; i <
spaces_.size(); ++i)
381 bool has_ack_eliciting =
false;
382 for (
const auto& [pn, pkt] : space.sent_packets)
384 if (pkt.ack_eliciting)
386 has_ack_eliciting =
true;
391 if (!has_ack_eliciting)
402 auto pto_time = space.time_of_last_ack_eliciting + pto_duration;
403 if (pto_time < earliest_pto_time)
405 earliest_pto_time = pto_time;
421 if (earliest_pto_time == std::chrono::steady_clock::time_point::max())
423 earliest_pto_time = std::chrono::steady_clock::time_point{};
426 return {earliest_pto_time, earliest_level};
431 auto [loss_time, loss_level] = get_loss_time_and_space();
433 if (loss_time != std::chrono::steady_clock::time_point{})
436 loss_detection_timer_ = loss_time;
442 bool any_ack_eliciting =
false;
443 for (
const auto& space : spaces_)
445 for (
const auto& [pn, pkt] : space.sent_packets)
447 if (pkt.ack_eliciting)
449 any_ack_eliciting =
true;
453 if (any_ack_eliciting)
459 if (!any_ack_eliciting)
461 timer_armed_ =
false;
465 auto [pto_time, pto_level] = get_pto_time_and_space();
466 if (pto_time != std::chrono::steady_clock::time_point{})
468 loss_detection_timer_ = pto_time;
473 timer_armed_ =
false;
479 auto idx = space_index(level);
480 return spaces_[idx].largest_acked;
485 auto idx = space_index(level);
486 return !spaces_[idx].sent_packets.empty();
491 auto idx = space_index(level);
492 return spaces_[idx].bytes_in_flight;
498 for (
const auto& space :
spaces_)
500 total += space.bytes_in_flight;
507 auto idx = space_index(level);
509 set_loss_detection_timer();
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)
@ application
1-RTT application data encryption
@ handshake
Handshake encryption.
@ initial
Initial encryption (derived from DCID)
@ zero_rtt
0-RTT early data encryption
@ congestion_signal
ECN-CE increased (congestion experienced)
@ packet_lost
Packet(s) declared lost.
@ pto_expired
Probe timeout expired.
ACK frame (RFC 9000 Section 19.3)
Result of loss detection operations.
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)