1use statime_base::{Clock, ClockError, Duration, LeapStatus, Timestamp, TAI, UTC};
2
3#[cfg(target_os = "linux")]
4use crate::linux_ioctls::{ptp_clock_getcaps, PtpClockCaps};
5use std::marker::PhantomData;
6#[cfg(target_os = "linux")]
7use std::mem::MaybeUninit;
8#[cfg(target_os = "linux")]
9use std::{
10 os::unix::io::{IntoRawFd, RawFd},
11 path::Path,
12};
13
14pub struct UnixClock<Timescale> {
16 clock: libc::clockid_t,
17 #[cfg(target_os = "linux")]
18 fd: Option<RawFd>,
19 _timescale: PhantomData<Timescale>,
20}
21
22impl<Timescale> std::fmt::Debug for UnixClock<Timescale> {
23 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
24 #[cfg(target_os = "linux")]
25 {
26 f.debug_struct("UnixClock")
27 .field("clock", &self.clock)
28 .field("fd", &self.fd)
29 .field("_timescale", &self._timescale)
30 .finish()
31 }
32
33 #[cfg(not(target_os = "linux"))]
34 {
35 f.debug_struct("UnixClock")
36 .field("clock", &self.clock)
37 .field("_timescale", &self._timescale)
38 .finish()
39 }
40 }
41}
42
43impl<Timescale> Clone for UnixClock<Timescale> {
44 fn clone(&self) -> Self {
45 *self
46 }
47}
48
49impl<Timescale> Copy for UnixClock<Timescale> {}
50
51impl UnixClock<UTC> {
52 pub const CLOCK_REALTIME: Self = UnixClock {
66 clock: libc::CLOCK_REALTIME,
67 #[cfg(target_os = "linux")]
68 fd: None,
69 _timescale: PhantomData,
70 };
71}
72
73impl UnixClock<TAI> {
74 #[cfg(target_os = "linux")]
88 pub const CLOCK_TAI: Self = UnixClock {
89 clock: libc::CLOCK_TAI,
90 fd: None,
91 _timescale: PhantomData,
92 };
93
94 #[cfg(target_os = "linux")]
111 pub fn open(path: impl AsRef<Path>) -> std::io::Result<Self> {
112 let file = std::fs::OpenOptions::new()
113 .write(true)
114 .read(true)
115 .open(path)?;
116
117 let clock = Self::safe_from_raw_fd(file.into_raw_fd());
119
120 Ok(clock)
121 }
122
123 #[cfg(target_os = "linux")]
127 fn safe_from_raw_fd(fd: RawFd) -> Self {
128 let clock = ((!(fd as libc::clockid_t)) << 3) | 3;
129
130 Self {
131 clock,
132 fd: Some(fd),
133 _timescale: PhantomData,
134 }
135 }
136
137 #[cfg(target_os = "linux")]
144 #[expect(clippy::type_complexity, reason = "FIXME: improve this")]
145 #[allow(
146 clippy::cast_possible_truncation,
147 reason = "Casts for timestamp conversion aren't lossless on all platforms"
148 )]
149 pub fn system_offset(
150 &self,
151 ) -> Result<(Timestamp<TAI>, Timestamp<TAI>, Timestamp<TAI>), ClockError> {
152 use libc::{ptp_clock_time, ptp_sys_offset, PTP_SYS_OFFSET};
153
154 let Some(fd) = self.fd else {
155 return Err(ClockError::InvalidValue);
156 };
157
158 let default_ptp_clock_time = ptp_clock_time {
159 sec: 0,
160 nsec: 0,
161 reserved: 0,
162 };
163
164 let mut offset = ptp_sys_offset {
165 n_samples: 1,
166 rsv: [0u32; 3],
167 ts: [default_ptp_clock_time; 51],
168 };
169
170 if unsafe { libc::ioctl(fd, PTP_SYS_OFFSET as _, &mut offset) } != 0 {
174 let t1 = Self::CLOCK_TAI.now();
175 let tp = self.now();
176 let t2 = Self::CLOCK_TAI.now();
177
178 Ok((t1?, tp?, t2?))
179 } else {
180 let tai_offset = Self::CLOCK_TAI.get_tai()?;
181
182 Ok((
183 Timestamp::from_seconds_nanos_since_unix_epoch(
184 (offset.ts[0].sec + i64::from(tai_offset)).cast_unsigned(),
185 offset.ts[0].nsec as _,
186 ),
187 Timestamp::from_seconds_nanos_since_unix_epoch(
188 offset.ts[1].sec.cast_unsigned(),
189 offset.ts[1].nsec as _,
190 ),
191 Timestamp::from_seconds_nanos_since_unix_epoch(
192 (offset.ts[2].sec + i64::from(tai_offset)).cast_unsigned(),
193 offset.ts[2].nsec as _,
194 ),
195 ))
196 }
197 }
198}
199
200impl<Timescale> UnixClock<Timescale> {
201 pub fn disable_kernel(&self) -> Result<(), ClockError> {
206 let mut timex = EMPTY_TIMEX;
207 self.adjtime(&mut timex)?;
208
209 timex.modes = libc::MOD_STATUS;
211
212 timex.status &= !(libc::STA_PLL | libc::STA_FLL | libc::STA_PPSTIME | libc::STA_PPSFREQ);
214
215 ignore_not_supported(self.adjtime(&mut timex))
217 }
218
219 #[allow(
220 clippy::trivially_copy_pass_by_ref,
221 reason = "Allows a consistent interface between platforms."
222 )]
223 fn clock_adjtime(&self, timex: &mut libc::timex) -> Result<(), ClockError> {
224 #[cfg(target_os = "linux")]
235 use libc::clock_adjtime as adjtime;
236
237 #[cfg(any(target_os = "freebsd", target_os = "macos"))]
238 unsafe fn adjtime(clk_id: libc::clockid_t, buf: *mut libc::timex) -> libc::c_int {
239 assert_eq!(
240 clk_id,
241 libc::CLOCK_REALTIME,
242 "only the REALTIME clock is supported"
243 );
244
245 libc::ntp_adjtime(buf)
246 }
247
248 if unsafe { adjtime(self.clock, timex) } == -1 {
249 Err(convert_errno())
250 } else {
251 Ok(())
252 }
253 }
254
255 fn ntp_adjtime(timex: &mut libc::timex) -> Result<(), ClockError> {
256 #[cfg(any(target_os = "freebsd", target_os = "macos", target_env = "gnu"))]
257 use libc::ntp_adjtime as adjtime;
258
259 #[cfg(all(target_os = "linux", target_env = "musl"))]
263 use libc::adjtimex as adjtime;
264
265 if unsafe { adjtime(timex) } == -1 {
270 Err(convert_errno())
271 } else {
272 Ok(())
273 }
274 }
275
276 #[allow(
284 clippy::trivially_copy_pass_by_ref,
285 reason = "Allows a consistent interface between platforms."
286 )]
287 fn adjtime(&self, timex: &mut libc::timex) -> Result<(), ClockError> {
288 if self.clock == libc::CLOCK_REALTIME {
289 Self::ntp_adjtime(timex)
290 } else {
291 self.clock_adjtime(timex)
292 }
293 }
294
295 #[cfg_attr(target_os = "linux", allow(unused))]
296 fn clock_gettime(&self) -> Result<libc::timespec, ClockError> {
297 let mut timespec = libc::timespec::default();
298
299 cerr(unsafe { libc::clock_gettime(self.clock, &raw mut timespec) })?;
306
307 Ok(timespec)
308 }
309
310 #[cfg_attr(target_os = "linux", allow(unused))]
311 fn clock_settime(&self, mut timespec: libc::timespec) -> Result<(), ClockError> {
312 while timespec.tv_nsec > 1_000_000_000 {
313 timespec.tv_sec += 1;
314 timespec.tv_nsec -= 1_000_000_000;
315 }
316
317 unsafe { cerr(libc::clock_settime(self.clock, &raw const timespec))? };
324
325 Ok(())
326 }
327
328 #[cfg_attr(target_os = "linux", allow(unused))]
329 #[allow(
330 clippy::trivially_copy_pass_by_ref,
331 reason = "Allows a consistent interface between platforms."
332 )]
333 fn step_clock_by_timespec(&self, offset: Duration) -> Result<Timestamp<Timescale>, ClockError> {
334 let steps = offset.as_raw_steps();
335 #[cfg_attr(
336 target_env = "musl",
337 allow(
338 deprecated,
339 reason = "see https://github.com/rust-lang/libc/issues/1848"
340 )
341 )]
342 let seconds: libc::time_t = (steps >> 64)
343 .try_into()
344 .map_err(|_| ClockError::InvalidValue)?;
345 #[allow(
346 clippy::cast_possible_truncation,
347 reason = "the resulting value will always fit in an u32"
348 )]
349 let nanos = (((steps.cast_unsigned() & u128::from(u64::MAX)) * 1_000_000_000) >> 64)
350 as libc::c_long;
351
352 let mut timespec = self.clock_gettime()?;
353
354 #[cfg_attr(target_env = "musl", allow(deprecated))]
356 #[allow(
357 clippy::cast_possible_wrap,
358 reason = "The actual value is always possible, but not unsigned on all platforms."
359 )]
360 #[allow(
361 clippy::cast_lossless,
362 reason = "The cast can be done lossless on some, but not all, platforms."
363 )]
364 {
365 timespec.tv_sec += seconds;
366 timespec.tv_nsec += nanos;
367 }
368
369 self.clock_settime(timespec)?;
370
371 Ok(current_time_timespec(timespec, Precision::Nano))
372 }
373
374 fn error_estimate_timex(esterror: Duration, maxerror: Duration) -> libc::timex {
375 let modes = libc::MOD_ESTERROR | libc::MOD_MAXERROR;
376
377 let esterror =
379 libc::c_long::try_from((esterror.as_nanos() + 500) / 1000).unwrap_or(libc::c_long::MAX);
380 let maxerror =
381 libc::c_long::try_from((maxerror.as_nanos() + 500) / 1000).unwrap_or(libc::c_long::MAX);
382
383 libc::timex {
384 modes,
385 esterror,
386 maxerror,
387 ..EMPTY_TIMEX
388 }
389 }
390
391 #[cfg(target_os = "linux")]
392 fn step_clock_timex(offset: Duration) -> Result<libc::timex, ClockError> {
393 let modes = libc::ADJ_SETOFFSET | libc::ADJ_NANO;
395
396 let nanos = offset.as_nanos();
397 let seconds = nanos
398 .div_euclid(1_000_000_000)
399 .try_into()
400 .map_err(|_| ClockError::InvalidValue)?;
401 #[allow(
402 clippy::cast_possible_truncation,
403 reason = "the resulting value will always fit in an u32"
404 )]
405 let nanos = nanos.rem_euclid(1_000_000_000) as _;
406
407 let time = libc::timeval {
408 tv_sec: seconds,
409 tv_usec: nanos,
410 };
411
412 Ok(libc::timex {
413 modes,
414 time,
415 ..EMPTY_TIMEX
416 })
417 }
418
419 #[cfg(target_os = "linux")]
420 fn step_clock_by_timex(&self, offset: Duration) -> Result<Timestamp<Timescale>, ClockError> {
421 let mut timex = Self::step_clock_timex(offset)?;
422 self.adjtime(&mut timex)?;
423 self.extract_current_time(&timex)
424 }
425
426 #[allow(
427 clippy::trivially_copy_pass_by_ref,
428 reason = "Allows a consistent interface between platforms."
429 )]
430 fn extract_current_time(
431 &self,
432 #[cfg_attr(target_os = "macos", allow(unused))] timex: &libc::timex,
433 ) -> Result<Timestamp<Timescale>, ClockError> {
434 #[cfg(target_os = "linux")]
435 if timex.time.tv_sec != 0 && timex.time.tv_usec != 0 {
437 let precision = match timex.status & libc::STA_NANO {
439 0 => Precision::Micro,
440 _ => Precision::Nano,
441 };
442
443 return Ok(current_time_timeval(timex.time, precision));
444 }
445
446 let timespec = self.clock_gettime()?;
448 Ok(current_time_timespec(timespec, Precision::Nano))
449 }
450
451 #[allow(
452 clippy::trivially_copy_pass_by_ref,
453 reason = "Allows a consistent interface between platforms."
454 )]
455 fn update_timex<F>(&self, f: F) -> Result<(), ClockError>
456 where
457 F: FnOnce(libc::timex) -> libc::timex,
458 {
459 let mut timex = EMPTY_TIMEX;
460 self.adjtime(&mut timex)?;
461
462 timex = f(timex);
463
464 self.adjtime(&mut timex)
465 }
466
467 #[allow(
468 clippy::trivially_copy_pass_by_ref,
469 reason = "Allows a consistent interface between platforms."
470 )]
471 fn update_status<F>(&self, f: F) -> Result<(), ClockError>
472 where
473 F: FnOnce(libc::c_int) -> libc::c_int,
474 {
475 self.update_timex(|mut timex| {
476 timex.modes = libc::MOD_STATUS;
478
479 timex.status = f(timex.status);
481
482 timex
483 })
484 }
485
486 #[expect(
487 clippy::cast_possible_truncation,
488 reason = "Truncating the frequency is acceptable here."
489 )]
490 fn set_frequency_timex(ppm: f64) -> libc::timex {
491 let mut timex = EMPTY_TIMEX;
493
494 timex.modes = libc::MOD_FREQUENCY;
496
497 let frequency = (ppm * 65536.0).round() as libc::c_long;
500
501 timex.freq = frequency.clamp(-32_768_000 + 1, 32_768_000 - 1);
505
506 timex
507 }
508
509 #[cfg(target_os = "linux")]
514 pub fn set_tai(&self, tai_offset: i32) -> Result<(), ClockError> {
515 #[allow(
516 clippy::useless_conversion,
517 reason = "This conversion is not useless on all platforms."
518 )]
519 let mut timex = libc::timex {
520 modes: libc::ADJ_TAI,
521 constant: tai_offset.into(),
522 ..EMPTY_TIMEX
523 };
524
525 self.clock_adjtime(&mut timex)
526 }
527
528 #[cfg(not(target_os = "linux"))]
533 pub fn set_tai(&self, _tai_offset: i32) -> Result<(), ClockError> {
534 Err(ClockError::NotSupported)
535 }
536
537 #[cfg(target_os = "linux")]
542 pub fn get_tai(&self) -> Result<i32, ClockError> {
543 let mut timex = EMPTY_TIMEX;
544 if self.clock_adjtime(&mut timex).is_ok() {
545 Ok(timex.tai)
546 } else {
547 Ok(0)
549 }
550 }
551
552 #[cfg(not(target_os = "linux"))]
557 pub fn get_tai(&self) -> Result<i32, ClockError> {
558 Err(ClockError::NotSupported)
559 }
560
561 pub fn resolution(&self) -> Result<Duration, ClockError> {
569 let mut timespec = libc::timespec::default();
570
571 cerr(unsafe { libc::clock_getres(self.clock, &raw mut timespec) })?;
572
573 Ok(if timespec.tv_nsec == 0 && timespec.tv_sec == 0 {
574 Duration::from_seconds_nanos(0, 1)
575 } else {
576 #[allow(
577 clippy::cast_possible_truncation,
578 reason = "tv_nsec is guaranteed to be between 0 and 1_000_000_000, truncation doesn't happen on all platforms though."
579 )]
580 #[expect(
581 clippy::cast_sign_loss,
582 reason = "tv_nsec is guaranteed to be between 0 and 1_000_000_000"
583 )]
584 #[allow(
585 clippy::useless_conversion,
586 reason = "Conversion is not useless on all platforms."
587 )]
588 Duration::from_seconds_nanos(timespec.tv_sec.into(), timespec.tv_nsec as u32)
589 })
590 }
591
592 #[cfg(target_os = "linux")]
593 fn fetch_max_frequency_adjustment(&self) -> Result<f64, ClockError> {
594 if let Some(fd) = self.fd {
595 let mut caps = MaybeUninit::<PtpClockCaps>::zeroed();
596
597 if unsafe { ptp_clock_getcaps(fd, caps.as_mut_ptr()) } == 0 {
598 let caps = unsafe { caps.assume_init() };
599
600 Ok(f64::from(caps.max_adj) * 1e-9)
601 } else if error_number() == libc::ENOTTY {
602 Ok(500e-6)
604 } else {
605 Err(convert_errno())
606 }
607 } else {
608 Ok(500e-6)
610 }
611 }
612
613 #[cfg(not(target_os = "linux"))]
614 #[allow(
615 clippy::trivially_copy_pass_by_ref,
616 reason = "Allows a consistent interface between platforms."
617 )]
618 #[allow(
619 clippy::unnecessary_wraps,
620 reason = "Allows a consistent interface between platforms."
621 )]
622 fn fetch_max_frequency_adjustment(&self) -> Result<f64, ClockError> {
623 Ok(500e-6)
624 }
625}
626
627impl<Timescale: Send + 'static> Clock<Timescale> for UnixClock<Timescale> {
628 fn now(&self) -> Result<Timestamp<Timescale>, ClockError> {
629 let mut ntp_kapi_timex = EMPTY_TIMEX;
630
631 if self.adjtime(&mut ntp_kapi_timex).is_ok() {
632 Ok(self.extract_current_time(&ntp_kapi_timex)?)
633 } else {
634 Ok(self
635 .clock_gettime()
636 .map(|ts| current_time_timespec(ts, Precision::Nano))?)
637 }
638 }
639
640 fn set_frequency(&self, freq: f64) -> Result<Timestamp<Timescale>, ClockError> {
641 let mut timex = Self::set_frequency_timex(freq * 1e6);
642 self.adjtime(&mut timex)?;
643 self.extract_current_time(&timex)
644 }
645
646 #[allow(
647 clippy::cast_precision_loss,
648 reason = "Precision loss is acceptable here."
649 )]
650 #[allow(
651 clippy::cast_lossless,
652 reason = "Cast is not lossless on all platforms."
653 )]
654 fn get_frequency(&self) -> Result<f64, ClockError> {
655 let mut timex = EMPTY_TIMEX;
656 self.adjtime(&mut timex)?;
657
658 Ok((timex.freq as f64) / 65536.0e6)
659 }
660
661 fn max_frequency(&self) -> Result<f64, ClockError> {
662 self.fetch_max_frequency_adjustment()
663 }
664
665 #[cfg(target_os = "linux")]
666 fn step_clock(&self, offset: Duration) -> Result<Timestamp<Timescale>, ClockError> {
667 self.step_clock_by_timex(offset)
668 }
669
670 #[cfg(not(target_os = "linux"))]
671 fn step_clock(&self, offset: Duration) -> Result<Timestamp<Timescale>, ClockError> {
672 self.step_clock_by_timespec(offset)
673 }
674
675 fn error_estimate_update(
676 &self,
677 est_error: Duration,
678 max_error: Duration,
679 ) -> Result<(), ClockError> {
680 let mut timex = Self::error_estimate_timex(est_error, max_error);
681 ignore_not_supported(self.adjtime(&mut timex))
682 }
683
684 fn leap_update(&self, leap_status: LeapStatus) -> Result<(), ClockError> {
685 self.update_status(|status| {
686 (status & !(libc::STA_INS | libc::STA_DEL))
687 | match leap_status {
688 LeapStatus::None => 0,
689 LeapStatus::Leap59 => libc::STA_DEL,
690 LeapStatus::Leap61 => libc::STA_INS,
691 }
692 })
693 }
694
695 fn synchronization_update(&self, synchronized: bool) -> Result<(), ClockError> {
696 self.update_status(|status| {
697 if synchronized {
698 status & !libc::STA_UNSYNC
699 } else {
700 status | libc::STA_UNSYNC
701 }
702 })
703 }
704}
705
706fn ignore_not_supported(result: Result<(), ClockError>) -> Result<(), ClockError> {
707 match result {
708 Err(ClockError::InvalidValue) => Ok(()),
709 result => result,
710 }
711}
712
713fn error_number() -> libc::c_int {
714 #[cfg(target_os = "linux")]
715 unsafe {
716 *libc::__errno_location()
717 }
718
719 #[cfg(not(target_os = "linux"))]
720 unsafe {
721 *libc::__error()
722 }
723}
724
725fn convert_errno() -> ClockError {
730 match error_number() {
731 libc::EINVAL | libc::ERANGE => ClockError::InvalidValue,
732 libc::ENODEV => ClockError::NoDevice,
735 libc::EOPNOTSUPP => ClockError::NotSupported,
736 libc::EPERM | libc::EACCES => ClockError::PermissionDenied,
737 _ => ClockError::Unknown,
738 }
739}
740
741fn cerr(c_int: libc::c_int) -> Result<(), ClockError> {
742 if c_int == -1 {
743 Err(convert_errno())
744 } else {
745 Ok(())
746 }
747}
748
749#[derive(Debug, Clone, Copy, PartialEq, Eq)]
750pub(crate) enum Precision {
751 Nano,
752 #[cfg_attr(any(target_os = "freebsd", target_os = "macos"), allow(unused))]
753 Micro,
754}
755
756#[cfg_attr(target_os = "linux", allow(unused))]
757#[allow(
758 clippy::cast_possible_truncation,
759 reason = "tv_nsec is always less than 1_000_000_000, which fits in an i32"
760)]
761#[allow(
762 clippy::useless_conversion,
763 reason = "Conversion is necessary on some platforms."
764)]
765fn current_time_timespec<Timescale>(
766 timespec: libc::timespec,
767 precision: Precision,
768) -> Timestamp<Timescale> {
769 let mut seconds = timespec.tv_sec;
770
771 let nanos: i32 = timespec.tv_nsec as _;
772
773 let mut nanos = match precision {
774 Precision::Nano => nanos,
775 Precision::Micro => nanos.checked_mul(1000).unwrap_or_default(),
776 };
777
778 while nanos > 1_000_000_000 {
780 seconds = seconds.wrapping_add(1);
781 nanos -= 1_000_000_000;
782 }
783
784 while nanos < 0 {
786 seconds = seconds.wrapping_sub(1);
787 nanos += 1_000_000_000;
788 }
789
790 Timestamp::from_seconds_nanos_since_unix_epoch(
791 seconds.cast_unsigned().into(),
792 nanos.cast_unsigned(),
793 )
794}
795
796#[cfg_attr(not(target_os = "linux"), allow(unused))]
797#[allow(
798 clippy::cast_possible_truncation,
799 reason = "tv_usec is always less than 1_000_000_000, which fits in an i32"
800)]
801#[expect(clippy::cast_sign_loss, reason = "tv_usec is guaranteed to be >=0")]
802fn current_time_timeval<Timescale>(
803 timespec: libc::timeval,
804 precision: Precision,
805) -> Timestamp<Timescale> {
806 let seconds = timespec.tv_sec;
807 let nanos = match precision {
808 Precision::Nano => timespec.tv_usec as u32,
809 Precision::Micro => (timespec.tv_usec as u32)
810 .checked_mul(1000)
811 .unwrap_or_default(),
812 };
813
814 Timestamp::from_seconds_nanos_since_unix_epoch(seconds as _, nanos)
815}
816
817#[cfg(all(target_os = "linux", target_env = "gnu"))]
821pub const EMPTY_TIMEX: libc::timex = libc::timex {
822 modes: 0,
823 offset: 0,
824 freq: 0,
825 maxerror: 0,
826 esterror: 0,
827 status: 0,
828 constant: 0,
829 precision: 0,
830 tolerance: 0,
831 time: libc::timeval {
832 tv_sec: 0,
833 tv_usec: 0,
834 },
835 tick: 0,
836 ppsfreq: 0,
837 jitter: 0,
838 shift: 0,
839 stabil: 0,
840 jitcnt: 0,
841 calcnt: 0,
842 errcnt: 0,
843 stbcnt: 0,
844 tai: 0,
845 __unused1: 0,
846 __unused2: 0,
847 __unused3: 0,
848 __unused4: 0,
849 __unused5: 0,
850 __unused6: 0,
851 __unused7: 0,
852 __unused8: 0,
853 __unused9: 0,
854 __unused10: 0,
855 __unused11: 0,
856};
857
858#[cfg(all(target_os = "linux", target_env = "musl"))]
860pub const EMPTY_TIMEX: libc::timex = libc::timex {
861 modes: 0,
862 offset: 0,
863 freq: 0,
864 maxerror: 0,
865 esterror: 0,
866 status: 0,
867 constant: 0,
868 precision: 0,
869 tolerance: 0,
870 time: libc::timeval {
871 tv_sec: 0,
872 tv_usec: 0,
873 },
874 tick: 0,
875 ppsfreq: 0,
876 jitter: 0,
877 shift: 0,
878 stabil: 0,
879 jitcnt: 0,
880 calcnt: 0,
881 errcnt: 0,
882 stbcnt: 0,
883 tai: 0,
884 __padding: [0; 11],
885};
886
887#[cfg(any(target_os = "freebsd", target_os = "macos"))]
889pub const EMPTY_TIMEX: libc::timex = libc::timex {
890 modes: 0,
891 offset: 0,
892 freq: 0,
893 maxerror: 0,
894 esterror: 0,
895 status: 0,
896 constant: 0,
897 precision: 0,
898 tolerance: 0,
899 ppsfreq: 0,
900 jitter: 0,
901 shift: 0,
902 stabil: 0,
903 jitcnt: 0,
904 calcnt: 0,
905 errcnt: 0,
906 stbcnt: 0,
907};
908
909#[cfg(test)]
910mod tests {
911 use statime_base::{Clock, Duration, Timestamp, UTC};
912
913 use super::UnixClock;
914
915 #[test]
916 fn test_time_now_does_not_crash() {
917 let clock = UnixClock::CLOCK_REALTIME;
918 assert_ne!(clock.now().unwrap(), Timestamp::UNIX_EPOCH,);
919 }
920
921 #[test]
922 fn realtime_gettime() {
923 let clock = UnixClock::CLOCK_REALTIME;
924 let time = clock.clock_gettime().unwrap();
925
926 assert_ne!((time.tv_sec, time.tv_nsec), (0, 0));
927 }
928
929 #[test]
930 #[ignore = "requires permissions, useful for testing permissions"]
931 fn ptp0_gettime() {
932 let clock = UnixClock::CLOCK_REALTIME;
933 let time = clock.clock_gettime().unwrap();
934
935 assert_ne!((time.tv_sec, time.tv_nsec), (0, 0));
936 }
937
938 #[test]
939 #[ignore = "requires permissions, useful for testing permissions"]
940 fn step_clock() {
941 UnixClock::CLOCK_REALTIME
942 .step_clock(Duration::from_seconds_nanos(0, 0))
943 .unwrap();
944 }
945
946 #[cfg(target_os = "linux")]
947 #[test]
948 fn test_step_clock() {
949 let offset = Duration::from_seconds_nanos(1, 200_000_000);
950 let timex = UnixClock::<UTC>::step_clock_timex(offset).unwrap();
951
952 assert_eq!(timex.modes, libc::ADJ_SETOFFSET | libc::ADJ_NANO);
953
954 assert_eq!(timex.time.tv_sec, 1);
955 assert_eq!(timex.time.tv_usec, 200_000_000);
956 }
957
958 #[test]
959 fn test_error_estimate() {
960 let est_error = Duration::from_seconds_nanos(0, 500_000_000);
961 let max_error = Duration::from_seconds_nanos(1, 200_000_000);
962 let timex = UnixClock::<UTC>::error_estimate_timex(est_error, max_error);
963
964 assert_eq!(timex.modes, libc::MOD_ESTERROR | libc::MOD_MAXERROR);
965
966 assert_eq!(timex.esterror, 500_000);
968 assert_eq!(timex.maxerror, 1_200_000);
969 }
970
971 #[test]
972 fn test_now() {
973 let resolution = UnixClock::CLOCK_REALTIME.now().unwrap();
974
975 assert_ne!(resolution, Timestamp::UNIX_EPOCH);
976 }
977
978 #[test]
979 fn test_resolution() {
980 let resolution = UnixClock::CLOCK_REALTIME.resolution().unwrap();
981
982 assert_ne!(resolution, Duration::ZERO);
983 }
984
985 #[test]
986 fn test_capabilities() {
987 let clock = UnixClock::CLOCK_REALTIME;
988
989 assert!(clock.max_frequency().is_ok());
990 }
991}