clock_steering/
unix.rs

1use crate::{Clock, LeapIndicator, TimeOffset, Timestamp};
2use std::time::Duration;
3#[cfg(target_os = "linux")]
4use std::{
5    os::unix::io::{IntoRawFd, RawFd},
6    path::Path,
7};
8
9/// A Unix OS clock
10#[derive(Debug, Clone, Copy)]
11pub struct UnixClock {
12    clock: libc::clockid_t,
13    #[cfg(target_os = "linux")]
14    fd: Option<RawFd>,
15}
16
17impl UnixClock {
18    /// The standard realtime clock on unix systems.
19    ///
20    /// ```no_run
21    /// use clock_steering::{Clock, unix::UnixClock};
22    ///
23    /// fn main() -> std::io::Result<()> {
24    ///     let clock = UnixClock::CLOCK_REALTIME;
25    ///     let now = clock.now()?;
26    ///
27    ///     println!("{now:?}");
28    ///
29    ///     Ok(())
30    /// }
31    /// ```
32    pub const CLOCK_REALTIME: Self = UnixClock {
33        clock: libc::CLOCK_REALTIME,
34        #[cfg(target_os = "linux")]
35        fd: None,
36    };
37
38    /// TAI time on linux systems.
39    ///
40    /// ```no_run
41    /// use clock_steering::{Clock, unix::UnixClock};
42    ///
43    /// fn main() -> std::io::Result<()> {
44    ///     let clock = UnixClock::CLOCK_TAI;
45    ///     let now = clock.now()?;
46    ///
47    ///     println!("{now:?}");
48    ///
49    ///     Ok(())
50    /// }
51    /// ```
52    #[cfg(target_os = "linux")]
53    pub const CLOCK_TAI: Self = UnixClock {
54        clock: libc::CLOCK_TAI,
55        fd: None,
56    };
57
58    /// Open a clock device.
59    ///
60    /// ```no_run
61    /// use clock_steering::{Clock, unix::UnixClock};
62    ///
63    /// fn main() -> std::io::Result<()> {
64    ///     let clock = UnixClock::open("/dev/ptp0")?;
65    ///     let now = clock.now()?;
66    ///
67    ///     println!("{now:?}");
68    ///
69    ///     Ok(())
70    /// }
71    /// ```
72    #[cfg(target_os = "linux")]
73    pub fn open(path: impl AsRef<Path>) -> std::io::Result<Self> {
74        let file = std::fs::OpenOptions::new()
75            .write(true)
76            .read(true)
77            .open(path)?;
78
79        // we need an owned fd. the file will be closed when the process exits.
80        Ok(Self::safe_from_raw_fd(file.into_raw_fd()))
81    }
82
83    // Consume an fd and produce a clock id. Clock id is only valid
84    // so long as the fd is open, so the RawFd here should
85    // not be borrowed.
86    #[cfg(target_os = "linux")]
87    fn safe_from_raw_fd(fd: RawFd) -> Self {
88        let clock = ((!(fd as libc::clockid_t)) << 3) | 3;
89
90        Self {
91            clock,
92            fd: Some(fd),
93        }
94    }
95
96    /// Determine offset between file clock and TAI clock (if any)
97    /// Returns two system timestamps sandwhiching a timestamp from the
98    /// hardware clock.
99    #[cfg(target_os = "linux")]
100    pub fn system_offset(&self) -> Result<(Timestamp, Timestamp, Timestamp), Error> {
101        let Some(fd) = self.fd else {
102            return Err(Error::Invalid);
103        };
104
105        // TODO: remove type and constant definitions once libc updates
106        #[repr(C)]
107        #[derive(Default, Clone, Copy, PartialEq, Eq)]
108        #[allow(non_camel_case_types)]
109        struct ptp_clock_time {
110            sec: libc::__s64,
111            nsec: libc::__u32,
112            reserved: libc::__u32,
113        }
114
115        #[repr(C)]
116        #[derive(Clone, Copy, PartialEq, Eq)]
117        #[allow(non_camel_case_types)]
118        struct ptp_sys_offset {
119            n_samples: libc::c_uint,
120            rsv: [libc::c_uint; 3],
121            ts: [ptp_clock_time; 51],
122        }
123
124        // Needed as arrays larger than 32 elements don't implement default.
125        impl Default for ptp_sys_offset {
126            fn default() -> Self {
127                Self {
128                    n_samples: Default::default(),
129                    rsv: Default::default(),
130                    ts: [Default::default(); 51],
131                }
132            }
133        }
134
135        const PTP_SYS_OFFSET: libc::c_uint = 0x43403d05;
136
137        let mut offset = ptp_sys_offset {
138            n_samples: 1,
139            ..Default::default()
140        };
141
142        // # Safety
143        // Safe since PTP_SYS_OFFSET expects as argument a mutable pointer to
144        // ptp_sys_offset and offset is valid during the call
145        if unsafe { libc::ioctl(fd, PTP_SYS_OFFSET as _, &mut offset as *mut ptp_sys_offset) } != 0
146        {
147            let t1 = Self::CLOCK_TAI.now();
148            let tp = self.now();
149            let t2 = Self::CLOCK_TAI.now();
150
151            Ok((t1?, tp?, t2?))
152        } else {
153            let tai_offset = Self::CLOCK_TAI.get_tai()?;
154
155            Ok((
156                Timestamp {
157                    seconds: (offset.ts[0].sec + tai_offset as i64) as _,
158                    nanos: offset.ts[0].nsec as _,
159                },
160                Timestamp {
161                    seconds: offset.ts[1].sec as _,
162                    nanos: offset.ts[1].nsec as _,
163                },
164                Timestamp {
165                    seconds: (offset.ts[2].sec + tai_offset as i64) as _,
166                    nanos: offset.ts[2].nsec as _,
167                },
168            ))
169        }
170    }
171
172    fn clock_adjtime(&self, timex: &mut libc::timex) -> Result<(), Error> {
173        // We don't care about the time status, so the non-error
174        // information in the return value of clock_adjtime can be ignored.
175        //
176        // # Safety
177        //
178        // The clock_adjtime call is safe because the reference always
179        // points to a valid libc::timex.
180        //
181        // using an invalid clock id is safe. `clock_adjtime` will return an EINVAL
182        // error https://man.archlinux.org/man/clock_adjtime.2.en#EINVAL~4
183        #[cfg(target_os = "linux")]
184        use libc::clock_adjtime as adjtime;
185
186        #[cfg(any(target_os = "freebsd", target_os = "macos"))]
187        unsafe fn adjtime(clk_id: libc::clockid_t, buf: *mut libc::timex) -> libc::c_int {
188            assert_eq!(
189                clk_id,
190                libc::CLOCK_REALTIME,
191                "only the REALTIME clock is supported"
192            );
193
194            libc::ntp_adjtime(buf)
195        }
196
197        if unsafe { adjtime(self.clock, timex) } == -1 {
198            Err(convert_errno())
199        } else {
200            Ok(())
201        }
202    }
203
204    fn ntp_adjtime(timex: &mut libc::timex) -> Result<(), Error> {
205        #[cfg(any(target_os = "freebsd", target_os = "macos", target_env = "gnu"))]
206        use libc::ntp_adjtime as adjtime;
207
208        // ntp_adjtime is equivalent to adjtimex for our purposes
209        //
210        // https://man7.org/linux/man-pages/man2/adjtimex.2.html
211        #[cfg(all(target_os = "linux", target_env = "musl"))]
212        use libc::adjtimex as adjtime;
213
214        // We don't care about the time status, so the non-error
215        // information in the return value of ntp_adjtime can be ignored.
216        // The ntp_adjtime call is safe because the reference always
217        // points to a valid libc::timex.
218        if unsafe { adjtime(timex) } == -1 {
219            Err(convert_errno())
220        } else {
221            Ok(())
222        }
223    }
224
225    /// Adjust the clock state with a [`libc::timex`] specifying the desired changes.
226    ///
227    /// This is a lowlevel function. If possible, use more specialized (trait) methods.
228    ///
229    /// Note that [`libc::timex`] has a different layout between different operating systems, and
230    /// not all fields are available on all operating systems. Keep this in mind when writing
231    /// platform-independent code.
232    fn adjtime(&self, timex: &mut libc::timex) -> Result<(), Error> {
233        if self.clock == libc::CLOCK_REALTIME {
234            Self::ntp_adjtime(timex)
235        } else {
236            self.clock_adjtime(timex)
237        }
238    }
239
240    #[cfg_attr(target_os = "linux", allow(unused))]
241    fn clock_gettime(&self) -> Result<libc::timespec, Error> {
242        let mut timespec = EMPTY_TIMESPEC;
243
244        // # Safety
245        //
246        // using an invalid clock id is safe. `clock_adjtime` will return an EINVAL
247        // error https://linux.die.net/man/3/clock_gettime
248        //
249        // The timespec pointer is valid.
250        cerr(unsafe { libc::clock_gettime(self.clock, &mut timespec) })?;
251
252        Ok(timespec)
253    }
254
255    #[cfg_attr(target_os = "linux", allow(unused))]
256    fn clock_settime(&self, mut timespec: libc::timespec) -> Result<(), Error> {
257        while timespec.tv_nsec > 1_000_000_000 {
258            timespec.tv_sec += 1;
259            timespec.tv_nsec -= 1_000_000_000;
260        }
261
262        // # Safety
263        //
264        // using an invalid clock id is safe. `clock_adjtime` will return an EINVAL
265        // error https://linux.die.net/man/3/clock_settime
266        //
267        // The timespec pointer is valid.
268        unsafe { cerr(libc::clock_settime(self.clock, &timespec))? };
269
270        Ok(())
271    }
272
273    #[cfg_attr(target_os = "linux", allow(unused))]
274    fn step_clock_by_timespec(&self, offset: TimeOffset) -> Result<Timestamp, Error> {
275        let mut timespec = self.clock_gettime()?;
276
277        // see https://github.com/rust-lang/libc/issues/1848
278        #[cfg_attr(target_env = "musl", allow(deprecated))]
279        {
280            timespec.tv_sec += offset.seconds as libc::time_t;
281            timespec.tv_nsec += offset.nanos as libc::c_long;
282        }
283
284        self.clock_settime(timespec)?;
285
286        Ok(current_time_timespec(timespec, Precision::Nano))
287    }
288
289    fn error_estimate_timex(est_error: Duration, max_error: Duration) -> libc::timex {
290        let modes = libc::MOD_ESTERROR | libc::MOD_MAXERROR;
291
292        // these fields are always in microseconds
293        let esterror = est_error.as_nanos() as libc::c_long / 1000;
294        let maxerror = max_error.as_nanos() as libc::c_long / 1000;
295
296        libc::timex {
297            modes,
298            esterror,
299            maxerror,
300            ..EMPTY_TIMEX
301        }
302    }
303
304    #[cfg(target_os = "linux")]
305    fn step_clock_timex(offset: TimeOffset) -> libc::timex {
306        // we provide the offset in nanoseconds
307        let modes = libc::ADJ_SETOFFSET | libc::ADJ_NANO;
308
309        let time = libc::timeval {
310            tv_sec: offset.seconds,
311            tv_usec: offset.nanos as libc::suseconds_t,
312        };
313
314        libc::timex {
315            modes,
316            time,
317            ..EMPTY_TIMEX
318        }
319    }
320
321    #[cfg(target_os = "linux")]
322    fn step_clock_by_timex(&self, offset: TimeOffset) -> Result<Timestamp, Error> {
323        let mut timex = Self::step_clock_timex(offset);
324        self.adjtime(&mut timex)?;
325        self.extract_current_time(&timex)
326    }
327
328    fn extract_current_time(&self, _timex: &libc::timex) -> Result<Timestamp, Error> {
329        #[cfg(target_os = "linux")]
330        // hardware clocks may not report the timestamp
331        if _timex.time.tv_sec != 0 && _timex.time.tv_usec != 0 {
332            // in a timex, the status flag determines precision
333            let precision = match _timex.status & libc::STA_NANO {
334                0 => Precision::Micro,
335                _ => Precision::Nano,
336            };
337
338            return Ok(current_time_timeval(_timex.time, precision));
339        }
340
341        // clock_gettime always gives nanoseconds
342        let timespec = self.clock_gettime()?;
343        Ok(current_time_timespec(timespec, Precision::Nano))
344    }
345
346    #[inline(always)]
347    fn update_timex<F>(&self, f: F) -> Result<(), Error>
348    where
349        F: FnOnce(libc::timex) -> libc::timex,
350    {
351        let mut timex = EMPTY_TIMEX;
352        self.adjtime(&mut timex)?;
353
354        timex = f(timex);
355
356        self.adjtime(&mut timex)
357    }
358
359    #[inline(always)]
360    fn update_status<F>(&self, f: F) -> Result<(), Error>
361    where
362        F: FnOnce(libc::c_int) -> libc::c_int,
363    {
364        self.update_timex(|mut timex| {
365            // We are setting the status bits
366            timex.modes = libc::MOD_STATUS;
367
368            // update the status flags
369            timex.status = f(timex.status);
370
371            timex
372        })
373    }
374
375    fn set_frequency_timex(ppm: f64) -> libc::timex {
376        // We do an offset with precision
377        let mut timex = EMPTY_TIMEX;
378
379        // set the frequency (MOD_FREQUENCY is an alias for ADJ_FREQUENCY on linux)
380        timex.modes = libc::MOD_FREQUENCY;
381
382        // NTP Kapi expects frequency adjustment in units of 2^-16 ppm
383        // but our input is in units of seconds drift per second, so convert.
384        let frequency = (ppm * 65536.0).round() as libc::c_long;
385
386        // Since Linux 2.6.26, the supplied value is clamped to the range (-32768000,
387        // +32768000). In older kernels, an EINVAL error occurs if the supplied value is
388        // out of range. (32768000 is 500 << 16)
389        timex.freq = frequency.clamp(-32_768_000 + 1, 32_768_000 - 1);
390
391        timex
392    }
393}
394
395impl Clock for UnixClock {
396    type Error = Error;
397
398    fn now(&self) -> Result<Timestamp, Self::Error> {
399        let mut ntp_kapi_timex = EMPTY_TIMEX;
400
401        if self.adjtime(&mut ntp_kapi_timex).is_ok() {
402            self.extract_current_time(&ntp_kapi_timex)
403        } else {
404            self.clock_gettime()
405                .map(|ts| current_time_timespec(ts, Precision::Nano))
406        }
407    }
408
409    fn resolution(&self) -> Result<Timestamp, Self::Error> {
410        let mut timespec = EMPTY_TIMESPEC;
411
412        cerr(unsafe { libc::clock_getres(self.clock, &mut timespec) })?;
413
414        Ok(current_time_timespec(timespec, Precision::Nano))
415    }
416
417    fn get_frequency(&self) -> Result<f64, Self::Error> {
418        let mut timex = EMPTY_TIMEX;
419        self.adjtime(&mut timex)?;
420
421        Ok((timex.freq as f64) / 65536.0)
422    }
423
424    fn set_frequency(&self, frequency: f64) -> Result<Timestamp, Self::Error> {
425        let mut timex = Self::set_frequency_timex(frequency);
426        self.adjtime(&mut timex)?;
427        self.extract_current_time(&timex)
428    }
429
430    #[cfg(target_os = "linux")]
431    fn step_clock(&self, offset: TimeOffset) -> Result<Timestamp, Self::Error> {
432        self.step_clock_by_timex(offset)
433    }
434
435    #[cfg(any(target_os = "freebsd", target_os = "macos"))]
436    fn step_clock(&self, offset: TimeOffset) -> Result<Timestamp, Self::Error> {
437        self.step_clock_by_timespec(offset)
438    }
439
440    fn set_leap_seconds(&self, leap_status: LeapIndicator) -> Result<(), Self::Error> {
441        self.update_status(|status| {
442            (status & !(libc::STA_UNSYNC | libc::STA_INS | libc::STA_DEL))
443                | leap_status.as_status_bit()
444        })
445    }
446
447    fn error_estimate_update(
448        &self,
449        est_error: Duration,
450        max_error: Duration,
451    ) -> Result<(), Self::Error> {
452        let mut timex = Self::error_estimate_timex(est_error, max_error);
453        Error::ignore_not_supported(self.adjtime(&mut timex))
454    }
455
456    fn disable_kernel_ntp_algorithm(&self) -> Result<(), Self::Error> {
457        let mut timex = EMPTY_TIMEX;
458        self.adjtime(&mut timex)?;
459
460        // We are setting the status bits
461        timex.modes = libc::MOD_STATUS;
462
463        // Disable all kernel time control loops (phase lock, frequency lock, pps time and pps frequency).
464        timex.status &= !(libc::STA_PLL | libc::STA_FLL | libc::STA_PPSTIME | libc::STA_PPSFREQ);
465
466        // ignore if we cannot disable the kernel time control loops (e.g. external clocks)
467        Error::ignore_not_supported(self.adjtime(&mut timex))
468    }
469
470    #[cfg(target_os = "linux")]
471    fn set_tai(&self, tai_offset: i32) -> Result<(), Error> {
472        let mut timex = libc::timex {
473            modes: libc::ADJ_TAI,
474            constant: tai_offset as _,
475            ..EMPTY_TIMEX
476        };
477
478        self.clock_adjtime(&mut timex)
479    }
480
481    #[cfg(not(target_os = "linux"))]
482    fn set_tai(&self, _tai_offset: i32) -> Result<(), Error> {
483        Err(Error::NotSupported)
484    }
485
486    #[cfg(target_os = "linux")]
487    fn get_tai(&self) -> Result<i32, Error> {
488        let mut timex = EMPTY_TIMEX;
489        if self.clock_adjtime(&mut timex).is_ok() {
490            Ok(timex.tai)
491        } else {
492            // hardware clock which doesn't have an offset anyway
493            Ok(0)
494        }
495    }
496
497    #[cfg(not(target_os = "linux"))]
498    fn get_tai(&self) -> Result<i32, Error> {
499        Err(Error::NotSupported)
500    }
501}
502
503/// Errors that can be thrown by modifying a unix clock
504#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
505pub enum Error {
506    /// Insufficient permissions to interact with the clock.
507    NoPermission,
508    /// No access to the clock.
509    NoAccess,
510    /// Invalid operation requested
511    Invalid,
512    /// Clock device has gone away
513    NoDevice,
514    /// Clock operation requested is not supported by operating system.
515    NotSupported,
516}
517
518impl core::fmt::Display for Error {
519    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
520        use Error::*;
521
522        let msg = match self {
523            NoPermission => "Insufficient permissions to interact with the clock.",
524            NoAccess => "No access to the clock.",
525            Invalid => "Invalid operation requested",
526            NoDevice => "Clock device has gone away",
527            NotSupported => "Clock operation requested is not supported by operating system.",
528        };
529
530        f.write_str(msg)
531    }
532}
533
534impl std::error::Error for Error {}
535
536impl Error {
537    /// Turn the `Error::NotSupported` error variant into `Ok(())`, to silently
538    /// ignore operations that are not supported by the current clock. All
539    /// other input values are untouched.
540    pub fn ignore_not_supported(res: Result<(), Error>) -> Result<(), Error> {
541        match res {
542            Err(Error::NotSupported) => Ok(()),
543            other => other,
544        }
545    }
546
547    // TODO: use https://doc.rust-lang.org/std/io/type.RawOsError.html when stable
548    fn into_raw_os_error(self) -> i32 {
549        match self {
550            Self::NoPermission => libc::EPERM,
551            Self::NoAccess => libc::EACCES,
552            Self::Invalid => libc::EINVAL,
553            Self::NoDevice => libc::ENODEV,
554            Self::NotSupported => libc::EOPNOTSUPP,
555        }
556    }
557}
558
559impl From<Error> for std::io::Error {
560    fn from(value: Error) -> Self {
561        std::io::Error::from_raw_os_error(value.into_raw_os_error())
562    }
563}
564
565fn error_number() -> libc::c_int {
566    #[cfg(target_os = "linux")]
567    unsafe {
568        *libc::__errno_location()
569    }
570
571    #[cfg(not(target_os = "linux"))]
572    unsafe {
573        *libc::__error()
574    }
575}
576
577// Convert those error numbers that can occur for calls to the following
578// functions
579// - ntp_adjtimex https://man7.org/linux/man-pages/man3/ntp_adjtime.3.html
580// - clock_gettime & clock_settime https://man7.org/linux/man-pages/man3/clock_gettime.3.html
581fn convert_errno() -> Error {
582    match error_number() {
583        libc::EINVAL => Error::Invalid,
584        // The documentation is a bit unclear if this can happen with
585        // non-dynamic clocks like the ntp kapi clock, however deal with it just in case.
586        libc::ENODEV => Error::NoDevice,
587        libc::EOPNOTSUPP => Error::NotSupported,
588        libc::EPERM => Error::NoPermission,
589        libc::EACCES => Error::NoAccess,
590        libc::EFAULT => unreachable!("we always pass in valid (accessible) buffers"),
591        // No other errors should occur
592        other => {
593            let error = std::io::Error::from_raw_os_error(other);
594            unreachable!("error code `{other}` ({error:?}) should not occur")
595        }
596    }
597}
598
599fn cerr(c_int: libc::c_int) -> Result<(), Error> {
600    if c_int == -1 {
601        Err(convert_errno())
602    } else {
603        Ok(())
604    }
605}
606
607pub(crate) enum Precision {
608    Nano,
609    #[cfg_attr(any(target_os = "freebsd", target_os = "macos"), allow(unused))]
610    Micro,
611}
612
613#[cfg_attr(target_os = "linux", allow(unused))]
614fn current_time_timespec(timespec: libc::timespec, precision: Precision) -> Timestamp {
615    let mut seconds = timespec.tv_sec;
616
617    let nanos: i32 = timespec.tv_nsec as _;
618
619    let mut nanos = match precision {
620        Precision::Nano => nanos,
621        Precision::Micro => nanos.checked_mul(1000).unwrap_or_default(),
622    };
623
624    // on macOS (at least) we've observed higher nanosecond counts than appear valid
625    while nanos > 1_000_000_000 {
626        seconds = seconds.wrapping_add(1);
627        nanos -= 1_000_000_000;
628    }
629
630    // we disallow negative nanoseconds
631    while nanos < 0 {
632        seconds = seconds.wrapping_sub(1);
633        nanos += 1_000_000_000;
634    }
635
636    Timestamp {
637        seconds,
638        nanos: nanos as u32,
639    }
640}
641
642#[cfg_attr(not(target_os = "linux"), allow(unused))]
643fn current_time_timeval(timespec: libc::timeval, precision: Precision) -> Timestamp {
644    let seconds = timespec.tv_sec;
645    let nanos = match precision {
646        Precision::Nano => timespec.tv_usec as u32,
647        Precision::Micro => (timespec.tv_usec as u32)
648            .checked_mul(1000)
649            .unwrap_or_default(),
650    };
651
652    Timestamp { seconds, nanos }
653}
654
655const EMPTY_TIMESPEC: libc::timespec = libc::timespec {
656    tv_sec: 0,
657    tv_nsec: 0,
658};
659
660// Libc has no good other way of obtaining this, so let's at least make our
661// functions more readable.
662#[cfg(all(target_os = "linux", target_env = "gnu"))]
663pub const EMPTY_TIMEX: libc::timex = libc::timex {
664    modes: 0,
665    offset: 0,
666    freq: 0,
667    maxerror: 0,
668    esterror: 0,
669    status: 0,
670    constant: 0,
671    precision: 0,
672    tolerance: 0,
673    time: libc::timeval {
674        tv_sec: 0,
675        tv_usec: 0,
676    },
677    tick: 0,
678    ppsfreq: 0,
679    jitter: 0,
680    shift: 0,
681    stabil: 0,
682    jitcnt: 0,
683    calcnt: 0,
684    errcnt: 0,
685    stbcnt: 0,
686    tai: 0,
687    __unused1: 0,
688    __unused2: 0,
689    __unused3: 0,
690    __unused4: 0,
691    __unused5: 0,
692    __unused6: 0,
693    __unused7: 0,
694    __unused8: 0,
695    __unused9: 0,
696    __unused10: 0,
697    __unused11: 0,
698};
699
700#[cfg(all(target_os = "linux", target_env = "musl"))]
701pub const EMPTY_TIMEX: libc::timex = libc::timex {
702    modes: 0,
703    offset: 0,
704    freq: 0,
705    maxerror: 0,
706    esterror: 0,
707    status: 0,
708    constant: 0,
709    precision: 0,
710    tolerance: 0,
711    time: libc::timeval {
712        tv_sec: 0,
713        tv_usec: 0,
714    },
715    tick: 0,
716    ppsfreq: 0,
717    jitter: 0,
718    shift: 0,
719    stabil: 0,
720    jitcnt: 0,
721    calcnt: 0,
722    errcnt: 0,
723    stbcnt: 0,
724    tai: 0,
725    __padding: [0; 11],
726};
727
728#[cfg(any(target_os = "freebsd", target_os = "macos"))]
729pub const EMPTY_TIMEX: libc::timex = libc::timex {
730    modes: 0,
731    offset: 0,
732    freq: 0,
733    maxerror: 0,
734    esterror: 0,
735    status: 0,
736    constant: 0,
737    precision: 0,
738    tolerance: 0,
739    ppsfreq: 0,
740    jitter: 0,
741    shift: 0,
742    stabil: 0,
743    jitcnt: 0,
744    calcnt: 0,
745    errcnt: 0,
746    stbcnt: 0,
747};
748
749impl LeapIndicator {
750    fn as_status_bit(self) -> libc::c_int {
751        match self {
752            LeapIndicator::NoWarning => 0,
753            LeapIndicator::Leap61 => libc::STA_INS,
754            LeapIndicator::Leap59 => libc::STA_DEL,
755            LeapIndicator::Unknown => libc::STA_UNSYNC,
756        }
757    }
758}
759
760#[cfg(test)]
761mod tests {
762    use super::*;
763
764    #[test]
765    fn test_time_now_does_not_crash() {
766        let clock = UnixClock::CLOCK_REALTIME;
767        assert_ne!(clock.now().unwrap(), Timestamp::default(),);
768    }
769
770    #[test]
771    fn realtime_gettime() {
772        let clock = UnixClock::CLOCK_REALTIME;
773        let time = clock.clock_gettime().unwrap();
774
775        assert_ne!((time.tv_sec, time.tv_nsec), (0, 0))
776    }
777
778    #[test]
779    #[ignore = "requires permissions, useful for testing permissions"]
780    fn ptp0_gettime() {
781        let clock = UnixClock::CLOCK_REALTIME;
782        let time = clock.clock_gettime().unwrap();
783
784        assert_ne!((time.tv_sec, time.tv_nsec), (0, 0))
785    }
786
787    #[test]
788    #[ignore = "requires permissions, useful for testing permissions"]
789    fn step_clock() {
790        UnixClock::CLOCK_REALTIME
791            .step_clock(TimeOffset {
792                seconds: 0,
793                nanos: 0,
794            })
795            .unwrap();
796    }
797
798    #[cfg(target_os = "linux")]
799    #[test]
800    fn test_step_clock() {
801        let offset = TimeOffset {
802            seconds: 1,
803            nanos: 200000000,
804        };
805        let timex = UnixClock::step_clock_timex(offset);
806
807        assert_eq!(timex.modes, libc::ADJ_SETOFFSET | libc::ADJ_NANO);
808
809        assert_eq!(timex.time.tv_sec, 1);
810        assert_eq!(timex.time.tv_usec, 200_000_000);
811    }
812
813    #[test]
814    fn test_error_estimate() {
815        let est_error = Duration::from_secs_f64(0.5);
816        let max_error = Duration::from_secs_f64(1.2);
817        let timex = UnixClock::error_estimate_timex(est_error, max_error);
818
819        assert_eq!(timex.modes, libc::MOD_ESTERROR | libc::MOD_MAXERROR);
820
821        // these fields are always in microseconds
822        assert_eq!(timex.esterror, 500_000);
823        assert_eq!(timex.maxerror, 1_200_000);
824    }
825
826    #[test]
827    fn test_now() {
828        let resolution = UnixClock::CLOCK_REALTIME.now().unwrap();
829
830        assert_ne!(resolution, Timestamp::default());
831    }
832
833    #[test]
834    fn test_resolution() {
835        let resolution = UnixClock::CLOCK_REALTIME.resolution().unwrap();
836
837        assert_ne!(resolution, Timestamp::default());
838    }
839}