1use std::fmt;
2use std::ops::{Add, Sub};
3use std::time::{Duration, SystemTime, UNIX_EPOCH};
4
5const NTP_UNIX_EPOCH_DIFF: u64 = 2_208_988_800;
7
8#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
12pub struct NtpTimestamp(pub u64);
13
14impl NtpTimestamp {
15 pub const ZERO: Self = Self(0);
16
17 pub fn new(seconds: u32, fraction: u32) -> Self {
19 Self(((seconds as u64) << 32) | (fraction as u64))
20 }
21
22 pub fn now() -> Self {
24 Self::from_system_time(SystemTime::now())
25 }
26
27 pub fn from_system_time(time: SystemTime) -> Self {
29 let duration = time.duration_since(UNIX_EPOCH).unwrap_or_default();
30 let ntp_seconds = duration.as_secs() + NTP_UNIX_EPOCH_DIFF;
31 let fraction = ((duration.subsec_nanos() as u64) << 32) / 1_000_000_000;
32 Self::new(ntp_seconds as u32, fraction as u32)
33 }
34
35 pub fn to_system_time(self) -> SystemTime {
37 let secs = self.seconds() as u64;
38 let unix_secs = secs.saturating_sub(NTP_UNIX_EPOCH_DIFF);
39 let nanos = ((self.fraction() as u64) * 1_000_000_000) >> 32;
40 UNIX_EPOCH + Duration::new(unix_secs, nanos as u32)
41 }
42
43 pub fn seconds(self) -> u32 {
45 (self.0 >> 32) as u32
46 }
47
48 pub fn fraction(self) -> u32 {
50 self.0 as u32
51 }
52
53 pub fn raw(self) -> u64 {
55 self.0
56 }
57
58 pub fn from_raw(raw: u64) -> Self {
60 Self(raw)
61 }
62
63 pub fn to_bytes(self) -> [u8; 8] {
65 self.0.to_be_bytes()
66 }
67
68 pub fn from_bytes(bytes: [u8; 8]) -> Self {
70 Self(u64::from_be_bytes(bytes))
71 }
72}
73
74impl fmt::Debug for NtpTimestamp {
75 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
76 write!(
77 f,
78 "NtpTimestamp({}.{:010})",
79 self.seconds(),
80 self.fraction()
81 )
82 }
83}
84
85impl fmt::Display for NtpTimestamp {
86 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
87 let nanos = ((self.fraction() as u64) * 1_000_000_000) >> 32;
88 write!(f, "{}.{:09}", self.seconds(), nanos)
89 }
90}
91
92#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
94pub struct PtpTimestamp {
95 pub seconds: u64,
97 pub nanoseconds: u32,
99}
100
101impl PtpTimestamp {
102 pub const ZERO: Self = Self {
103 seconds: 0,
104 nanoseconds: 0,
105 };
106
107 pub fn new(seconds: u64, nanoseconds: u32) -> Self {
108 Self {
109 seconds,
110 nanoseconds,
111 }
112 }
113
114 pub fn to_ntp_timestamp(self) -> NtpTimestamp {
116 let ntp_secs = self.seconds + NTP_UNIX_EPOCH_DIFF;
117 let fraction = ((self.nanoseconds as u64) << 32) / 1_000_000_000;
118 NtpTimestamp::new(ntp_secs as u32, fraction as u32)
119 }
120
121 pub fn to_bytes(self) -> [u8; 10] {
123 let mut buf = [0u8; 10];
124 let sec_bytes = self.seconds.to_be_bytes();
125 buf[0..6].copy_from_slice(&sec_bytes[2..8]);
126 buf[6..10].copy_from_slice(&self.nanoseconds.to_be_bytes());
127 buf
128 }
129
130 pub fn from_bytes(bytes: [u8; 10]) -> Self {
132 let mut sec_buf = [0u8; 8];
133 sec_buf[2..8].copy_from_slice(&bytes[0..6]);
134 let seconds = u64::from_be_bytes(sec_buf);
135 let nanoseconds = u32::from_be_bytes([bytes[6], bytes[7], bytes[8], bytes[9]]);
136 Self {
137 seconds,
138 nanoseconds,
139 }
140 }
141}
142
143impl fmt::Display for PtpTimestamp {
144 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
145 write!(f, "{}.{:09}", self.seconds, self.nanoseconds)
146 }
147}
148
149#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
153pub struct NtpDuration(i128);
154
155impl NtpDuration {
156 pub const ZERO: Self = Self(0);
157
158 const FRAC_BITS: u32 = 32;
160
161 pub fn from_nanos(nanos: i64) -> Self {
163 Self((nanos as i128) << Self::FRAC_BITS)
164 }
165
166 pub fn from_seconds_f64(secs: f64) -> Self {
168 let nanos = secs * 1_000_000_000.0;
169 Self((nanos as i128) << Self::FRAC_BITS)
170 }
171
172 pub fn from_millis(ms: i64) -> Self {
174 Self::from_nanos(ms * 1_000_000)
175 }
176
177 pub fn to_nanos(self) -> i64 {
179 (self.0 >> Self::FRAC_BITS) as i64
180 }
181
182 pub fn to_seconds_f64(self) -> f64 {
184 (self.0 as f64) / ((1i128 << Self::FRAC_BITS) as f64) / 1_000_000_000.0
185 }
186
187 pub fn to_millis_f64(self) -> f64 {
189 self.to_seconds_f64() * 1_000.0
190 }
191
192 pub fn abs(self) -> Self {
194 Self(self.0.abs())
195 }
196
197 pub fn from_ntp_short(raw: u32) -> Self {
199 let seconds = (raw >> 16) as i64;
200 let fraction = (raw & 0xFFFF) as i64;
201 let nanos = seconds * 1_000_000_000 + (fraction * 1_000_000_000) / 65536;
202 Self::from_nanos(nanos)
203 }
204
205 pub fn to_ntp_short(self) -> u32 {
208 let nanos = self.to_nanos().max(0) as u64;
209 let seconds = nanos / 1_000_000_000;
210 let frac_nanos = nanos % 1_000_000_000;
211 let frac = (frac_nanos * 65536) / 1_000_000_000;
212 ((seconds as u32) << 16) | (frac as u32)
213 }
214
215 pub fn between(a: NtpTimestamp, b: NtpTimestamp) -> Self {
217 let diff = (b.0 as i128) - (a.0 as i128);
218 Self(diff * 1_000_000_000 / (1i128 << 32) * (1i128 << Self::FRAC_BITS))
225 }
226
227 pub fn raw(self) -> i128 {
229 self.0
230 }
231
232 pub fn from_raw(raw: i128) -> Self {
234 Self(raw)
235 }
236}
237
238impl Add for NtpDuration {
239 type Output = Self;
240 fn add(self, rhs: Self) -> Self {
241 Self(self.0 + rhs.0)
242 }
243}
244
245impl Sub for NtpDuration {
246 type Output = Self;
247 fn sub(self, rhs: Self) -> Self {
248 Self(self.0 - rhs.0)
249 }
250}
251
252impl std::ops::Div<i64> for NtpDuration {
253 type Output = Self;
254 fn div(self, rhs: i64) -> Self {
255 Self(self.0 / rhs as i128)
256 }
257}
258
259impl std::ops::Neg for NtpDuration {
260 type Output = Self;
261 fn neg(self) -> Self {
262 Self(-self.0)
263 }
264}
265
266impl fmt::Debug for NtpDuration {
267 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
268 write!(f, "NtpDuration({:.9}s)", self.to_seconds_f64())
269 }
270}
271
272impl fmt::Display for NtpDuration {
273 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
274 let secs = self.to_seconds_f64();
275 if secs.abs() < 0.001 {
276 write!(f, "{:.3}us", secs * 1_000_000.0)
277 } else if secs.abs() < 1.0 {
278 write!(f, "{:.3}ms", secs * 1_000.0)
279 } else {
280 write!(f, "{:.6}s", secs)
281 }
282 }
283}
284
285#[cfg(test)]
286mod tests {
287 use super::*;
288
289 #[test]
290 fn ntp_timestamp_roundtrip_bytes() {
291 let ts = NtpTimestamp::new(3_900_000_000, 2_147_483_648);
292 let bytes = ts.to_bytes();
293 let ts2 = NtpTimestamp::from_bytes(bytes);
294 assert_eq!(ts, ts2);
295 }
296
297 #[test]
298 fn ntp_timestamp_system_time_roundtrip() {
299 let now = SystemTime::now();
300 let ts = NtpTimestamp::from_system_time(now);
301 let back = ts.to_system_time();
302 let diff = now
303 .duration_since(back)
304 .or_else(|e| Ok::<_, std::convert::Infallible>(e.duration()))
305 .unwrap();
306 assert!(diff < Duration::from_micros(1));
308 }
309
310 #[test]
311 fn ntp_timestamp_zero() {
312 let ts = NtpTimestamp::ZERO;
313 assert_eq!(ts.seconds(), 0);
314 assert_eq!(ts.fraction(), 0);
315 }
316
317 #[test]
318 fn ptp_timestamp_roundtrip_bytes() {
319 let ts = PtpTimestamp::new(1_000_000, 500_000_000);
320 let bytes = ts.to_bytes();
321 let ts2 = PtpTimestamp::from_bytes(bytes);
322 assert_eq!(ts, ts2);
323 }
324
325 #[test]
326 fn ptp_timestamp_48bit_seconds() {
327 let ts = PtpTimestamp::new(0x0000_FFFF_FFFF_FFFF, 0);
329 let bytes = ts.to_bytes();
330 let ts2 = PtpTimestamp::from_bytes(bytes);
331 assert_eq!(ts2.seconds, 0x0000_FFFF_FFFF_FFFF);
332 }
333
334 #[test]
335 fn ntp_duration_from_nanos() {
336 let d = NtpDuration::from_nanos(1_000_000_000); assert!((d.to_seconds_f64() - 1.0).abs() < 1e-9);
338 }
339
340 #[test]
341 fn ntp_duration_from_seconds_f64() {
342 let d = NtpDuration::from_seconds_f64(0.5);
343 assert!((d.to_seconds_f64() - 0.5).abs() < 1e-9);
344 }
345
346 #[test]
347 fn ntp_duration_arithmetic() {
348 let a = NtpDuration::from_nanos(100);
349 let b = NtpDuration::from_nanos(50);
350 assert_eq!((a + b).to_nanos(), 150);
351 assert_eq!((a - b).to_nanos(), 50);
352 assert_eq!((a / 2).to_nanos(), 50);
353 assert_eq!((-a).to_nanos(), -100);
354 }
355
356 #[test]
357 fn ntp_duration_abs() {
358 let d = NtpDuration::from_nanos(-500);
359 assert_eq!(d.abs().to_nanos(), 500);
360 }
361
362 #[test]
363 fn ntp_duration_between_timestamps() {
364 let t1 = NtpTimestamp::new(1000, 0);
365 let t2 = NtpTimestamp::new(1001, 0);
366 let diff = NtpDuration::between(t1, t2);
367 assert!((diff.to_seconds_f64() - 1.0).abs() < 1e-6);
368 }
369
370 #[test]
371 fn ntp_duration_ntp_short_roundtrip() {
372 let d = NtpDuration::from_millis(500);
373 let short = d.to_ntp_short();
374 let d2 = NtpDuration::from_ntp_short(short);
375 assert!((d.to_millis_f64() - d2.to_millis_f64()).abs() < 0.1);
376 }
377
378 #[test]
379 fn ntp_duration_display() {
380 let d = NtpDuration::from_nanos(500);
381 assert!(format!("{}", d).contains("us"));
382
383 let d = NtpDuration::from_millis(50);
384 assert!(format!("{}", d).contains("ms"));
385
386 let d = NtpDuration::from_seconds_f64(2.5);
387 assert!(format!("{}", d).contains("s"));
388 }
389}