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() -> Self {
24        Self {
25            instant: 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    /// Converts a Unix-epoch duration back into the monotonic [`Instant`] it corresponds to.
33    pub fn instant(&self, duration_since_unix_epoch: Duration) -> Instant {
34        self.instant + duration_since_unix_epoch - self.duration_since_unix_epoch
35    }
36
37    /// The wall-clock time, as a duration since the Unix epoch, captured at construction.
38    pub fn duration_since_unix_epoch(&self) -> Duration {
39        self.duration_since_unix_epoch
40    }
41
42    /// Converts the monotonic `now` into wall-clock time as a duration since the Unix epoch.
43    pub fn unix(&self, now: Instant) -> Duration {
44        now.duration_since(self.instant)
45            .add(self.duration_since_unix_epoch)
46    }
47
48    /// Converts the monotonic `now` into a 64-bit NTP timestamp, as RTCP Sender Reports carry.
49    pub fn ntp(&self, now: Instant) -> u64 {
50        SystemInstant::unix2ntp(self.unix(now))
51    }
52
53    /// Converts a Unix-epoch duration into a 64-bit NTP timestamp.
54    ///
55    /// The result is seconds since the NTP epoch (1900-01-01) in the high 32 bits and a binary
56    /// fraction of a second in the low 32.
57    pub fn unix2ntp(duration_since_unix_epoch: Duration) -> u64 {
58        let u = duration_since_unix_epoch.as_nanos() as u64;
59
60        let mut s = u / 1_000_000_000;
61        s += 0x83AA7E80; //offset in seconds between unix epoch and ntp epoch
62        let mut f = u % 1_000_000_000;
63        f <<= 32;
64        f /= 1_000_000_000;
65        s <<= 32;
66
67        s | f
68    }
69
70    /// Converts a 64-bit NTP timestamp into a duration since the Unix epoch.
71    ///
72    /// The inverse of [`Self::unix2ntp`].
73    pub fn ntp2unix(ntp: u64) -> Duration {
74        let mut s = ntp >> 32;
75        let mut f = ntp & 0xFFFFFFFF;
76        f *= 1_000_000_000;
77        f >>= 32;
78        s -= 0x83AA7E80;
79        let u = s * 1_000_000_000 + f;
80
81        /*let duration_since_unix_epoch =*/
82        Duration::new(u / 1_000_000_000, (u % 1_000_000_000) as u32)
83    }
84}