use super::{CompressionTable, DEFAULT_COMPRESSION_TABLE, MessageBuilder};
use crate::{
Name,
error::EncodeError,
wire::{Header, MessageReader, ResourceClass, ResourceType},
};
#[test]
fn builds_minimal_query() {
let mut buf = [0u8; 512];
let name = Name::try_from_str("foo.local.").unwrap();
let header = Header::new().with_id(0x1234);
let mut b: MessageBuilder<'_, DEFAULT_COMPRESSION_TABLE> =
MessageBuilder::try_new(&mut buf, header).unwrap();
b.push_question(&name, ResourceType::A, ResourceClass::In, false)
.unwrap();
let n = b.finish().unwrap();
let msg = buf.get(..n).unwrap();
let reader = MessageReader::try_parse(msg).unwrap();
assert_eq!(reader.header().id(), 0x1234);
assert_eq!(reader.header().question_count(), 1);
let q = reader.questions().next().unwrap().unwrap();
assert!(q.qtype().is_a());
assert!(q.qclass().is_in());
}
#[test]
fn builds_ptr_txt_authority_and_nsec_additional() {
let mut buf = [0u8; 512];
let stype = Name::try_from_str("_http._tcp.local.").unwrap();
let inst = Name::try_from_str("Dev._http._tcp.local.").unwrap();
let mut b: MessageBuilder<'_, DEFAULT_COMPRESSION_TABLE> =
MessageBuilder::try_new(&mut buf, Header::new()).unwrap();
b.push_ptr_authority(&stype, 120, &inst).unwrap();
b.push_txt_authority(&inst, 120, [b"path=/".as_slice()])
.unwrap();
b.push_nsec_additional(&inst, 120, &[1, 33, 300], true)
.unwrap();
let n = b.finish().unwrap();
let reader = MessageReader::try_parse(buf.get(..n).unwrap()).unwrap();
assert_eq!(reader.header().authority_count(), 2); assert_eq!(reader.header().additional_count(), 1); }
#[test]
fn push_txt_authority_rejects_oversized_segment() {
let mut buf = [0u8; 512];
let name = Name::try_from_str("x.local.").unwrap();
let mut b: MessageBuilder<'_, DEFAULT_COMPRESSION_TABLE> =
MessageBuilder::try_new(&mut buf, Header::new()).unwrap();
let big = [b'a'; 256];
assert!(b.push_txt_authority(&name, 120, [big.as_slice()]).is_err());
}
#[test]
fn push_txt_authority_empty_writes_single_zero_segment() {
let mut buf = [0u8; 512];
let name = Name::try_from_str("x.local.").unwrap();
let mut b: MessageBuilder<'_, DEFAULT_COMPRESSION_TABLE> =
MessageBuilder::try_new(&mut buf, Header::new()).unwrap();
let empty: [&[u8]; 0] = [];
b.push_txt_authority(&name, 120, empty).unwrap();
let n = b.finish().unwrap();
let reader = MessageReader::try_parse(buf.get(..n).unwrap()).unwrap();
assert_eq!(reader.header().authority_count(), 1);
}
#[test]
fn compression_table_default_equals_new() {
let from_default: CompressionTable<DEFAULT_COMPRESSION_TABLE> = CompressionTable::default();
assert_eq!(from_default.lookup(0), None);
assert_eq!(from_default.lookup(0xdead_beef), None);
let mut t: CompressionTable<4> = CompressionTable::default();
assert_eq!(t.lookup(42), None);
t.insert(42, 12);
assert_eq!(t.lookup(42), Some(12));
}
#[test]
fn write_name_without_trailing_dot_canonicalizes_like_dotted() {
let dotted = Name::try_from_str("foo.local.").unwrap();
let bare = Name::try_from_str("foo.local").unwrap();
assert_eq!(bare.as_str(), "foo.local");
let mut buf_dotted = [0u8; 64];
let mut bd: MessageBuilder<'_, DEFAULT_COMPRESSION_TABLE> =
MessageBuilder::try_new(&mut buf_dotted, Header::new()).unwrap();
bd.push_question(&dotted, ResourceType::A, ResourceClass::In, false)
.unwrap();
let nd = bd.finish().unwrap();
let mut buf_bare = [0u8; 64];
let mut bb: MessageBuilder<'_, DEFAULT_COMPRESSION_TABLE> =
MessageBuilder::try_new(&mut buf_bare, Header::new()).unwrap();
bb.push_question(&bare, ResourceType::A, ResourceClass::In, false)
.unwrap();
let nb = bb.finish().unwrap();
assert_eq!(buf_dotted.get(..nd), buf_bare.get(..nb));
let reader = MessageReader::try_parse(buf_bare.get(..nb).unwrap()).unwrap();
assert_eq!(reader.header().question_count(), 1);
let q = reader.questions().next().unwrap().unwrap();
assert!(q.qtype().is_a());
}
#[test]
fn write_name_empty_emits_root_label() {
let empty = Name::try_from_str("").unwrap();
assert!(empty.as_str().is_empty());
let owner = Name::try_from_str("_svc._tcp.local.").unwrap();
let mut buf = [0u8; 128];
let mut b: MessageBuilder<'_, DEFAULT_COMPRESSION_TABLE> =
MessageBuilder::try_new(&mut buf, Header::new()).unwrap();
b.push_ptr_answer(&owner, 120, &empty).unwrap();
let n = b.finish().unwrap();
let reader = MessageReader::try_parse(buf.get(..n).unwrap()).unwrap();
assert_eq!(reader.header().answer_count(), 1);
let rec = reader.answers().next().unwrap().unwrap();
assert!(rec.rtype().is_ptr());
assert_eq!(rec.rdata(), &[0u8]);
}
#[test]
fn push_nsec_additional_without_cache_flush_sets_bitmap() {
let name = Name::try_from_str("host.local.").unwrap();
let mut buf = [0u8; 256];
let mut b: MessageBuilder<'_, DEFAULT_COMPRESSION_TABLE> =
MessageBuilder::try_new(&mut buf, Header::new()).unwrap();
b.push_nsec_additional(&name, 120, &[1, 28, 256], false)
.unwrap();
let n = b.finish().unwrap();
let reader = MessageReader::try_parse(buf.get(..n).unwrap()).unwrap();
assert_eq!(reader.header().additional_count(), 1);
let rec = reader.additional().next().unwrap().unwrap();
assert!(rec.rtype().is_nsec());
assert!(rec.rclass().is_in());
assert!(!rec.cache_flush());
let rdata = rec.rdata();
assert!(
rdata.len() >= 2 + 4,
"rdata too short for window+bitmap: {rdata:?}"
);
}
#[test]
fn push_a_answer_into_undersized_buffer_errors() {
let name = Name::try_from_str("a.local.").unwrap();
let mut buf = [0u8; 12];
let mut b: MessageBuilder<'_, DEFAULT_COMPRESSION_TABLE> =
MessageBuilder::try_new(&mut buf, Header::new()).unwrap();
let err = b
.push_a_answer(&name, 120, core::net::Ipv4Addr::LOCALHOST, true)
.unwrap_err();
assert!(matches!(err, EncodeError::BufferTooSmall(_)));
assert!(err.is_buffer_too_small());
}
#[test]
fn push_srv_answer_truncated_rdata_errors() {
let name = Name::try_from_str("s.local.").unwrap();
let target = Name::try_from_str("t.local.").unwrap();
let mut buf = [0u8; 12 + 19];
let mut b: MessageBuilder<'_, DEFAULT_COMPRESSION_TABLE> =
MessageBuilder::try_new(&mut buf, Header::new()).unwrap();
let err = b
.push_srv_answer(&name, 120, 0, 0, 8080, &target, true)
.unwrap_err();
assert!(err.is_buffer_too_small());
}
#[test]
fn push_nsec_additional_into_undersized_buffer_errors() {
let name = Name::try_from_str("host.local.").unwrap();
let mut buf = [0u8; 12];
let mut b: MessageBuilder<'_, DEFAULT_COMPRESSION_TABLE> =
MessageBuilder::try_new(&mut buf, Header::new()).unwrap();
let err = b.push_nsec_additional(&name, 120, &[1], true).unwrap_err();
assert!(err.is_buffer_too_small());
}
#[test]
fn finish_propagates_record_buffer_error_late() {
let name = Name::try_from_str("a.local.").unwrap();
let mut buf = [0u8; 14]; let mut b: MessageBuilder<'_, DEFAULT_COMPRESSION_TABLE> =
MessageBuilder::try_new(&mut buf, Header::new()).unwrap();
assert!(
b.push_a_answer(&name, 120, core::net::Ipv4Addr::LOCALHOST, false)
.is_err()
);
let n = b.finish().unwrap();
assert_eq!(n, 14); }