Skip to main content

mdns_proto/wire/
resource_type.rs

1//! DNS resource record types relevant to mDNS (RFC 1035 §3.2.2 + RFC 6762).
2
3use derive_more::{Display, IsVariant, TryUnwrap, Unwrap};
4
5/// Resource record type code.
6#[derive(
7  Debug, Display, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd, IsVariant, Unwrap, TryUnwrap,
8)]
9#[display("{}", self.as_str())]
10#[non_exhaustive]
11// The `AAAA` variant keeps the canonical DNS record-type spelling.
12#[allow(clippy::upper_case_acronyms)]
13pub enum ResourceType {
14  /// IPv4 address (`1`).
15  A,
16  /// IPv6 address (`28`).
17  AAAA,
18  /// Domain name pointer (`12`).
19  Ptr,
20  /// Server location (`33`).
21  Srv,
22  /// Text record (`16`).
23  Txt,
24  /// Next secure (`47`, used for negative responses in RFC 6762 §6.1).
25  Nsec,
26  /// Host info (`13`, rarely used).
27  Hinfo,
28  /// Canonical name alias (`5`).
29  Cname,
30  /// Wildcard query type (`255`).
31  Any,
32  /// Lossless escape for unknown rtypes.
33  Unknown(u16),
34}
35
36impl ResourceType {
37  /// Canonical lowercase slug for this resource type.
38  pub const fn as_str(&self) -> &'static str {
39    match self {
40      Self::A => "a",
41      Self::AAAA => "aaaa",
42      Self::Ptr => "ptr",
43      Self::Srv => "srv",
44      Self::Txt => "txt",
45      Self::Nsec => "nsec",
46      Self::Hinfo => "hinfo",
47      Self::Cname => "cname",
48      Self::Any => "any",
49      Self::Unknown(_) => "unknown",
50    }
51  }
52
53  /// Returns the wire-format `u16` value.
54  #[inline(always)]
55  pub const fn to_u16(self) -> u16 {
56    match self {
57      Self::A => 1,
58      Self::AAAA => 28,
59      Self::Ptr => 12,
60      Self::Srv => 33,
61      Self::Txt => 16,
62      Self::Nsec => 47,
63      Self::Hinfo => 13,
64      Self::Cname => 5,
65      Self::Any => 255,
66      Self::Unknown(v) => v,
67    }
68  }
69
70  cfg_heap! {
71  /// Whether this is a standardized RR type whose RDATA contains a (potentially
72  /// compressed) domain name but which this stack does NOT type-specifically
73  /// parse. Per RFC 1035 §3.3 the compression-eligible
74  /// name-bearing types are NS(2), MD(3), MF(4), CNAME(5), SOA(6), MB(7),
75  /// MG(8), MR(9), PTR(12), MINFO(14), MX(15) — plus DNAME(39). We parse CNAME
76  /// and PTR; the rest map to `Unknown` and a sender that knows them MAY
77  /// compress / case-vary their embedded names, so storing raw bytes would be
78  /// compression/case-sensitive and break cache dedup / TTL=0 removal. They are
79  /// not mDNS/DNS-SD record types, so callers DROP them rather than cache a
80  /// non-canonical entry. (RFC 3597 §4: types defined after RFC 1035 are sent
81  /// uncompressed, so genuinely-unknown opaque RDATA is safe to store verbatim.)
82  ///
83  /// Gated to the heap tiers: the sole caller is `Rdata::canonical_rdata_inner`
84  /// (`wire::record`, gated on `alloc` / `std` / `no-atomic`), so without a
85  /// matching gate this is dead code in the `heapless` / core-only tiers and
86  /// trips `#[deny(dead_code)]`.
87    #[inline(always)]
88    pub(crate) const fn is_unhandled_compressible_name(self) -> bool {
89      matches!(self.to_u16(), 2 | 3 | 4 | 6 | 7 | 8 | 9 | 14 | 15 | 39)
90    }
91  }
92
93  /// Reconstructs a `ResourceType` from a wire-format `u16`. Always succeeds —
94  /// unknown values land in `Unknown(v)`.
95  #[inline(always)]
96  pub const fn from_u16(v: u16) -> Self {
97    match v {
98      1 => Self::A,
99      28 => Self::AAAA,
100      12 => Self::Ptr,
101      33 => Self::Srv,
102      16 => Self::Txt,
103      47 => Self::Nsec,
104      13 => Self::Hinfo,
105      5 => Self::Cname,
106      255 => Self::Any,
107      other => Self::Unknown(other),
108    }
109  }
110}
111
112#[cfg(test)]
113#[allow(clippy::unwrap_used)]
114mod tests;