use std::net::Ipv4Addr;
use chrono::{DateTime, Utc};
use crate::{checks::application::ntp::*, errors::application::ntp::NtpPacketParseError};
#[cfg_attr(doc, aquamarine::aquamarine)]
#[derive(Debug)]
pub struct NtpPacket {
pub flags: (u8, u8, u8),
pub stratum: u8,
pub poll: u8,
pub precision: i8,
pub root_delay: u32,
pub root_dispersion: u32,
pub reference_id: Refid,
pub reference_timestamp: DateTime<Utc>,
pub originate_timestamp: DateTime<Utc>,
pub receive_timestamp: DateTime<Utc>,
pub transmit_timestamp: DateTime<Utc>,
}
#[derive(Debug, PartialEq)]
pub enum Refid {
Ipv4(Ipv4Addr),
KissCode(String),
ClockSource(String),
}
impl TryFrom<&[u8]> for NtpPacket {
type Error = NtpPacketParseError;
fn try_from(payload: &[u8]) -> Result<Self, Self::Error> {
validate_ntp_packet_length(payload)?;
let flags = extract_flags(&payload[0])?;
let stratum = extract_stratum(&payload[1])?;
let poll = extract_poll(&payload[2])?;
let precision = extract_precision(&payload[3])?;
let root_delay = extract_root_delay(&payload[4..8])?; let root_dispersion = extract_root_dispersion(&payload[8..12])?;
let reference_id = extract_reference_id(stratum, &payload[12..16])?;
let reference_timestamp = extract_timestamp(&payload[16..24])?;
let originate_timestamp = extract_timestamp(&payload[24..32])?;
let receive_timestamp = extract_timestamp(&payload[32..40])?;
let transmit_timestamp = extract_timestamp(&payload[40..48])?;
validate_datetime_ordering(
reference_timestamp,
originate_timestamp,
receive_timestamp,
transmit_timestamp,
)?;
Ok(NtpPacket {
flags,
stratum,
poll,
precision,
root_delay,
root_dispersion,
reference_id,
reference_timestamp,
originate_timestamp,
receive_timestamp,
transmit_timestamp,
})
}
}
#[cfg(test)]
mod tests {
use crate::errors::application::ntp::NtpPacketParseError;
use crate::parse::application::NtpPacket;
use crate::parse::application::protocols::ntp::*;
#[test]
fn test_valid_ntp_packet() {
let binding = hex::decode(
"d9000afa000000000001029000000000000000000000000000000000000000000000000000000000c50204eceed33c52",
);
let result = NtpPacket::try_from(binding.expect("REASON").as_slice())
.expect("Expected a valid NTP packet");
assert_eq!(result.flags, (3, 3, 1));
assert_eq!(result.stratum, 0x00);
assert_eq!(result.poll, 10);
assert_eq!(result.precision, -6);
assert_eq!(result.root_delay, 0x00000000);
assert_eq!(result.root_dispersion, 66192);
assert_eq!(result.reference_id, Refid::KissCode("NULL".to_string()));
}
#[test]
fn test_invalid_ntp_packet_length() {
let short_payload = vec![0x1B, 0x00, 0x04];
let result = NtpPacket::try_from(short_payload.as_slice());
assert!(matches!(
result,
Err(NtpPacketParseError::InvalidPacketLength)
));
}
#[test]
fn test_invalid_ntp_version() {
let invalid_version_payload = vec![
0x7B, 0x00, 0x04, 0xFA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4E, 0x49,
0x4E, 0x00, 0xDC, 0xC0, 0x00, 0x00, 0xE1, 0x44, 0xC6, 0x71, 0xDC, 0xC0, 0x00, 0x00,
0xE1, 0x44, 0xC6, 0x71, 0xDC, 0xC0, 0x00, 0x00, 0xE1, 0x44, 0xC6, 0x71, 0xDC, 0xC0,
0x00, 0x00, 0xE1, 0x44, 0xC6, 0x71,
];
let result = NtpPacket::try_from(invalid_version_payload.as_slice());
assert!(matches!(
result,
Err(NtpPacketParseError::InvalidVersion { .. })
));
}
#[test]
fn test_invalid_ntp_mode() {
let invalid_mode_payload = vec![
0x18, 0x00, 0x04, 0xFA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4E, 0x49,
0x4E, 0x00, 0xDC, 0xC0, 0x00, 0x00, 0xE1, 0x44, 0xC6, 0x71, 0xDC, 0xC0, 0x00, 0x00,
0xE1, 0x44, 0xC6, 0x71, 0xDC, 0xC0, 0x00, 0x00, 0xE1, 0x44, 0xC6, 0x71, 0xDC, 0xC0,
0x00, 0x00, 0xE1, 0x44, 0xC6, 0x71,
];
let result = NtpPacket::try_from(invalid_mode_payload.as_slice());
assert!(matches!(
result,
Err(NtpPacketParseError::InvalidMode { .. })
));
}
#[test]
fn test_check_ntp_packet() {
let binding = hex::decode(
"d9000afa000000000001029000000000000000000000000000000000000000000000000000000000c50204eceed33c52",
);
let result = NtpPacket::try_from(binding.expect("REASON").as_slice());
assert!((result.is_ok()));
let short_ntp_packet = vec![0x1B, 0x00, 0x04];
let result = NtpPacket::try_from(short_ntp_packet.as_slice());
assert!(matches!(
result,
Err(NtpPacketParseError::InvalidPacketLength)
));
let invalid_version_packet = vec![
0x7B, 0x00, 0x04, 0xFA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4E, 0x49,
0x4E, 0x00, 0xDC, 0xC0, 0x00, 0x00, 0xE1, 0x44, 0xC6, 0x71, 0xDC, 0xC0, 0x00, 0x00,
0xE1, 0x44, 0xC6, 0x71, 0xDC, 0xC0, 0x00, 0x00, 0xE1, 0x44, 0xC6, 0x71, 0xDC, 0xC0,
0x00, 0x00, 0xE1, 0x44, 0xC6, 0x71,
];
let result = NtpPacket::try_from(invalid_version_packet.as_slice());
assert!(matches!(
result,
Err(NtpPacketParseError::InvalidVersion { version: _ })
));
}
}