ntp-client 0.1.0

The ntp-client is a Rust library designed for interacting with NTP (Network Time Protocol) servers. It enables developers to easily obtain accurate time information from NTP servers. This can be applied in scenarios such as calibrating system time and performing time synchronization. The library offers a simple and user - friendly API, and also features good performance and stability.
Documentation
use crate::formats::timestamp::{ShortFormat, TimestampFormat};
use crate::formats::{LeapIndicator, Mode, PrimarySource, ReferenceIdentifier, Stratum, Version};
use byteorder::{NetworkEndian, ReadBytesExt, WriteBytesExt};

#[derive(Debug, PartialEq, Default, Clone)]
pub struct Packet {
    pub li: LeapIndicator,
    pub vn: Version,
    pub mode: Mode,
    pub stratum: Stratum,
    pub poll: i8, // Is this unsigned?
    pub precision: i8,
    pub delay: ShortFormat,
    pub dispersion: ShortFormat,
    pub ref_id: ReferenceIdentifier,
    pub ref_time: TimestampFormat,
    pub orig_time: TimestampFormat,
    pub recv_time: TimestampFormat,
    pub transmit_time: TimestampFormat,
}

impl Packet {
    pub fn new_client() -> Packet {
        Packet {
            mode: Mode::Client,
            vn: Version::Ver2,
            transmit_time: TimestampFormat::now(),
            ..Default::default()
        }
    }

    pub fn try_from<T: ReadBytesExt>(mut rdr: T) -> e_utils::AnyResult<Packet> {
        let li_vn_mode = rdr.read_u8()?;
        let li = LeapIndicator::from_repr(li_vn_mode >> 6).ok_or("From LeapIndicator")?;
        let vn = Version::from_repr((li_vn_mode >> 3) & 0b111).ok_or("From Version")?;
        let mode = Mode::from_repr(li_vn_mode & 0b111).ok_or("From Mode")?;

        let stratum = Stratum::new(rdr.read_u8()?);

        let poll = rdr.read_i8()?;
        let precision = rdr.read_i8()?;
        let delay = rdr.read_u32::<NetworkEndian>()?.into();
        let dispersion = rdr.read_u32::<NetworkEndian>()?.into();
        let ref_id_raw = rdr.read_u32::<NetworkEndian>()?.into();
        let ref_id = if stratum.primary() {
            let source = PrimarySource::from_repr(ref_id_raw).ok_or("From PrimarySource")?;
            ReferenceIdentifier::Primary(source)
        } else if stratum.secondary() {
            ReferenceIdentifier::Secondary(ref_id_raw)
        } else {
            return Err(format!("Unsupported stratum: {}", stratum.get_value()).into());
        };
        let ref_time = rdr.read_u64::<NetworkEndian>()?.into();
        let orig_time = rdr.read_u64::<NetworkEndian>()?.into();
        let recv_time = rdr.read_u64::<NetworkEndian>()?.into();
        let transmit_time = rdr.read_u64::<NetworkEndian>()?.into();

        Ok(Packet {
            li: li,
            vn: vn,
            mode: mode,
            stratum: stratum,
            poll: poll,
            precision: precision,
            delay: delay,
            dispersion: dispersion,
            ref_id: ref_id,
            ref_time: ref_time,
            orig_time: orig_time,
            recv_time: recv_time,
            transmit_time: transmit_time,
        })
    }
}

impl From<Packet> for Vec<u8> {
    fn from(p: Packet) -> Vec<u8> {
        let mut buf = Vec::with_capacity(48);
        let mut li_vn_mode = 0;
        li_vn_mode |= (p.li as u8) << 6;
        li_vn_mode |= (p.vn as u8) << 3;
        li_vn_mode |= p.mode as u8;
        buf.push(li_vn_mode);
        buf.push(p.stratum.get_value());
        buf.push(p.poll as u8);
        buf.push(p.precision as u8);
        buf.write_u32::<NetworkEndian>(p.delay.into())
            .expect("can't fail");
        buf.write_u32::<NetworkEndian>(p.dispersion.into())
            .expect("can't fail");
        buf.write_u32::<NetworkEndian>(p.ref_id.into())
            .expect("can't fail");
        buf.write_u64::<NetworkEndian>(p.ref_time.into())
            .expect("can't fail");
        buf.write_u64::<NetworkEndian>(p.orig_time.into())
            .expect("can't fail");
        buf.write_u64::<NetworkEndian>(p.recv_time.into())
            .expect("can't fail");
        buf.write_u64::<NetworkEndian>(p.transmit_time.into())
            .expect("can't fail");
        buf
    }
}

