use std::net::{Ipv4Addr, Ipv6Addr};
use crafter::prelude::*;
#[test]
fn ntp_header_golden_bytes_compile_decode_and_roundtrip() -> crafter::Result<()> {
let ntp = Ntp::client()
.poll(6)
.precision(-20)
.root_delay_raw(0x0001_0000)
.root_dispersion_raw(0x0002_0000)
.reference_id(*b"LOCL")
.transmit_timestamp(NtpTimestamp::from_parts(0xecc0_0000, 0x1234_5678));
let compiled = Packet::from_layer(ntp).compile()?;
let bytes = compiled.as_bytes();
assert_eq!(bytes.len(), NTP_FIXED_HEADER_LEN);
assert_eq!(bytes[0], 0x23);
assert_eq!(bytes[1], 0);
assert_eq!(bytes[2], 6);
assert_eq!(bytes[3], 0xec);
assert_eq!(&bytes[4..8], &0x0001_0000u32.to_be_bytes());
assert_eq!(&bytes[8..12], &0x0002_0000u32.to_be_bytes());
assert_eq!(&bytes[12..16], b"LOCL");
assert_eq!(&bytes[40..48], &0xecc0_0000_1234_5678u64.to_be_bytes());
let decoded = Ntp::decode(bytes)?;
assert_eq!(decoded.first_octet_value(), 0x23);
assert_eq!(decoded.poll_value(), 6);
assert_eq!(decoded.precision_value(), -20);
assert_eq!(decoded.reference_id_value().bytes(), *b"LOCL");
assert_eq!(
decoded.transmit_timestamp_value().raw(),
0xecc0_0000_1234_5678
);
assert_eq!(Packet::from_layer(decoded).compile()?.as_bytes(), bytes);
Ok(())
}
#[test]
fn ntp_extension_fields_and_legacy_mac_roundtrip() -> crafter::Result<()> {
let ntp = Ntp::client()
.extension_field(NtpExtensionField::nts_cookie([1, 2, 3]))
.legacy_mac(NtpLegacyMac::from_key_id_and_digest(
0x0102_0304,
[0xaa; 16],
));
let compiled = Packet::from_layer(ntp).compile()?;
let bytes = compiled.as_bytes();
assert!(looks_like_ntp_payload(bytes));
let decoded = Ntp::decode(bytes)?;
assert_eq!(decoded.extension_fields_value().len(), 1);
assert_eq!(decoded.extension_fields_value()[0].field_type(), 0x0204);
assert_eq!(
decoded.extension_fields_value()[0].declared_length_value(),
Some(16)
);
assert_eq!(
decoded.legacy_mac_value().and_then(NtpLegacyMac::key_id),
Some(0x0102_0304)
);
assert_eq!(Packet::from_layer(decoded).compile()?.as_bytes(), bytes);
Ok(())
}
#[test]
fn ntp_unknown_extensions_decode_as_typed_fields_not_raw() -> crafter::Result<()> {
let value = vec![0xde, 0xad, 0xbe, 0xef];
let padding = (0u8..20).map(|offset| 0xa0u8 + offset).collect::<Vec<_>>();
let expected_body = value
.iter()
.chain(padding.iter())
.copied()
.collect::<Vec<_>>();
let unknown_extension = NtpExtensionField::unknown(0x2222, value)
.padding(padding)
.declared_length(28);
let ntp_payload = Packet::from_layer(Ntp::client().extension_field(unknown_extension))
.compile()?
.into_bytes();
assert!(looks_like_ntp_payload(&ntp_payload));
let packet = Ipv4::new()
.src(Ipv4Addr::new(192, 0, 2, 10))
.dst(Ipv4Addr::new(198, 51, 100, 20))
/ Udp::new().source_port(49_152).destination_port(NTP_PORT)
/ Raw::from_bytes(ntp_payload.clone());
let decoded = Packet::decode_from_l3(NetworkLayer::Ipv4, packet.compile()?.as_bytes())?;
assert!(decoded.layer::<Raw>().is_none());
let decoded_ntp = decoded.layer::<Ntp>().expect("decoded NTP layer");
assert_eq!(decoded_ntp.extension_fields_value().len(), 1);
let field = &decoded_ntp.extension_fields_value()[0];
assert_eq!(field.field_type(), 0x2222);
assert_eq!(field.declared_length_value(), Some(28));
assert_eq!(field.value(), expected_body.as_slice());
assert_eq!(field.padding_value(), None);
assert_eq!(field.label(), "extension-field-0x2222");
assert_eq!(field.summary_label(), "extension-field-0x2222");
assert!(field.is_unknown_or_unassigned());
assert_eq!(field.registry_meta().status, NtpRegistryStatus::Unassigned);
let summary = decoded.summary();
assert!(summary.contains("extensions=1"), "{summary}");
assert!(summary.contains("tail=extensions"), "{summary}");
let show = decoded.show();
assert!(
show.contains(
"extension: type=0x2222 label=extension-field-0x2222 status=unassigned declared_len=28 encoded_len=28 value_len=24 category=unknown-or-unassigned"
),
"{show}"
);
assert!(!show.contains("deadbeef"), "{show}");
let recompiled = Packet::from_layer(decoded_ntp.clone()).compile()?;
assert_eq!(recompiled.as_bytes(), ntp_payload.as_slice());
Ok(())
}
#[test]
fn ntp_malformed_extension_length_is_structured_error() {
let mut bytes = Packet::from_layer(Ntp::client())
.compile()
.unwrap()
.into_bytes();
bytes.extend_from_slice(&0x0204u16.to_be_bytes());
bytes.extend_from_slice(&12u16.to_be_bytes());
bytes.extend_from_slice(&[0; 8]);
let err = Ntp::decode(&bytes).unwrap_err();
assert_eq!(
err,
CrafterError::invalid_field_value(
"ntp.extension.length",
"extension length must be at least 16 bytes",
)
);
}
#[test]
fn ntp_final_extension_without_mac_uses_28_byte_rule() -> crafter::Result<()> {
let mut payload = Packet::from_layer(Ntp::client()).compile()?.into_bytes();
payload.extend_from_slice(&0x0204u16.to_be_bytes());
payload.extend_from_slice(&16u16.to_be_bytes());
payload.extend_from_slice(&[0; 12]);
let err = Ntp::decode(&payload).unwrap_err();
assert_eq!(
err,
CrafterError::invalid_field_value(
"ntp.extension.length",
"final extension without MAC must be at least 28 bytes",
)
);
assert!(!looks_like_ntp_payload(&payload));
let packet = Ipv4::new()
.src(Ipv4Addr::new(192, 0, 2, 10))
.dst(Ipv4Addr::new(198, 51, 100, 20))
/ Udp::new().source_port(NTP_PORT).destination_port(NTP_PORT)
/ Raw::from_bytes(payload.clone());
let decoded = Packet::decode_from_l3(NetworkLayer::Ipv4, packet.compile()?.as_bytes())?;
assert!(decoded.layer::<Ntp>().is_none());
assert_eq!(
decoded.layer::<Raw>().unwrap().as_bytes(),
payload.as_slice()
);
Ok(())
}
#[test]
fn ntp_udp_ipv4_and_ipv6_decode_through_registry() -> crafter::Result<()> {
let ipv4 = ntp_ipv4_client_request(
Ipv4Addr::new(192, 0, 2, 10),
Ipv4Addr::new(198, 51, 100, 20),
);
let decoded = Packet::decode_from_l3(NetworkLayer::Ipv4, ipv4.compile()?.as_bytes())?;
assert!(decoded.layer::<Udp>().is_some());
assert!(decoded.layer::<Ntp>().is_some());
assert!(decoded.summary().contains("Ntp("));
let ipv6 = ntp_ipv6_client_request(
Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1),
Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 2),
);
let decoded = Packet::decode_from_l3(NetworkLayer::Ipv6, ipv6.compile()?.as_bytes())?;
assert!(decoded.layer::<Udp>().is_some());
assert!(decoded.layer::<Ntp>().is_some());
Ok(())
}
#[test]
fn ntp_udp_123_non_ntp_payload_stays_raw() -> crafter::Result<()> {
let packet = Ipv4::new()
.src(Ipv4Addr::new(192, 0, 2, 10))
.dst(Ipv4Addr::new(198, 51, 100, 20))
/ Udp::new().source_port(NTP_PORT).destination_port(NTP_PORT)
/ Raw::from_bytes([1, 2, 3, 4]);
let decoded = Packet::decode_from_l3(NetworkLayer::Ipv4, packet.compile()?.as_bytes())?;
assert!(decoded.layer::<Ntp>().is_none());
assert_eq!(decoded.layer::<Raw>().unwrap().as_bytes(), &[1, 2, 3, 4]);
Ok(())
}
#[test]
fn ntp_public_api_is_available_from_prelude() {
let packet = Ntp::client().transmit_timestamp(NtpTimestamp::from_raw(1));
assert_eq!(packet.mode_value(), NtpMode::Client);
assert_eq!(NTP_PORT, 123);
}