use super::*;
fn message_with_pointered_record(rtype: u16, rdata: &[u8]) -> std::vec::Vec<u8> {
let mut m = std::vec::Vec::new();
m.extend_from_slice(&[0u8; 12]); m.extend_from_slice(&[3, b's', b'v', b'c', 5, b'l', b'o', b'c', b'a', b'l', 0]);
debug_assert_eq!(m.len(), 23);
m.extend_from_slice(&[0xC0, 0x0C]); m.extend_from_slice(&rtype.to_be_bytes());
m.extend_from_slice(&1u16.to_be_bytes()); m.extend_from_slice(&120u32.to_be_bytes()); #[allow(clippy::cast_possible_truncation)]
m.extend_from_slice(&(rdata.len() as u16).to_be_bytes());
m.extend_from_slice(rdata);
m
}
const SVC_LOCAL_WIRE: &[u8] = &[3, b's', b'v', b'c', 5, b'l', b'o', b'c', b'a', b'l', 0];
#[test]
fn canonical_rdata_expands_srv_target() {
let rdata = [0, 10, 0, 20, 0x1F, 0x90, 0xC0, 0x0C];
let msg = message_with_pointered_record(33 , &rdata);
let (rec, _) = Ref::try_parse(&msg, 23).unwrap();
let out = rec.canonical_rdata().unwrap();
let mut expected = std::vec::Vec::from(&[0u8, 10, 0, 20, 0x1F, 0x90][..]);
expected.extend_from_slice(SVC_LOCAL_WIRE);
assert_eq!(out, expected, "SRV target must be decompressed in place");
}
#[test]
fn canonical_rdata_expands_cname_target() {
let rdata = [0xC0, 0x0C];
let msg = message_with_pointered_record(5 , &rdata);
let (rec, _) = Ref::try_parse(&msg, 23).unwrap();
assert_eq!(
rec.canonical_rdata().unwrap(),
std::vec::Vec::from(SVC_LOCAL_WIRE),
"CNAME target must be decompressed in place"
);
}
#[test]
fn canonical_rdata_expands_nsec_next_name() {
let rdata = [0xC0, 0x0C, 0x00, 0x01, 0x40];
let msg = message_with_pointered_record(47 , &rdata);
let (rec, _) = Ref::try_parse(&msg, 23).unwrap();
let out = rec.canonical_rdata().unwrap();
let mut expected = std::vec::Vec::from(SVC_LOCAL_WIRE);
expected.extend_from_slice(&[0x00, 0x01, 0x40]); assert_eq!(
out, expected,
"NSEC next_name must be decompressed, bitmap preserved"
);
}
#[test]
fn canonical_rdata_rejects_malformed_name() {
let rdata = [0xC0, 0xFF];
let msg = message_with_pointered_record(12 , &rdata);
let (rec, _) = Ref::try_parse(&msg, 23).unwrap();
assert!(
rec.canonical_rdata().is_err(),
"a record with an undecodable name must be rejected"
);
}
#[test]
fn canonical_rdata_validates_txt_segments() {
let malformed = [5u8, b'a', b'b']; let msg = message_with_pointered_record(16 , &malformed);
let (rec, _) = Ref::try_parse(&msg, 23).unwrap();
assert!(
rec.canonical_rdata().is_err(),
"a TXT record whose segment length overruns its RDATA must be rejected"
);
let valid = [3u8, b'k', b'e', b'y', 1, b'x']; let msg = message_with_pointered_record(16, &valid);
let (rec, _) = Ref::try_parse(&msg, 23).unwrap();
assert_eq!(
rec.canonical_rdata().unwrap(),
std::vec::Vec::from(&valid[..]),
"a valid multi-segment TXT must canonicalize to its verbatim segments"
);
let msg = message_with_pointered_record(16, &[]);
let (rec, _) = Ref::try_parse(&msg, 23).unwrap();
assert_eq!(
rec.canonical_rdata().unwrap(),
std::vec![0u8],
"an empty TXT must canonicalize to a single zero-length string (§6.1)"
);
}
#[test]
fn canonical_rdata_folds_case_but_preserved_form_does_not() {
let rdata = [4, b'I', b'n', b'S', b't', 0xC0, 0x0C];
let msg = message_with_pointered_record(12 , &rdata);
let (rec, _) = Ref::try_parse(&msg, 23).unwrap();
let mut preserved_expected = std::vec::Vec::from(&[4u8, b'I', b'n', b'S', b't'][..]);
preserved_expected.extend_from_slice(SVC_LOCAL_WIRE);
assert_eq!(rec.canonical_rdata().unwrap(), preserved_expected);
let mut folded_expected = std::vec::Vec::from(&[4u8, b'i', b'n', b's', b't'][..]);
folded_expected.extend_from_slice(SVC_LOCAL_WIRE);
assert_eq!(rec.canonical_rdata_folded().unwrap(), folded_expected);
}
#[test]
fn canonical_rdata_rejects_unhandled_name_bearing_type() {
let rdata = [0xC0, 0x0C];
let msg = message_with_pointered_record(2 , &rdata);
let (rec, _) = Ref::try_parse(&msg, 23).unwrap();
assert!(
matches!(
rec.canonical_rdata(),
Err(ParseError::UnsupportedNameBearingType(2))
),
"NS must be dropped as an unsupported name-bearing type"
);
let opaque = [0x01, 0x02, 0x03];
let msg2 = message_with_pointered_record(64, &opaque);
let (rec2, _) = Ref::try_parse(&msg2, 23).unwrap();
assert_eq!(
rec2.canonical_rdata().unwrap(),
std::vec::Vec::from(&opaque[..])
);
let msg3 = message_with_pointered_record(14 , &[0xC0, 0x0C]);
let (rec3, _) = Ref::try_parse(&msg3, 23).unwrap();
assert!(matches!(
rec3.canonical_rdata(),
Err(ParseError::UnsupportedNameBearingType(14))
));
}
#[test]
fn canonical_rdata_rejects_overlong_encoded_name() {
let mut rdata = std::vec::Vec::new();
for _ in 0..128 {
rdata.push(1u8);
rdata.push(b'a');
}
rdata.push(0); let msg = message_with_pointered_record(12 , &rdata);
let (rec, _) = Ref::try_parse(&msg, 23).unwrap();
assert!(
rec.canonical_rdata().is_err(),
"an over-length encoded name must be rejected"
);
}
#[test]
fn rdata_view_propagates_malformed_cname() {
let mut rdata = std::vec::Vec::from(SVC_LOCAL_WIRE);
rdata.push(0xFF); let msg = message_with_pointered_record(5 , &rdata);
let (rec, _) = Ref::try_parse(&msg, 23).unwrap();
assert!(
matches!(rec.rdata_view(), Err(ParseError::BufferTooShort(_))),
"a CNAME whose name does not exactly fill RDLENGTH must be rejected"
);
assert!(rec.canonical_rdata().is_err());
}
#[test]
fn rdata_view_propagates_malformed_nsec() {
let mut m = std::vec::Vec::new();
m.extend_from_slice(&[0u8; 12]); m.extend_from_slice(SVC_LOCAL_WIRE); debug_assert_eq!(m.len(), 23);
m.extend_from_slice(&[0xC0, 0x0C]); m.extend_from_slice(&47u16.to_be_bytes()); m.extend_from_slice(&1u16.to_be_bytes()); m.extend_from_slice(&120u32.to_be_bytes()); m.extend_from_slice(&1u16.to_be_bytes()); m.extend_from_slice(&[0xC0, 0x0C]); let (rec, _) = Ref::try_parse(&m, 23).unwrap();
assert!(
matches!(rec.rdata_view(), Err(ParseError::BufferTooShort(_))),
"an NSEC whose next_name overruns RDLENGTH must be rejected"
);
assert!(rec.canonical_rdata().is_err());
}
#[test]
fn try_parse_rejects_message_too_short_for_fixed_header() {
let msg: [u8; 12] = [1, b'x', 5, b'l', b'o', b'c', b'a', b'l', 0, 0, 1, 2];
assert!(Ref::try_parse(&msg, 0).is_err());
}
#[test]
fn try_parse_rejects_rdlength_overrun() {
let msg: [u8; 19] = [
1, b'x', 5, b'l', b'o', b'c', b'a', b'l', 0, 0, 12, 0, 1, 0, 0, 0, 120, 0, 100, ];
assert!(matches!(
Ref::try_parse(&msg, 0),
Err(ParseError::RdlengthOverrun(_))
));
}