use byteorder::{ReadBytesExt, WriteBytesExt, BE};
use conv::TryFrom;
use std::{fmt, io};
pub const PORT: u8 = 123;
pub const TOLERANCE: f64 = 15e-6;
pub const MINPOLL: u8 = 4;
pub const MAXPOLL: u8 = 17;
pub const MAXDISP: f64 = 16.0;
pub const MINDISP: f64 = 0.005;
pub const MAXDIST: u8 = 1;
pub const MAXSTRAT: u8 = 16;
pub trait WriteBytes {
fn write_bytes<P: WriteToBytes>(&mut self, protocol: P) -> io::Result<()>;
}
pub trait ReadBytes {
fn read_bytes<P: ReadFromBytes>(&mut self) -> io::Result<P>;
}
pub trait WriteToBytes {
fn write_to_bytes<W: WriteBytesExt>(&self, writer: W) -> io::Result<()>;
}
pub trait ReadFromBytes: Sized {
fn read_from_bytes<R: ReadBytesExt>(reader: R) -> io::Result<Self>;
}
pub trait ConstPackedSizeBytes {
const PACKED_SIZE_BYTES: usize;
}
#[repr(C)]
#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct ShortFormat {
pub seconds: u16,
pub fraction: u16,
}
#[repr(C)]
#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct TimestampFormat {
pub seconds: u32,
pub fraction: u32,
}
#[repr(C)]
#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct DateFormat {
pub era_number: i32,
pub era_offset: u32,
pub fraction: u64,
}
custom_derive! {
#[repr(u8)]
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, TryFrom(u8))]
pub enum LeapIndicator {
NoWarning = 0,
AddOne = 1,
SubOne = 2,
Unknown = 3,
}
}
#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct Version(u8);
custom_derive! {
#[repr(u8)]
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, TryFrom(u8))]
pub enum Mode {
Reserved = 0,
SymmetricActive = 1,
SymmetricPassive = 2,
Client = 3,
Server = 4,
Broadcast = 5,
NtpControlMessage = 6,
ReservedForPrivateUse = 7,
}
}
#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct Stratum(pub u8);
#[repr(u32)]
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum ReferenceIdentifier {
PrimarySource(PrimarySource),
SecondaryOrClient([u8; 4]),
KissOfDeath(KissOfDeath),
}
macro_rules! code_to_u32 {
($w:expr) => {
(($w[3] as u32) << 0) |
(($w[2] as u32) << 8) |
(($w[1] as u32) << 16) |
(($w[0] as u32) << 24) |
((*$w as [u8; 4])[0] as u32 * 0)
};
}
custom_derive! {
#[repr(u32)]
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, TryFrom(u32))]
pub enum PrimarySource {
Goes = code_to_u32!(b"GOES"),
Gps = code_to_u32!(b"GPS\0"),
Cdma = code_to_u32!(b"CDMA"),
Gal = code_to_u32!(b"GAL\0"),
Pps = code_to_u32!(b"PPS\0"),
Irig = code_to_u32!(b"IRIG"),
Wwvb = code_to_u32!(b"WWVB"),
Dcf = code_to_u32!(b"DCF\0"),
Hgb = code_to_u32!(b"HGB\0"),
Msf = code_to_u32!(b"MSF\0"),
Jjy = code_to_u32!(b"JJY\0"),
Lorc = code_to_u32!(b"LORC"),
Tdf = code_to_u32!(b"TDF\0"),
Chu = code_to_u32!(b"CHU\0"),
Wwv = code_to_u32!(b"WWV\0"),
Wwvh = code_to_u32!(b"WWVH"),
Nist = code_to_u32!(b"NIST"),
Acts = code_to_u32!(b"ACTS"),
Usno = code_to_u32!(b"USNO"),
Ptb = code_to_u32!(b"PTB\0"),
Goog = code_to_u32!(b"GOOG"),
Locl = code_to_u32!(b"LOCL"),
Cesm = code_to_u32!(b"CESM"),
Rbdm = code_to_u32!(b"RBDM"),
Omeg = code_to_u32!(b"OMEG"),
Dcn = code_to_u32!(b"DCN\0"),
Tsp = code_to_u32!(b"TSP\0"),
Dts = code_to_u32!(b"DTS\0"),
Atom = code_to_u32!(b"ATOM"),
Vlf = code_to_u32!(b"VLF\0"),
Opps = code_to_u32!(b"OPPS"),
Free = code_to_u32!(b"FREE"),
Init = code_to_u32!(b"INIT"),
Null = 0,
}
}
custom_derive! {
#[repr(u32)]
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, TryFrom(u32))]
pub enum KissOfDeath {
Deny = code_to_u32!(b"DENY"),
Rstr = code_to_u32!(b"RSTR"),
Rate = code_to_u32!(b"RATE"),
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct Packet {
pub leap_indicator: LeapIndicator,
pub version: Version,
pub mode: Mode,
pub stratum: Stratum,
pub poll: i8,
pub precision: i8,
pub root_delay: ShortFormat,
pub root_dispersion: ShortFormat,
pub reference_id: ReferenceIdentifier,
pub reference_timestamp: TimestampFormat,
pub origin_timestamp: TimestampFormat,
pub receive_timestamp: TimestampFormat,
pub transmit_timestamp: TimestampFormat,
}
pub type PacketByte1 = (LeapIndicator, Version, Mode);
impl PrimarySource {
pub fn bytes(&self) -> [u8; 4] {
be_u32_to_bytes(*self as u32)
}
}
impl Version {
pub const V1: Self = Version(1);
pub const V2: Self = Version(2);
pub const V3: Self = Version(3);
pub const V4: Self = Version(4);
pub fn is_known(&self) -> bool {
self.0 >= 1 && self.0 <= 4
}
}
impl Stratum {
pub const UNSPECIFIED: Self = Stratum(0);
pub const PRIMARY: Self = Stratum(1);
pub const SECONDARY_MIN: Self = Stratum(2);
pub const SECONDARY_MAX: Self = Stratum(15);
pub const UNSYNCHRONIZED: Self = Stratum(16);
pub const MAX: Self = Stratum(16);
pub fn is_secondary(&self) -> bool {
Self::SECONDARY_MIN <= *self && *self <= Self::SECONDARY_MAX
}
pub fn is_reserved(&self) -> bool {
*self > Self::MAX
}
}
impl ConstPackedSizeBytes for ShortFormat {
const PACKED_SIZE_BYTES: usize = 4;
}
impl ConstPackedSizeBytes for TimestampFormat {
const PACKED_SIZE_BYTES: usize = 8;
}
impl ConstPackedSizeBytes for DateFormat {
const PACKED_SIZE_BYTES: usize = 16;
}
impl ConstPackedSizeBytes for Stratum {
const PACKED_SIZE_BYTES: usize = 1;
}
impl ConstPackedSizeBytes for ReferenceIdentifier {
const PACKED_SIZE_BYTES: usize = 4;
}
impl ConstPackedSizeBytes for PacketByte1 {
const PACKED_SIZE_BYTES: usize = 1;
}
impl ConstPackedSizeBytes for Packet {
const PACKED_SIZE_BYTES: usize =
PacketByte1::PACKED_SIZE_BYTES
+ Stratum::PACKED_SIZE_BYTES
+ 2
+ ShortFormat::PACKED_SIZE_BYTES * 2
+ ReferenceIdentifier::PACKED_SIZE_BYTES
+ TimestampFormat::PACKED_SIZE_BYTES * 4;
}
impl<W> WriteBytes for W
where
W: WriteBytesExt,
{
fn write_bytes<P: WriteToBytes>(&mut self, protocol: P) -> io::Result<()> {
protocol.write_to_bytes(self)
}
}
impl<'a, P> WriteToBytes for &'a P
where
P: WriteToBytes,
{
fn write_to_bytes<W: WriteBytesExt>(&self, writer: W) -> io::Result<()> {
(*self).write_to_bytes(writer)
}
}
impl WriteToBytes for ShortFormat {
fn write_to_bytes<W: WriteBytesExt>(&self, mut writer: W) -> io::Result<()> {
writer.write_u16::<BE>(self.seconds)?;
writer.write_u16::<BE>(self.fraction)?;
Ok(())
}
}
impl WriteToBytes for TimestampFormat {
fn write_to_bytes<W: WriteBytesExt>(&self, mut writer: W) -> io::Result<()> {
writer.write_u32::<BE>(self.seconds)?;
writer.write_u32::<BE>(self.fraction)?;
Ok(())
}
}
impl WriteToBytes for DateFormat {
fn write_to_bytes<W: WriteBytesExt>(&self, mut writer: W) -> io::Result<()> {
writer.write_i32::<BE>(self.era_number)?;
writer.write_u32::<BE>(self.era_offset)?;
writer.write_u64::<BE>(self.fraction)?;
Ok(())
}
}
impl WriteToBytes for Stratum {
fn write_to_bytes<W: WriteBytesExt>(&self, mut writer: W) -> io::Result<()> {
writer.write_u8(self.0)?;
Ok(())
}
}
impl WriteToBytes for ReferenceIdentifier {
fn write_to_bytes<W: WriteBytesExt>(&self, mut writer: W) -> io::Result<()> {
match *self {
ReferenceIdentifier::KissOfDeath(kod) => {
writer.write_u32::<BE>(kod as u32)?;
}
ReferenceIdentifier::PrimarySource(src) => {
writer.write_u32::<BE>(src as u32)?;
}
ReferenceIdentifier::SecondaryOrClient(arr) => {
writer.write_u32::<BE>(code_to_u32!(&arr))?;
}
}
Ok(())
}
}
impl WriteToBytes for (LeapIndicator, Version, Mode) {
fn write_to_bytes<W: WriteBytesExt>(&self, mut writer: W) -> io::Result<()> {
let (li, vn, mode) = *self;
let mut li_vn_mode = 0;
li_vn_mode |= (li as u8) << 6;
li_vn_mode |= vn.0 << 3;
li_vn_mode |= mode as u8;
writer.write_u8(li_vn_mode)?;
Ok(())
}
}
impl WriteToBytes for Packet {
fn write_to_bytes<W: WriteBytesExt>(&self, mut writer: W) -> io::Result<()> {
let li_vn_mode = (self.leap_indicator, self.version, self.mode);
writer.write_bytes(li_vn_mode)?;
writer.write_bytes(self.stratum)?;
writer.write_i8(self.poll)?;
writer.write_i8(self.precision)?;
writer.write_bytes(self.root_delay)?;
writer.write_bytes(self.root_dispersion)?;
writer.write_bytes(self.reference_id)?;
writer.write_bytes(self.reference_timestamp)?;
writer.write_bytes(self.origin_timestamp)?;
writer.write_bytes(self.receive_timestamp)?;
writer.write_bytes(self.transmit_timestamp)?;
Ok(())
}
}
impl<R> ReadBytes for R
where
R: ReadBytesExt,
{
fn read_bytes<P: ReadFromBytes>(&mut self) -> io::Result<P> {
P::read_from_bytes(self)
}
}
impl ReadFromBytes for ShortFormat {
fn read_from_bytes<R: ReadBytesExt>(mut reader: R) -> io::Result<Self> {
let seconds = reader.read_u16::<BE>()?;
let fraction = reader.read_u16::<BE>()?;
let short_format = ShortFormat { seconds, fraction };
Ok(short_format)
}
}
impl ReadFromBytes for TimestampFormat {
fn read_from_bytes<R: ReadBytesExt>(mut reader: R) -> io::Result<Self> {
let seconds = reader.read_u32::<BE>()?;
let fraction = reader.read_u32::<BE>()?;
let timestamp_format = TimestampFormat { seconds, fraction };
Ok(timestamp_format)
}
}
impl ReadFromBytes for DateFormat {
fn read_from_bytes<R: ReadBytesExt>(mut reader: R) -> io::Result<Self> {
let era_number = reader.read_i32::<BE>()?;
let era_offset = reader.read_u32::<BE>()?;
let fraction = reader.read_u64::<BE>()?;
let date_format = DateFormat { era_number, era_offset, fraction };
Ok(date_format)
}
}
impl ReadFromBytes for Stratum {
fn read_from_bytes<R: ReadBytesExt>(mut reader: R) -> io::Result<Self> {
let stratum = Stratum(reader.read_u8()?);
Ok(stratum)
}
}
impl ReadFromBytes for (LeapIndicator, Version, Mode) {
fn read_from_bytes<R: ReadBytesExt>(mut reader: R) -> io::Result<Self> {
let li_vn_mode = reader.read_u8()?;
let li_u8 = li_vn_mode >> 6;
let vn_u8 = (li_vn_mode >> 3) & 0b111;
let mode_u8 = li_vn_mode & 0b111;
let li = match LeapIndicator::try_from(li_u8).ok() {
Some(li) => li,
None => {
let err_msg = "unknown leap indicator";
return Err(io::Error::new(io::ErrorKind::InvalidData, err_msg));
},
};
let vn = Version(vn_u8);
let mode = match Mode::try_from(mode_u8).ok() {
Some(mode) => mode,
None => {
let err_msg = "unknown association mode";
return Err(io::Error::new(io::ErrorKind::InvalidData, err_msg));
},
};
Ok((li, vn, mode))
}
}
impl ReadFromBytes for Packet {
fn read_from_bytes<R: ReadBytesExt>(mut reader: R) -> io::Result<Self> {
let (leap_indicator, version, mode) = reader.read_bytes()?;
let stratum = reader.read_bytes::<Stratum>()?;
let poll = reader.read_i8()?;
let precision = reader.read_i8()?;
let root_delay = reader.read_bytes()?;
let root_dispersion = reader.read_bytes()?;
let reference_id = {
let u = reader.read_u32::<BE>()?;
if stratum == Stratum::PRIMARY {
match PrimarySource::try_from(u) {
Ok(src) => ReferenceIdentifier::PrimarySource(src),
Err(_) => match KissOfDeath::try_from(u) {
Ok(kod) => ReferenceIdentifier::KissOfDeath(kod),
Err(_) => {
let err_msg = "unknown reference id";
return Err(io::Error::new(io::ErrorKind::InvalidData, err_msg));
}
},
}
} else if stratum.is_secondary() {
let arr = be_u32_to_bytes(u);
ReferenceIdentifier::SecondaryOrClient(arr)
} else {
let err_msg = "unsupported stratum";
return Err(io::Error::new(io::ErrorKind::InvalidData, err_msg));
}
};
let reference_timestamp = reader.read_bytes()?;
let origin_timestamp = reader.read_bytes()?;
let receive_timestamp = reader.read_bytes()?;
let transmit_timestamp = reader.read_bytes()?;
Ok(Packet {
leap_indicator,
version,
mode,
stratum,
poll,
precision,
root_delay,
root_dispersion,
reference_id,
reference_timestamp,
origin_timestamp,
receive_timestamp,
transmit_timestamp,
})
}
}
impl Default for LeapIndicator {
fn default() -> Self {
LeapIndicator::NoWarning
}
}
impl fmt::Display for PrimarySource {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let bytes = self.bytes();
let s = String::from_utf8_lossy(&bytes);
write!(f, "{}", s)
}
}
fn be_u32_to_bytes(u: u32) -> [u8; 4] {
[
(u >> 24 & 0xff) as u8,
(u >> 16 & 0xff) as u8,
(u >> 8 & 0xff) as u8,
(u & 0xff) as u8,
]
}