srt_protocol/protocol/time/
base.rs

1use std::time::Instant;
2
3use crate::packet::{TimeSpan, TimeStamp};
4
5#[derive(Copy, Clone, Debug)]
6pub struct TimeBase {
7    // this field is only here for diagnostics and debugging
8    // it is similar to start_time in other contexts, but is adjusted for drift
9    origin_time: Instant,
10
11    // the two "reference" fields are two equivalent time points from different
12    // time scales they are reference points used for mapping between the Instance
13    // and a sender TimeStamp time scales
14    reference_time: Instant,
15    reference_ts: TimeStamp,
16}
17
18impl TimeBase {
19    pub fn new(start_time: Instant) -> Self {
20        Self {
21            origin_time: start_time,
22            reference_time: start_time,
23            reference_ts: TimeStamp::MIN,
24        }
25    }
26
27    pub fn timestamp_from(&self, instant: Instant) -> TimeStamp {
28        if instant < self.reference_time {
29            self.reference_ts - (self.reference_time - instant)
30        } else {
31            self.reference_ts + (instant - self.reference_time)
32        }
33    }
34
35    pub fn instant_from(&self, timestamp: TimeStamp) -> Instant {
36        self.reference_time + (timestamp - self.reference_ts)
37    }
38
39    #[allow(clippy::assign_op_pattern)]
40    pub fn adjust(&mut self, now: Instant, drift: TimeSpan) {
41        self.origin_time = self.origin_time - drift;
42        self.reference_time = self.reference_time - drift;
43
44        if now > self.reference_time {
45            let delta = now - self.reference_time;
46            self.reference_time = self.reference_time + delta;
47            self.reference_ts = self.reference_ts + delta;
48        }
49    }
50}
51
52#[cfg(test)]
53mod timebase {
54    use proptest::prelude::*;
55
56    use super::*;
57
58    use std::time::Duration;
59
60    proptest! {
61        #[test]
62        fn timestamp_roundtrip(expected_ts: u32) {
63            let timebase = TimeBase::new(Instant::now() + Duration::from_micros(expected_ts as u64));
64            let expected_ts = TimeStamp::from_micros(expected_ts);
65
66            let ts = timebase.timestamp_from(timebase.instant_from(expected_ts));
67
68            prop_assert_eq!(ts, expected_ts);
69        }
70
71        #[test]
72        fn timestamp_from(expected_offset: i32, n in -2..2) {
73            let expected_offset = TimeSpan::from_micros(expected_offset / 2);
74            let now = Instant::now() + Duration::from_micros(u32::MAX as u64) * 3;
75            let timebase = TimeBase::new(now);
76            // adjust the test instant time enough so that
77            // 1) underflow and overflow are avoided
78            // 2) TimeStamp rollover is covered
79            let adjustment = ((std::u32::MAX as u64 + 1) as i64) * (n as i64);
80            let instant = if adjustment > 0 {
81                now + Duration::from_micros(adjustment as u64) + expected_offset
82            } else {
83                now.checked_sub(Duration::from_micros(-adjustment as u64)).unwrap() + expected_offset
84            };
85
86            let ts = timebase.timestamp_from(instant);
87
88            prop_assert_eq!(ts, TimeStamp::MIN + expected_offset);
89        }
90
91        #[test]
92        fn adjust(drift: i16, clock_delta: i16) {
93            let start = Instant::now();
94            let drift = TimeSpan::from_micros(drift as i32);
95            let clock_delta = TimeSpan::from_micros(clock_delta as i32);
96            let mut timebase = TimeBase::new(start);
97            let original_ts = timebase.timestamp_from(start);
98            let now = start + clock_delta;
99
100            timebase.adjust(now, drift);
101
102            let original_time = timebase.instant_from(original_ts);
103            assert_eq!(start - drift - original_time, Duration::from_micros(0));
104
105            let ts = timebase.timestamp_from(start);
106            prop_assert_eq!(ts, original_ts - drift);
107        }
108    }
109
110    #[test]
111    fn timestamp_from_past() {
112        let now = Instant::now() + Duration::from_micros(u32::MAX as u64);
113        let timebase = TimeBase::new(now);
114
115        let ts = timebase.timestamp_from(now + TimeSpan::MIN);
116
117        assert_eq!(ts, TimeStamp::MIN + TimeSpan::MIN);
118    }
119
120    #[test]
121    fn timestamp_from_future() {
122        let now = Instant::now();
123        let timebase = TimeBase::new(now);
124
125        let ts = timebase.timestamp_from(now + TimeSpan::MAX);
126
127        assert_eq!(ts, TimeStamp::MIN + TimeSpan::MAX);
128    }
129}