lilkaoxide/keira_rtc/
time.rs1const MIN_UNIX_US: u64 = 1_577_836_800_000_000;
4const MAX_UNIX_US: u64 = 4_354_819_200_000_000;
5
6#[derive(Clone, Copy, Debug, PartialEq, Eq)]
8pub enum RtcTimeError {
9 NotSynchronized,
11 MonotonicWentBackwards,
13 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}