Coverage Report

Created: 2026-02-14 06:08

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/quiche/quiche/src/recovery/gcongestion/recovery.rs
Line
Count
Source
1
use crate::packet;
2
use crate::recovery::OnLossDetectionTimeoutOutcome;
3
use crate::recovery::INITIAL_TIME_THRESHOLD_OVERHEAD;
4
use crate::recovery::TIME_THRESHOLD_OVERHEAD_MULTIPLIER;
5
use crate::Error;
6
use crate::Result;
7
8
use std::collections::VecDeque;
9
use std::time::Duration;
10
use std::time::Instant;
11
12
use smallvec::SmallVec;
13
14
#[cfg(feature = "qlog")]
15
use qlog::events::EventData;
16
17
#[cfg(feature = "qlog")]
18
use crate::recovery::QlogMetrics;
19
20
use crate::frame;
21
22
use crate::recovery::bytes_in_flight::BytesInFlight;
23
use crate::recovery::gcongestion::Bandwidth;
24
use crate::recovery::rtt::RttStats;
25
use crate::recovery::CongestionControlAlgorithm;
26
use crate::recovery::HandshakeStatus;
27
use crate::recovery::LossDetectionTimer;
28
use crate::recovery::OnAckReceivedOutcome;
29
use crate::recovery::RangeSet;
30
use crate::recovery::RecoveryConfig;
31
use crate::recovery::RecoveryOps;
32
use crate::recovery::RecoveryStats;
33
use crate::recovery::ReleaseDecision;
34
use crate::recovery::Sent;
35
use crate::recovery::StartupExit;
36
use crate::recovery::GRANULARITY;
37
use crate::recovery::INITIAL_PACKET_THRESHOLD;
38
use crate::recovery::INITIAL_TIME_THRESHOLD;
39
use crate::recovery::MAX_OUTSTANDING_NON_ACK_ELICITING;
40
use crate::recovery::MAX_PACKET_THRESHOLD;
41
use crate::recovery::MAX_PTO_PROBES_COUNT;
42
use crate::recovery::PACKET_REORDER_TIME_THRESHOLD;
43
44
use super::bbr2::BBRv2;
45
use super::pacer::Pacer;
46
use super::Acked;
47
use super::Lost;
48
49
// Congestion Control
50
const MAX_WINDOW_PACKETS: usize = 20_000;
51
52
#[derive(Debug)]
53
struct SentPacket {
54
    pkt_num: u64,
55
    status: SentStatus,
56
}
57
58
#[derive(Debug)]
59
enum SentStatus {
60
    Sent {
61
        time_sent: Instant,
62
        ack_eliciting: bool,
63
        in_flight: bool,
64
        has_data: bool,
65
        is_pmtud_probe: bool,
66
        sent_bytes: usize,
67
        frames: SmallVec<[frame::Frame; 1]>,
68
    },
69
    Acked,
70
    Lost,
71
}
72
73
impl SentStatus {
74
0
    fn ack(&mut self) -> Self {
75
0
        std::mem::replace(self, SentStatus::Acked)
76
0
    }
77
78
0
    fn lose(&mut self) -> Self {
79
0
        if !matches!(self, SentStatus::Acked) {
80
0
            std::mem::replace(self, SentStatus::Lost)
81
        } else {
82
0
            SentStatus::Acked
83
        }
84
0
    }
85
}
86
87
#[derive(Default)]
88
struct RecoveryEpoch {
89
    /// The time the most recent ack-eliciting packet was sent.
90
    time_of_last_ack_eliciting_packet: Option<Instant>,
91
92
    /// The largest packet number acknowledged in the packet number space so
93
    /// far.
94
    largest_acked_packet: Option<u64>,
95
96
    /// The time at which the next packet in that packet number space can be
97
    /// considered lost based on exceeding the reordering window in time.
98
    loss_time: Option<Instant>,
99
100
    /// An association of packet numbers in a packet number space to information
101
    /// about them.
102
    sent_packets: VecDeque<SentPacket>,
103
104
    loss_probes: usize,
105
    pkts_in_flight: usize,
106
107
    acked_frames: VecDeque<frame::Frame>,
108
    lost_frames: VecDeque<frame::Frame>,
109
110
    /// The largest packet number sent in the packet number space so far.
111
    #[allow(dead_code)]
112
    test_largest_sent_pkt_num_on_path: Option<u64>,
113
}
114
115
struct AckedDetectionResult {
116
    acked_bytes: usize,
117
    spurious_losses: usize,
118
    spurious_pkt_thresh: Option<u64>,
119
    has_ack_eliciting: bool,
120
}
121
122
struct LossDetectionResult {
123
    lost_bytes: usize,
124
    lost_packets: usize,
125
126
    pmtud_lost_bytes: usize,
127
    pmtud_lost_packets: SmallVec<[u64; 1]>,
128
}
129
130
impl RecoveryEpoch {
131
    /// Discard the Epoch state and return the total size of unacked packets
132
    /// that were discarded
133
0
    fn discard(&mut self, cc: &mut Pacer) -> usize {
134
0
        let unacked_bytes = self
135
0
            .sent_packets
136
0
            .drain(..)
137
0
            .map(|p| {
138
                if let SentPacket {
139
                    status:
140
                        SentStatus::Sent {
141
0
                            in_flight,
142
0
                            sent_bytes,
143
                            ..
144
                        },
145
0
                    pkt_num,
146
0
                } = p
147
                {
148
0
                    cc.on_packet_neutered(pkt_num);
149
0
                    if in_flight {
150
0
                        return sent_bytes;
151
0
                    }
152
0
                }
153
0
                0
154
0
            })
155
0
            .sum();
156
157
0
        std::mem::take(&mut self.sent_packets);
158
0
        self.time_of_last_ack_eliciting_packet = None;
159
0
        self.loss_time = None;
160
0
        self.loss_probes = 0;
161
0
        self.pkts_in_flight = 0;
162
163
0
        unacked_bytes
164
0
    }
165
166
    // `peer_sent_ack_ranges` should not be used without validation.
167
0
    fn detect_and_remove_acked_packets(
168
0
        &mut self, peer_sent_ack_ranges: &RangeSet, newly_acked: &mut Vec<Acked>,
169
0
        skip_pn: Option<u64>, trace_id: &str,
170
0
    ) -> Result<AckedDetectionResult> {
171
0
        newly_acked.clear();
172
173
0
        let mut acked_bytes = 0;
174
0
        let mut spurious_losses = 0;
175
0
        let mut spurious_pkt_thresh = None;
176
0
        let mut has_ack_eliciting = false;
177
178
0
        let largest_ack_received = peer_sent_ack_ranges.last().unwrap();
179
0
        let largest_acked = self
180
0
            .largest_acked_packet
181
0
            .unwrap_or(0)
182
0
            .max(largest_ack_received);
183
184
0
        for peer_sent_range in peer_sent_ack_ranges.iter() {
185
0
            if skip_pn.is_some_and(|skip_pn| peer_sent_range.contains(&skip_pn)) {
186
                // https://www.rfc-editor.org/rfc/rfc9000#section-13.1
187
                // An endpoint SHOULD treat receipt of an acknowledgment
188
                // for a packet it did not send as
189
                // a connection error of type PROTOCOL_VIOLATION
190
0
                return Err(Error::OptimisticAckDetected);
191
0
            }
192
193
            // Because packets always have incrementing numbers, they are always
194
            // in sorted order.
195
0
            let start = if self
196
0
                .sent_packets
197
0
                .front()
198
0
                .filter(|e| e.pkt_num >= peer_sent_range.start)
199
0
                .is_some()
200
            {
201
                // Usually it will be the first packet.
202
0
                0
203
            } else {
204
0
                self.sent_packets
205
0
                    .binary_search_by_key(&peer_sent_range.start, |p| p.pkt_num)
206
0
                    .unwrap_or_else(|e| e)
207
            };
208
209
0
            for SentPacket { pkt_num, status } in
210
0
                self.sent_packets.range_mut(start..)
211
            {
212
0
                if *pkt_num < peer_sent_range.end {
213
0
                    match status.ack() {
214
                        SentStatus::Sent {
215
0
                            time_sent,
216
0
                            in_flight,
217
0
                            sent_bytes,
218
0
                            frames,
219
0
                            ack_eliciting,
220
                            ..
221
                        } => {
222
0
                            if in_flight {
223
0
                                self.pkts_in_flight -= 1;
224
0
                                acked_bytes += sent_bytes;
225
0
                            }
226
0
                            newly_acked.push(Acked {
227
0
                                pkt_num: *pkt_num,
228
0
                                time_sent,
229
0
                            });
230
231
0
                            self.acked_frames.extend(frames);
232
233
0
                            has_ack_eliciting |= ack_eliciting;
234
235
0
                            trace!("{trace_id} packet newly acked {pkt_num}");
236
                        },
237
238
0
                        SentStatus::Acked => {},
239
0
                        SentStatus::Lost => {
240
0
                            // An acked packet was already declared lost
241
0
                            spurious_losses += 1;
242
0
                            spurious_pkt_thresh
243
0
                                .get_or_insert(largest_acked - *pkt_num + 1);
244
0
                        },
245
                    }
246
                } else {
247
0
                    break;
248
                }
249
            }
250
        }
251
252
0
        self.drain_acked_and_lost_packets();
253
254
0
        Ok(AckedDetectionResult {
255
0
            acked_bytes,
256
0
            spurious_losses,
257
0
            spurious_pkt_thresh,
258
0
            has_ack_eliciting,
259
0
        })
260
0
    }
261
262
0
    fn detect_and_remove_lost_packets(
263
0
        &mut self, loss_delay: Duration, pkt_thresh: Option<u64>, now: Instant,
264
0
        newly_lost: &mut Vec<Lost>,
265
0
    ) -> LossDetectionResult {
266
0
        newly_lost.clear();
267
0
        let mut lost_bytes = 0;
268
0
        self.loss_time = None;
269
270
0
        let lost_send_time = now.checked_sub(loss_delay).unwrap();
271
0
        let largest_acked = self.largest_acked_packet.unwrap_or(0);
272
0
        let mut pmtud_lost_bytes = 0;
273
0
        let mut pmtud_lost_packets = SmallVec::new();
274
275
0
        for SentPacket { pkt_num, status } in &mut self.sent_packets {
276
0
            if *pkt_num > largest_acked {
277
0
                break;
278
0
            }
279
280
0
            if let SentStatus::Sent { time_sent, .. } = status {
281
0
                let loss_by_time = *time_sent <= lost_send_time;
282
0
                let loss_by_pkt = match pkt_thresh {
283
0
                    Some(pkt_thresh) => largest_acked >= *pkt_num + pkt_thresh,
284
0
                    None => false,
285
                };
286
287
0
                if loss_by_time || loss_by_pkt {
288
                    if let SentStatus::Sent {
289
0
                        in_flight,
290
0
                        sent_bytes,
291
0
                        frames,
292
0
                        is_pmtud_probe,
293
                        ..
294
0
                    } = status.lose()
295
                    {
296
0
                        self.lost_frames.extend(frames);
297
298
0
                        if in_flight {
299
0
                            self.pkts_in_flight -= 1;
300
301
0
                            if is_pmtud_probe {
302
0
                                pmtud_lost_bytes += sent_bytes;
303
0
                                pmtud_lost_packets.push(*pkt_num);
304
                                // Do not track PMTUD probes losses
305
0
                                continue;
306
0
                            }
307
308
0
                            lost_bytes += sent_bytes;
309
0
                        }
310
311
0
                        newly_lost.push(Lost {
312
0
                            packet_number: *pkt_num,
313
0
                            bytes_lost: sent_bytes,
314
0
                        });
315
0
                    }
316
                } else {
317
0
                    self.loss_time = Some(*time_sent + loss_delay);
318
0
                    break;
319
                }
320
0
            }
321
        }
322
323
0
        LossDetectionResult {
324
0
            lost_bytes,
325
0
            lost_packets: newly_lost.len(),
326
0
327
0
            pmtud_lost_bytes,
328
0
            pmtud_lost_packets,
329
0
        }
330
0
    }
331
332
    /// Remove packets that were already handled from the front of the queue,
333
    /// but avoid removing packets from the middle of the queue to avoid
334
    /// compaction
335
0
    fn drain_acked_and_lost_packets(&mut self) {
336
        while let Some(SentPacket {
337
            status: SentStatus::Acked | SentStatus::Lost,
338
            ..
339
0
        }) = self.sent_packets.front()
340
0
        {
341
0
            self.sent_packets.pop_front();
342
0
        }
343
0
    }
344
345
0
    fn least_unacked(&self) -> u64 {
346
0
        for pkt in self.sent_packets.iter() {
347
            if let SentPacket {
348
0
                pkt_num,
349
                status: SentStatus::Sent { .. },
350
0
            } = pkt
351
            {
352
0
                return *pkt_num;
353
0
            }
354
        }
355
356
0
        self.largest_acked_packet.unwrap_or(0) + 1
357
0
    }
358
}
359
360
struct LossThreshold {
361
    pkt_thresh: Option<u64>,
362
    time_thresh: f64,
363
364
    // # Experiment: enable_relaxed_loss_threshold
365
    //
366
    // If `Some` this will disable pkt_thresh on the first loss and then double
367
    // time_thresh on subsequent loss.
368
    //
369
    // The actual threshold is calcualted as `1.0 +
370
    // INITIAL_TIME_THRESHOLD_OVERHEAD` and equivalent to the initial value
371
    // of INITIAL_TIME_THRESHOLD.
372
    time_thresh_overhead: Option<f64>,
373
}
374
375
impl LossThreshold {
376
0
    fn new(recovery_config: &RecoveryConfig) -> Self {
377
0
        let time_thresh_overhead =
378
0
            if recovery_config.enable_relaxed_loss_threshold {
379
0
                Some(INITIAL_TIME_THRESHOLD_OVERHEAD)
380
            } else {
381
0
                None
382
            };
383
0
        LossThreshold {
384
0
            pkt_thresh: Some(INITIAL_PACKET_THRESHOLD),
385
0
            time_thresh: INITIAL_TIME_THRESHOLD,
386
0
            time_thresh_overhead,
387
0
        }
388
0
    }
389
390
0
    fn pkt_thresh(&self) -> Option<u64> {
391
0
        self.pkt_thresh
392
0
    }
393
394
0
    fn time_thresh(&self) -> f64 {
395
0
        self.time_thresh
396
0
    }
397
398
0
    fn on_spurious_loss(&mut self, new_pkt_thresh: u64) {
399
0
        match &mut self.time_thresh_overhead {
400
0
            Some(time_thresh_overhead) => {
401
0
                if self.pkt_thresh.is_some() {
402
0
                    // Disable packet threshold on first spurious loss.
403
0
                    self.pkt_thresh = None;
404
0
                } else {
405
0
                    // Double time threshold but cap it at `1.0`, which ends up
406
0
                    // being 2x the RTT.
407
0
                    *time_thresh_overhead *= TIME_THRESHOLD_OVERHEAD_MULTIPLIER;
408
0
                    *time_thresh_overhead = time_thresh_overhead.min(1.0);
409
0
410
0
                    self.time_thresh = 1.0 + *time_thresh_overhead;
411
0
                }
412
            },
413
0
            None => {
414
0
                let new_packet_threshold = self
415
0
                    .pkt_thresh
416
0
                    .expect("packet threshold should always be Some when `enable_relaxed_loss_threshold` is false")
417
0
                    .max(new_pkt_thresh.min(MAX_PACKET_THRESHOLD));
418
0
                self.pkt_thresh = Some(new_packet_threshold);
419
0
420
0
                self.time_thresh = PACKET_REORDER_TIME_THRESHOLD;
421
0
            },
422
        }
423
0
    }
424
}
425
426
pub struct GRecovery {
427
    epochs: [RecoveryEpoch; packet::Epoch::count()],
428
429
    loss_timer: LossDetectionTimer,
430
431
    pto_count: u32,
432
433
    rtt_stats: RttStats,
434
435
    recovery_stats: RecoveryStats,
436
437
    pub lost_count: usize,
438
439
    pub lost_spurious_count: usize,
440
441
    loss_thresh: LossThreshold,
442
443
    bytes_in_flight: BytesInFlight,
444
445
    bytes_sent: usize,
446
447
    pub bytes_lost: u64,
448
449
    max_datagram_size: usize,
450
    time_sent_set_to_now: bool,
451
452
    #[cfg(feature = "qlog")]
453
    qlog_metrics: QlogMetrics,
454
455
    #[cfg(feature = "qlog")]
456
    qlog_prev_cc_state: &'static str,
457
458
    /// How many non-ack-eliciting packets have been sent.
459
    outstanding_non_ack_eliciting: usize,
460
461
    /// A resusable list of acks.
462
    newly_acked: Vec<Acked>,
463
464
    /// A [`Vec`] that can be reused for calls of
465
    /// [`Self::detect_and_remove_lost_packets`] to avoid allocations
466
    lost_reuse: Vec<Lost>,
467
468
    pacer: Pacer,
469
}
470
471
impl GRecovery {
472
    #[cfg(feature = "qlog")]
473
    fn send_rate(&self) -> Bandwidth {
474
        self.pacer.send_rate().unwrap_or(Bandwidth::zero())
475
    }
476
477
    #[cfg(feature = "qlog")]
478
    fn ack_rate(&self) -> Bandwidth {
479
        self.pacer.ack_rate().unwrap_or(Bandwidth::zero())
480
    }
481
482
47.3k
    pub fn new(recovery_config: &RecoveryConfig) -> Option<Self> {
483
47.3k
        let cc = match recovery_config.cc_algorithm {
484
0
            CongestionControlAlgorithm::Bbr2Gcongestion => BBRv2::new(
485
0
                recovery_config.initial_congestion_window_packets,
486
                MAX_WINDOW_PACKETS,
487
0
                recovery_config.max_send_udp_payload_size,
488
0
                recovery_config.initial_rtt,
489
0
                recovery_config.custom_bbr_params.as_ref(),
490
            ),
491
47.3k
            _ => return None,
492
        };
493
494
0
        Some(Self {
495
0
            epochs: Default::default(),
496
0
            rtt_stats: RttStats::new(
497
0
                recovery_config.initial_rtt,
498
0
                recovery_config.max_ack_delay,
499
0
            ),
500
0
            recovery_stats: RecoveryStats::default(),
501
0
            loss_timer: Default::default(),
502
0
            pto_count: 0,
503
0
504
0
            lost_count: 0,
505
0
            lost_spurious_count: 0,
506
0
507
0
            loss_thresh: LossThreshold::new(recovery_config),
508
0
            bytes_in_flight: Default::default(),
509
0
            bytes_sent: 0,
510
0
            bytes_lost: 0,
511
0
512
0
            max_datagram_size: recovery_config.max_send_udp_payload_size,
513
0
            time_sent_set_to_now: cc.time_sent_set_to_now(),
514
0
515
0
            #[cfg(feature = "qlog")]
516
0
            qlog_metrics: QlogMetrics::default(),
517
0
518
0
            #[cfg(feature = "qlog")]
519
0
            qlog_prev_cc_state: "",
520
0
521
0
            outstanding_non_ack_eliciting: 0,
522
0
523
0
            pacer: Pacer::new(
524
0
                recovery_config.pacing,
525
0
                cc,
526
0
                recovery_config
527
0
                    .max_pacing_rate
528
0
                    .map(Bandwidth::from_mbits_per_second),
529
0
            ),
530
0
531
0
            newly_acked: Vec::new(),
532
0
            lost_reuse: Vec::new(),
533
0
        })
534
47.3k
    }
535
536
0
    fn detect_and_remove_lost_packets(
537
0
        &mut self, epoch: packet::Epoch, now: Instant,
538
0
    ) -> (usize, usize) {
539
0
        let loss_delay =
540
0
            self.rtt_stats.loss_delay(self.loss_thresh.time_thresh());
541
0
        let lost = &mut self.lost_reuse;
542
543
        let LossDetectionResult {
544
0
            lost_bytes,
545
0
            lost_packets,
546
0
            pmtud_lost_bytes,
547
0
            pmtud_lost_packets,
548
0
        } = self.epochs[epoch].detect_and_remove_lost_packets(
549
0
            loss_delay,
550
0
            self.loss_thresh.pkt_thresh(),
551
0
            now,
552
0
            lost,
553
0
        );
554
555
0
        self.bytes_in_flight
556
0
            .saturating_subtract(lost_bytes + pmtud_lost_bytes, now);
557
558
0
        for pkt in pmtud_lost_packets {
559
0
            self.pacer.on_packet_neutered(pkt);
560
0
        }
561
562
0
        (lost_bytes, lost_packets)
563
0
    }
564
565
0
    fn loss_time_and_space(&self) -> (Option<Instant>, packet::Epoch) {
566
0
        let mut epoch = packet::Epoch::Initial;
567
0
        let mut time = self.epochs[epoch].loss_time;
568
569
        // Iterate over all packet number spaces starting from Handshake.
570
0
        for e in [packet::Epoch::Handshake, packet::Epoch::Application] {
571
0
            let new_time = self.epochs[e].loss_time;
572
0
            if time.is_none() || new_time < time {
573
0
                time = new_time;
574
0
                epoch = e;
575
0
            }
576
        }
577
578
0
        (time, epoch)
579
0
    }
580
581
0
    fn pto_time_and_space(
582
0
        &self, handshake_status: HandshakeStatus, now: Instant,
583
0
    ) -> (Option<Instant>, packet::Epoch) {
584
0
        let mut duration = self.pto() * (1 << self.pto_count);
585
586
        // Arm PTO from now when there are no inflight packets.
587
0
        if self.bytes_in_flight.is_zero() {
588
0
            if handshake_status.has_handshake_keys {
589
0
                return (Some(now + duration), packet::Epoch::Handshake);
590
            } else {
591
0
                return (Some(now + duration), packet::Epoch::Initial);
592
            }
593
0
        }
594
595
0
        let mut pto_timeout = None;
596
0
        let mut pto_space = packet::Epoch::Initial;
597
598
        // Iterate over all packet number spaces.
599
0
        for &e in packet::Epoch::epochs(
600
0
            packet::Epoch::Initial..=packet::Epoch::Application,
601
        ) {
602
0
            if self.epochs[e].pkts_in_flight == 0 {
603
0
                continue;
604
0
            }
605
606
0
            if e == packet::Epoch::Application {
607
                // Skip Application Data until handshake completes.
608
0
                if !handshake_status.completed {
609
0
                    return (pto_timeout, pto_space);
610
0
                }
611
612
                // Include max_ack_delay and backoff for Application Data.
613
0
                duration +=
614
0
                    self.rtt_stats.max_ack_delay * 2_u32.pow(self.pto_count);
615
0
            }
616
617
0
            let new_time = self.epochs[e]
618
0
                .time_of_last_ack_eliciting_packet
619
0
                .map(|t| t + duration);
620
621
0
            if pto_timeout.is_none() || new_time < pto_timeout {
622
0
                pto_timeout = new_time;
623
0
                pto_space = e;
624
0
            }
625
        }
626
627
0
        (pto_timeout, pto_space)
628
0
    }
629
630
0
    fn set_loss_detection_timer(
631
0
        &mut self, handshake_status: HandshakeStatus, now: Instant,
632
0
    ) {
633
0
        if let (Some(earliest_loss_time), _) = self.loss_time_and_space() {
634
            // Time threshold loss detection.
635
0
            self.loss_timer.update(earliest_loss_time);
636
0
            return;
637
0
        }
638
639
0
        if self.bytes_in_flight.is_zero() &&
640
0
            handshake_status.peer_verified_address
641
        {
642
0
            self.loss_timer.clear();
643
0
            return;
644
0
        }
645
646
        // PTO timer.
647
0
        if let (Some(timeout), _) = self.pto_time_and_space(handshake_status, now)
648
0
        {
649
0
            self.loss_timer.update(timeout);
650
0
        }
651
0
    }
652
}
653
654
impl RecoveryOps for GRecovery {
655
0
    fn lost_count(&self) -> usize {
656
0
        self.lost_count
657
0
    }
658
659
0
    fn bytes_lost(&self) -> u64 {
660
0
        self.bytes_lost
661
0
    }
662
663
0
    fn should_elicit_ack(&self, epoch: packet::Epoch) -> bool {
664
0
        self.epochs[epoch].loss_probes > 0 ||
665
0
            self.outstanding_non_ack_eliciting >=
666
0
                MAX_OUTSTANDING_NON_ACK_ELICITING
667
0
    }
668
669
0
    fn next_acked_frame(&mut self, epoch: packet::Epoch) -> Option<frame::Frame> {
670
0
        self.epochs[epoch].acked_frames.pop_front()
671
0
    }
672
673
0
    fn next_lost_frame(&mut self, epoch: packet::Epoch) -> Option<frame::Frame> {
674
0
        self.epochs[epoch].lost_frames.pop_front()
675
0
    }
676
677
0
    fn get_largest_acked_on_epoch(&self, epoch: packet::Epoch) -> Option<u64> {
678
0
        self.epochs[epoch].largest_acked_packet
679
0
    }
680
681
0
    fn has_lost_frames(&self, epoch: packet::Epoch) -> bool {
682
0
        !self.epochs[epoch].lost_frames.is_empty()
683
0
    }
684
685
0
    fn loss_probes(&self, epoch: packet::Epoch) -> usize {
686
0
        self.epochs[epoch].loss_probes
687
0
    }
688
689
    #[cfg(test)]
690
    fn inc_loss_probes(&mut self, epoch: packet::Epoch) {
691
        self.epochs[epoch].loss_probes += 1;
692
    }
693
694
0
    fn ping_sent(&mut self, epoch: packet::Epoch) {
695
0
        self.epochs[epoch].loss_probes =
696
0
            self.epochs[epoch].loss_probes.saturating_sub(1);
697
0
    }
698
699
0
    fn on_packet_sent(
700
0
        &mut self, pkt: Sent, epoch: packet::Epoch,
701
0
        handshake_status: HandshakeStatus, now: Instant, trace_id: &str,
702
0
    ) {
703
0
        let time_sent = if self.time_sent_set_to_now {
704
0
            now
705
        } else {
706
0
            self.get_next_release_time().time(now).unwrap_or(now)
707
        };
708
709
0
        let epoch = &mut self.epochs[epoch];
710
711
0
        let ack_eliciting = pkt.ack_eliciting;
712
0
        let in_flight = pkt.in_flight;
713
0
        let is_pmtud_probe = pkt.is_pmtud_probe;
714
0
        let pkt_num = pkt.pkt_num;
715
0
        let sent_bytes = pkt.size;
716
717
0
        if let Some(SentPacket { pkt_num, .. }) = epoch.sent_packets.back() {
718
0
            assert!(*pkt_num < pkt.pkt_num, "Packet numbers must increase");
719
0
        }
720
721
0
        let status = SentStatus::Sent {
722
0
            time_sent,
723
0
            ack_eliciting,
724
0
            in_flight,
725
0
            is_pmtud_probe,
726
0
            has_data: pkt.has_data,
727
0
            sent_bytes,
728
0
            frames: pkt.frames,
729
0
        };
730
731
        #[cfg(test)]
732
        {
733
            epoch.test_largest_sent_pkt_num_on_path = epoch
734
                .test_largest_sent_pkt_num_on_path
735
                .max(Some(pkt.pkt_num));
736
        }
737
738
0
        epoch.sent_packets.push_back(SentPacket { pkt_num, status });
739
740
0
        if ack_eliciting {
741
0
            epoch.time_of_last_ack_eliciting_packet = Some(time_sent);
742
0
            self.outstanding_non_ack_eliciting = 0;
743
0
        } else {
744
0
            self.outstanding_non_ack_eliciting += 1;
745
0
        }
746
747
0
        if in_flight {
748
0
            self.pacer.on_packet_sent(
749
0
                time_sent,
750
0
                self.bytes_in_flight.get(),
751
0
                pkt_num,
752
0
                sent_bytes,
753
0
                pkt.has_data,
754
0
                &self.rtt_stats,
755
0
            );
756
0
757
0
            self.bytes_in_flight.add(sent_bytes, now);
758
0
            epoch.pkts_in_flight += 1;
759
0
            self.set_loss_detection_timer(handshake_status, time_sent);
760
0
        }
761
762
0
        self.bytes_sent += sent_bytes;
763
764
0
        trace!("{trace_id} {self:?}");
765
0
    }
766
767
0
    fn get_packet_send_time(&self, now: Instant) -> Instant {
768
0
        self.pacer.get_next_release_time().time(now).unwrap_or(now)
769
0
    }
770
771
    // `peer_sent_ack_ranges` should not be used without validation.
772
0
    fn on_ack_received(
773
0
        &mut self, peer_sent_ack_ranges: &RangeSet, ack_delay: u64,
774
0
        epoch: packet::Epoch, handshake_status: HandshakeStatus, now: Instant,
775
0
        skip_pn: Option<u64>, trace_id: &str,
776
0
    ) -> Result<OnAckReceivedOutcome> {
777
0
        let prior_in_flight = self.bytes_in_flight.get();
778
779
        let AckedDetectionResult {
780
0
            acked_bytes,
781
0
            spurious_losses,
782
0
            spurious_pkt_thresh,
783
0
            has_ack_eliciting,
784
0
        } = self.epochs[epoch].detect_and_remove_acked_packets(
785
0
            peer_sent_ack_ranges,
786
0
            &mut self.newly_acked,
787
0
            skip_pn,
788
0
            trace_id,
789
0
        )?;
790
791
0
        self.lost_spurious_count += spurious_losses;
792
0
        if let Some(thresh) = spurious_pkt_thresh {
793
0
            self.loss_thresh.on_spurious_loss(thresh);
794
0
        }
795
796
0
        if self.newly_acked.is_empty() {
797
0
            return Ok(OnAckReceivedOutcome {
798
0
                acked_bytes,
799
0
                spurious_losses,
800
0
                ..Default::default()
801
0
            });
802
0
        }
803
804
0
        self.bytes_in_flight.saturating_subtract(acked_bytes, now);
805
806
0
        let largest_newly_acked = self.newly_acked.last().unwrap();
807
808
        // Update `largest_acked_packet` based on the validated `newly_acked`
809
        // value.
810
0
        let largest_acked_pkt_num = self.epochs[epoch]
811
0
            .largest_acked_packet
812
0
            .unwrap_or(0)
813
0
            .max(largest_newly_acked.pkt_num);
814
0
        self.epochs[epoch].largest_acked_packet = Some(largest_acked_pkt_num);
815
816
        // Check if largest packet is newly acked.
817
0
        let update_rtt = largest_newly_acked.pkt_num == largest_acked_pkt_num &&
818
0
            has_ack_eliciting;
819
0
        if update_rtt {
820
0
            let latest_rtt = now - largest_newly_acked.time_sent;
821
0
            self.rtt_stats.update_rtt(
822
0
                latest_rtt,
823
0
                Duration::from_micros(ack_delay),
824
0
                now,
825
0
                handshake_status.completed,
826
0
            );
827
0
        }
828
829
0
        let (lost_bytes, lost_packets) =
830
0
            self.detect_and_remove_lost_packets(epoch, now);
831
832
0
        self.pacer.on_congestion_event(
833
0
            update_rtt,
834
0
            prior_in_flight,
835
0
            self.bytes_in_flight.get(),
836
0
            now,
837
0
            &self.newly_acked,
838
0
            &self.lost_reuse,
839
0
            self.epochs[epoch].least_unacked(),
840
0
            &self.rtt_stats,
841
0
            &mut self.recovery_stats,
842
        );
843
844
0
        self.pto_count = 0;
845
0
        self.lost_count += lost_packets;
846
847
0
        self.set_loss_detection_timer(handshake_status, now);
848
849
0
        trace!("{trace_id} {self:?}");
850
851
0
        Ok(OnAckReceivedOutcome {
852
0
            lost_packets,
853
0
            lost_bytes,
854
0
            acked_bytes,
855
0
            spurious_losses,
856
0
        })
857
0
    }
858
859
0
    fn on_loss_detection_timeout(
860
0
        &mut self, handshake_status: HandshakeStatus, now: Instant,
861
0
        trace_id: &str,
862
0
    ) -> OnLossDetectionTimeoutOutcome {
863
0
        let (earliest_loss_time, epoch) = self.loss_time_and_space();
864
865
0
        if earliest_loss_time.is_some() {
866
0
            let prior_in_flight = self.bytes_in_flight.get();
867
868
0
            let (lost_bytes, lost_packets) =
869
0
                self.detect_and_remove_lost_packets(epoch, now);
870
871
0
            self.pacer.on_congestion_event(
872
                false,
873
0
                prior_in_flight,
874
0
                self.bytes_in_flight.get(),
875
0
                now,
876
0
                &[],
877
0
                &self.lost_reuse,
878
0
                self.epochs[epoch].least_unacked(),
879
0
                &self.rtt_stats,
880
0
                &mut self.recovery_stats,
881
            );
882
883
0
            self.lost_count += lost_packets;
884
885
0
            self.set_loss_detection_timer(handshake_status, now);
886
887
0
            trace!("{trace_id} {self:?}");
888
0
            return OnLossDetectionTimeoutOutcome {
889
0
                lost_packets,
890
0
                lost_bytes,
891
0
            };
892
0
        }
893
894
0
        let epoch = if self.bytes_in_flight.get() > 0 {
895
            // Send new data if available, else retransmit old data. If neither
896
            // is available, send a single PING frame.
897
0
            let (_, e) = self.pto_time_and_space(handshake_status, now);
898
899
0
            e
900
        } else {
901
            // Client sends an anti-deadlock packet: Initial is padded to earn
902
            // more anti-amplification credit, a Handshake packet proves address
903
            // ownership.
904
0
            if handshake_status.has_handshake_keys {
905
0
                packet::Epoch::Handshake
906
            } else {
907
0
                packet::Epoch::Initial
908
            }
909
        };
910
911
0
        self.pto_count += 1;
912
913
0
        let epoch = &mut self.epochs[epoch];
914
915
0
        epoch.loss_probes = MAX_PTO_PROBES_COUNT.min(self.pto_count as usize);
916
917
        // Skip packets that have already been acked or lost, and packets
918
        // that don't contain either CRYPTO or STREAM frames and only return as
919
        // many packets as the number of probe packets that will be sent.
920
0
        let unacked_frames = epoch
921
0
            .sent_packets
922
0
            .iter_mut()
923
0
            .filter_map(|p| {
924
                if let SentStatus::Sent {
925
                    has_data: true,
926
0
                    frames,
927
                    ..
928
0
                } = &p.status
929
                {
930
0
                    Some(frames)
931
                } else {
932
0
                    None
933
                }
934
0
            })
935
0
            .take(epoch.loss_probes)
936
0
            .flatten()
937
0
            .filter(|f| !matches!(f, frame::Frame::DatagramHeader { .. }));
938
939
        // Retransmit the frames from the oldest sent packets on PTO. However
940
        // the packets are not actually declared lost (so there is no effect to
941
        // congestion control), we just reschedule the data they carried.
942
        //
943
        // This will also trigger sending an ACK and retransmitting frames like
944
        // HANDSHAKE_DONE and MAX_DATA / MAX_STREAM_DATA as well, in addition
945
        // to CRYPTO and STREAM, if the original packet carried them.
946
0
        epoch.lost_frames.extend(unacked_frames.cloned());
947
948
0
        self.pacer
949
0
            .on_retransmission_timeout(!epoch.lost_frames.is_empty());
950
951
0
        self.set_loss_detection_timer(handshake_status, now);
952
953
0
        trace!("{trace_id} {self:?}");
954
0
        OnLossDetectionTimeoutOutcome {
955
0
            lost_packets: 0,
956
0
            lost_bytes: 0,
957
0
        }
958
0
    }
959
960
0
    fn on_pkt_num_space_discarded(
961
0
        &mut self, epoch: packet::Epoch, handshake_status: HandshakeStatus,
962
0
        now: Instant,
963
0
    ) {
964
0
        let epoch = &mut self.epochs[epoch];
965
0
        self.bytes_in_flight
966
0
            .saturating_subtract(epoch.discard(&mut self.pacer), now);
967
0
        self.set_loss_detection_timer(handshake_status, now);
968
0
    }
969
970
0
    fn on_path_change(
971
0
        &mut self, epoch: packet::Epoch, now: Instant, _trace_id: &str,
972
0
    ) -> (usize, usize) {
973
0
        let (lost_bytes, lost_packets) =
974
0
            self.detect_and_remove_lost_packets(epoch, now);
975
976
0
        (lost_packets, lost_bytes)
977
0
    }
978
979
0
    fn loss_detection_timer(&self) -> Option<Instant> {
980
0
        self.loss_timer.time
981
0
    }
982
983
0
    fn cwnd(&self) -> usize {
984
0
        self.pacer.get_congestion_window()
985
0
    }
986
987
0
    fn cwnd_available(&self) -> usize {
988
        // Ignore cwnd when sending probe packets.
989
0
        if self.epochs.iter().any(|e| e.loss_probes > 0) {
990
0
            return usize::MAX;
991
0
        }
992
993
0
        self.cwnd().saturating_sub(self.bytes_in_flight.get())
994
0
    }
995
996
0
    fn rtt(&self) -> Duration {
997
0
        self.rtt_stats.rtt()
998
0
    }
999
1000
0
    fn min_rtt(&self) -> Option<Duration> {
1001
0
        self.rtt_stats.min_rtt()
1002
0
    }
1003
1004
0
    fn max_rtt(&self) -> Option<Duration> {
1005
0
        self.rtt_stats.max_rtt()
1006
0
    }
1007
1008
0
    fn rttvar(&self) -> Duration {
1009
0
        self.rtt_stats.rttvar()
1010
0
    }
1011
1012
0
    fn pto(&self) -> Duration {
1013
0
        let r = &self.rtt_stats;
1014
0
        r.rtt() + (r.rttvar() * 4).max(GRANULARITY)
1015
0
    }
1016
1017
    /// The most recent data delivery rate estimate.
1018
0
    fn delivery_rate(&self) -> Bandwidth {
1019
0
        self.pacer.bandwidth_estimate(&self.rtt_stats)
1020
0
    }
1021
1022
0
    fn max_bandwidth(&self) -> Option<Bandwidth> {
1023
0
        Some(self.pacer.max_bandwidth())
1024
0
    }
1025
1026
    /// Statistics from when a CCA first exited the startup phase.
1027
0
    fn startup_exit(&self) -> Option<StartupExit> {
1028
0
        self.recovery_stats.startup_exit
1029
0
    }
1030
1031
0
    fn max_datagram_size(&self) -> usize {
1032
0
        self.max_datagram_size
1033
0
    }
1034
1035
0
    fn pmtud_update_max_datagram_size(&mut self, new_max_datagram_size: usize) {
1036
0
        self.max_datagram_size = new_max_datagram_size;
1037
0
        self.pacer.update_mss(self.max_datagram_size);
1038
0
    }
1039
1040
0
    fn update_max_datagram_size(&mut self, new_max_datagram_size: usize) {
1041
0
        self.pmtud_update_max_datagram_size(
1042
0
            self.max_datagram_size.min(new_max_datagram_size),
1043
        )
1044
0
    }
1045
1046
    // FIXME only used by gcongestion
1047
0
    fn on_app_limited(&mut self) {
1048
0
        self.pacer.on_app_limited(self.bytes_in_flight.get())
1049
0
    }
1050
1051
    #[cfg(test)]
1052
    fn sent_packets_len(&self, epoch: packet::Epoch) -> usize {
1053
        self.epochs[epoch].sent_packets.len()
1054
    }
1055
1056
    #[cfg(test)]
1057
    fn in_flight_count(&self, epoch: packet::Epoch) -> usize {
1058
        self.epochs[epoch].pkts_in_flight
1059
    }
1060
1061
0
    fn bytes_in_flight(&self) -> usize {
1062
0
        self.bytes_in_flight.get()
1063
0
    }
1064
1065
0
    fn bytes_in_flight_duration(&self) -> Duration {
1066
0
        self.bytes_in_flight.get_duration()
1067
0
    }
1068
1069
    #[cfg(test)]
1070
    fn pacing_rate(&self) -> u64 {
1071
        self.pacer
1072
            .pacing_rate(self.bytes_in_flight.get(), &self.rtt_stats)
1073
            .to_bytes_per_period(Duration::from_secs(1))
1074
    }
1075
1076
    #[cfg(test)]
1077
    fn pto_count(&self) -> u32 {
1078
        self.pto_count
1079
    }
1080
1081
    #[cfg(test)]
1082
    fn pkt_thresh(&self) -> Option<u64> {
1083
        self.loss_thresh.pkt_thresh()
1084
    }
1085
1086
    #[cfg(test)]
1087
    fn time_thresh(&self) -> f64 {
1088
        self.loss_thresh.time_thresh()
1089
    }
1090
1091
    #[cfg(test)]
1092
    fn lost_spurious_count(&self) -> usize {
1093
        self.lost_spurious_count
1094
    }
1095
1096
    #[cfg(test)]
1097
    fn detect_lost_packets_for_test(
1098
        &mut self, epoch: packet::Epoch, now: Instant,
1099
    ) -> (usize, usize) {
1100
        let ret = self.detect_and_remove_lost_packets(epoch, now);
1101
        self.epochs[epoch].drain_acked_and_lost_packets();
1102
        ret
1103
    }
1104
1105
    #[cfg(test)]
1106
    fn largest_sent_pkt_num_on_path(&self, epoch: packet::Epoch) -> Option<u64> {
1107
        self.epochs[epoch].test_largest_sent_pkt_num_on_path
1108
    }
1109
1110
    #[cfg(test)]
1111
    fn app_limited(&self) -> bool {
1112
        self.pacer.is_app_limited(self.bytes_in_flight.get())
1113
    }
1114
1115
    // FIXME only used by congestion
1116
0
    fn update_app_limited(&mut self, _v: bool) {
1117
        // TODO
1118
0
    }
1119
1120
    // FIXME only used by congestion
1121
0
    fn delivery_rate_update_app_limited(&mut self, _v: bool) {
1122
        // TODO
1123
0
    }
1124
1125
0
    fn update_max_ack_delay(&mut self, max_ack_delay: Duration) {
1126
0
        self.rtt_stats.max_ack_delay = max_ack_delay;
1127
0
    }
1128
1129
0
    fn get_next_release_time(&self) -> ReleaseDecision {
1130
0
        self.pacer.get_next_release_time()
1131
0
    }
1132
1133
0
    fn gcongestion_enabled(&self) -> bool {
1134
0
        true
1135
0
    }
1136
1137
    #[cfg(feature = "qlog")]
1138
    fn state_str(&self, _now: Instant) -> &'static str {
1139
        self.pacer.state_str()
1140
    }
