use rand::{
distributions::{Distribution, Standard},
Rng,
};
use serde::{de::Unexpected, Deserialize, Serialize};
use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign};
use std::time::{Duration, Instant};
#[derive(Debug, Copy, Clone, Eq, PartialEq, PartialOrd, Ord)]
pub struct NtpInstant {
instant: Instant,
}
impl NtpInstant {
pub fn now() -> Self {
Self {
instant: Instant::now(),
}
}
pub fn abs_diff(self, rhs: Self) -> NtpDuration {
debug_assert!(
self >= rhs,
"self >= rhs, this could indicate another program adjusted the clock"
);
let duration = if self.instant >= rhs.instant {
self.instant - rhs.instant
} else {
rhs.instant - self.instant
};
NtpDuration::from_system_duration(duration)
}
pub fn elapsed(&self) -> std::time::Duration {
self.instant.elapsed()
}
}
impl Add<Duration> for NtpInstant {
type Output = NtpInstant;
fn add(mut self, rhs: Duration) -> Self::Output {
self.instant += rhs;
self
}
}
#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Default, Serialize, Deserialize)]
pub struct NtpTimestamp {
timestamp: u64,
}
impl std::fmt::Debug for NtpTimestamp {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("NtpTimestamp")
.field(&self.timestamp)
.finish()
}
}
impl NtpTimestamp {
pub(crate) const fn from_bits(bits: [u8; 8]) -> NtpTimestamp {
NtpTimestamp {
timestamp: u64::from_be_bytes(bits),
}
}
pub(crate) const fn to_bits(self) -> [u8; 8] {
self.timestamp.to_be_bytes()
}
pub const fn from_seconds_nanos_since_ntp_era(seconds: u32, nanos: u32) -> Self {
debug_assert!(nanos < 1_000_000_000);
let fraction = ((nanos as u64) << 32) / 1_000_000_000;
let timestamp = ((seconds as u64) << 32) + fraction;
NtpTimestamp::from_bits(timestamp.to_be_bytes())
}
pub fn is_before(self, other: NtpTimestamp) -> bool {
self - other < NtpDuration::ZERO
}
#[cfg(any(test, feature = "fuzz"))]
pub(crate) const fn from_fixed_int(timestamp: u64) -> NtpTimestamp {
NtpTimestamp { timestamp }
}
}
impl Distribution<NtpTimestamp> for Standard {
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> NtpTimestamp {
NtpTimestamp {
timestamp: rng.gen(),
}
}
}
impl Add<NtpDuration> for NtpTimestamp {
type Output = NtpTimestamp;
fn add(self, rhs: NtpDuration) -> Self::Output {
NtpTimestamp {
timestamp: self.timestamp.wrapping_add(rhs.duration as u64),
}
}
}
impl AddAssign<NtpDuration> for NtpTimestamp {
fn add_assign(&mut self, rhs: NtpDuration) {
self.timestamp = self.timestamp.wrapping_add(rhs.duration as u64);
}
}
impl Sub for NtpTimestamp {
type Output = NtpDuration;
fn sub(self, rhs: Self) -> Self::Output {
NtpDuration {
duration: self.timestamp.wrapping_sub(rhs.timestamp) as i64,
}
}
}
impl Sub<NtpDuration> for NtpTimestamp {
type Output = NtpTimestamp;
fn sub(self, rhs: NtpDuration) -> Self::Output {
NtpTimestamp {
timestamp: self.timestamp.wrapping_sub(rhs.duration as u64),
}
}
}
impl SubAssign<NtpDuration> for NtpTimestamp {
fn sub_assign(&mut self, rhs: NtpDuration) {
self.timestamp = self.timestamp.wrapping_sub(rhs.duration as u64);
}
}
#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Default)]
pub struct NtpDuration {
duration: i64,
}
impl std::fmt::Debug for NtpDuration {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "NtpDuration({} ms)", self.to_seconds() * 1e3)
}
}
impl NtpDuration {
pub const ZERO: Self = Self { duration: 0 };
pub(crate) const ONE: Self = Self { duration: 1 << 32 };
pub(crate) const STEP_THRESHOLD: Self = Self { duration: 1 << 29 };
pub(crate) const MAX_DISPERSION: Self = Self {
duration: 68719476736,
};
pub(crate) const MIN_DISPERSION: Self = Self { duration: 21474836 };
pub(crate) const fn from_bits(bits: [u8; 8]) -> Self {
Self {
duration: i64::from_be_bytes(bits),
}
}
pub(crate) const fn from_bits_short(bits: [u8; 4]) -> Self {
NtpDuration {
duration: (u32::from_be_bytes(bits) as i64) << 16,
}
}
pub(crate) const fn to_bits_short(self) -> [u8; 4] {
assert!(self.duration >= 0);
debug_assert!(self.duration <= 0x0000FFFFFFFFFFFF);
match self.duration > 0x0000FFFFFFFFFFFF {
true => 0xFFFFFFFF_u32,
false => ((self.duration & 0x0000FFFFFFFF0000) >> 16) as u32,
}
.to_be_bytes()
}
pub fn to_seconds(self) -> f64 {
self.duration as f64 / u32::MAX as f64
}
pub fn from_seconds(seconds: f64) -> Self {
debug_assert!(!(seconds.is_nan() || seconds.is_infinite()));
let i = seconds.floor();
let f = seconds - i;
let duration = match i as i64 {
i if i >= std::i32::MIN as i64 && i <= std::i32::MAX as i64 => {
(i << 32) | (f * u32::MAX as f64) as i64
}
i if i < std::i32::MIN as i64 => std::i64::MIN,
i if i > std::i32::MAX as i64 => std::i64::MAX,
_ => unreachable!(),
};
Self { duration }
}
pub const fn abs(self) -> Self {
Self {
duration: self.duration.abs(),
}
}
pub const fn as_seconds_nanos(self) -> (i32, u32) {
(
(self.duration >> 32) as i32,
(((self.duration & 0xFFFFFFFF) * 1_000_000_000) >> 32) as u32,
)
}
pub fn from_exponent(input: i8) -> Self {
Self {
duration: match input {
exp if exp > 30 => std::i64::MAX,
exp if exp > 0 && exp <= 30 => 0x1_0000_0000_i64 << exp,
exp if (-32..=0).contains(&exp) => 0x1_0000_0000_i64 >> -exp,
_ => 0,
},
}
}
pub fn log2(self) -> i8 {
if self == NtpDuration::ZERO {
return i8::MIN;
}
31 - (self.duration.leading_zeros() as i8)
}
pub fn from_system_duration(duration: Duration) -> Self {
let seconds = duration.as_secs();
let nanos = duration.subsec_nanos();
debug_assert!(nanos < 1_000_000_000);
let fraction = ((nanos as u64) << 32) / 1_000_000_000;
let timestamp = (seconds << 32) + fraction;
NtpDuration::from_bits(timestamp.to_be_bytes())
}
#[cfg(any(test, feature = "fuzz"))]
pub(crate) const fn from_fixed_int(duration: i64) -> NtpDuration {
NtpDuration { duration }
}
}
impl Serialize for NtpDuration {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let seconds = self.to_seconds();
seconds.serialize(serializer)
}
}
impl<'de> Deserialize<'de> for NtpDuration {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let seconds: f64 = Deserialize::deserialize(deserializer)?;
if seconds.is_nan() || seconds.is_infinite() {
return Err(serde::de::Error::invalid_value(
Unexpected::Float(seconds),
&"a valid number",
));
}
Ok(NtpDuration::from_seconds(seconds))
}
}
impl Add for NtpDuration {
type Output = NtpDuration;
fn add(self, rhs: Self) -> Self::Output {
NtpDuration {
duration: self.duration.saturating_add(rhs.duration),
}
}
}
impl AddAssign for NtpDuration {
fn add_assign(&mut self, rhs: Self) {
self.duration = self.duration.saturating_add(rhs.duration);
}
}
impl Sub for NtpDuration {
type Output = NtpDuration;
fn sub(self, rhs: Self) -> Self::Output {
NtpDuration {
duration: self.duration.saturating_sub(rhs.duration),
}
}
}
impl SubAssign for NtpDuration {
fn sub_assign(&mut self, rhs: Self) {
self.duration = self.duration.saturating_sub(rhs.duration);
}
}
impl Neg for NtpDuration {
type Output = NtpDuration;
fn neg(self) -> Self::Output {
NtpDuration {
duration: -self.duration,
}
}
}
macro_rules! ntp_duration_scalar_mul {
($scalar_type:ty) => {
impl Mul<NtpDuration> for $scalar_type {
type Output = NtpDuration;
fn mul(self, rhs: NtpDuration) -> NtpDuration {
NtpDuration {
duration: rhs.duration.saturating_mul(self as i64),
}
}
}
impl Mul<$scalar_type> for NtpDuration {
type Output = NtpDuration;
fn mul(self, rhs: $scalar_type) -> NtpDuration {
NtpDuration {
duration: self.duration.saturating_mul(rhs as i64),
}
}
}
impl MulAssign<$scalar_type> for NtpDuration {
fn mul_assign(&mut self, rhs: $scalar_type) {
self.duration = self.duration.saturating_mul(rhs as i64);
}
}
};
}
ntp_duration_scalar_mul!(i8);
ntp_duration_scalar_mul!(i16);
ntp_duration_scalar_mul!(i32);
ntp_duration_scalar_mul!(i64);
ntp_duration_scalar_mul!(isize);
ntp_duration_scalar_mul!(u8);
ntp_duration_scalar_mul!(u16);
ntp_duration_scalar_mul!(u32);
macro_rules! ntp_duration_scalar_div {
($scalar_type:ty) => {
impl Div<$scalar_type> for NtpDuration {
type Output = NtpDuration;
fn div(self, rhs: $scalar_type) -> NtpDuration {
NtpDuration {
duration: self.duration / (rhs as i64),
}
}
}
impl DivAssign<$scalar_type> for NtpDuration {
fn div_assign(&mut self, rhs: $scalar_type) {
self.duration /= (rhs as i64);
}
}
};
}
ntp_duration_scalar_div!(i8);
ntp_duration_scalar_div!(i16);
ntp_duration_scalar_div!(i32);
ntp_duration_scalar_div!(i64);
ntp_duration_scalar_div!(isize);
ntp_duration_scalar_div!(u8);
ntp_duration_scalar_div!(u16);
ntp_duration_scalar_div!(u32);
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct PollInterval(i8);
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct PollIntervalLimits {
pub min: PollInterval,
pub max: PollInterval,
}
impl Default for PollIntervalLimits {
fn default() -> Self {
Self {
min: PollInterval(4),
max: PollInterval(10),
}
}
}
impl std::fmt::Debug for PollInterval {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "PollInterval({} s)", 2.0_f64.powf(self.0 as _))
}
}
impl PollInterval {
#[must_use]
pub fn inc(self, limits: PollIntervalLimits) -> Self {
Self(self.0 + 1).min(limits.max)
}
#[must_use]
pub fn dec(self, limits: PollIntervalLimits) -> Self {
Self(self.0 - 1).max(limits.min)
}
pub const fn as_log(self) -> i8 {
self.0
}
pub const fn as_duration(self) -> NtpDuration {
NtpDuration {
duration: 1 << (self.0 + 32),
}
}
pub const fn as_system_duration(self) -> Duration {
Duration::from_secs(1 << self.0)
}
}
impl Default for PollInterval {
fn default() -> Self {
Self(4)
}
}
#[derive(Debug, Clone, Copy)]
pub struct FrequencyTolerance {
ppm: u32,
}
impl<'de> Deserialize<'de> for FrequencyTolerance {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let val: u32 = Deserialize::deserialize(deserializer)?;
Ok(FrequencyTolerance { ppm: val })
}
}
impl FrequencyTolerance {
pub const fn ppm(ppm: u32) -> Self {
Self { ppm }
}
}
impl Mul<FrequencyTolerance> for NtpDuration {
type Output = NtpDuration;
fn mul(self, rhs: FrequencyTolerance) -> Self::Output {
(self * rhs.ppm) / 1_000_000
}
}
#[cfg(feature = "fuzz")]
pub fn fuzz_duration_from_seconds(v: f64) {
if v.is_finite() {
let duration = NtpDuration::from_seconds(v);
assert!(v.signum() as i64 * duration.duration.signum() >= 0);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_timestamp_sub() {
let a = NtpTimestamp::from_fixed_int(5);
let b = NtpTimestamp::from_fixed_int(3);
assert_eq!(a - b, NtpDuration::from_fixed_int(2));
assert_eq!(b - a, NtpDuration::from_fixed_int(-2));
}
#[test]
fn test_timestamp_era_change() {
let mut a = NtpTimestamp::from_fixed_int(1);
let b = NtpTimestamp::from_fixed_int(0xFFFFFFFFFFFFFFFF);
assert_eq!(a - b, NtpDuration::from_fixed_int(2));
assert_eq!(b - a, NtpDuration::from_fixed_int(-2));
let c = NtpDuration::from_fixed_int(2);
let d = NtpDuration::from_fixed_int(-2);
assert_eq!(b + c, a);
assert_eq!(b - d, a);
assert_eq!(a - c, b);
assert_eq!(a + d, b);
a -= c;
assert_eq!(a, b);
a += c;
assert_eq!(a, NtpTimestamp::from_fixed_int(1));
}
#[test]
fn test_timestamp_from_seconds_nanos() {
assert_eq!(
NtpTimestamp::from_seconds_nanos_since_ntp_era(0, 500_000_000),
NtpTimestamp::from_fixed_int(0x80000000)
);
assert_eq!(
NtpTimestamp::from_seconds_nanos_since_ntp_era(1, 0),
NtpTimestamp::from_fixed_int(1 << 32)
);
}
#[test]
fn test_timestamp_duration_math() {
let mut a = NtpTimestamp::from_fixed_int(5);
let b = NtpDuration::from_fixed_int(2);
assert_eq!(a + b, NtpTimestamp::from_fixed_int(7));
assert_eq!(a - b, NtpTimestamp::from_fixed_int(3));
a += b;
assert_eq!(a, NtpTimestamp::from_fixed_int(7));
a -= b;
assert_eq!(a, NtpTimestamp::from_fixed_int(5));
}
#[test]
fn test_duration_as_seconds_nanos() {
assert_eq!(
NtpDuration::from_fixed_int(0x80000000).as_seconds_nanos(),
(0, 500_000_000)
);
assert_eq!(
NtpDuration::from_fixed_int(1 << 33).as_seconds_nanos(),
(2, 0)
);
}
#[test]
fn test_duration_math() {
let mut a = NtpDuration::from_fixed_int(5);
let b = NtpDuration::from_fixed_int(2);
assert_eq!(a + b, NtpDuration::from_fixed_int(7));
assert_eq!(a - b, NtpDuration::from_fixed_int(3));
a += b;
assert_eq!(a, NtpDuration::from_fixed_int(7));
a -= b;
assert_eq!(a, NtpDuration::from_fixed_int(5));
}
macro_rules! ntp_duration_scaling_test {
($name:ident, $scalar_type:ty) => {
#[test]
fn $name() {
let mut a = NtpDuration::from_fixed_int(31);
let b: $scalar_type = 2;
assert_eq!(a * b, NtpDuration::from_fixed_int(62));
assert_eq!(b * a, NtpDuration::from_fixed_int(62));
assert_eq!(a / b, NtpDuration::from_fixed_int(15));
a /= b;
assert_eq!(a, NtpDuration::from_fixed_int(15));
a *= b;
assert_eq!(a, NtpDuration::from_fixed_int(30));
}
};
}
ntp_duration_scaling_test!(ntp_duration_scaling_i8, i8);
ntp_duration_scaling_test!(ntp_duration_scaling_i16, i16);
ntp_duration_scaling_test!(ntp_duration_scaling_i32, i32);
ntp_duration_scaling_test!(ntp_duration_scaling_i64, i64);
ntp_duration_scaling_test!(ntp_duration_scaling_isize, isize);
ntp_duration_scaling_test!(ntp_duration_scaling_u8, u8);
ntp_duration_scaling_test!(ntp_duration_scaling_u16, u16);
ntp_duration_scaling_test!(ntp_duration_scaling_u32, u32);
macro_rules! assert_eq_epsilon {
($a:expr, $b:expr, $epsilon:expr) => {
assert!(
($a - $b).abs() < $epsilon,
"Left not nearly equal to right:\nLeft: {}\nRight: {}\n",
$a,
$b
);
};
}
#[test]
fn duration_seconds_roundtrip() {
assert_eq_epsilon!(NtpDuration::from_seconds(0.0).to_seconds(), 0.0, 1e-9);
assert_eq_epsilon!(NtpDuration::from_seconds(1.0).to_seconds(), 1.0, 1e-9);
assert_eq_epsilon!(NtpDuration::from_seconds(1.5).to_seconds(), 1.5, 1e-9);
assert_eq_epsilon!(NtpDuration::from_seconds(2.0).to_seconds(), 2.0, 1e-9);
}
#[test]
fn duration_from_exponent() {
assert_eq_epsilon!(NtpDuration::from_exponent(0).to_seconds(), 1.0, 1e-9);
assert_eq_epsilon!(NtpDuration::from_exponent(1).to_seconds(), 2.0, 1e-9);
assert_eq_epsilon!(
NtpDuration::from_exponent(17).to_seconds(),
2.0f64.powi(17),
1e-4 );
assert_eq_epsilon!(NtpDuration::from_exponent(-1).to_seconds(), 0.5, 1e-9);
assert_eq_epsilon!(
NtpDuration::from_exponent(-5).to_seconds(),
1.0 / 2.0f64.powi(5),
1e-9
);
}
#[test]
fn duration_from_exponent_reasonable() {
for i in -32..=127 {
assert!(NtpDuration::from_exponent(i) > NtpDuration::from_fixed_int(0));
}
for i in -128..-32 {
NtpDuration::from_exponent(i); }
}
#[test]
fn duration_from_float_seconds_saturates() {
assert_eq!(
NtpDuration::from_seconds(1e40),
NtpDuration::from_fixed_int(std::i64::MAX)
);
assert_eq!(
NtpDuration::from_seconds(-1e40),
NtpDuration::from_fixed_int(std::i64::MIN)
);
}
#[test]
fn poll_interval_clamps() {
let mut interval = PollInterval::default();
let limits = PollIntervalLimits::default();
for _ in 0..100 {
interval = interval.inc(limits);
assert!(interval <= limits.max);
}
for _ in 0..100 {
interval = interval.dec(limits);
assert!(interval >= limits.min);
}
for _ in 0..100 {
interval = interval.inc(limits);
assert!(interval <= limits.max);
}
}
#[test]
fn poll_interval_to_duration() {
assert_eq!(
PollInterval(4).as_duration(),
NtpDuration::from_fixed_int(16 << 32)
);
assert_eq!(
PollInterval(5).as_duration(),
NtpDuration::from_fixed_int(32 << 32)
);
let mut interval = PollInterval::default();
for _ in 0..100 {
assert_eq!(
interval.as_duration().as_seconds_nanos().0,
interval.as_system_duration().as_secs() as i32
);
interval = interval.inc(PollIntervalLimits::default());
}
for _ in 0..100 {
assert_eq!(
interval.as_duration().as_seconds_nanos().0,
interval.as_system_duration().as_secs() as i32
);
interval = interval.dec(PollIntervalLimits::default());
}
}
#[test]
fn frequency_tolerance() {
assert_eq!(
NtpDuration::from_seconds(1.0),
NtpDuration::from_seconds(1.0) * FrequencyTolerance::ppm(1_000_000),
);
}
}