Skip to main content

rtime_core/
clock.rs

1use crate::timestamp::{NtpDuration, NtpTimestamp};
2
3/// Abstraction over system clock access.
4///
5/// Implementations:
6/// - `UnixClock` (real system clock via clock_gettime/clock_adjtime)
7/// - `PhcClock` (PTP hardware clock via /dev/ptpN ioctl) -- Phase 7
8/// - `MockClock` (deterministic testing)
9pub trait Clock: Send + Sync {
10    /// Read current time from this clock.
11    fn now(&self) -> Result<NtpTimestamp, ClockError>;
12
13    /// Apply a step adjustment (instant offset). Requires privilege.
14    fn step(&self, offset: NtpDuration) -> Result<(), ClockError>;
15
16    /// Apply a slew adjustment (frequency change in PPM). Requires privilege.
17    fn adjust_frequency(&self, ppm: f64) -> Result<(), ClockError>;
18
19    /// Read current frequency offset in PPM.
20    fn frequency_offset(&self) -> Result<f64, ClockError>;
21
22    /// Get clock resolution as a duration.
23    fn resolution(&self) -> NtpDuration;
24
25    /// Maximum allowed frequency adjustment in PPM.
26    fn max_frequency_adjustment(&self) -> f64;
27
28    /// Whether this clock supports discipline (has CAP_SYS_TIME or equivalent).
29    fn is_adjustable(&self) -> bool;
30}
31
32#[derive(Debug, thiserror::Error)]
33pub enum ClockError {
34    #[error("insufficient privileges for clock adjustment")]
35    PermissionDenied,
36
37    #[error("clock device not found")]
38    DeviceNotFound,
39
40    #[error("operation not supported")]
41    NotSupported,
42
43    #[error("OS error: {0}")]
44    Os(#[from] std::io::Error),
45}
46
47/// Status of clock synchronization.
48#[derive(Debug, Clone, Copy, PartialEq, Eq)]
49pub enum ClockStatus {
50    /// Not synchronized to any source.
51    Unsynchronized,
52    /// Synchronizing (converging).
53    Synchronizing,
54    /// Synchronized and stable.
55    Synchronized,
56}
57
58/// Leap indicator values per NTPv4.
59#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
60#[repr(u8)]
61pub enum LeapIndicator {
62    #[default]
63    NoWarning = 0,
64    LastMinute61Seconds = 1,
65    LastMinute59Seconds = 2,
66    AlarmUnsynchronized = 3,
67}
68
69impl LeapIndicator {
70    pub fn from_u8(val: u8) -> Self {
71        match val {
72            0 => Self::NoWarning,
73            1 => Self::LastMinute61Seconds,
74            2 => Self::LastMinute59Seconds,
75            _ => Self::AlarmUnsynchronized,
76        }
77    }
78}