Skip to main content

quiche/recovery/congestion/
recovery.rs

1// Copyright (C) 2018-2019, Cloudflare, Inc.
2// All rights reserved.
3//
4// Redistribution and use in source and binary forms, with or without
5// modification, are permitted provided that the following conditions are
6// met:
7//
8//     * Redistributions of source code must retain the above copyright notice,
9//       this list of conditions and the following disclaimer.
10//
11//     * Redistributions in binary form must reproduce the above copyright
12//       notice, this list of conditions and the following disclaimer in the
13//       documentation and/or other materials provided with the distribution.
14//
15// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
16// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
17// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
19// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
22// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
23// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
24// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
27use std::cmp;
28
29use std::time::Duration;
30use std::time::Instant;
31
32use std::collections::VecDeque;
33
34use super::RecoveryConfig;
35use super::Sent;
36
37use crate::packet::Epoch;
38use crate::ranges::RangeSet;
39use crate::recovery::Bandwidth;
40use crate::recovery::HandshakeStatus;
41use crate::recovery::OnLossDetectionTimeoutOutcome;
42use crate::recovery::RecoveryOps;
43use crate::recovery::StartupExit;
44use crate::Error;
45use crate::Result;
46
47#[cfg(feature = "qlog")]
48use crate::recovery::QlogMetrics;
49
50use crate::frame;
51
52#[cfg(feature = "qlog")]
53use qlog::events::EventData;
54
55use super::Congestion;
56use crate::recovery::bytes_in_flight::BytesInFlight;
57use crate::recovery::rtt::RttStats;
58use crate::recovery::LossDetectionTimer;
59use crate::recovery::OnAckReceivedOutcome;
60use crate::recovery::ReleaseDecision;
61use crate::recovery::ReleaseTime;
62use crate::recovery::GRANULARITY;
63use crate::recovery::INITIAL_PACKET_THRESHOLD;
64use crate::recovery::INITIAL_TIME_THRESHOLD;
65use crate::recovery::MAX_OUTSTANDING_NON_ACK_ELICITING;
66use crate::recovery::MAX_PACKET_THRESHOLD;
67use crate::recovery::MAX_PTO_PROBES_COUNT;
68use crate::recovery::PACKET_REORDER_TIME_THRESHOLD;
69
70#[derive(Default)]
71struct RecoveryEpoch {
72    /// The time the most recent ack-eliciting packet was sent.
73    time_of_last_ack_eliciting_packet: Option<Instant>,
74
75    /// The largest packet number acknowledged in the packet number space so
76    /// far.
77    largest_acked_packet: Option<u64>,
78
79    /// The time at which the next packet in that packet number space can be
80    /// considered lost based on exceeding the reordering window in time.
81    loss_time: Option<Instant>,
82
83    /// An association of packet numbers in a packet number space to information
84    /// about them.
85    sent_packets: VecDeque<Sent>,
86
87    loss_probes: usize,
88    in_flight_count: usize,
89
90    acked_frames: Vec<frame::Frame>,
91
92    // Frames scheduled for retransmission due to PTO are tracked
93    // separately so we can check that frames were drained before
94    // generating more PTO probes.
95    lost_frames_ack: Vec<frame::Frame>,
96    lost_frames_pto: Vec<frame::Frame>,
97
98    /// The largest packet number sent in the packet number space so far.
99    #[cfg(test)]
100    test_largest_sent_pkt_num_on_path: Option<u64>,
101}
102
103struct AckedDetectionResult {
104    acked_bytes: usize,
105    spurious_losses: usize,
106    spurious_pkt_thresh: Option<u64>,
107    has_ack_eliciting: bool,
108    has_in_flight_spurious_loss: bool,
109}
110
111struct LossDetectionResult {
112    largest_lost_pkt: Option<Sent>,
113    lost_packets: usize,
114    lost_bytes: usize,
115    pmtud_lost_bytes: usize,
116}
117
118impl RecoveryEpoch {
119    // `peer_sent_ack_ranges` should not be used without validation.
120    fn detect_and_remove_acked_packets(
121        &mut self, now: Instant, peer_sent_ack_ranges: &RangeSet,
122        newly_acked: &mut Vec<Acked>, rtt_stats: &RttStats, skip_pn: Option<u64>,
123        trace_id: &str,
124    ) -> Result<AckedDetectionResult> {
125        newly_acked.clear();
126
127        let mut acked_bytes = 0;
128        let mut spurious_losses = 0;
129        let mut spurious_pkt_thresh = None;
130        let mut has_ack_eliciting = false;
131        let mut has_in_flight_spurious_loss = false;
132
133        let largest_ack_received = peer_sent_ack_ranges
134            .last()
135            .expect("ACK frames should always have at least one ack range");
136        let largest_acked = self
137            .largest_acked_packet
138            .unwrap_or(0)
139            .max(largest_ack_received);
140
141        for peer_sent_range in peer_sent_ack_ranges.iter() {
142            if skip_pn.is_some_and(|skip_pn| peer_sent_range.contains(&skip_pn)) {
143                // https://www.rfc-editor.org/rfc/rfc9000#section-13.1
144                // An endpoint SHOULD treat receipt of an acknowledgment
145                // for a packet it did not send as
146                // a connection error of type PROTOCOL_VIOLATION
147                return Err(Error::OptimisticAckDetected);
148            }
149
150            // Because packets always have incrementing numbers, they are always
151            // in sorted order.
152            let start = if self
153                .sent_packets
154                .front()
155                .filter(|e| e.pkt_num >= peer_sent_range.start)
156                .is_some()
157            {
158                // Usually it will be the first packet.
159                0
160            } else {
161                self.sent_packets
162                    .binary_search_by_key(&peer_sent_range.start, |p| p.pkt_num)
163                    .unwrap_or_else(|e| e)
164            };
165
166            for unacked in self.sent_packets.range_mut(start..) {
167                if unacked.pkt_num >= peer_sent_range.end {
168                    break;
169                }
170
171                if unacked.time_acked.is_some() {
172                    // Already acked.
173                } else if unacked.time_lost.is_some() {
174                    // An acked packet was already declared lost.
175                    spurious_losses += 1;
176                    spurious_pkt_thresh
177                        .get_or_insert(largest_acked - unacked.pkt_num + 1);
178                    unacked.time_acked = Some(now);
179
180                    if unacked.in_flight {
181                        has_in_flight_spurious_loss = true;
182                    }
183                } else {
184                    if unacked.in_flight {
185                        self.in_flight_count -= 1;
186                        acked_bytes += unacked.size;
187                    }
188
189                    newly_acked.push(Acked {
190                        pkt_num: unacked.pkt_num,
191                        time_sent: unacked.time_sent,
192                        size: unacked.size,
193
194                        rtt: now.saturating_duration_since(unacked.time_sent),
195                        delivered: unacked.delivered,
196                        delivered_time: unacked.delivered_time,
197                        first_sent_time: unacked.first_sent_time,
198                        is_app_limited: unacked.is_app_limited,
199                    });
200
201                    trace!("{} packet newly acked {}", trace_id, unacked.pkt_num);
202
203                    self.acked_frames
204                        .extend(std::mem::take(&mut unacked.frames));
205
206                    has_ack_eliciting |= unacked.ack_eliciting;
207                    unacked.time_acked = Some(now);
208                }
209            }
210        }
211
212        self.drain_acked_and_lost_packets(now - rtt_stats.rtt());
213
214        Ok(AckedDetectionResult {
215            acked_bytes,
216            spurious_losses,
217            spurious_pkt_thresh,
218            has_ack_eliciting,
219            has_in_flight_spurious_loss,
220        })
221    }
222
223    fn detect_lost_packets(
224        &mut self, loss_delay: Duration, pkt_thresh: u64, now: Instant,
225        trace_id: &str, epoch: Epoch,
226    ) -> LossDetectionResult {
227        self.loss_time = None;
228
229        // Minimum time of kGranularity before packets are deemed lost.
230        let loss_delay = cmp::max(loss_delay, GRANULARITY);
231        let largest_acked = self.largest_acked_packet.unwrap_or(0);
232
233        // Packets sent before this time are deemed lost.
234        let lost_send_time = now.checked_sub(loss_delay).unwrap();
235
236        let mut lost_packets = 0;
237        let mut lost_bytes = 0;
238        let mut pmtud_lost_bytes = 0;
239
240        let mut largest_lost_pkt = None;
241
242        let unacked_iter = self.sent_packets
243        .iter_mut()
244        // Skip packets that follow the largest acked packet.
245        .take_while(|p| p.pkt_num <= largest_acked)
246        // Skip packets that have already been acked or lost.
247        .filter(|p| p.time_acked.is_none() && p.time_lost.is_none());
248
249        for unacked in unacked_iter {
250            // Mark packet as lost, or set time when it should be marked.
251            if unacked.time_sent <= lost_send_time ||
252                largest_acked >= unacked.pkt_num + pkt_thresh
253            {
254                self.lost_frames_ack.extend(unacked.frames.drain(..));
255
256                unacked.time_lost = Some(now);
257
258                if unacked.is_pmtud_probe {
259                    pmtud_lost_bytes += unacked.size;
260                    self.in_flight_count -= 1;
261
262                    // Do not track PMTUD probes losses.
263                    continue;
264                }
265
266                if unacked.in_flight {
267                    lost_bytes += unacked.size;
268
269                    // Frames have already been removed from the packet, so
270                    // cloning the whole packet should be relatively cheap.
271                    largest_lost_pkt = Some(unacked.clone());
272
273                    self.in_flight_count -= 1;
274
275                    trace!(
276                        "{} packet {} lost on epoch {}",
277                        trace_id,
278                        unacked.pkt_num,
279                        epoch
280                    );
281                }
282
283                lost_packets += 1;
284            } else {
285                let loss_time = match self.loss_time {
286                    None => unacked.time_sent + loss_delay,
287
288                    Some(loss_time) =>
289                        cmp::min(loss_time, unacked.time_sent + loss_delay),
290                };
291
292                self.loss_time = Some(loss_time);
293                break;
294            }
295        }
296
297        LossDetectionResult {
298            largest_lost_pkt,
299            lost_packets,
300            lost_bytes,
301            pmtud_lost_bytes,
302        }
303    }
304
305    fn drain_acked_and_lost_packets(&mut self, loss_thresh: Instant) {
306        // In order to avoid removing elements from the middle of the list
307        // (which would require copying other elements to compact the list),
308        // we only remove a contiguous range of elements from the start of the
309        // list.
310        //
311        // This means that acked or lost elements coming after this will not
312        // be removed at this point, but their removal is delayed for a later
313        // time, once the gaps have been filled.
314        while let Some(pkt) = self.sent_packets.front() {
315            if let Some(time_lost) = pkt.time_lost {
316                if time_lost > loss_thresh {
317                    break;
318                }
319            }
320
321            if pkt.time_acked.is_none() && pkt.time_lost.is_none() {
322                break;
323            }
324
325            self.sent_packets.pop_front();
326        }
327    }
328
329    /// Returns the next lost frame, trying ACK-based lost frames first,
330    /// then PTO-based lost frames.
331    fn next_lost_frame(&mut self) -> Option<frame::Frame> {
332        self.lost_frames_ack
333            .pop()
334            .or_else(|| self.lost_frames_pto.pop())
335    }
336
337    /// Returns true if there are any lost frames (ACK or PTO).
338    fn has_lost_frames(&self) -> bool {
339        !self.lost_frames_ack.is_empty() || !self.lost_frames_pto.is_empty()
340    }
341
342    /// Returns the total count of lost frames (ACK + PTO).
343    #[cfg(test)]
344    fn lost_frames_count(&self) -> usize {
345        self.lost_frames_ack.len() + self.lost_frames_pto.len()
346    }
347
348    /// Clears all lost frames (both ACK and PTO).
349    fn clear_lost_frames(&mut self) {
350        self.lost_frames_ack.clear();
351        self.lost_frames_pto.clear();
352    }
353}
354
355pub struct LegacyRecovery {
356    epochs: [RecoveryEpoch; Epoch::count()],
357
358    loss_timer: LossDetectionTimer,
359
360    pto_count: u32,
361
362    rtt_stats: RttStats,
363
364    lost_spurious_count: usize,
365
366    pkt_thresh: u64,
367
368    time_thresh: f64,
369
370    bytes_in_flight: BytesInFlight,
371
372    bytes_sent: usize,
373
374    bytes_lost: u64,
375
376    pub max_datagram_size: usize,
377
378    #[cfg(feature = "qlog")]
379    qlog_metrics: QlogMetrics,
380
381    #[cfg(feature = "qlog")]
382    qlog_prev_cc_state: &'static str,
383
384    /// How many non-ack-eliciting packets have been sent.
385    outstanding_non_ack_eliciting: usize,
386
387    pub congestion: Congestion,
388
389    /// A resusable list of acks.
390    newly_acked: Vec<Acked>,
391}
392
393impl LegacyRecovery {
394    pub fn new_with_config(recovery_config: &RecoveryConfig) -> Self {
395        Self {
396            epochs: Default::default(),
397
398            loss_timer: Default::default(),
399
400            pto_count: 0,
401
402            rtt_stats: RttStats::new(
403                recovery_config.initial_rtt,
404                recovery_config.max_ack_delay,
405            ),
406
407            lost_spurious_count: 0,
408
409            pkt_thresh: INITIAL_PACKET_THRESHOLD,
410
411            time_thresh: INITIAL_TIME_THRESHOLD,
412
413            bytes_in_flight: Default::default(),
414
415            bytes_sent: 0,
416
417            bytes_lost: 0,
418
419            max_datagram_size: recovery_config.max_send_udp_payload_size,
420
421            #[cfg(feature = "qlog")]
422            qlog_metrics: QlogMetrics::default(),
423
424            #[cfg(feature = "qlog")]
425            qlog_prev_cc_state: "",
426
427            outstanding_non_ack_eliciting: 0,
428
429            congestion: Congestion::from_config(recovery_config),
430
431            newly_acked: Vec::new(),
432        }
433    }
434
435    #[cfg(test)]
436    pub fn new(config: &crate::Config) -> Self {
437        Self::new_with_config(&RecoveryConfig::from_config(config))
438    }
439
440    fn loss_time_and_space(&self) -> (Option<Instant>, Epoch) {
441        let mut epoch = Epoch::Initial;
442        let mut time = self.epochs[epoch].loss_time;
443
444        // Iterate over all packet number spaces starting from Handshake.
445        for e in [Epoch::Handshake, Epoch::Application] {
446            let new_time = self.epochs[e].loss_time;
447            if time.is_none() || new_time < time {
448                time = new_time;
449                epoch = e;
450            }
451        }
452
453        (time, epoch)
454    }
455
456    fn pto_time_and_space(
457        &self, handshake_status: HandshakeStatus, now: Instant,
458    ) -> (Option<Instant>, Epoch) {
459        let mut duration = self.pto() * 2_u32.saturating_pow(self.pto_count);
460
461        // Arm PTO from now when there are no inflight packets.
462        if self.bytes_in_flight.is_zero() {
463            if handshake_status.has_handshake_keys {
464                return (Some(now + duration), Epoch::Handshake);
465            } else {
466                return (Some(now + duration), Epoch::Initial);
467            }
468        }
469
470        let mut pto_timeout = None;
471        let mut pto_space = Epoch::Initial;
472
473        // Iterate over all packet number spaces.
474        for e in [Epoch::Initial, Epoch::Handshake, Epoch::Application] {
475            let epoch = &self.epochs[e];
476            if epoch.in_flight_count == 0 {
477                continue;
478            }
479
480            if e == Epoch::Application {
481                // Skip Application Data until handshake completes.
482                if !handshake_status.completed {
483                    return (pto_timeout, pto_space);
484                }
485
486                // Include max_ack_delay and backoff for Application Data.
487                duration += self.rtt_stats.max_ack_delay *
488                    2_u32.saturating_pow(self.pto_count);
489            }
490
491            let new_time = epoch
492                .time_of_last_ack_eliciting_packet
493                .map(|t| t + duration);
494
495            if pto_timeout.is_none() || new_time < pto_timeout {
496                pto_timeout = new_time;
497                pto_space = e;
498            }
499        }
500
501        (pto_timeout, pto_space)
502    }
503
504    fn set_loss_detection_timer(
505        &mut self, handshake_status: HandshakeStatus, now: Instant,
506    ) {
507        let (earliest_loss_time, _) = self.loss_time_and_space();
508
509        if let Some(to) = earliest_loss_time {
510            // Time threshold loss detection.
511            self.loss_timer.update(to);
512            return;
513        }
514
515        if self.bytes_in_flight.is_zero() &&
516            handshake_status.peer_verified_address
517        {
518            self.loss_timer.clear();
519            return;
520        }
521
522        // PTO timer.
523        if let (Some(timeout), _) = self.pto_time_and_space(handshake_status, now)
524        {
525            self.loss_timer.update(timeout);
526        } else {
527            self.loss_timer.clear();
528        }
529    }
530
531    fn detect_lost_packets(
532        &mut self, epoch: Epoch, now: Instant, trace_id: &str,
533    ) -> (usize, usize) {
534        let loss_delay = cmp::max(self.rtt_stats.latest_rtt, self.rtt())
535            .mul_f64(self.time_thresh);
536
537        let loss = self.epochs[epoch].detect_lost_packets(
538            loss_delay,
539            self.pkt_thresh,
540            now,
541            trace_id,
542            epoch,
543        );
544
545        if let Some(pkt) = loss.largest_lost_pkt {
546            if !self.congestion.in_congestion_recovery(pkt.time_sent) {
547                (self.congestion.cc_ops.checkpoint)(&mut self.congestion);
548            }
549
550            (self.congestion.cc_ops.congestion_event)(
551                &mut self.congestion,
552                self.bytes_in_flight.get(),
553                loss.lost_bytes,
554                &pkt,
555                now,
556            );
557
558            self.bytes_in_flight
559                .saturating_subtract(loss.lost_bytes, now);
560        };
561
562        self.bytes_in_flight
563            .saturating_subtract(loss.pmtud_lost_bytes, now);
564
565        self.epochs[epoch]
566            .drain_acked_and_lost_packets(now - self.rtt_stats.rtt());
567
568        self.congestion.lost_count += loss.lost_packets;
569
570        (loss.lost_packets, loss.lost_bytes)
571    }
572}
573
574impl RecoveryOps for LegacyRecovery {
575    /// Returns whether or not we should elicit an ACK even if we wouldn't
576    /// otherwise have constructed an ACK eliciting packet.
577    fn should_elicit_ack(&self, epoch: Epoch) -> bool {
578        self.epochs[epoch].loss_probes > 0 ||
579            self.outstanding_non_ack_eliciting >=
580                MAX_OUTSTANDING_NON_ACK_ELICITING
581    }
582
583    fn next_acked_frame(&mut self, epoch: Epoch) -> Option<frame::Frame> {
584        self.epochs[epoch].acked_frames.pop()
585    }
586
587    fn next_lost_frame(&mut self, epoch: Epoch) -> Option<frame::Frame> {
588        self.epochs[epoch].next_lost_frame()
589    }
590
591    fn get_largest_acked_on_epoch(&self, epoch: Epoch) -> Option<u64> {
592        self.epochs[epoch].largest_acked_packet
593    }
594
595    fn has_lost_frames(&self, epoch: Epoch) -> bool {
596        self.epochs[epoch].has_lost_frames()
597    }
598
599    fn loss_probes(&self, epoch: Epoch) -> usize {
600        self.epochs[epoch].loss_probes
601    }
602
603    #[cfg(test)]
604    fn inc_loss_probes(&mut self, epoch: Epoch) {
605        self.epochs[epoch].loss_probes += 1;
606    }
607
608    #[cfg(test)]
609    fn lost_frames_count(&self, epoch: Epoch) -> usize {
610        self.epochs[epoch].lost_frames_count()
611    }
612
613    fn ping_sent(&mut self, epoch: Epoch) {
614        self.epochs[epoch].loss_probes =
615            self.epochs[epoch].loss_probes.saturating_sub(1);
616    }
617
618    fn on_packet_sent(
619        &mut self, mut pkt: Sent, epoch: Epoch,
620        handshake_status: HandshakeStatus, now: Instant, trace_id: &str,
621    ) {
622        let ack_eliciting = pkt.ack_eliciting;
623        let in_flight = pkt.in_flight;
624        let sent_bytes = pkt.size;
625
626        if ack_eliciting {
627            self.outstanding_non_ack_eliciting = 0;
628        } else {
629            self.outstanding_non_ack_eliciting += 1;
630        }
631
632        if in_flight && ack_eliciting {
633            self.epochs[epoch].time_of_last_ack_eliciting_packet = Some(now);
634        }
635
636        self.congestion.on_packet_sent(
637            self.bytes_in_flight.get(),
638            sent_bytes,
639            now,
640            &mut pkt,
641            self.bytes_lost,
642            in_flight,
643        );
644
645        if in_flight {
646            self.epochs[epoch].in_flight_count += 1;
647            self.bytes_in_flight.add(sent_bytes, now);
648
649            self.set_loss_detection_timer(handshake_status, now);
650        }
651
652        self.bytes_sent += sent_bytes;
653
654        #[cfg(test)]
655        {
656            self.epochs[epoch].test_largest_sent_pkt_num_on_path = self.epochs
657                [epoch]
658                .test_largest_sent_pkt_num_on_path
659                .max(Some(pkt.pkt_num));
660        }
661
662        self.epochs[epoch].sent_packets.push_back(pkt);
663
664        trace!("{trace_id} {self:?}");
665    }
666
667    fn get_packet_send_time(&self, now: Instant) -> Instant {
668        now
669    }
670
671    // `peer_sent_ack_ranges` should not be used without validation.
672    fn on_ack_received(
673        &mut self, peer_sent_ack_ranges: &RangeSet, ack_delay: u64, epoch: Epoch,
674        handshake_status: HandshakeStatus, now: Instant, skip_pn: Option<u64>,
675        trace_id: &str,
676    ) -> Result<OnAckReceivedOutcome> {
677        let AckedDetectionResult {
678            acked_bytes,
679            spurious_losses,
680            spurious_pkt_thresh,
681            has_ack_eliciting,
682            has_in_flight_spurious_loss,
683        } = self.epochs[epoch].detect_and_remove_acked_packets(
684            now,
685            peer_sent_ack_ranges,
686            &mut self.newly_acked,
687            &self.rtt_stats,
688            skip_pn,
689            trace_id,
690        )?;
691
692        self.lost_spurious_count += spurious_losses;
693        if let Some(thresh) = spurious_pkt_thresh {
694            self.pkt_thresh =
695                self.pkt_thresh.max(thresh.min(MAX_PACKET_THRESHOLD));
696            self.time_thresh = PACKET_REORDER_TIME_THRESHOLD;
697        }
698
699        // Undo congestion window update.
700        if has_in_flight_spurious_loss {
701            (self.congestion.cc_ops.rollback)(&mut self.congestion);
702        }
703
704        if self.newly_acked.is_empty() {
705            return Ok(OnAckReceivedOutcome::default());
706        }
707
708        let largest_newly_acked = self.newly_acked.last().unwrap();
709
710        // Update `largest_acked_packet` based on the validated `newly_acked`
711        // value.
712        let largest_acked_pkt_num = self.epochs[epoch]
713            .largest_acked_packet
714            .unwrap_or(0)
715            .max(largest_newly_acked.pkt_num);
716        self.epochs[epoch].largest_acked_packet = Some(largest_acked_pkt_num);
717
718        // Check if largest packet is newly acked.
719        if largest_newly_acked.pkt_num == largest_acked_pkt_num &&
720            has_ack_eliciting
721        {
722            let latest_rtt = now - largest_newly_acked.time_sent;
723            self.rtt_stats.update_rtt(
724                latest_rtt,
725                Duration::from_micros(ack_delay),
726                now,
727                handshake_status.completed,
728            );
729        }
730
731        // Detect and mark lost packets without removing them from the sent
732        // packets list.
733        let (lost_packets, lost_bytes) =
734            self.detect_lost_packets(epoch, now, trace_id);
735
736        self.congestion.on_packets_acked(
737            self.bytes_in_flight.get(),
738            &mut self.newly_acked,
739            &self.rtt_stats,
740            now,
741        );
742
743        self.bytes_in_flight.saturating_subtract(acked_bytes, now);
744
745        self.pto_count = 0;
746
747        self.set_loss_detection_timer(handshake_status, now);
748
749        self.epochs[epoch]
750            .drain_acked_and_lost_packets(now - self.rtt_stats.rtt());
751
752        Ok(OnAckReceivedOutcome {
753            lost_packets,
754            lost_bytes,
755            acked_bytes,
756            spurious_losses,
757        })
758    }
759
760    fn on_loss_detection_timeout(
761        &mut self, handshake_status: HandshakeStatus, now: Instant,
762        trace_id: &str,
763    ) -> OnLossDetectionTimeoutOutcome {
764        let (earliest_loss_time, epoch) = self.loss_time_and_space();
765
766        if earliest_loss_time.is_some() {
767            // Time threshold loss detection.
768            let (lost_packets, lost_bytes) =
769                self.detect_lost_packets(epoch, now, trace_id);
770
771            self.set_loss_detection_timer(handshake_status, now);
772
773            trace!("{trace_id} {self:?}");
774            return OnLossDetectionTimeoutOutcome {
775                lost_packets,
776                lost_bytes,
777            };
778        }
779
780        let epoch = if self.bytes_in_flight.get() > 0 {
781            // Send new data if available, else retransmit old data. If neither
782            // is available, send a single PING frame.
783            let (_, e) = self.pto_time_and_space(handshake_status, now);
784
785            e
786        } else {
787            // Client sends an anti-deadlock packet: Initial is padded to earn
788            // more anti-amplification credit, a Handshake packet proves address
789            // ownership.
790            if handshake_status.has_handshake_keys {
791                Epoch::Handshake
792            } else {
793                Epoch::Initial
794            }
795        };
796
797        self.pto_count += 1;
798
799        let epoch = &mut self.epochs[epoch];
800
801        epoch.loss_probes =
802            cmp::min(self.pto_count as usize, MAX_PTO_PROBES_COUNT);
803
804        let sent_packets_iter_limit = if !epoch.lost_frames_pto.is_empty() {
805            // Skip the search for frames to add to PTO probes if frames
806            // added in a prior PTO haven't been processed yet.
807            0
808        } else {
809            usize::MAX
810        };
811
812        let unacked_iter = epoch.sent_packets
813            .iter()
814            .take(sent_packets_iter_limit)
815            // Skip packets that have already been acked or lost, and packets
816            // that don't contain either CRYPTO or STREAM frames.
817            .filter(|p| p.has_data && p.time_acked.is_none() && p.time_lost.is_none())
818            // Only return as many packets as the number of probe packets that
819            // will be sent.
820            .take(epoch.loss_probes);
821
822        // Retransmit the frames from the oldest sent packets on PTO. However
823        // the packets are not actually declared lost (so there is no effect to
824        // congestion control), we just reschedule the data they carried.
825        //
826        // This will also trigger sending an ACK and retransmitting frames like
827        // HANDSHAKE_DONE and MAX_DATA / MAX_STREAM_DATA as well, in addition
828        // to CRYPTO and STREAM, if the original packet carried them.
829        for unacked in unacked_iter {
830            epoch.lost_frames_pto.extend_from_slice(&unacked.frames);
831        }
832
833        self.set_loss_detection_timer(handshake_status, now);
834
835        trace!("{trace_id} {self:?}");
836
837        OnLossDetectionTimeoutOutcome {
838            lost_packets: 0,
839            lost_bytes: 0,
840        }
841    }
842
843    fn on_pkt_num_space_discarded(
844        &mut self, epoch: Epoch, handshake_status: HandshakeStatus, now: Instant,
845    ) {
846        let epoch = &mut self.epochs[epoch];
847
848        let unacked_bytes = epoch
849            .sent_packets
850            .iter()
851            .filter(|p| {
852                p.in_flight && p.time_acked.is_none() && p.time_lost.is_none()
853            })
854            .fold(0, |acc, p| acc + p.size);
855
856        self.bytes_in_flight.saturating_subtract(unacked_bytes, now);
857
858        epoch.sent_packets.clear();
859        epoch.clear_lost_frames();
860        epoch.acked_frames.clear();
861
862        epoch.time_of_last_ack_eliciting_packet = None;
863        epoch.loss_time = None;
864        epoch.loss_probes = 0;
865        epoch.in_flight_count = 0;
866
867        self.set_loss_detection_timer(handshake_status, now);
868    }
869
870    fn on_path_change(
871        &mut self, epoch: Epoch, now: Instant, trace_id: &str,
872    ) -> (usize, usize) {
873        // Time threshold loss detection.
874        self.detect_lost_packets(epoch, now, trace_id)
875    }
876
877    fn loss_detection_timer(&self) -> Option<Instant> {
878        self.loss_timer.time
879    }
880
881    fn cwnd(&self) -> usize {
882        self.congestion.congestion_window()
883    }
884
885    fn cwnd_available(&self) -> usize {
886        // Ignore cwnd when sending probe packets.
887        if self.epochs.iter().any(|e| e.loss_probes > 0) {
888            return usize::MAX;
889        }
890
891        // Open more space (snd_cnt) for PRR when allowed.
892        self.cwnd().saturating_sub(self.bytes_in_flight.get()) +
893            self.congestion.prr.snd_cnt
894    }
895
896    fn rtt(&self) -> Duration {
897        self.rtt_stats.rtt()
898    }
899
900    fn min_rtt(&self) -> Option<Duration> {
901        self.rtt_stats.min_rtt()
902    }
903
904    fn max_rtt(&self) -> Option<Duration> {
905        self.rtt_stats.max_rtt()
906    }
907
908    fn rttvar(&self) -> Duration {
909        self.rtt_stats.rttvar
910    }
911
912    fn pto(&self) -> Duration {
913        self.rtt() + cmp::max(self.rtt_stats.rttvar * 4, GRANULARITY)
914    }
915
916    /// The most recent data delivery rate estimate.
917    fn delivery_rate(&self) -> Bandwidth {
918        self.congestion.delivery_rate()
919    }
920
921    fn max_bandwidth(&self) -> Option<Bandwidth> {
922        // TODO implement
923        None
924    }
925
926    fn rtt_persistent_jump_count(&self) -> u64 {
927        // Persistent RTT jump counts are produced by the BBR2 RTT jump
928        // detector. Legacy Reno/CUBIC recovery does not own a BBR2 network
929        // model or run that detector, but it still implements
930        // RecoveryOps so PathStats can be populated through one shared
931        // interface. Report zero to indicate that no detector is active on
932        // this path.
933        0
934    }
935
936    /// Statistics from when a CCA first exited the startup phase.
937    fn startup_exit(&self) -> Option<StartupExit> {
938        self.congestion.ssthresh.startup_exit()
939    }
940
941    fn max_datagram_size(&self) -> usize {
942        self.max_datagram_size
943    }
944
945    fn pmtud_update_max_datagram_size(&mut self, new_max_datagram_size: usize) {
946        // Congestion Window is updated only when it's not updated already.
947        // Update cwnd if it hasn't been updated yet.
948        if self.cwnd() ==
949            self.max_datagram_size *
950                self.congestion.initial_congestion_window_packets
951        {
952            self.congestion.congestion_window = new_max_datagram_size *
953                self.congestion.initial_congestion_window_packets;
954        }
955
956        self.max_datagram_size = new_max_datagram_size;
957    }
958
959    fn update_max_datagram_size(&mut self, new_max_datagram_size: usize) {
960        self.pmtud_update_max_datagram_size(
961            self.max_datagram_size.min(new_max_datagram_size),
962        )
963    }
964
965    #[cfg(test)]
966    fn sent_packets_len(&self, epoch: Epoch) -> usize {
967        self.epochs[epoch].sent_packets.len()
968    }
969
970    #[cfg(test)]
971    fn in_flight_count(&self, epoch: Epoch) -> usize {
972        self.epochs[epoch].in_flight_count
973    }
974
975    fn bytes_in_flight(&self) -> usize {
976        self.bytes_in_flight.get()
977    }
978
979    fn bytes_in_flight_duration(&self) -> Duration {
980        self.bytes_in_flight.get_duration()
981    }
982
983    #[cfg(test)]
984    fn pacing_rate(&self) -> u64 {
985        0
986    }
987
988    #[cfg(test)]
989    fn pto_count(&self) -> u32 {
990        self.pto_count
991    }
992
993    #[cfg(test)]
994    fn pkt_thresh(&self) -> Option<u64> {
995        Some(self.pkt_thresh)
996    }
997
998    #[cfg(test)]
999    fn time_thresh(&self) -> f64 {
1000        self.time_thresh
1001    }
1002
1003    #[cfg(test)]
1004    fn lost_spurious_count(&self) -> usize {
1005        self.lost_spurious_count
1006    }
1007
1008    #[cfg(test)]
1009    fn detect_lost_packets_for_test(
1010        &mut self, epoch: Epoch, now: Instant,
1011    ) -> (usize, usize) {
1012        self.detect_lost_packets(epoch, now, "")
1013    }
1014
1015    // FIXME only used by gcongestion
1016    fn on_app_limited(&mut self) {
1017        // Not implemented for legacy recovery, update_app_limited and
1018        // delivery_rate_update_app_limited used instead.
1019    }
1020
1021    #[cfg(test)]
1022    fn largest_sent_pkt_num_on_path(&self, epoch: Epoch) -> Option<u64> {
1023        self.epochs[epoch].test_largest_sent_pkt_num_on_path
1024    }
1025
1026    #[cfg(any(test, feature = "qlog"))]
1027    fn app_limited(&self) -> bool {
1028        self.congestion.app_limited
1029    }
1030
1031    fn update_app_limited(&mut self, v: bool) {
1032        self.congestion.update_app_limited(v);
1033    }
1034
1035    // FIXME only used by congestion
1036    fn delivery_rate_update_app_limited(&mut self, v: bool) {
1037        self.congestion.delivery_rate.update_app_limited(v);
1038    }
1039
1040    // FIXME only used by congestion
1041    fn update_max_ack_delay(&mut self, max_ack_delay: Duration) {
1042        self.rtt_stats.max_ack_delay = max_ack_delay;
1043    }
1044
1045    #[cfg(feature = "qlog")]
1046    fn state_str(&self, now: Instant) -> &'static str {
1047        (self.congestion.cc_ops.state_str)(&self.congestion, now)
1048    }
1049
1050    #[cfg(feature = "qlog")]
1051    fn get_updated_qlog_event_data(&mut self) -> Option<EventData> {
1052        let qlog_metrics = QlogMetrics {
1053            min_rtt: *self.rtt_stats.min_rtt,
1054            smoothed_rtt: self.rtt(),
1055            latest_rtt: self.rtt_stats.latest_rtt,
1056            rttvar: self.rtt_stats.rttvar,
1057            cwnd: self.cwnd() as u64,
1058            bytes_in_flight: self.bytes_in_flight.get() as u64,
1059            ssthresh: Some(self.congestion.ssthresh.get() as u64),
1060            lost_packets: Some(self.congestion.lost_count as u64),
1061            lost_bytes: Some(self.bytes_lost),
1062            pto_count: Some(self.pto_count),
1063            app_limited: Some(self.app_limited()),
1064            ..Default::default()
1065        };
1066
1067        self.qlog_metrics.maybe_update(qlog_metrics)
1068    }
1069
1070    #[cfg(feature = "qlog")]
1071    fn get_updated_qlog_cc_state(
1072        &mut self, now: Instant,
1073    ) -> Option<&'static str> {
1074        let cc_state = self.state_str(now);
1075        if cc_state != self.qlog_prev_cc_state {
1076            self.qlog_prev_cc_state = cc_state;
1077            Some(cc_state)
1078        } else {
1079            None
1080        }
1081    }
1082
1083    fn send_quantum(&self) -> usize {
1084        self.congestion.send_quantum()
1085    }
1086
1087    fn get_next_release_time(&self) -> ReleaseDecision {
1088        ReleaseDecision {
1089            time: ReleaseTime::Immediate,
1090            allow_burst: false,
1091        }
1092    }
1093
1094    fn gcongestion_enabled(&self) -> bool {
1095        false
1096    }
1097
1098    fn lost_count(&self) -> usize {
1099        self.congestion.lost_count
1100    }
1101
1102    fn bytes_lost(&self) -> u64 {
1103        self.bytes_lost
1104    }
1105}
1106
1107impl std::fmt::Debug for LegacyRecovery {
1108    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
1109        write!(f, "timer={:?} ", self.loss_timer)?;
1110        write!(f, "latest_rtt={:?} ", self.rtt_stats.latest_rtt)?;
1111        write!(f, "srtt={:?} ", self.rtt_stats.smoothed_rtt)?;
1112        write!(f, "min_rtt={:?} ", *self.rtt_stats.min_rtt)?;
1113        write!(f, "rttvar={:?} ", self.rtt_stats.rttvar)?;
1114        write!(f, "cwnd={} ", self.cwnd())?;
1115        write!(f, "ssthresh={} ", self.congestion.ssthresh.get())?;
1116        write!(f, "bytes_in_flight={} ", self.bytes_in_flight.get())?;
1117        write!(f, "app_limited={} ", self.congestion.app_limited)?;
1118        write!(
1119            f,
1120            "congestion_recovery_start_time={:?} ",
1121            self.congestion.congestion_recovery_start_time
1122        )?;
1123        write!(f, "{:?} ", self.congestion.delivery_rate)?;
1124
1125        if self.congestion.hystart.enabled() {
1126            write!(f, "hystart={:?} ", self.congestion.hystart)?;
1127        }
1128
1129        // CC-specific debug info
1130        (self.congestion.cc_ops.debug_fmt)(&self.congestion, f)?;
1131
1132        Ok(())
1133    }
1134}
1135
1136#[derive(Clone)]
1137pub struct Acked {
1138    pub pkt_num: u64,
1139
1140    pub time_sent: Instant,
1141
1142    pub size: usize,
1143
1144    pub rtt: Duration,
1145
1146    pub delivered: usize,
1147
1148    pub delivered_time: Instant,
1149
1150    pub first_sent_time: Instant,
1151
1152    pub is_app_limited: bool,
1153}
1154
1155#[cfg(test)]
1156mod tests {
1157    use super::*;
1158    use crate::recovery::HandshakeStatus;
1159    use crate::recovery::RecoveryConfig;
1160    use std::time::Instant;
1161
1162    #[test]
1163    fn test_high_pto_count_no_panic() {
1164        let config = crate::Config::new(crate::PROTOCOL_VERSION).unwrap();
1165        let recovery_config = RecoveryConfig::from_config(&config);
1166        let mut r = LegacyRecovery::new_with_config(&recovery_config);
1167
1168        r.pto_count = 99999;
1169
1170        let handshake_status = HandshakeStatus {
1171            completed: true,
1172            has_handshake_keys: true,
1173            peer_verified_address: true,
1174        };
1175        let now = Instant::now();
1176
1177        let _ = r.pto_time_and_space(handshake_status, now);
1178    }
1179}