1use crate::clock::{ClockAnchor, ClockSource};
16use crate::error::{ClockError, Result};
17
18pub trait PtpTimeProvider: Send + Sync {
24 fn ptp_now_ns(&self) -> Result<i64>;
31}
32
33pub struct PtpClock {
39 anchor: ClockAnchor,
40 provider: Box<dyn PtpTimeProvider>,
41}
42
43impl std::fmt::Debug for PtpClock {
44 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
45 f.debug_struct("PtpClock")
46 .field("anchor", &self.anchor)
47 .field("provider", &"<dyn PtpTimeProvider>")
48 .finish()
49 }
50}
51
52impl PtpClock {
53 #[must_use]
55 pub fn with_provider(anchor: ClockAnchor, provider: Box<dyn PtpTimeProvider>) -> Self {
56 Self { anchor, provider }
57 }
58
59 #[must_use]
65 pub fn unavailable(anchor: ClockAnchor) -> Self {
66 Self {
67 anchor,
68 provider: Box::new(NoPtp),
69 }
70 }
71
72 #[must_use]
74 pub fn anchor(&self) -> ClockAnchor {
75 self.anchor
76 }
77}
78
79impl ClockSource for PtpClock {
80 fn ptp_now_ns(&self) -> Result<i64> {
81 self.provider.ptp_now_ns()
82 }
83
84 fn sample_to_ptp(&self, sample_idx: i64) -> i64 {
85 self.anchor
86 .sample_to_ptp_checked(sample_idx)
87 .unwrap_or(i64::MIN)
88 }
89
90 fn ptp_to_sample(&self, ptp_ns: i64) -> i64 {
91 self.anchor
92 .ptp_to_sample_checked(ptp_ns)
93 .unwrap_or(i64::MIN)
94 }
95}
96
97struct NoPtp;
99
100impl PtpTimeProvider for NoPtp {
101 fn ptp_now_ns(&self) -> Result<i64> {
102 Err(ClockError::Unavailable(
103 "no PTP hardware timestamping available".into(),
104 ))
105 }
106}
107
108#[cfg(test)]
109mod tests {
110 use super::*;
111 use std::sync::atomic::{AtomicI64, Ordering};
112
113 struct StubProvider {
115 t: AtomicI64,
116 }
117
118 impl PtpTimeProvider for StubProvider {
119 fn ptp_now_ns(&self) -> Result<i64> {
120 Ok(self.t.load(Ordering::Relaxed))
121 }
122 }
123
124 #[test]
125 fn unavailable_reports_unavailable() {
126 let clock = PtpClock::unavailable(ClockAnchor::new(48_000, 0, 0).unwrap());
127 let err = clock.ptp_now_ns().unwrap_err();
128 assert!(matches!(err, ClockError::Unavailable(_)));
129 }
130
131 #[test]
132 fn unavailable_conversions_still_work() {
133 let clock = PtpClock::unavailable(ClockAnchor::new(48_000, 0, 0).unwrap());
134 assert_eq!(clock.sample_to_ptp(48_000), 1_000_000_000);
135 assert_eq!(clock.ptp_to_sample(1_000_000_000), 48_000);
136 }
137
138 #[test]
139 fn with_provider_reads_live_timestamp() {
140 let provider = StubProvider {
141 t: AtomicI64::new(7_000_000_000),
142 };
143 let clock =
144 PtpClock::with_provider(ClockAnchor::new(48_000, 0, 0).unwrap(), Box::new(provider));
145 assert_eq!(clock.ptp_now_ns().unwrap(), 7_000_000_000);
146 }
147
148 #[test]
149 fn provider_not_synced_propagates() {
150 struct Unsynced;
151 impl PtpTimeProvider for Unsynced {
152 fn ptp_now_ns(&self) -> Result<i64> {
153 Err(ClockError::NotSynced("ptp master not locked".into()))
154 }
155 }
156 let clock =
157 PtpClock::with_provider(ClockAnchor::new(48_000, 0, 0).unwrap(), Box::new(Unsynced));
158 let err = clock.ptp_now_ns().unwrap_err();
159 assert!(matches!(err, ClockError::NotSynced(_)));
160 }
161
162 #[test]
163 fn dyn_clock_source_object_with_provider() {
164 let clock: Box<dyn ClockSource> = Box::new(PtpClock::unavailable(
165 ClockAnchor::new(48_000, 0, 0).unwrap(),
166 ));
167 assert!(clock.ptp_now_ns().is_err());
169 assert_eq!(clock.sample_to_ptp(0), 0);
170 }
171}