#[cfg(test)]
mod tests {
    use super::Packet;
    use crate::formats::timestamp::{ShortFormat, TimestampFormat};
    use crate::formats::{
        LeapIndicator, Mode, PrimarySource, ReferenceIdentifier, Stratum, Version,
    };
    use std::io::Cursor;

    #[test]
    fn packet_from_bytes() {
        let input = vec![
            20u8, 1, 3, 240, 0, 0, 0, 0, 0, 0, 0, 24, 67, 68, 77, 65, 215, 188, 128, 105, 198, 169,
            46, 99, 215, 187, 177, 194, 159, 47, 120, 0, 215, 188, 128, 113, 45, 236, 230, 45, 215,
            188, 128, 113, 46, 35, 158, 108,
        ];
        let expected_output = Packet {
            li: LeapIndicator::NoWarning,
            vn: Version::Ver2,
            mode: Mode::Server,
            stratum: Stratum::new(1),
            poll: 3,
            precision: -16,
            delay: ShortFormat { sec: 0, frac: 0 },
            dispersion: ShortFormat { sec: 0, frac: 24 },
            ref_id: ReferenceIdentifier::Primary(PrimarySource::CDMA),
            ref_time: TimestampFormat {
                sec: 3619455081,
                frac: 3332976227,
            },
            orig_time: TimestampFormat {
                sec: 3619402178,
                frac: 2670688256,
            },
            recv_time: TimestampFormat {
                sec: 3619455089,
                frac: 770500141,
            },
            transmit_time: TimestampFormat {
                sec: 3619455089,
                frac: 774086252,
            },
        };

        let rdr = Cursor::new(input);
        assert_eq!(expected_output, Packet::try_from(rdr).unwrap());
    }

    #[test]
    fn packet_to_bytes() {
        let expected_output = vec![
            20, 1, 3, 240, 0, 0, 0, 0, 0, 0, 0, 24, 67, 68, 77, 65, 215, 188, 128, 105, 198, 169,
            46, 99, 215, 187, 177, 194, 159, 47, 120, 0, 215, 188, 128, 113, 45, 236, 230, 45, 215,
            188, 128, 113, 46, 35, 158, 108,
        ];
        let input = Packet {
            li: LeapIndicator::NoWarning,
            vn: Version::Ver2,
            mode: Mode::Server,
            stratum: Stratum::new(1),
            poll: 3,
            precision: -16,
            delay: ShortFormat { sec: 0, frac: 0 },
            dispersion: ShortFormat { sec: 0, frac: 24 },
            ref_id: ReferenceIdentifier::Primary(PrimarySource::CDMA),
            ref_time: TimestampFormat {
                sec: 3619455081,
                frac: 3332976227,
            },
            orig_time: TimestampFormat {
                sec: 3619402178,
                frac: 2670688256,
            },
            recv_time: TimestampFormat {
                sec: 3619455089,
                frac: 770500141,
            },
            transmit_time: TimestampFormat {
                sec: 3619455089,
                frac: 774086252,
            },
        };
        let output: Vec<u8> = input.into();
        assert_eq!(output, expected_output);
    }

    #[test]
    fn packet_conversion_roundtrip() {
        let input = vec![
            20, 1, 3, 240, 0, 0, 0, 0, 0, 0, 0, 24, 67, 68, 77, 65, 215, 188, 128, 105, 198, 169,
            46, 99, 215, 187, 177, 194, 159, 47, 120, 0, 215, 188, 128, 113, 45, 236, 230, 45, 215,
            188, 128, 113, 46, 35, 158, 108,
        ];
        let rdr = Cursor::new(&input);
        let output: Vec<u8> = Packet::try_from(rdr).unwrap().into();
        assert_eq!(input.as_slice(), output.as_slice());
    }
}