ntp_proto/
config.rs

1use std::fmt;
2
3use serde::{
4    de::{self, MapAccess, Unexpected, Visitor},
5    Deserialize, Deserializer,
6};
7
8use crate::time_types::{NtpDuration, PollInterval, PollIntervalLimits};
9
10fn deserialize_option_accumulated_step_panic_threshold<'de, D>(
11    deserializer: D,
12) -> Result<Option<NtpDuration>, D::Error>
13where
14    D: Deserializer<'de>,
15{
16    let duration: NtpDuration = Deserialize::deserialize(deserializer)?;
17    Ok(if duration == NtpDuration::ZERO {
18        None
19    } else {
20        Some(duration)
21    })
22}
23
24#[derive(Debug, Default, Copy, Clone)]
25pub struct ReferenceIdConfig {
26    id: u32,
27}
28
29impl ReferenceIdConfig {
30    pub(crate) fn to_reference_id(self) -> crate::ReferenceId {
31        crate::ReferenceId::from_int(self.id)
32    }
33}
34
35// Deserialize from the string type in config
36impl<'de> Deserialize<'de> for ReferenceIdConfig {
37    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
38    where
39        D: Deserializer<'de>,
40    {
41        struct ReferenceIdConfigVisitor;
42
43        impl Visitor<'_> for ReferenceIdConfigVisitor {
44            type Value = ReferenceIdConfig;
45
46            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
47                formatter.write_str("up to 4-character string")
48            }
49
50            fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
51            where
52                E: de::Error,
53            {
54                let mut chars: Vec<char> = v.chars().collect();
55                if chars.len() > 4 {
56                    return Err(E::invalid_length(chars.len(), &self));
57                }
58
59                // Pad with spaces
60                while chars.len() < 4 {
61                    chars.push(' ');
62                }
63
64                let encoded = chars.iter().fold(0u32, |acc, &c| (acc << 8) | (c as u32));
65
66                Ok(ReferenceIdConfig { id: encoded })
67            }
68        }
69
70        deserializer.deserialize_str(ReferenceIdConfigVisitor)
71    }
72}
73
74#[derive(Debug, Default, Copy, Clone)]
75pub struct StepThreshold {
76    pub forward: Option<NtpDuration>,
77    pub backward: Option<NtpDuration>,
78}
79
80impl StepThreshold {
81    pub fn is_within(&self, duration: NtpDuration) -> bool {
82        self.forward.map(|v| duration < v).unwrap_or(true)
83            && self.backward.map(|v| duration > -v).unwrap_or(true)
84    }
85}
86
87#[derive(Debug, Copy, Clone)]
88struct ThresholdPart(Option<NtpDuration>);
89
90impl<'de> Deserialize<'de> for ThresholdPart {
91    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
92    where
93        D: Deserializer<'de>,
94    {
95        struct ThresholdPartVisitor;
96
97        impl Visitor<'_> for ThresholdPartVisitor {
98            type Value = ThresholdPart;
99
100            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
101                formatter.write_str("float or \"inf\"")
102            }
103
104            fn visit_f64<E>(self, v: f64) -> Result<Self::Value, E>
105            where
106                E: de::Error,
107            {
108                Ok(ThresholdPart(Some(NtpDuration::from_seconds(v))))
109            }
110
111            fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
112            where
113                E: de::Error,
114            {
115                self.visit_f64(v as f64)
116            }
117
118            fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
119            where
120                E: de::Error,
121            {
122                self.visit_f64(v as f64)
123            }
124
125            fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
126            where
127                E: de::Error,
128            {
129                if v != "inf" {
130                    return Err(de::Error::invalid_value(
131                        de::Unexpected::Str(v),
132                        &"float or \"inf\"",
133                    ));
134                }
135                Ok(ThresholdPart(None))
136            }
137        }
138
139        deserializer.deserialize_any(ThresholdPartVisitor)
140    }
141}
142
143// We have a custom deserializer for StepThreshold because we
144// want to deserialize it from either a number or map
145impl<'de> Deserialize<'de> for StepThreshold {
146    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
147    where
148        D: Deserializer<'de>,
149    {
150        struct StepThresholdVisitor;
151
152        impl<'de> Visitor<'de> for StepThresholdVisitor {
153            type Value = StepThreshold;
154
155            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
156                formatter.write_str("float, map or \"inf\"")
157            }
158
159            fn visit_f64<E>(self, v: f64) -> Result<Self::Value, E>
160            where
161                E: de::Error,
162            {
163                if v.is_nan() || v.is_infinite() || v < 0.0 {
164                    return Err(serde::de::Error::invalid_value(
165                        Unexpected::Float(v),
166                        &"a positive number",
167                    ));
168                }
169
170                let duration = NtpDuration::from_seconds(v);
171
172                Ok(StepThreshold {
173                    forward: Some(duration),
174                    backward: Some(duration),
175                })
176            }
177
178            fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
179            where
180                E: de::Error,
181            {
182                self.visit_f64(v as f64)
183            }
184
185            fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
186            where
187                E: de::Error,
188            {
189                self.visit_f64(v as f64)
190            }
191
192            fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
193            where
194                E: de::Error,
195            {
196                if v != "inf" {
197                    return Err(de::Error::invalid_value(
198                        de::Unexpected::Str(v),
199                        &"float, map or \"inf\"",
200                    ));
201                }
202                Ok(StepThreshold {
203                    forward: None,
204                    backward: None,
205                })
206            }
207
208            fn visit_map<M: MapAccess<'de>>(self, mut map: M) -> Result<StepThreshold, M::Error> {
209                let mut forward = None;
210                let mut backward = None;
211
212                while let Some(key) = map.next_key::<String>()? {
213                    match key.as_str() {
214                        "forward" => {
215                            if forward.is_some() {
216                                return Err(de::Error::duplicate_field("forward"));
217                            }
218                            let raw: ThresholdPart = map.next_value()?;
219                            forward = Some(raw.0);
220                        }
221                        "backward" => {
222                            if backward.is_some() {
223                                return Err(de::Error::duplicate_field("backward"));
224                            }
225                            let raw: ThresholdPart = map.next_value()?;
226                            backward = Some(raw.0);
227                        }
228                        _ => {
229                            return Err(de::Error::unknown_field(
230                                key.as_str(),
231                                &["forward", "backward"],
232                            ));
233                        }
234                    }
235                }
236
237                Ok(StepThreshold {
238                    forward: forward.flatten(),
239                    backward: backward.flatten(),
240                })
241            }
242        }
243
244        deserializer.deserialize_any(StepThresholdVisitor)
245    }
246}
247
248#[derive(Deserialize, Debug, Clone, Copy)]
249#[serde(rename_all = "kebab-case", deny_unknown_fields)]
250pub struct SourceConfig {
251    /// Minima and maxima for the poll interval of clients
252    #[serde(default)]
253    pub poll_interval_limits: PollIntervalLimits,
254
255    /// Initial poll interval of the system
256    #[serde(default = "default_initial_poll_interval")]
257    pub initial_poll_interval: PollInterval,
258}
259
260impl Default for SourceConfig {
261    fn default() -> Self {
262        Self {
263            poll_interval_limits: Default::default(),
264            initial_poll_interval: default_initial_poll_interval(),
265        }
266    }
267}
268
269fn default_initial_poll_interval() -> PollInterval {
270    PollIntervalLimits::default().min
271}
272
273#[derive(Deserialize, Debug, Clone, Copy)]
274#[serde(rename_all = "kebab-case", deny_unknown_fields)]
275pub struct SynchronizationConfig {
276    /// Minimum number of survivors needed to be able to discipline the system clock.
277    /// More survivors (so more servers from which to get the time) means a more accurate time.
278    ///
279    /// The spec notes (CMIN was renamed to MIN_INTERSECTION_SURVIVORS in our implementation):
280    ///
281    /// > CMIN defines the minimum number of servers consistent with the correctness requirements.
282    /// > Suspicious operators would set CMIN to ensure multiple redundant servers are available for the
283    /// > algorithms to mitigate properly. However, for historic reasons the default value for CMIN is one.
284    #[serde(default = "default_minimum_agreeing_sources")]
285    pub minimum_agreeing_sources: usize,
286
287    /// The maximum amount the system clock is allowed to change in a single go
288    /// before we conclude something is seriously wrong. This is used to limit
289    /// the changes to the clock to reasonable amounts, and stop issues with
290    /// remote servers from causing us to drift too far.
291    ///
292    /// Note that this is not used during startup. To limit system clock changes
293    /// during startup, use startup_panic_threshold
294    #[serde(default = "default_single_step_panic_threshold")]
295    pub single_step_panic_threshold: StepThreshold,
296
297    /// The maximum amount the system clock is allowed to change during startup.
298    /// This can be used to limit the impact of bad servers if the system clock
299    /// is known to be reasonable on startup
300    #[serde(default = "default_startup_step_panic_threshold")]
301    pub startup_step_panic_threshold: StepThreshold,
302
303    /// The maximum amount distributed amongst all steps except at startup the
304    /// daemon is allowed to step the system clock.
305    #[serde(
306        deserialize_with = "deserialize_option_accumulated_step_panic_threshold",
307        default
308    )]
309    pub accumulated_step_panic_threshold: Option<NtpDuration>,
310
311    /// Stratum of the local clock, when not synchronized through ntp. This
312    /// can be used in servers to indicate that there are external mechanisms
313    /// synchronizing the clock
314    #[serde(default = "default_local_stratum")]
315    pub local_stratum: u8,
316
317    /// Reference ID for clock synchronization. When stratum is 1 this value
318    /// is used - the value is left justified, limited to four characters
319    /// and zero padded.
320    ///
321    /// From RFC 5905:
322    ///
323    ///  +------+----------------------------------------------------------+
324    ///  | ID   | Clock Source                                             |
325    ///  +------+----------------------------------------------------------+
326    ///  | GOES | Geosynchronous Orbit Environment Satellite               |
327    ///  | GPS  | Global Position System                                   |
328    ///  | GAL  | Galileo Positioning System                               |
329    ///  | PPS  | Generic pulse-per-second                                 |
330    ///  | IRIG | Inter-Range Instrumentation Group                        |
331    ///  | WWVB | LF Radio WWVB Ft. Collins, CO 60 kHz                     |
332    ///  | DCF  | LF Radio DCF77 Mainflingen, DE 77.5 kHz                  |
333    ///  | HBG  | LF Radio HBG Prangins, HB 75 kHz                         |
334    ///  | MSF  | LF Radio MSF Anthorn, UK 60 kHz                          |
335    ///  | JJY  | LF Radio JJY Fukushima, JP 40 kHz, Saga, JP 60 kHz       |
336    ///  | LORC | MF Radio LORAN C station, 100 kHz                        |
337    ///  | TDF  | MF Radio Allouis, FR 162 kHz                             |
338    ///  | CHU  | HF Radio CHU Ottawa, Ontario                             |
339    ///  | WWV  | HF Radio WWV Ft. Collins, CO                             |
340    ///  | WWVH | HF Radio WWVH Kauai, HI                                  |
341    ///  | NIST | NIST telephone modem                                     |
342    ///  | ACTS | NIST telephone modem                                     |
343    ///  | USNO | USNO telephone modem                                     |
344    ///  | PTB  | European telephone modem                                 |
345    ///  +------+----------------------------------------------------------+
346    ///
347    /// Any string beginning with the ASCII character "X" is can be used for
348    /// experimentation and development.
349    ///
350    /// The default value is "XNON" (i.e. NONE)
351    ///
352    /// When the local-stratum not 1 the reference-id is ignored.
353    ///
354    #[serde(default = "default_reference_id")]
355    pub reference_id: ReferenceIdConfig,
356
357    /// Should a warning be emitted on jumps in the clock
358    #[serde(default = "default_warn_on_jump")]
359    pub warn_on_jump: bool,
360}
361
362impl Default for SynchronizationConfig {
363    fn default() -> Self {
364        Self {
365            minimum_agreeing_sources: default_minimum_agreeing_sources(),
366
367            single_step_panic_threshold: default_single_step_panic_threshold(),
368            startup_step_panic_threshold: default_startup_step_panic_threshold(),
369            accumulated_step_panic_threshold: None,
370
371            local_stratum: default_local_stratum(),
372            reference_id: default_reference_id(),
373
374            warn_on_jump: default_warn_on_jump(),
375        }
376    }
377}
378
379fn default_minimum_agreeing_sources() -> usize {
380    3
381}
382
383fn default_reference_id() -> ReferenceIdConfig {
384    ReferenceIdConfig {
385        id: ['X', 'N', 'O', 'N']
386            .iter()
387            .fold(0u32, |acc, &c| (acc << 8) | (c as u32)),
388    }
389}
390
391fn default_single_step_panic_threshold() -> StepThreshold {
392    let raw = NtpDuration::from_seconds(1000.);
393    StepThreshold {
394        forward: Some(raw),
395        backward: Some(raw),
396    }
397}
398
399fn default_startup_step_panic_threshold() -> StepThreshold {
400    // No forward limit, backwards max. 1 day
401    StepThreshold {
402        forward: None,
403        backward: Some(NtpDuration::from_seconds(86400.)),
404    }
405}
406
407fn default_local_stratum() -> u8 {
408    16
409}
410
411fn default_warn_on_jump() -> bool {
412    true
413}