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