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}