srt_protocol/protocol/time/
mod.rs

1mod base;
2mod rtt;
3mod timer;
4
5pub use base::*;
6pub use rtt::*;
7pub use timer::*;
8
9use std::{
10    cmp::{max, min},
11    time::{Duration, Instant},
12};
13
14//   The recommended granularity of their periods is microseconds. The
15//   system time is queried after each time bounded UDP receiving (there
16//   will be additional necessary data processing time if a UDP packet is
17//   received) to check if any of the ACK, NAK, or EXP event should be
18//   triggered. The timeout value of UDP receiving should be at least SYN.
19#[derive(Debug)]
20pub struct Timers {
21    snd: Timer,
22
23    //   ACK is used to trigger an acknowledgement (ACK). Its period is set by
24    //   the congestion control module. However, UDT will send an ACK no
25    //   longer than every 0.01 second, even though the congestion control
26    //   does not need timer-based ACK. Here, 0.01 second is defined as the
27    //   SYN time, or synchronization time, and it affects many of the other
28    //   timers used in UDT.
29    full_ack: Timer,
30
31    //   NAK is used to trigger a negative acknowledgement (NAK). Its period
32    //   is dynamically updated to 4 * RTT_+ RTTVar + SYN, where RTTVar is the
33    //   variance of RTT samples.
34    nak: Timer,
35
36    //   EXP is used to trigger data packets retransmission and maintain
37    //   connection status. Its period is dynamically updated to 4 * RTT +
38    //   RTTVar + SYN.
39    exp: Timer,
40    exp_count: u32,
41    peer_idle: Timer,
42
43    statistics: Timer,
44}
45
46impl Timers {
47    pub const SYN: Duration = Duration::from_millis(10);
48    const EXP_MAX: u32 = 16;
49
50    pub fn new(now: Instant, statistics_interval: Duration, peer_idle_timeout: Duration) -> Self {
51        let (ack, nak, exp) = Self::calculate_periods(1, &Rtt::default());
52        Self {
53            snd: Timer::new(now, Duration::from_millis(1)),
54            full_ack: Timer::new(now, ack),
55            nak: Timer::new(now, nak),
56            exp: Timer::new(now, exp),
57            exp_count: 1,
58            peer_idle: Timer::new(now, peer_idle_timeout),
59            // this isn't in the spec, but it's in the reference implementation
60            // https://github.com/Haivision/srt/blob/1d7b391905d7e344d80b86b39ac5c90fda8764a9/srtcore/core.cpp#L10610-L10614
61            statistics: Timer::new(now, statistics_interval),
62        }
63    }
64
65    pub fn next_timer(
66        &self,
67        now: Instant,
68        has_packets_to_send: bool,
69        next_message: Option<Instant>,
70        unacked_packets: u32,
71    ) -> Instant {
72        let timer = min(self.exp.next_instant(), self.nak.next_instant());
73        let timer = next_message.map_or(timer, |message| min(timer, message));
74        let timer = if unacked_packets > 0 {
75            min(self.full_ack.next_instant(), timer)
76        } else {
77            timer
78        };
79        let timer = if has_packets_to_send {
80            min(self.snd.next_instant(), timer)
81        } else {
82            timer
83        };
84
85        max(now, timer)
86    }
87
88    pub fn check_snd(&mut self, now: Instant) -> Option<u32> {
89        self.snd.check_expired(now)
90    }
91
92    pub fn check_full_ack(&mut self, now: Instant) -> Option<u32> {
93        self.full_ack.check_expired(now)
94    }
95
96    pub fn check_nak(&mut self, now: Instant) -> Option<u32> {
97        self.nak.check_expired(now)
98    }
99
100    pub fn check_peer_idle_timeout(&mut self, now: Instant) -> Option<u32> {
101        let _ = self.exp.check_expired(now)?;
102
103        self.peer_idle
104            .check_expired(now)
105            .filter(|_| self.exp_count > Self::EXP_MAX)
106            .or_else(|| {
107                self.exp_count += 1;
108                None
109            })
110    }
111
112    pub fn check_statistics(&mut self, now: Instant) -> Option<u32> {
113        self.statistics.check_expired(now)
114    }
115
116    pub fn update_snd_period(&mut self, period: Duration) {
117        self.snd.set_period(period)
118    }
119
120    pub fn update_rtt(&mut self, rtt: &Rtt) {
121        let (ack, nak, exp) = Self::calculate_periods(self.exp_count, rtt);
122        self.full_ack.set_period(ack);
123        self.nak.set_period(nak);
124        self.exp.set_period(exp);
125    }
126
127    pub fn reset_exp(&mut self, now: Instant) {
128        self.exp_count = 1;
129        self.peer_idle.reset(now)
130    }
131
132    fn calculate_periods(exp_count: u32, rtt: &Rtt) -> (Duration, Duration, Duration) {
133        let ms = Duration::from_millis;
134
135        // NAKInterval = min((RTT + 4 * RTTVar) / 2, 20000) - i.e. floor of 20ms
136        let nak_rtt_period = (rtt.mean_as_duration() + 4 * rtt.variance_as_duration()) / 2;
137        let nak_period = max(nak_rtt_period, ms(20));
138
139        // 0.5s minimum, according to page 9
140        // but 0.3s in reference implementation
141        let exp_rtt_period = 4 * rtt.mean_as_duration() + rtt.variance_as_duration() + Self::SYN;
142        let exp_period = max(exp_count * exp_rtt_period, exp_count * ms(300));
143
144        // full ack period is always 10 ms
145        (ms(10), nak_period, exp_period)
146    }
147}
148
149#[cfg(test)]
150mod tests {
151    use proptest::prelude::*;
152
153    use super::*;
154
155    use crate::packet::TimeSpan;
156
157    proptest! {
158        #[test]
159        fn nak(simulated_rtt in 0i32..500_000) {
160            prop_assume!(simulated_rtt >= 0);
161            let mut rtt = Rtt::default();
162            for _ in 0..1000 {
163                rtt.update(TimeSpan::from_micros(simulated_rtt));
164            }
165
166            let ms = Duration::from_millis;
167            let rtt_mean = rtt.mean_as_duration();
168            let rtt_variance = rtt.variance_as_duration();
169
170            // above lower bound of NAK
171            prop_assume!((rtt_mean + 4 * rtt_variance) / 2 > ms(20));
172
173            let start = Instant::now();
174            let mut timers = Timers::new(start, ms(10_000), ms(5_000));
175
176            timers.update_rtt(&rtt);
177
178            // NAKInterval = min(RTT + 4 * RTTVar / 2, 20ms) - i.e. floor 20ms
179            assert_eq!(timers.nak.next_instant() - start, (rtt_mean + 4 * rtt_variance) / 2);
180
181            // ACK is always 10ms
182            assert_eq!(timers.full_ack.next_instant() - start, ms(10));
183        }
184
185        #[test]
186        fn exp(simulated_rtt in 0i32..500_000) {
187            prop_assume!(simulated_rtt >= 0);
188            let mut rtt = Rtt::default();
189            for _ in 0..1000 {
190                rtt.update(TimeSpan::from_micros(simulated_rtt));
191            }
192
193            let ms = Duration::from_millis;
194            let syn = ms(10);
195            let rtt_mean = rtt.mean_as_duration();
196            let rtt_variance = rtt.variance_as_duration();
197
198            // above lower bound of EXP
199            prop_assume!(4 * rtt_mean + rtt_variance + syn > ms(300));
200
201            let start = Instant::now();
202            let mut timers = Timers::new(start, ms(10_000), ms(5_000));
203
204            timers.update_rtt(&rtt);
205
206            // 4 * RTT + RTTVar + SYN
207            assert_eq!(timers.exp.next_instant() - start, 4 * rtt_mean + rtt_variance + syn);
208
209            // ACK is always 10ms
210            assert_eq!(timers.full_ack.next_instant() - start, ms(10));
211
212            // ACK is always 10ms
213            assert_eq!(timers.full_ack.next_instant() - start, ms(10));
214        }
215
216        #[test]
217        fn nak_lower_bound(simulated_rtt in 0i32..100_000) {
218            prop_assume!(simulated_rtt >= 10_000);
219            let mut rtt = Rtt::default();
220            for _ in 0..1000 {
221                rtt.update(TimeSpan::from_micros(simulated_rtt));
222            }
223
224            let ms = Duration::from_millis;
225            let rtt_mean = rtt.mean_as_duration();
226            let rtt_variance = rtt.variance_as_duration();
227
228            // below lower bound of NAK
229            prop_assume!((rtt_mean + 4 * rtt_variance) / 2 <= ms(20));
230
231            let start = Instant::now();
232            let mut timers = Timers::new(start, ms(10_000), ms(5_000));
233
234            timers.update_rtt(&rtt);
235
236            // NAKInterval = min(RTT + 4 * RTTVar / 2, 20ms) - i.e. floor 20ms
237            assert_eq!(timers.nak.next_instant() - start, ms(20));
238
239            // ACK is always 10ms
240            assert_eq!(timers.full_ack.next_instant() - start, ms(10));
241        }
242
243        #[test]
244        fn exp_lower_bound(simulated_rtt in 0i32..100_000) {
245            prop_assume!(simulated_rtt >= 0);
246            let mut rtt = Rtt::default();
247            for _ in 0..1000 {
248                rtt.update(TimeSpan::from_micros(simulated_rtt));
249            }
250
251            let ms = Duration::from_millis;
252            let syn = ms(10);
253            let rtt_mean = rtt.mean_as_duration();
254            let rtt_variance = rtt.variance_as_duration();
255
256            // below lower bound of EXP
257            prop_assume!(4 * rtt_mean + rtt_variance + syn <= ms(300));
258
259            let start = Instant::now();
260            let mut timers = Timers::new(start, ms(10_000), ms(5_000));
261
262            timers.update_rtt(&rtt);
263
264            // exp has a lower bound period of 300ms
265            assert_eq!(timers.exp.next_instant() - start, ms(300));
266
267            // ACK is always 10ms
268            assert_eq!(timers.full_ack.next_instant() - start, ms(10));
269        }
270    }
271
272    #[test]
273    fn next_timer() {
274        let ms = TimeSpan::from_millis;
275        let start = Instant::now();
276        let mut timers = Timers::new(start, Duration::MAX, Duration::from_millis(5_000));
277
278        // next timer should be ack, 10ms
279        let now = start;
280        let actual_timer = timers.next_timer(now, false, None, 1);
281        assert_eq!(TimeSpan::from_interval(now, actual_timer), ms(10));
282
283        // ack should be disabled if there are no packets waiting acknowledgement
284        let actual_timer = timers.next_timer(now, false, None, 0);
285        assert!(TimeSpan::from_interval(now, actual_timer) > ms(10));
286
287        // fast forward the clock, ACK will fire before other timers
288        let now = start + ms(15);
289        // only ACK timer should fire
290        assert!(timers.check_full_ack(now).is_some());
291        assert!(timers.check_nak(now).is_none());
292        assert!(timers.check_peer_idle_timeout(now).is_none());
293
294        // NAK will have a lower bound period of 20ms
295        let nak = ms(20);
296        let actual_timer = timers.next_timer(now, false, Some(start + ms(10_000)), 1);
297        assert_eq!(TimeSpan::from_interval(start, actual_timer), nak);
298
299        // the NAK timer should trigger
300        assert!(timers.check_full_ack(actual_timer).is_some());
301        assert!(timers.check_nak(actual_timer).is_some());
302        assert!(timers.check_peer_idle_timeout(actual_timer).is_none());
303
304        // EXP will have a lower bound period of 300ms
305        let exp_lower_bound = ms(300);
306        let now = start + exp_lower_bound;
307
308        // push time forward for ACK and NAK first
309        assert!(timers.check_full_ack(now).is_some());
310        assert!(timers.check_nak(now).is_some());
311
312        // next timer should be EXP
313        let actual_timer = timers.next_timer(now, false, Some(start + ms(10_000)), 1);
314        assert_eq!(
315            TimeSpan::from_interval(start, actual_timer),
316            exp_lower_bound
317        );
318
319        // EXP timer should trigger
320        assert!(timers.check_full_ack(actual_timer).is_none());
321        assert!(timers.check_nak(actual_timer).is_none());
322        let last_input = start;
323        timers.reset_exp(last_input);
324        for exp_count in 1..=16 {
325            assert!(timers
326                .check_peer_idle_timeout(last_input + exp_count * exp_lower_bound)
327                .is_none());
328        }
329        assert!(timers
330            .check_peer_idle_timeout(last_input + 17 * exp_lower_bound)
331            .is_some());
332    }
333}