1use crate::clock::Clock;
10use crate::monotonic::mono_floor;
11use crate::quality::{ClockSource, SyncQuality};
12use crate::timestamp::{Timestamp, WallTime};
13
14#[cfg(all(feature = "std", feature = "ptp"))]
15use core::sync::atomic::{AtomicU8, Ordering};
16#[cfg(all(not(feature = "std"), feature = "libc"))]
17use libc::{CLOCK_MONOTONIC, CLOCK_REALTIME};
18#[cfg(feature = "std")]
19use std::sync::OnceLock;
20#[cfg(feature = "std")]
21use std::time::Instant;
22
23#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
30pub struct SystemClock;
31
32impl SystemClock {
33 #[must_use]
35 pub const fn new() -> Self {
36 Self
37 }
38}
39
40impl Clock for SystemClock {
41 fn now(&self) -> Timestamp {
42 Timestamp {
43 wall: WallTime::from_unix_nanos(wall_nanos()),
44 mono: mono_floor(monotonic_nanos()),
45 }
46 }
47
48 fn quality(&self) -> SyncQuality {
49 #[cfg(feature = "ptp")]
50 {
51 cached_quality()
52 }
53 #[cfg(not(feature = "ptp"))]
54 {
55 SyncQuality::Fallback
57 }
58 }
59
60 fn source(&self) -> ClockSource {
61 #[cfg(feature = "std")]
62 {
63 ClockSource::Instant
64 }
65 #[cfg(not(feature = "std"))]
66 {
67 ClockSource::LibcMono
68 }
69 }
70}
71
72pub(crate) fn wall_nanos() -> u64 {
74 #[cfg(feature = "std")]
75 {
76 crate::timestamp::WallTime::from_system_now().unix_nanos()
77 }
78 #[cfg(all(not(feature = "std"), feature = "libc"))]
79 {
80 crate::low::clock_gettime_ns(CLOCK_REALTIME).unwrap_or(0)
81 }
82}
83
84pub(crate) fn monotonic_nanos() -> u64 {
87 #[cfg(feature = "std")]
88 {
89 static ANCHOR: OnceLock<Instant> = OnceLock::new();
90 let anchor = ANCHOR.get_or_init(Instant::now);
91 u64::try_from(anchor.elapsed().as_nanos()).unwrap_or(u64::MAX)
92 }
93 #[cfg(all(not(feature = "std"), feature = "libc"))]
94 {
95 crate::low::clock_gettime_ns(CLOCK_MONOTONIC).unwrap_or(0)
96 }
97}
98
99#[cfg(all(not(feature = "std"), feature = "libc"))]
100use libc::{CLOCK_MONOTONIC, CLOCK_REALTIME};
101
102#[cfg(feature = "ptp")]
103pub(crate) fn cached_quality() -> SyncQuality {
104 static CACHE: AtomicU8 = AtomicU8::new(0);
106 let seen = CACHE.load(Ordering::Acquire);
107 if seen != 0 {
108 return num_to_quality(seen);
109 }
110 let detected = crate::ptp::detect_sync_quality();
111 CACHE.store(discriminant(detected), Ordering::Release);
112 detected
113}
114
115#[cfg(feature = "ptp")]
116fn discriminant(q: SyncQuality) -> u8 {
117 match q {
118 SyncQuality::Ptp => 1,
119 SyncQuality::Ntp => 2,
120 SyncQuality::Fallback => 3,
121 }
122}
123
124#[cfg(feature = "ptp")]
125fn num_to_quality(n: u8) -> SyncQuality {
126 match n {
127 1 => SyncQuality::Ptp,
128 2 => SyncQuality::Ntp,
129 _ => SyncQuality::Fallback,
130 }
131}
132
133#[cfg(all(test, feature = "std", not(miri)))]
135mod tests {
136 use crate::clock::Clock;
137 use crate::quality::{ClockSource, SyncQuality};
138
139 #[cfg(not(miri))]
144 #[test]
145 fn advisory_surface_is_total_and_cached() {
146 let clock = crate::SystemClock::new();
147 let q1 = clock.quality();
148 let q2 = clock.quality();
149 assert_eq!(q1, q2, "cached quality must be stable");
150 assert!(matches!(
151 q1,
152 SyncQuality::Ptp | SyncQuality::Ntp | SyncQuality::Fallback
153 ));
154
155 #[cfg(feature = "ptp")]
156 assert_eq!(q1, crate::ptp::detect_sync_quality());
157 #[cfg(not(feature = "ptp"))]
158 assert_eq!(q1, SyncQuality::Fallback);
159
160 #[cfg(feature = "std")]
161 assert_eq!(clock.source(), ClockSource::Instant);
162 assert!(matches!(
163 clock.source(),
164 ClockSource::Instant | ClockSource::LibcMono
165 ));
166
167 let a = clock.now();
169 let b = clock.now();
170 assert!(b.mono >= a.mono);
171 assert!(b.wall.unix_nanos() >= a.wall.unix_nanos() || a.mono == 0);
172 }
173}