Skip to main content

quiche/recovery/gcongestion/bbr2/rtt_jump_detector/
mod.rs

1// Copyright (C) 2026, 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::time::Duration;
28use std::time::Instant;
29
30use crate::recovery::gcongestion::BbrRttJumpDetector;
31
32mod global_min;
33mod hmm;
34
35use self::global_min::GlobalMinDetector;
36use self::hmm::HmmDetector;
37
38#[derive(Debug)]
39pub(super) struct RttJumpDetector {
40    /// Total number of confirmed persistent RTT jump episodes.
41    persistent_jump_count: u64,
42    /// The start time of the most recently confirmed persistent RTT jump
43    /// episode, if any.
44    last_persistent_jump_time: Option<Instant>,
45    /// The detector selected for this connection.
46    detector: ActiveRttJumpDetector,
47}
48
49#[derive(Debug)]
50enum ActiveRttJumpDetector {
51    Disabled,
52    GlobalMin(GlobalMinDetector),
53    Hmm(HmmDetector),
54}
55
56#[derive(Debug, Clone, Copy, PartialEq, Eq)]
57enum RttJumpUpdate {
58    None,
59    PersistentConfirmed { episode_start_time: Instant },
60}
61
62impl RttJumpDetector {
63    pub(super) fn new(mode: BbrRttJumpDetector) -> Self {
64        let detector = match mode {
65            BbrRttJumpDetector::Disabled => ActiveRttJumpDetector::Disabled,
66            BbrRttJumpDetector::GlobalMin =>
67                ActiveRttJumpDetector::GlobalMin(GlobalMinDetector::default()),
68            BbrRttJumpDetector::Hmm =>
69                ActiveRttJumpDetector::Hmm(HmmDetector::default()),
70        };
71
72        Self {
73            persistent_jump_count: 0,
74            last_persistent_jump_time: None,
75            detector,
76        }
77    }
78
79    /// Runs the connection's selected RTT jump detector for one RTT sample.
80    pub(super) fn on_rtt_sample(
81        &mut self, rtt_sample: Duration, event_time: Instant,
82        full_bandwidth_reached: bool,
83    ) {
84        let update = match &mut self.detector {
85            ActiveRttJumpDetector::Disabled => RttJumpUpdate::None,
86            ActiveRttJumpDetector::GlobalMin(detector) => detector.on_rtt_sample(
87                rtt_sample,
88                event_time,
89                full_bandwidth_reached,
90            ),
91            ActiveRttJumpDetector::Hmm(detector) => detector.on_rtt_sample(
92                rtt_sample,
93                event_time,
94                full_bandwidth_reached,
95            ),
96        };
97
98        self.apply_update(update);
99    }
100
101    fn apply_update(&mut self, update: RttJumpUpdate) {
102        match update {
103            RttJumpUpdate::None => {},
104            RttJumpUpdate::PersistentConfirmed { episode_start_time } => {
105                self.persistent_jump_count += 1;
106                self.last_persistent_jump_time = Some(episode_start_time);
107            },
108        }
109    }
110
111    /// Total number of confirmed persistent RTT jump episodes.
112    pub(super) fn rtt_persistent_jump_count(&self) -> u64 {
113        self.persistent_jump_count
114    }
115
116    /// The start time of the most recently confirmed persistent RTT jump
117    /// episode, if any.
118    #[cfg(test)]
119    pub(super) fn last_persistent_jump_time(&self) -> Option<Instant> {
120        self.last_persistent_jump_time
121    }
122
123    /// Whether an RTT jump episode is currently active.
124    #[cfg(test)]
125    pub(super) fn is_rtt_jump_active(&self) -> bool {
126        match &self.detector {
127            ActiveRttJumpDetector::Disabled => false,
128            ActiveRttJumpDetector::GlobalMin(detector) =>
129                detector.is_rtt_jump_active(),
130            ActiveRttJumpDetector::Hmm(detector) => detector.is_rtt_jump_active(),
131        }
132    }
133
134    /// Whether the current RTT jump episode has been confirmed as persistent.
135    #[cfg(test)]
136    pub(super) fn is_rtt_jump_persistent(&self) -> bool {
137        match &self.detector {
138            ActiveRttJumpDetector::Disabled => false,
139            ActiveRttJumpDetector::GlobalMin(detector) =>
140                detector.is_rtt_jump_persistent(),
141            ActiveRttJumpDetector::Hmm(detector) =>
142                detector.is_rtt_jump_persistent(),
143        }
144    }
145}
146
147#[cfg(test)]
148mod tests {
149    use std::time::Duration;
150    use std::time::Instant;
151
152    use crate::recovery::gcongestion::BbrRttJumpDetector;
153
154    use super::*;
155
156    const RTT_JUMP: Duration = Duration::from_millis(151);
157
158    fn ms(millis: u64) -> Duration {
159        Duration::from_millis(millis)
160    }
161
162    fn sample(
163        detector: &mut RttJumpDetector, base: Instant, offset: Duration,
164        rtt: Duration,
165    ) {
166        detector.on_rtt_sample(rtt, base + offset, true);
167    }
168
169    #[test]
170    fn disabled_detector_does_not_store_active_behavior() {
171        let mut detector = RttJumpDetector::new(BbrRttJumpDetector::Disabled);
172        let base = Instant::now();
173
174        for pkt in 1..20 {
175            sample(&mut detector, base, ms(pkt * 100), RTT_JUMP);
176        }
177
178        assert!(!detector.is_rtt_jump_active());
179        assert!(!detector.is_rtt_jump_persistent());
180        assert_eq!(detector.rtt_persistent_jump_count(), 0);
181        assert_eq!(detector.last_persistent_jump_time(), None);
182    }
183}