lilkaoxide/keira_rtc/
time.rs

1//! Hardware-independent validation and monotonic progression of an RTC sample.
2
3const MIN_UNIX_US: u64 = 1_577_836_800_000_000;
4const MAX_UNIX_US: u64 = 4_354_819_200_000_000;
5
6/// Why a retained RTC clock cannot supply a usable wall time.
7#[derive(Clone, Copy, Debug, PartialEq, Eq)]
8pub enum RtcTimeError {
9    /// RTC did not contain a date in the accepted 2020..2107 range.
10    NotSynchronized,
11    /// The monotonic timer moved behind the sampled value.
12    MonotonicWentBackwards,
13    /// Arithmetic overflow or the clock advanced outside the supported range.
14    OutOfRange,
15}
16impl core::fmt::Display for RtcTimeError {
17    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
18        f.write_str(match self {
19            Self::NotSynchronized => "RTC wall time is unavailable or implausible",
20            Self::MonotonicWentBackwards => "monotonic timer moved backwards",
21            Self::OutOfRange => "wall time is outside the supported range",
22        })
23    }
24}
25impl core::error::Error for RtcTimeError {}
26
27pub(crate) struct TimeAnchor {
28    unix_us: u64,
29    uptime_us: u64,
30}
31impl TimeAnchor {
32    pub(crate) fn new(unix_us: u64, uptime_us: u64) -> Result<Self, RtcTimeError> {
33        if !(MIN_UNIX_US..MAX_UNIX_US).contains(&unix_us) {
34            return Err(RtcTimeError::NotSynchronized);
35        }
36        Ok(Self { unix_us, uptime_us })
37    }
38    pub(crate) fn at(&self, uptime_us: u64) -> Result<u64, RtcTimeError> {
39        let elapsed = uptime_us
40            .checked_sub(self.uptime_us)
41            .ok_or(RtcTimeError::MonotonicWentBackwards)?;
42        let unix_us = self
43            .unix_us
44            .checked_add(elapsed)
45            .ok_or(RtcTimeError::OutOfRange)?;
46        if !(MIN_UNIX_US..MAX_UNIX_US).contains(&unix_us) {
47            return Err(RtcTimeError::OutOfRange);
48        }
49        Ok(unix_us)
50    }
51}
52
53#[cfg(test)]
54mod tests {
55    use super::*;
56    #[test]
57    fn preserves_subseconds_and_long_monotonic_uptime() {
58        let clock = TimeAnchor::new(1_792_670_400_123_456, 9_000_000_000_000).unwrap();
59        assert_eq!(clock.at(9_000_061_000_001), Ok(1_792_670_461_123_457));
60    }
61    #[test]
62    fn rejects_unset_and_out_of_range_rtc() {
63        for value in [0, 60_000_000, MIN_UNIX_US - 1, MAX_UNIX_US, u64::MAX] {
64            assert!(matches!(
65                TimeAnchor::new(value, 0),
66                Err(RtcTimeError::NotSynchronized)
67            ));
68        }
69        assert!(TimeAnchor::new(MIN_UNIX_US, 0).is_ok());
70    }
71    #[test]
72    fn refuses_counter_rewind_overflow_and_end_of_range() {
73        let clock = TimeAnchor::new(MIN_UNIX_US, 100).unwrap();
74        assert_eq!(clock.at(99), Err(RtcTimeError::MonotonicWentBackwards));
75        assert_eq!(clock.at(u64::MAX), Err(RtcTimeError::OutOfRange));
76        let clock = TimeAnchor::new(MAX_UNIX_US - 1, 0).unwrap();
77        assert_eq!(clock.at(1), Err(RtcTimeError::OutOfRange));
78    }
79}