1141
1142
    #[cfg(feature = "qlog")]
1143
    fn get_updated_qlog_event_data(&mut self) -> Option<EventData> {
1144
        let qlog_metrics = QlogMetrics {
1145
            min_rtt: *self.rtt_stats.min_rtt,
1146
            smoothed_rtt: self.rtt(),
1147
            latest_rtt: self.rtt_stats.latest_rtt(),
1148
            rttvar: self.rtt_stats.rttvar(),
1149
            cwnd: self.cwnd() as u64,
1150
            bytes_in_flight: self.bytes_in_flight.get() as u64,
1151
            ssthresh: self.pacer.ssthresh(),
1152
1153
            pacing_rate: Some(
1154
                self.pacer
1155
                    .pacing_rate(self.bytes_in_flight.get(), &self.rtt_stats)
1156
                    .to_bytes_per_second(),
1157
            ),
1158
            delivery_rate: Some(self.delivery_rate().to_bytes_per_second()),
1159
            send_rate: Some(self.send_rate().to_bytes_per_second()),
1160
            ack_rate: Some(self.ack_rate().to_bytes_per_second()),
1161
        };
1162
1163
        self.qlog_metrics.maybe_update(qlog_metrics)
1164
    }
1165
1166
    #[cfg(feature = "qlog")]
