use std::fmt;
use std::ops::{Add, Sub};
use std::time::{Duration, SystemTime, UNIX_EPOCH};
const NTP_UNIX_EPOCH_DIFF: u64 = 2_208_988_800;
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct NtpTimestamp(pub u64);
impl NtpTimestamp {
pub const ZERO: Self = Self(0);
pub fn new(seconds: u32, fraction: u32) -> Self {
Self(((seconds as u64) << 32) | (fraction as u64))
}
pub fn now() -> Self {
Self::from_system_time(SystemTime::now())
}
pub fn from_system_time(time: SystemTime) -> Self {
let duration = time.duration_since(UNIX_EPOCH).unwrap_or_default();
let ntp_seconds = duration.as_secs() + NTP_UNIX_EPOCH_DIFF;
let fraction = ((duration.subsec_nanos() as u64) << 32) / 1_000_000_000;
Self::new(ntp_seconds as u32, fraction as u32)
}
pub fn to_system_time(self) -> SystemTime {
let secs = self.seconds() as u64;
let unix_secs = secs.saturating_sub(NTP_UNIX_EPOCH_DIFF);
let nanos = ((self.fraction() as u64) * 1_000_000_000) >> 32;
UNIX_EPOCH + Duration::new(unix_secs, nanos as u32)
}
pub fn seconds(self) -> u32 {
(self.0 >> 32) as u32
}
pub fn fraction(self) -> u32 {
self.0 as u32
}
pub fn raw(self) -> u64 {
self.0
}
pub fn from_raw(raw: u64) -> Self {
Self(raw)
}
pub fn to_bytes(self) -> [u8; 8] {
self.0.to_be_bytes()
}
pub fn from_bytes(bytes: [u8; 8]) -> Self {
Self(u64::from_be_bytes(bytes))
}
}
impl fmt::Debug for NtpTimestamp {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"NtpTimestamp({}.{:010})",
self.seconds(),
self.fraction()
)
}
}
impl fmt::Display for NtpTimestamp {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let nanos = ((self.fraction() as u64) * 1_000_000_000) >> 32;
write!(f, "{}.{:09}", self.seconds(), nanos)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct PtpTimestamp {
pub seconds: u64,
pub nanoseconds: u32,
}
impl PtpTimestamp {
pub const ZERO: Self = Self {
seconds: 0,
nanoseconds: 0,
};
pub fn new(seconds: u64, nanoseconds: u32) -> Self {
Self {
seconds,
nanoseconds,
}
}
pub fn to_ntp_timestamp(self) -> NtpTimestamp {
let ntp_secs = self.seconds + NTP_UNIX_EPOCH_DIFF;
let fraction = ((self.nanoseconds as u64) << 32) / 1_000_000_000;
NtpTimestamp::new(ntp_secs as u32, fraction as u32)
}
pub fn to_bytes(self) -> [u8; 10] {
let mut buf = [0u8; 10];
let sec_bytes = self.seconds.to_be_bytes();
buf[0..6].copy_from_slice(&sec_bytes[2..8]);
buf[6..10].copy_from_slice(&self.nanoseconds.to_be_bytes());
buf
}
pub fn from_bytes(bytes: [u8; 10]) -> Self {
let mut sec_buf = [0u8; 8];
sec_buf[2..8].copy_from_slice(&bytes[0..6]);
let seconds = u64::from_be_bytes(sec_buf);
let nanoseconds = u32::from_be_bytes([bytes[6], bytes[7], bytes[8], bytes[9]]);
Self {
seconds,
nanoseconds,
}
}
}
impl fmt::Display for PtpTimestamp {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}.{:09}", self.seconds, self.nanoseconds)
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct NtpDuration(i128);
impl NtpDuration {
pub const ZERO: Self = Self(0);
const FRAC_BITS: u32 = 32;
pub fn from_nanos(nanos: i64) -> Self {
Self((nanos as i128) << Self::FRAC_BITS)
}
pub fn from_seconds_f64(secs: f64) -> Self {
let nanos = secs * 1_000_000_000.0;
Self((nanos as i128) << Self::FRAC_BITS)
}
pub fn from_millis(ms: i64) -> Self {
Self::from_nanos(ms * 1_000_000)
}
pub fn to_nanos(self) -> i64 {
(self.0 >> Self::FRAC_BITS) as i64
}
pub fn to_seconds_f64(self) -> f64 {
(self.0 as f64) / ((1i128 << Self::FRAC_BITS) as f64) / 1_000_000_000.0
}
pub fn to_millis_f64(self) -> f64 {
self.to_seconds_f64() * 1_000.0
}
pub fn abs(self) -> Self {
Self(self.0.abs())
}
pub fn from_ntp_short(raw: u32) -> Self {
let seconds = (raw >> 16) as i64;
let fraction = (raw & 0xFFFF) as i64;
let nanos = seconds * 1_000_000_000 + (fraction * 1_000_000_000) / 65536;
Self::from_nanos(nanos)
}
pub fn to_ntp_short(self) -> u32 {
let nanos = self.to_nanos().max(0) as u64;
let seconds = nanos / 1_000_000_000;
let frac_nanos = nanos % 1_000_000_000;
let frac = (frac_nanos * 65536) / 1_000_000_000;
((seconds as u32) << 16) | (frac as u32)
}
pub fn between(a: NtpTimestamp, b: NtpTimestamp) -> Self {
let diff = (b.0 as i128) - (a.0 as i128);
Self(diff * 1_000_000_000 / (1i128 << 32) * (1i128 << Self::FRAC_BITS))
}
pub fn raw(self) -> i128 {
self.0
}
pub fn from_raw(raw: i128) -> Self {
Self(raw)
}
}
impl Add for NtpDuration {
type Output = Self;
fn add(self, rhs: Self) -> Self {
Self(self.0 + rhs.0)
}
}
impl Sub for NtpDuration {
type Output = Self;
fn sub(self, rhs: Self) -> Self {
Self(self.0 - rhs.0)
}
}
impl std::ops::Div<i64> for NtpDuration {
type Output = Self;
fn div(self, rhs: i64) -> Self {
Self(self.0 / rhs as i128)
}
}
impl std::ops::Neg for NtpDuration {
type Output = Self;
fn neg(self) -> Self {
Self(-self.0)
}
}
impl fmt::Debug for NtpDuration {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "NtpDuration({:.9}s)", self.to_seconds_f64())
}
}
impl fmt::Display for NtpDuration {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let secs = self.to_seconds_f64();
if secs.abs() < 0.001 {
write!(f, "{:.3}us", secs * 1_000_000.0)
} else if secs.abs() < 1.0 {
write!(f, "{:.3}ms", secs * 1_000.0)
} else {
write!(f, "{:.6}s", secs)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ntp_timestamp_roundtrip_bytes() {
let ts = NtpTimestamp::new(3_900_000_000, 2_147_483_648);
let bytes = ts.to_bytes();
let ts2 = NtpTimestamp::from_bytes(bytes);
assert_eq!(ts, ts2);
}
#[test]
fn ntp_timestamp_system_time_roundtrip() {
let now = SystemTime::now();
let ts = NtpTimestamp::from_system_time(now);
let back = ts.to_system_time();
let diff = now
.duration_since(back)
.or_else(|e| Ok::<_, std::convert::Infallible>(e.duration()))
.unwrap();
assert!(diff < Duration::from_micros(1));
}
#[test]
fn ntp_timestamp_zero() {
let ts = NtpTimestamp::ZERO;
assert_eq!(ts.seconds(), 0);
assert_eq!(ts.fraction(), 0);
}
#[test]
fn ptp_timestamp_roundtrip_bytes() {
let ts = PtpTimestamp::new(1_000_000, 500_000_000);
let bytes = ts.to_bytes();
let ts2 = PtpTimestamp::from_bytes(bytes);
assert_eq!(ts, ts2);
}
#[test]
fn ptp_timestamp_48bit_seconds() {
let ts = PtpTimestamp::new(0x0000_FFFF_FFFF_FFFF, 0);
let bytes = ts.to_bytes();
let ts2 = PtpTimestamp::from_bytes(bytes);
assert_eq!(ts2.seconds, 0x0000_FFFF_FFFF_FFFF);
}
#[test]
fn ntp_duration_from_nanos() {
let d = NtpDuration::from_nanos(1_000_000_000); assert!((d.to_seconds_f64() - 1.0).abs() < 1e-9);
}
#[test]
fn ntp_duration_from_seconds_f64() {
let d = NtpDuration::from_seconds_f64(0.5);
assert!((d.to_seconds_f64() - 0.5).abs() < 1e-9);
}
#[test]
fn ntp_duration_arithmetic() {
let a = NtpDuration::from_nanos(100);
let b = NtpDuration::from_nanos(50);
assert_eq!((a + b).to_nanos(), 150);
assert_eq!((a - b).to_nanos(), 50);
assert_eq!((a / 2).to_nanos(), 50);
assert_eq!((-a).to_nanos(), -100);
}
#[test]
fn ntp_duration_abs() {
let d = NtpDuration::from_nanos(-500);
assert_eq!(d.abs().to_nanos(), 500);
}
#[test]
fn ntp_duration_between_timestamps() {
let t1 = NtpTimestamp::new(1000, 0);
let t2 = NtpTimestamp::new(1001, 0);
let diff = NtpDuration::between(t1, t2);
assert!((diff.to_seconds_f64() - 1.0).abs() < 1e-6);
}
#[test]
fn ntp_duration_ntp_short_roundtrip() {
let d = NtpDuration::from_millis(500);
let short = d.to_ntp_short();
let d2 = NtpDuration::from_ntp_short(short);
assert!((d.to_millis_f64() - d2.to_millis_f64()).abs() < 0.1);
}
#[test]
fn ntp_duration_display() {
let d = NtpDuration::from_nanos(500);
assert!(format!("{}", d).contains("us"));
let d = NtpDuration::from_millis(50);
assert!(format!("{}", d).contains("ms"));
let d = NtpDuration::from_seconds_f64(2.5);
assert!(format!("{}", d).contains("s"));
}
}