Skip to main content

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;