1167
    fn get_updated_qlog_cc_state(
1168
        &mut self, now: Instant,
1169
    ) -> Option<&'static str> {
1170
        let cc_state = self.state_str(now);
1171
        if cc_state != self.qlog_prev_cc_state {
1172
            self.qlog_prev_cc_state = cc_state;
1173
            Some(cc_state)
1174
        } else {
1175
            None
1176
        }
1177
    }
1178
1179
0
    fn send_quantum(&self) -> usize {
1180
0
        let pacing_rate = self
1181
0
            .pacer
1182
0
            .pacing_rate(self.bytes_in_flight.get(), &self.rtt_stats);
1183
1184
0
        let floor = if pacing_rate < Bandwidth::from_kbits_per_second(1200) {
1185
0
            self.max_datagram_size
1186
        } else {
1187
0
            2 * self.max_datagram_size
1188
        };
1189
1190
0
        pacing_rate
1191
0
            .to_bytes_per_period(ReleaseDecision::EQUAL_THRESHOLD)
1192
0
            .min(64 * 1024)
1193
0
            .max(floor as u64) as usize
1194
0
    }
1195
}
1196
1197
impl std::fmt::Debug for GRecovery {
1198
0
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
1199
0
        write!(f, "timer={:?} ", self.loss_detection_timer())?;
1200
0
        write!(f, "rtt_stats={:?} ", self.rtt_stats)?;
1201
0
        write!(f, "bytes_in_flight={} ", self.bytes_in_flight.get())?;
1202
0
        write!(f, "{:?} ", self.pacer)?;
1203
0
        Ok(())
1204
0
    }
