use crate::error::SntpProtocolError;
#[derive(Debug)]
pub struct SntpMessage {
pub leap_indicator: LeapIndicator,
pub version: Version,
pub mode: Mode,
pub stratum: u8,
pub poll: u8,
pub precision: u8,
pub root_delay: u32,
pub root_dispersion: u32,
pub reference_identifier: u32,
pub reference_timestamp: Timestamp,
pub originate_timestamp: Timestamp,
pub receive_timestamp: Timestamp,
pub transmit_timestamp: Timestamp,
}
impl SntpMessage {
pub const BUFFER_SIZE: usize = 48;
pub fn new_v4() -> Self {
Self {
leap_indicator: LeapIndicator::NoWarning,
version: Version::V4,
mode: Mode::Client,
stratum: 0,
poll: 0,
precision: 0,
root_delay: 0,
root_dispersion: 0,
reference_identifier: 0,
reference_timestamp: Timestamp(0),
originate_timestamp: Timestamp(0),
receive_timestamp: Timestamp(0),
transmit_timestamp: Timestamp(0),
}
}
pub fn write_to_buffer(&self, buffer: &mut [u8]) -> Result<(), SntpProtocolError> {
if buffer.len() < Self::BUFFER_SIZE {
return Err(SntpProtocolError::SntpBufferTooSmall {
size: buffer.len(),
expected: Self::BUFFER_SIZE,
});
}
let leap_indicator = match self.leap_indicator {
LeapIndicator::NoWarning => 0,
LeapIndicator::LastMinuteHas61Seconds => 1,
LeapIndicator::LastMinuteHas59Seconds => 2,
LeapIndicator::AlarmCondition => 3,
};
let version = match self.version {
Version::V4 => 4,
Version::V3 => 3,
};
let mode = match self.mode {
Mode::Reserved => 0,
Mode::SymmetricActive => 1,
Mode::SymmetricPassive => 2,
Mode::Client => 3,
Mode::Server => 4,
Mode::Broadcast => 5,
Mode::Reserved6 => 6,
Mode::Reserved7 => 7,
};
buffer[0] = mode | (version << 3) | (leap_indicator << 6);
buffer[1] = self.stratum;
buffer[2] = self.poll;
buffer[3] = self.precision;
buffer[4..8].copy_from_slice(&self.root_delay.to_be_bytes());
buffer[8..12].copy_from_slice(&self.root_dispersion.to_be_bytes());
buffer[12..16].copy_from_slice(&self.reference_identifier.to_be_bytes());
buffer[16..24].copy_from_slice(&self.reference_timestamp.to_be_bytes());
buffer[24..32].copy_from_slice(&self.originate_timestamp.to_be_bytes());
buffer[32..40].copy_from_slice(&self.receive_timestamp.to_be_bytes());
buffer[40..48].copy_from_slice(&self.transmit_timestamp.to_be_bytes());
Ok(())
}
pub fn read_from_buffer(&mut self, buffer: &[u8]) -> Result<(), SntpProtocolError> {
if buffer.len() < Self::BUFFER_SIZE {
return Err(SntpProtocolError::SntpBufferTooSmall {
size: buffer.len(),
expected: Self::BUFFER_SIZE,
});
}
self.mode = match buffer[0] & 0x7 {
0 => Mode::Reserved,
1 => Mode::SymmetricActive,
2 => Mode::SymmetricPassive,
3 => Mode::Client,
4 => Mode::Server,
5 => Mode::Broadcast,
6 => Mode::Reserved6,
7 => Mode::Reserved7,
invalid => return Err(SntpProtocolError::InvalidSntpMode(invalid)),
};
self.version = match (buffer[0] >> 3) & 0x7 {
4 => Version::V4,
3 => Version::V3,
invalid => return Err(SntpProtocolError::InvalidSntpVersion(invalid)),
};
self.leap_indicator = match (buffer[0] >> 6) & 0x3 {
0 => LeapIndicator::NoWarning,
1 => LeapIndicator::LastMinuteHas61Seconds,
2 => LeapIndicator::LastMinuteHas59Seconds,
3 => LeapIndicator::AlarmCondition,
invalid => {
return Err(SntpProtocolError::InvalidSntpLeadIndicator(invalid));
}
};
self.stratum = buffer[1];
self.poll = buffer[2];
self.precision = buffer[3];
self.root_delay = u32::from_be_bytes([buffer[4], buffer[5], buffer[6], buffer[7]]);
self.root_dispersion = u32::from_be_bytes([buffer[8], buffer[9], buffer[10], buffer[11]]);
self.reference_identifier =
u32::from_be_bytes([buffer[12], buffer[13], buffer[14], buffer[15]]);
self.reference_timestamp = Timestamp::from_be_bytes([
buffer[16], buffer[17], buffer[18], buffer[19], buffer[20], buffer[21], buffer[22],
buffer[23],
]);
self.originate_timestamp = Timestamp::from_be_bytes([
buffer[24], buffer[25], buffer[26], buffer[27], buffer[28], buffer[29], buffer[30],
buffer[31],
]);
self.receive_timestamp = Timestamp::from_be_bytes([
buffer[32], buffer[33], buffer[34], buffer[35], buffer[36], buffer[37], buffer[38],
buffer[39],
]);
self.transmit_timestamp = Timestamp::from_be_bytes([
buffer[40], buffer[41], buffer[42], buffer[43], buffer[44], buffer[45], buffer[46],
buffer[47],
]);
Ok(())
}
}
#[repr(u8)]
#[derive(Debug)]
pub enum LeapIndicator {
NoWarning = 0,
LastMinuteHas61Seconds = 1,
LastMinuteHas59Seconds = 2,
AlarmCondition = 3,
}
#[repr(u8)]
#[derive(Debug)]
pub enum Mode {
Reserved = 0,
SymmetricActive = 1,
SymmetricPassive = 2,
Client = 3,
Server = 4,
Broadcast = 5,
Reserved6 = 6,
Reserved7 = 7,
}
#[repr(u8)]
#[derive(Debug)]
pub enum Version {
V4 = 4,
V3 = 3,
}
#[repr(transparent)]
#[derive(Debug)]
pub struct Timestamp(pub(crate) u64);
impl Timestamp {
pub fn new(seconds: u32, fraction: u32) -> Self {
Self(((seconds as u64) << 32) | (fraction as u64))
}
pub(crate) fn to_be_bytes(&self) -> [u8; 8] {
self.0.to_be_bytes()
}
pub(crate) fn from_be_bytes(bytes: [u8; 8]) -> Self {
Self(u64::from_be_bytes(bytes))
}
pub fn msb_set(&self) -> bool {
self.0 & (1 << 63) != 0
}
pub fn seconds(&self) -> u32 {
(self.0 >> 32) as u32
}
pub fn seconds_fraction(&self) -> u32 {
self.0 as u32
}
pub fn microseconds(&self) -> u64 {
(self.0 >> 32) * 1_000_000 + ((self.0 & 0xFFFFFFFF) * 1_000_000 / 0x100000000)
}
pub fn milliseconds(&self) -> u64 {
(self.0 >> 32) * 1_000 + ((self.0 & 0xFFFFFFFF) * 1_000 / 0x100000000)
}
pub fn utc_micros(&self) -> i64 {
let ntp_epoch_micros = self.microseconds() as i64;
if self.msb_set() {
ntp_epoch_micros - 2208988800000000i64
} else {
ntp_epoch_micros + 2085978496000000i64
}
}
pub fn utc_millis(&self) -> i64 {
let ntp_epoch_micros = self.milliseconds() as i64;
if self.msb_set() {
ntp_epoch_micros - 2208988800000i64
} else {
ntp_epoch_micros + 2085978496000i64
}
}
pub fn utc_seconds(&self) -> i64 {
let seconds = self.seconds() as i64;
if self.msb_set() {
seconds - 2208988800i64
} else {
seconds + 2085978496i64
}
}
}
#[cfg(feature = "chrono")]
impl From<Timestamp> for chrono::NaiveDateTime {
fn from(timestamp: Timestamp) -> Self {
chrono::DateTime::<chrono::Utc>::from(timestamp).naive_utc()
}
}
#[cfg(feature = "chrono")]
impl From<Timestamp> for chrono::DateTime<chrono::Utc> {
fn from(timestamp: Timestamp) -> Self {
chrono::DateTime::<chrono::Utc>::from_timestamp_micros(timestamp.utc_micros())
.expect("timestamp is out of range")
}
}