mdns_proto/wire/record/ptr.rs
1//! PTR record (domain name pointer, RFC 1035 §3.3.12).
2
3use crate::{error::ParseError, wire::NameRef};
4
5/// Parsed PTR record rdata.
6#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
7pub struct Ptr<'a> {
8 target: NameRef<'a>,
9}
10
11impl<'a> Ptr<'a> {
12 /// Parses a PTR record's rdata. `message` is the full DNS message so
13 /// compression pointers can resolve; `rdata_offset` is the start of the
14 /// rdata bytes within it; `rdata_len` is the declared RDLENGTH.
15 ///
16 /// the inline portion of the encoded name (label bytes plus any
17 /// initial compression pointer) MUST fit within the declared `rdata_len`.
18 /// Without this check a record advertising a short `rdlength` could let
19 /// inline labels run past the declared boundary and consume bytes from
20 /// the next record, corrupting downstream conflict-routing,
21 /// known-answer suppression, and cache decisions.
22 pub fn try_from_message(
23 message: &'a [u8],
24 rdata_offset: usize,
25 rdata_len: usize,
26 ) -> Result<Self, ParseError> {
27 // NameRef::try_parse returns (NameRef, consumed_bytes).
28 // PTR rdata is EXACTLY one domain name — require the
29 // consumed bytes to equal rdata_len. Accepting `consumed <
30 // rdata_len` would let a peer append trailing garbage inside the
31 // declared rdlength and still see the record canonicalize as a
32 // valid KAS hint, suppressing legitimate outgoing answers.
33 let (target, consumed) = NameRef::try_parse(message, rdata_offset)?;
34 if consumed != rdata_len {
35 return Err(ParseError::BufferTooShort(
36 crate::error::BufferTooShortDetail::new(consumed, rdata_offset, rdata_len),
37 ));
38 }
39 Ok(Self { target })
40 }
41
42 /// Returns the target name.
43 #[inline(always)]
44 pub const fn target(&self) -> &NameRef<'a> {
45 &self.target
46 }
47}
48
49#[cfg(test)]
50#[allow(clippy::unwrap_used)]
51mod tests;