use core::fmt;
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct RefclockSample {
pub local_s: f64,
pub offset_s: f64,
pub precision_log2: i8,
pub leap: LeapWarning,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum LeapWarning {
None,
AddSecond,
DeleteSecond,
NotSynchronized,
}
impl LeapWarning {
pub fn from_wire(value: i32) -> LeapWarning {
match value {
0 => LeapWarning::None,
1 => LeapWarning::AddSecond,
2 => LeapWarning::DeleteSecond,
_ => LeapWarning::NotSynchronized,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum RefclockError {
NotSynchronized,
OffsetTooLarge,
Implausible,
Stale,
}
impl fmt::Display for RefclockError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = match self {
RefclockError::NotSynchronized => "reference clock reports it is unsynchronized",
RefclockError::OffsetTooLarge => "reference offset is implausibly large",
RefclockError::Implausible => "reference timestamps are not plausible times",
RefclockError::Stale => "reference sample is not newer than the last one",
};
f.write_str(s)
}
}
pub const MAX_REFCLOCK_OFFSET_S: f64 = 16.0;
const MIN_PLAUSIBLE_UNIX_S: f64 = 1_577_836_800.0;
impl RefclockSample {
pub fn offset_s(&self) -> f64 {
self.offset_s
}
pub fn reference_s(&self) -> f64 {
self.local_s + self.offset_s
}
pub fn validate(&self, last_local_s: Option<f64>) -> Result<(), RefclockError> {
if self.leap == LeapWarning::NotSynchronized {
return Err(RefclockError::NotSynchronized);
}
if !self.local_s.is_finite()
|| !self.offset_s.is_finite()
|| self.local_s < MIN_PLAUSIBLE_UNIX_S
|| self.reference_s() < MIN_PLAUSIBLE_UNIX_S
{
return Err(RefclockError::Implausible);
}
if let Some(previous) = last_local_s
&& self.local_s <= previous
{
return Err(RefclockError::Stale);
}
if self.offset_s().abs() > MAX_REFCLOCK_OFFSET_S {
return Err(RefclockError::OffsetTooLarge);
}
Ok(())
}
pub fn dispersion_s(&self) -> f64 {
2f64.powi(self.precision_log2 as i32)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn sample(local: f64, reference: f64) -> RefclockSample {
RefclockSample {
local_s: local,
offset_s: reference - local,
precision_log2: -20,
leap: LeapWarning::None,
}
}
fn with_offset(local: f64, offset: f64) -> RefclockSample {
RefclockSample {
local_s: local,
offset_s: offset,
precision_log2: -20,
leap: LeapWarning::None,
}
}
#[test]
fn offset_is_reference_minus_local() {
let s = sample(1_756_224_000.0, 1_756_224_000.5);
assert!((s.offset_s() - 0.5).abs() < 1e-12);
assert!(s.validate(None).is_ok());
}
#[test]
fn an_unsynchronized_reference_is_refused() {
let mut s = sample(1_756_224_000.0, 1_756_224_000.0);
s.leap = LeapWarning::NotSynchronized;
assert_eq!(s.validate(None), Err(RefclockError::NotSynchronized));
}
#[test]
fn a_stale_segment_cannot_keep_confirming_a_frozen_time() {
let s = sample(1_756_224_000.0, 1_756_224_000.0);
assert!(s.validate(Some(1_756_223_999.0)).is_ok());
assert_eq!(
s.validate(Some(1_756_224_000.0)),
Err(RefclockError::Stale),
"the same timestamp twice is not two samples"
);
assert_eq!(s.validate(Some(1_756_224_001.0)), Err(RefclockError::Stale));
}
#[test]
fn a_zeroed_segment_reads_as_implausible_not_as_1970() {
let s = sample(0.0, 0.0);
assert_eq!(s.validate(None), Err(RefclockError::Implausible));
let half = sample(1_756_224_000.0, 0.0);
assert_eq!(half.validate(None), Err(RefclockError::Implausible));
}
#[test]
fn a_wildly_stale_reading_is_refused_however_well_formed() {
let s = sample(1_756_224_000.0, 1_756_224_000.0 - 3600.0);
assert_eq!(s.validate(None), Err(RefclockError::OffsetTooLarge));
let ok = sample(1_756_224_000.0, 1_756_224_000.0 + 15.0);
assert!(ok.validate(None).is_ok());
}
#[test]
fn an_offset_survives_a_round_trip_exactly() {
for offset in [1e-9, -1e-9, 1.5e-3, -1.5e-3, 123e-6] {
let s = with_offset(1_756_224_000.0, offset);
assert_eq!(
s.offset_s(),
offset,
"offset {offset} did not survive storage exactly"
);
}
}
#[test]
fn nan_and_infinity_are_not_times() {
for bad in [f64::NAN, f64::INFINITY, f64::NEG_INFINITY] {
assert_eq!(
sample(bad, 1_756_224_000.0).validate(None),
Err(RefclockError::Implausible)
);
assert_eq!(
with_offset(1_756_224_000.0, bad).validate(None),
Err(RefclockError::Implausible)
);
}
}
#[test]
fn leap_decoding_matches_the_wire_values() {
assert_eq!(LeapWarning::from_wire(0), LeapWarning::None);
assert_eq!(LeapWarning::from_wire(1), LeapWarning::AddSecond);
assert_eq!(LeapWarning::from_wire(2), LeapWarning::DeleteSecond);
for unknown in [3, 4, -1, 99] {
assert_eq!(
LeapWarning::from_wire(unknown),
LeapWarning::NotSynchronized
);
}
}
#[test]
fn dispersion_follows_the_precision_claim() {
let mut s = sample(1_756_224_000.0, 1_756_224_000.0);
s.precision_log2 = -20; assert!((s.dispersion_s() - 9.5367e-7).abs() < 1e-9);
s.precision_log2 = 0; assert!((s.dispersion_s() - 1.0).abs() < 1e-12);
}
}