Skip to main content

statime_base/
algorithm.rs

1use crate::{Clock, ClockId, Direction, Duration, LeapStatus, LinkId, TAI, Timestamp};
2
3#[cfg(feature = "serde")]
4use serde::{Deserialize, Serialize};
5
6#[cfg(not(feature = "std"))]
7use crate::float_polyfill::FloatPolyfill;
8
9/// A controller for clocks in a system.
10pub trait Controller {
11    /// Type of clocks which are managed by this controller
12    type Clock: Clock<TAI>;
13    /// Measurement links between clocks belonging to the controller.
14    type Link<ControllerRef: AsRef<Self>>: Link<Error = Self::Error>;
15    /// Errors returned by the controller
16    // FIXME: Change this to the error trait once we have that in statime-algo.
17    type Error: core::fmt::Debug;
18    /// Configuration for internal clocks
19    type ClockConfig: Default;
20    /// Configuration for links
21    type LinkConfig: Default;
22    /// Configuration for tracked links (links on which delay is automatically estimated)
23    type TrackedLinkConfig: Default;
24
25    /// Add an internal, steered clock to the controller.
26    ///
27    /// # Errors
28    /// May error if the controller is unable to handle more internal clocks
29    fn add_clock(
30        &self,
31        clock: Self::Clock,
32        config: Self::ClockConfig,
33    ) -> Result<ClockId, Self::Error>;
34
35    /// Remove an internal, steered clock from the controller
36    ///
37    /// # Errors
38    /// May error if the internal clock in question is not known to the controller.
39    fn remove_clock(&self, clock_id: ClockId) -> Result<(), Self::Error>;
40
41    /// Create a measurement link between clocks where the delay and noise from the
42    /// link itself are automatically determined.
43    ///
44    /// The resulting link may need measurement in both directions to succesfully
45    /// work
46    ///
47    /// This is an assocatiated function to allow links to store references to the
48    /// controller in a manner most convenient for the user.
49    ///
50    /// # Errors
51    /// May fail if clocks are unknown to the controller, or when both clocks are
52    /// external clocks.
53    fn create_tracked_link<ControllerRef: AsRef<Self>>(
54        this: ControllerRef,
55        clock_a: ClockId,
56        clock_b: Option<ClockId>,
57        config: Self::LinkConfig,
58        tracked_config: Self::TrackedLinkConfig,
59    ) -> Result<Self::Link<ControllerRef>, Self::Error>;
60
61    /// Create a measurement link between clocks without delay estimation.
62    ///
63    /// This is an associated function instead of a method to allow links to store
64    /// references to the controller in a manner most convenient for the user.
65    ///
66    /// # Errors
67    /// May fail if clocks are unknown to the controller, or when both clocks are
68    /// external clocks.
69    fn create_untracked_link<ControllerRef: AsRef<Self>>(
70        this: ControllerRef,
71        clock_a: ClockId,
72        clock_b: Option<ClockId>,
73        config: Self::LinkConfig,
74    ) -> Result<Self::Link<ControllerRef>, Self::Error>;
75
76    /// Get a time snapshot of the synchronization status of a clock.
77    ///
78    /// # Errors
79    /// May fail if the clock is unknown to the controller.
80    fn clock_snapshot(&self, clock: ClockId) -> Result<TimeSnapshot, Self::Error>;
81
82    /// Get a future which runs all the background tasks needed for the controller.
83    fn run<Fut: Future<Output = ()> + Send, F: Send + Fn(core::time::Duration) -> Fut>(
84        this: impl AsRef<Self> + Send,
85        sleep: F,
86    ) -> impl Future<Output = Result<(), Self::Error>> + Send;
87}
88
89/// Information on an active link provided by the controller.
90#[cfg(feature = "std")]
91#[derive(Debug, Clone, Copy, PartialEq)]
92pub struct ActiveLinkData {
93    /// The identifier of the active link.
94    pub id: LinkId,
95    /// A number indicating the relative contribution of this link to the phase
96    /// of the system clock.
97    ///
98    /// The units of this are arbitrary, and no assumptions should be made on the
99    /// size of these numbers. A link with twice the importance will have roughly
100    /// twice as much impact on the phase of the system clock.
101    pub importance: f64,
102}
103
104/// Extensions to the controller trait which can only be implemented with the standard
105/// library available.
106#[cfg(feature = "std")]
107pub trait StdController: Controller {
108    /// Get the currently active links in the controller
109    fn active_links(&self) -> std::vec::Vec<ActiveLinkData>;
110}
111
112/// A measurement link between clocks.
113pub trait Link {
114    /// Errors returned by the controller
115    // FIXME: Change this to the error trait once we have that in statime-algo.
116    type Error: core::fmt::Debug;
117
118    /// Process a measurement on a connection.
119    ///
120    /// # Errors
121    /// May fail if there are any issues processing the measurement, mostly resulting
122    /// from unexpected behavior of the underlying clock.
123    fn measurement(
124        &self,
125        measurement: Measurement,
126        direction: Direction,
127    ) -> Result<(), Self::Error>;
128
129    /// Update additional time keeping information provided by the remote for this link.
130    ///
131    /// # Errors
132    /// May fail if the link does not contain an external clock.
133    fn external_data_update(
134        &self,
135        root_delay: Duration,
136        leap_status: Option<LeapStatus>,
137        usable: bool,
138    ) -> Result<(), Self::Error>;
139
140    /// Returns whether the link actively contributed to the current time estimates on
141    /// the last measurement.
142    ///
143    /// # Errors
144    /// May fail only when something is bugged in the library.
145    fn active(&self) -> Result<bool, Self::Error>;
146
147    /// Importance of the links contribution to the phase of the system clock, assuming it
148    /// is an external clock. This is the same number provided in
149    /// [`StdController::active_links`], which has arbitrary units and shouldn't be
150    /// assumed to be of a certain order of magnitude. A link with twice the importance will
151    /// have roughly twice as much impact on the phase of the system clock.
152    ///
153    /// The importance is only available if the link is active and external.
154    ///
155    /// # Errors
156    /// May fail when the link is not external
157    fn importance(&self) -> Result<Option<f64>, Self::Error>;
158
159    /// Returns the poll rate needed to get the desired accuracy from this link.
160    ///
161    /// # Errors
162    /// May fail only when something is bugged in the library.
163    fn desired_poll_interval(&self) -> Result<Duration, Self::Error>;
164
165    /// Identifier of this link
166    fn id(&self) -> LinkId;
167}
168
169/// A measurement done on a link.
170#[derive(Debug, Copy, Clone, PartialEq, Eq)]
171pub struct Measurement {
172    /// The timestamp at which the synchronization signal was sent.
173    pub send_timestamp: Timestamp<TAI>,
174    /// The timestamp at which the synchronization signal was received.
175    pub recv_timestamp: Timestamp<TAI>,
176    /// The uncertainty of the timestamps.
177    pub uncertainty: Duration,
178}
179
180/// Snapshot of the synchronization state of a clock.
181#[derive(Debug, Clone, Copy, PartialEq)]
182#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
183pub struct TimeSnapshot {
184    /// Precision of the local clock
185    pub precision: Duration,
186    /// Current root delay
187    pub root_delay: Duration,
188    /// t=0 for root variance calculation
189    pub root_variance_base_time: Timestamp<TAI>,
190    /// Constant contribution for root variance
191    pub root_variance_base: f64,
192    /// Linear (*t) contribution for root variance
193    pub root_variance_linear: f64,
194    /// Quadratic (*t*t) contribution for root variance
195    pub root_variance_quadratic: f64,
196    /// Cubic (*t*t*t) contribution for root variance
197    pub root_variance_cubic: f64,
198    /// Current leap indicator state
199    pub leap_indicator: Option<LeapStatus>,
200    /// Total amount that the clock has stepped
201    pub accumulated_steps: Duration,
202    /// Crossing this amount of stepping will cause a Panic
203    pub accumulated_steps_threshold: Option<Duration>,
204}
205
206impl TimeSnapshot {
207    /// Root dispersion at the current time.
208    #[must_use]
209    pub fn root_dispersion(&self, now: Timestamp<TAI>) -> Duration {
210        let t = (now - self.root_variance_base_time).as_seconds();
211        // Note: dispersion is the standard deviation, so we need a sqrt here.
212        Duration::from_f64_seconds(
213            (self.root_variance_base
214                + t * self.root_variance_linear
215                + t.powi(2) * self.root_variance_quadratic
216                + t.powi(3) * self.root_variance_cubic)
217                .sqrt(),
218        )
219    }
220}
221
222impl Default for TimeSnapshot {
223    fn default() -> Self {
224        Self {
225            precision: Duration::from_seconds_nanos(0, 1),
226            root_delay: Duration::ZERO,
227            root_variance_base_time: Timestamp::UNIX_EPOCH,
228            root_variance_base: 0.0,
229            root_variance_linear: 0.0,
230            root_variance_quadratic: 0.0,
231            root_variance_cubic: 0.0,
232            leap_indicator: None,
233            accumulated_steps: Duration::ZERO,
234            accumulated_steps_threshold: None,
235        }
236    }
237}
238
239#[cfg(test)]
240mod tests {
241    use super::TimeSnapshot;
242    use crate::{Duration, Timestamp};
243
244    #[test]
245    fn test_root_dispersion() {
246        let snapshot = TimeSnapshot {
247            root_variance_base_time: Timestamp::from_seconds_nanos_since_unix_epoch(100, 0),
248            root_variance_base: 1.0,
249            root_variance_linear: 2.0,
250            root_variance_quadratic: 3.0,
251            root_variance_cubic: 4.0,
252            ..Default::default()
253        };
254        let base_time = snapshot.root_variance_base_time;
255
256        assert_eq!(
257            snapshot.root_dispersion(base_time),
258            Duration::from_seconds_nanos(1, 0)
259        );
260        assert_eq!(
261            snapshot.root_dispersion(base_time + Duration::from_seconds_nanos(2, 0)),
262            Duration::from_seconds_nanos(7, 0)
263        );
264
265        // At half a second the variance is 3.25; allow rounding in its square root.
266        let dispersion = snapshot
267            .root_dispersion(base_time + Duration::from_seconds_nanos(0, 500_000_000))
268            .as_seconds();
269        assert!((dispersion - 3.25_f64.sqrt()).abs() < 1e-12);
270    }
271
272    #[test]
273    fn test_root_dispersion_zero_variance() {
274        let snapshot = TimeSnapshot::default();
275        let base_time = snapshot.root_variance_base_time;
276
277        assert_eq!(snapshot.root_dispersion(base_time), Duration::ZERO);
278        assert_eq!(
279            snapshot.root_dispersion(base_time + Duration::from_seconds_nanos(2, 0)),
280            Duration::ZERO
281        );
282    }
283}