From 594ed337f82259776e078b548a033988b839d4c1 Mon Sep 17 00:00:00 2001 From: LinkTed Date: Sat, 20 Dec 2025 15:18:57 +0000 Subject: [PATCH 1/6] feat(decode): extend impl_decode macro The macro now allows specifying the function name to be generated and implemented. --- src/decode/macros.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/decode/macros.rs b/src/decode/macros.rs index a3f472e..645890c 100644 --- a/src/decode/macros.rs +++ b/src/decode/macros.rs @@ -1,7 +1,10 @@ macro_rules! impl_decode { ($i:path, $m:ident) => { + impl_decode!($i, $m, decode); + }; + ($i:path, $m:ident, $n:ident) => { impl $i { - pub fn decode(bytes: bytes::Bytes) -> crate::DecodeResult<$i> { + pub fn $n(bytes: bytes::Bytes) -> crate::DecodeResult<$i> { let mut decoder = crate::decode::Decoder::main(bytes); decoder.$m() } From 90d19168baeb1d6d5e75a1b2eed2a308b85084a1 Mon Sep 17 00:00:00 2001 From: LinkTed Date: Sat, 20 Dec 2025 15:20:19 +0000 Subject: [PATCH 2/6] feat(encode): extend impl_encode macro The macro now allows specifying the function name to be called. --- src/encode/macros.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/encode/macros.rs b/src/encode/macros.rs index 78d414d..3c29fb0 100644 --- a/src/encode/macros.rs +++ b/src/encode/macros.rs @@ -12,8 +12,11 @@ macro_rules! impl_encode_without_result { macro_rules! impl_encode { ($i:path, $m:ident) => { + impl_encode!($i, $m, encode); + }; + ($i:path, $m:ident, $n:ident) => { impl $i { - pub fn encode(&self) -> crate::EncodeResult { + pub fn $n(&self) -> crate::EncodeResult { let mut encoder = crate::encode::Encoder::default(); encoder.$m(self)?; Ok(encoder.bytes) From 6224796c29c9b8b968ca8d708ea3062adaf5252d Mon Sep 17 00:00:00 2001 From: LinkTed Date: Sat, 20 Dec 2025 15:22:15 +0000 Subject: [PATCH 3/6] feat(encode): add domain_name_without_compression function This function encodes a domain name without applying any label compression. --- src/encode/domain_name.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/encode/domain_name.rs b/src/encode/domain_name.rs index 214f413..27785c2 100644 --- a/src/encode/domain_name.rs +++ b/src/encode/domain_name.rs @@ -71,6 +71,17 @@ impl Encoder { self.merge_domain_name_index(domain_name_index, 0)?; Ok(()) } + + pub(super) fn domain_name_without_compression( + &mut self, + domain_name: &DomainName, + ) -> EncodeResult<()> { + for (label, _) in domain_name.iter() { + self.label(&label)?; + } + self.string_with_len("")?; + Ok(()) + } } impl DomainName { @@ -98,3 +109,8 @@ impl<'a> Iterator for DomainNameIter<'a> { } } impl_encode!(DomainName, domain_name); +impl_encode!( + DomainName, + domain_name_without_compression, + encode_without_compression +); From b1eb80d66f2bd6f0f71082fb2fb8d594e84abd28 Mon Sep 17 00:00:00 2001 From: LinkTed Date: Sat, 20 Dec 2025 15:25:34 +0000 Subject: [PATCH 4/6] feat(decode): add domain_name_without_compression function This function decodes a domain name while explicitly disallowing label compression. --- src/decode/domain_name.rs | 21 +++++++++++++++++++++ src/decode/error.rs | 2 ++ 2 files changed, 23 insertions(+) diff --git a/src/decode/domain_name.rs b/src/decode/domain_name.rs index e73ef59..0720925 100644 --- a/src/decode/domain_name.rs +++ b/src/decode/domain_name.rs @@ -82,6 +82,27 @@ impl<'a, 'b: 'a> Decoder<'a, 'b> { } } } + + pub(super) fn domain_name_without_compression(&mut self) -> DecodeResult { + let mut domain_name = DomainName::default(); + + loop { + match self.domain_name_length()? { + DomainNameLength::Compressed(offset) => { + return Err(DecodeError::DomainNameCompressed(offset)) + } + DomainNameLength::Label(0) => return Ok(domain_name), + DomainNameLength::Label(length) => { + self.domain_name_label(&mut domain_name, length)? + } + } + } + } } impl_decode!(DomainName, domain_name); +impl_decode!( + DomainName, + domain_name_without_compression, + decode_without_compression +); diff --git a/src/decode/error.rs b/src/decode/error.rs index dcfe06b..e27403f 100644 --- a/src/decode/error.rs +++ b/src/decode/error.rs @@ -105,4 +105,6 @@ pub enum DecodeError { TagError(#[from] TagError), #[error("ECH length mismatch. Expected {0} got {1}")] ECHLengthMismatch(usize, usize), + #[error("The domain name is compressed: {0}")] + DomainNameCompressed(u16), } From 37d562708037c07401bf37ed562b4f0bef32b3b9 Mon Sep 17 00:00:00 2001 From: LinkTed Date: Sat, 20 Dec 2025 17:03:19 +0000 Subject: [PATCH 5/6] test(encode): add tests for domain_name_without_compression --- src/encode/domain_name.rs | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/encode/domain_name.rs b/src/encode/domain_name.rs index 27785c2..d4ff39c 100644 --- a/src/encode/domain_name.rs +++ b/src/encode/domain_name.rs @@ -114,3 +114,32 @@ impl_encode!( domain_name_without_compression, encode_without_compression ); + +#[test] +fn domain_name_without_compression_1() { + let domain_name: DomainName = "example.org.".parse().unwrap(); + let mut encoder = Encoder::default(); + encoder + .domain_name_without_compression(&domain_name) + .unwrap(); + encoder + .domain_name_without_compression(&domain_name) + .unwrap(); + assert_eq!( + encoder.bytes, + &b"\x07example\x03org\0\x07example\x03org\0"[..] + ); +} + +#[test] +fn domain_name_without_compression_2() { + let domain_name = DomainName::default(); + let mut encoder = Encoder::default(); + encoder + .domain_name_without_compression(&domain_name) + .unwrap(); + encoder + .domain_name_without_compression(&domain_name) + .unwrap(); + assert_eq!(encoder.bytes, &b"\0\0"[..]); +} From f064b72b71253058c0ba8038d7cc6a62cd4b2b8c Mon Sep 17 00:00:00 2001 From: LinkTed Date: Sat, 20 Dec 2025 17:03:44 +0000 Subject: [PATCH 6/6] test(decode): add tests for domain_name_without_compression --- src/decode/domain_name.rs | 55 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/src/decode/domain_name.rs b/src/decode/domain_name.rs index 0720925..a57fe22 100644 --- a/src/decode/domain_name.rs +++ b/src/decode/domain_name.rs @@ -106,3 +106,58 @@ impl_decode!( domain_name_without_compression, decode_without_compression ); + +#[test] +fn domain_name_without_compression_1() { + let domain_name: DomainName = "example.org.".parse().unwrap(); + let mut decoder = Decoder::main(bytes::Bytes::copy_from_slice( + &b"\x07example\x03org\0\x07example\x03org\0"[..], + )); + assert_eq!( + decoder.domain_name_without_compression().unwrap(), + domain_name + ); + assert_eq!( + decoder.domain_name_without_compression().unwrap(), + domain_name + ); + assert!(decoder.is_finished().unwrap()); +} + +#[test] +fn domain_name_without_compression_2() { + let domain_name: DomainName = "example.org.".parse().unwrap(); + let mut decoder = Decoder::main(bytes::Bytes::copy_from_slice( + &b"\x07example\x03org\0\x07example\x03org\0"[..], + )); + assert_eq!( + decoder.domain_name_without_compression().unwrap(), + domain_name + ); + assert_eq!( + decoder.domain_name_without_compression().unwrap(), + domain_name + ); + assert!(decoder.is_finished().unwrap()); +} + +#[test] +fn domain_name_without_compression_3() { + let domain_name: DomainName = "example.org.".parse().unwrap(); + let mut decoder = Decoder::main(bytes::Bytes::copy_from_slice( + &b"\x07example\x03org\0\x07example\x03org\0\xc0\r"[..], + )); + assert_eq!( + decoder.domain_name_without_compression().unwrap(), + domain_name + ); + assert_eq!( + decoder.domain_name_without_compression().unwrap(), + domain_name + ); + assert_eq!( + decoder.domain_name_without_compression(), + Err(DecodeError::DomainNameCompressed(13)) + ); + assert!(decoder.is_finished().unwrap()); +}