Skip to main content

rtc_shared/
time.rs

1//! Monotonic, Unix and NTP time.
2//!
3//! Protocol logic measures time with a monotonic [`Instant`](std::time::Instant), which cannot go
4//! backwards but has no absolute meaning. RTCP timestamps need the opposite: wall-clock time in
5//! NTP format. [`SystemInstant`](crate::time::SystemInstant) captures both once, so either can be derived from the other later
6//! without re-reading a clock that may have been adjusted in between.
7use std::ops::Add;
8use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
9
10#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
11/// A monotonic [`Instant`] paired with the wall-clock time it was taken at.
12///
13/// Sans-I/O protocol code measures time with a monotonic [`Instant`], but RTCP timestamps
14/// and NTP fields need wall-clock time. Capturing both once lets either be derived from the
15/// other later without re-reading the (non-monotonic) system clock.
16pub struct SystemInstant {
17    instant: Instant,
18    duration_since_unix_epoch: Duration,
19}
20
21impl SystemInstant {
22    /// Captures the current monotonic instant together with the current wall-clock time.
23    pub fn now(now: Instant) -> Self {
24        Self {
25            instant: now,
26            duration_since_unix_epoch: SystemTime::now()
27                .duration_since(UNIX_EPOCH)
28                .unwrap_or_else(|_| Duration::from_secs(0)),
29        }
30    }
31
32    /// Only used for deserialization
33    pub fn from_epoch(duration_since_unix_epoch: Duration) -> Self {
34        let system_now = SystemTime::now(); // Exemption: wall-clock is correct here to deserialization only
35        let instant_now = Instant::now(); // Exemption: Instant-now is correct here to deserialization only
36
37        let duration_since_approx = system_now
38            .duration_since(UNIX_EPOCH + duration_since_unix_epoch)
39            .unwrap_or_else(|_| Duration::from_secs(0));
40
41        let instant = instant_now - duration_since_approx;
42
43        Self {
44            instant,
45            duration_since_unix_epoch,
46        }
47    }
48
49    /// Converts a Unix-epoch duration back into the monotonic [`Instant`] it corresponds to.
50    pub fn instant(&self, duration_since_unix_epoch: Duration) -> Instant {
51        self.instant + duration_since_unix_epoch - self.duration_since_unix_epoch
52    }
53
54    /// The wall-clock time, as a duration since the Unix epoch, captured at construction.
55    pub fn duration_since_unix_epoch(&self) -> Duration {
56        self.duration_since_unix_epoch
57    }
58
59    /// Converts the monotonic `now` into wall-clock time as a duration since the Unix epoch.
60    pub fn unix(&self, now: Instant) -> Duration {
61        now.duration_since(self.instant)
62            .add(self.duration_since_unix_epoch)
63    }
64
65    /// Converts the monotonic `now` into a 64-bit NTP timestamp, as RTCP Sender Reports carry.
66    pub fn ntp(&self, now: Instant) -> u64 {
67        SystemInstant::unix2ntp(self.unix(now))
68    }
69
70    /// Converts a Unix-epoch duration into a 64-bit NTP timestamp.
71    ///
72    /// The result is seconds since the NTP epoch (1900-01-01) in the high 32 bits and a binary
73    /// fraction of a second in the low 32.
74    pub fn unix2ntp(duration_since_unix_epoch: Duration) -> u64 {
75        let u = duration_since_unix_epoch.as_nanos() as u64;
76
77        let mut s = u / 1_000_000_000;
78        s += 0x83AA7E80; //offset in seconds between unix epoch and ntp epoch
79        let mut f = u % 1_000_000_000;
80        f <<= 32;
81        f /= 1_000_000_000;
82        s <<= 32;
83
84        s | f
85    }
86
87    /// Converts a 64-bit NTP timestamp into a duration since the Unix epoch.
88    ///
89    /// The inverse of [`Self::unix2ntp`].
90    pub fn ntp2unix(ntp: u64) -> Duration {
91        let mut s = ntp >> 32;
92        let mut f = ntp & 0xFFFFFFFF;
93        f *= 1_000_000_000;
94        f >>= 32;
95        s -= 0x83AA7E80;
96        let u = s * 1_000_000_000 + f;
97
98        /*let duration_since_unix_epoch =*/
99        Duration::new(u / 1_000_000_000, (u % 1_000_000_000) as u32)
100    }
101}