1205
}
1206
1207
#[cfg(test)]
1208
mod tests {
1209
    use super::*;
1210
    use crate::Config;
1211
1212
    #[test]
1213
    fn loss_threshold() {
1214
        let config = Config::new(crate::PROTOCOL_VERSION).unwrap();
1215
        let recovery_config = RecoveryConfig::from_config(&config);
1216
        assert!(!recovery_config.enable_relaxed_loss_threshold);
1217
1218
        let mut loss_thresh = LossThreshold::new(&recovery_config);
1219
        assert_eq!(loss_thresh.time_thresh_overhead, None);
1220
        assert_eq!(loss_thresh.pkt_thresh().unwrap(), INITIAL_PACKET_THRESHOLD);
1221
        assert_eq!(loss_thresh.time_thresh(), INITIAL_TIME_THRESHOLD);
1222
1223
        // First spurious loss.
1224
        loss_thresh.on_spurious_loss(INITIAL_PACKET_THRESHOLD);
1225
        assert_eq!(loss_thresh.pkt_thresh().unwrap(), INITIAL_PACKET_THRESHOLD);
1226
        assert_eq!(loss_thresh.time_thresh(), PACKET_REORDER_TIME_THRESHOLD);
1227
1228
        // Packet gaps < INITIAL_PACKET_THRESHOLD will NOT change packet
1229
        // threshold.
1230
        for packet_gap in 0..INITIAL_PACKET_THRESHOLD {
1231
            loss_thresh.on_spurious_loss(packet_gap);
1232
1233
            // Packet threshold only increases once the packet gap increases.
1234
            assert_eq!(
1235
                loss_thresh.pkt_thresh().unwrap(),
1236
                INITIAL_PACKET_THRESHOLD
1237
            );
1238
            assert_eq!(loss_thresh.time_thresh(), PACKET_REORDER_TIME_THRESHOLD);
1239
        }
1240
1241
        // Subsequent spurious loss with packet_gaps > INITIAL_PACKET_THRESHOLD.
1242
        // Test values much larger than MAX_PACKET_THRESHOLD, i.e.
1243
        // `MAX_PACKET_THRESHOLD * 2`
1244
        for packet_gap in INITIAL_PACKET_THRESHOLD + 1..MAX_PACKET_THRESHOLD * 2 {
1245
            loss_thresh.on_spurious_loss(packet_gap);
1246
1247
            // Packet threshold is equal to packet gap beyond
1248
            // INITIAL_PACKET_THRESHOLD, but capped
1249
            // at MAX_PACKET_THRESHOLD.
1250
            let new_packet_threshold = if packet_gap < MAX_PACKET_THRESHOLD {
1251
                packet_gap
1252
            } else {
1253
                MAX_PACKET_THRESHOLD
1254
            };
1255
            assert_eq!(loss_thresh.pkt_thresh().unwrap(), new_packet_threshold);
1256
            assert_eq!(loss_thresh.time_thresh(), PACKET_REORDER_TIME_THRESHOLD);
1257
        }
1258
        // Packet threshold is capped at MAX_PACKET_THRESHOLD
1259
        assert_eq!(loss_thresh.pkt_thresh().unwrap(), MAX_PACKET_THRESHOLD);
1260
        assert_eq!(loss_thresh.time_thresh(), PACKET_REORDER_TIME_THRESHOLD);
1261
1262
        // Packet threshold is monotonically increasing
1263
        loss_thresh.on_spurious_loss(INITIAL_PACKET_THRESHOLD);
1264
        assert_eq!(loss_thresh.pkt_thresh().unwrap(), MAX_PACKET_THRESHOLD);
1265
        assert_eq!(loss_thresh.time_thresh(), PACKET_REORDER_TIME_THRESHOLD);
1266
    }
1267
1268
    #[test]
1269
    fn relaxed_loss_threshold() {
1270
        // The max time threshold when operating in relaxed loss mode.
1271
        const MAX_TIME_THRESHOLD: f64 = 2.0;
1272
1273
        let mut config = Config::new(crate::PROTOCOL_VERSION).unwrap();
1274
        config.set_enable_relaxed_loss_threshold(true);
1275
        let recovery_config = RecoveryConfig::from_config(&config);
1276
        assert!(recovery_config.enable_relaxed_loss_threshold);
1277
1278
        let mut loss_thresh = LossThreshold::new(&recovery_config);
1279
        assert_eq!(
1280
            loss_thresh.time_thresh_overhead,
1281
            Some(INITIAL_TIME_THRESHOLD_OVERHEAD)
1282
        );
1283
        assert_eq!(loss_thresh.pkt_thresh().unwrap(), INITIAL_PACKET_THRESHOLD);
1284
        assert_eq!(loss_thresh.time_thresh(), INITIAL_TIME_THRESHOLD);
1285
1286
        // First spurious loss.
1287
        loss_thresh.on_spurious_loss(INITIAL_PACKET_THRESHOLD);
1288
        assert_eq!(loss_thresh.pkt_thresh(), None);
1289
        assert_eq!(loss_thresh.time_thresh(), INITIAL_TIME_THRESHOLD);
1290
1291
        // Subsequent spurious loss.
1292
        for subsequent_loss_count in 1..100 {
1293
            // Double the overhead until it caps at `2.0`.
1294
            //
1295
            // It takes `3` rounds of doubling for INITIAL_TIME_THRESHOLD_OVERHEAD
1296
            // to equal `1.0`.
1297
            let new_time_threshold = if subsequent_loss_count <= 3 {
1298
                1.0 + INITIAL_TIME_THRESHOLD_OVERHEAD *
1299
                    2_f64.powi(subsequent_loss_count as i32)
1300
            } else {
1301
                2.0
1302
            };
1303
1304
            loss_thresh.on_spurious_loss(subsequent_loss_count);
1305
            assert_eq!(loss_thresh.pkt_thresh(), None);
1306
            assert_eq!(loss_thresh.time_thresh(), new_time_threshold);
1307
        }
1308
        // Time threshold is capped at 2.0.
1309
        assert_eq!(loss_thresh.pkt_thresh(), None);
1310
        assert_eq!(loss_thresh.time_thresh(), MAX_TIME_THRESHOLD);
1311
    }
1312
}