agnostic_mdns/
service.rs

1use core::{error::Error, net::IpAddr};
2
3use std::{
4  io,
5  net::{Ipv4Addr, Ipv6Addr, ToSocketAddrs},
6  str::FromStr,
7  sync::atomic::{AtomicU32, Ordering},
8};
9
10use super::{IPV4_SIZE, IPV6_SIZE, invalid_input_err, is_fqdn};
11
12use mdns_proto::proto::{Label, ResourceRecord, ResourceType};
13use smallvec_wrapper::{SmallVec, TinyVec};
14use smol_str::{SmolStr, ToSmolStr, format_smolstr};
15use triomphe::Arc;
16
17const DEFAULT_TTL: u32 = 120;
18const DNS_CLASS_IN: u16 = 1;
19
20/// The error of the service
21#[derive(Debug, thiserror::Error)]
22enum ServiceError {
23  /// Service port is missing
24  #[error("missing service port")]
25  PortNotFound,
26  /// Cannot determine the host ip addresses for the host name
27  #[error("could not determine the host ip addresses for {hostname}: {error}")]
28  IpNotFound {
29    /// the host name
30    hostname: SmolStr,
31    /// the error
32    #[source]
33    error: Box<dyn Error + Send + Sync + 'static>,
34  },
35  /// Not a fully qualified domain name
36  #[error("{0} is not a fully qualified domain name")]
37  NotFQDN(SmolStr),
38  /// The TXT data is too long
39  #[error("TXT record is too long")]
40  TxtDataTooLong,
41}
42
43use ptr::PTR;
44use srv::SRV;
45use txt::TXT;
46
47mod ptr;
48mod srv;
49mod txt;
50
51/// ```text
52/// -- RFC 1035 -- Domain Implementation and Specification    November 1987
53///
54/// 3.4. Internet specific RRs
55///
56/// 3.4.1. A RDATA format
57///
58///     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
59///     |                    ADDRESS                    |
60///     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
61///
62/// where:
63///
64/// ADDRESS         A 32 bit Internet address.
65///
66/// Hosts that have multiple Internet addresses will have multiple A
67/// records.
68///
69/// A records cause no additional section processing.  The RDATA section of
70/// an A line in a Zone File is an Internet address expressed as four
71/// decimal numbers separated by dots without any embedded spaces (e.g.,
72/// "10.2.data.52" or "192.data.5.6").
73/// ```
74#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
75struct A([u8; IPV4_SIZE]);
76
77impl FromStr for A {
78  type Err = <Ipv4Addr as FromStr>::Err;
79
80  fn from_str(s: &str) -> Result<Self, Self::Err> {
81    s.parse::<Ipv4Addr>().map(Into::into)
82  }
83}
84
85impl A {
86  /// Creates a new `A` record data.
87  #[inline]
88  pub const fn new(addr: Ipv4Addr) -> Self {
89    Self(addr.octets())
90  }
91
92  /// Returns the IPv4 address of the `A` record data.
93  #[inline]
94  pub const fn addr(&self) -> Ipv4Addr {
95    Ipv4Addr::new(self.0[0], self.0[1], self.0[2], self.0[3])
96  }
97
98  /// Returns the bytes format of the `A` record data.
99  #[inline]
100  pub const fn data(&self) -> &[u8] {
101    &self.0
102  }
103}
104
105impl From<Ipv4Addr> for A {
106  #[inline]
107  fn from(value: Ipv4Addr) -> Self {
108    Self::new(value)
109  }
110}
111
112impl From<A> for Ipv4Addr {
113  #[inline]
114  fn from(value: A) -> Self {
115    value.addr()
116  }
117}
118
119/// ```text
120/// -- RFC 1886 -- IPv6 DNS Extensions              December 1995
121///
122/// 2.2 AAAA data format
123///
124///    A 128 bit IPv6 address is encoded in the data portion of an AAAA
125///    resource record in network byte order (high-order byte first).
126/// ```
127#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
128#[allow(clippy::upper_case_acronyms)]
129struct AAAA([u8; IPV6_SIZE]);
130
131impl FromStr for AAAA {
132  type Err = <Ipv6Addr as FromStr>::Err;
133
134  fn from_str(s: &str) -> Result<Self, Self::Err> {
135    s.parse::<Ipv6Addr>().map(Into::into)
136  }
137}
138
139impl AAAA {
140  /// Creates a new `AAAA` record data.
141  #[inline]
142  pub const fn new(addr: Ipv6Addr) -> Self {
143    Self(addr.octets())
144  }
145
146  /// Returns the IPv6 address of the `AAAA` record data.
147  #[inline]
148  pub fn addr(&self) -> Ipv6Addr {
149    Ipv6Addr::from(self.0)
150  }
151
152  /// Returns the bytes format of the `AAAA` record data.
153  #[inline]
154  pub const fn data(&self) -> &[u8] {
155    &self.0
156  }
157}
158
159impl From<Ipv6Addr> for AAAA {
160  #[inline]
161  fn from(value: Ipv6Addr) -> Self {
162    Self::new(value)
163  }
164}
165
166impl From<AAAA> for Ipv6Addr {
167  #[inline]
168  fn from(value: AAAA) -> Self {
169    value.addr()
170  }
171}
172
173/// A builder for creating a new [`Service`].
174pub struct ServiceBuilder<'a> {
175  instance: Label<'a>,
176  service: Label<'a>,
177  domain: Option<Label<'a>>,
178  hostname: Option<Label<'a>>,
179  port: Option<u16>,
180  ipv4s: TinyVec<Ipv4Addr>,
181  ipv6s: TinyVec<Ipv6Addr>,
182  txt: TinyVec<SmolStr>,
183  ttl: u32,
184  srv_priority: u16,
185  srv_weight: u16,
186}
187
188impl<'a> ServiceBuilder<'a> {
189  /// Returns a new ServiceBuilder with default values.
190  pub fn new(instance: Label<'a>, service: Label<'a>) -> Self {
191    Self {
192      instance,
193      service,
194      domain: None,
195      hostname: None,
196      port: None,
197      ipv4s: TinyVec::new(),
198      ipv6s: TinyVec::new(),
199      txt: TinyVec::new(),
200      ttl: DEFAULT_TTL,
201      srv_priority: 10,
202      srv_weight: 1,
203    }
204  }
205
206  /// Gets the current instance name.
207  ///
208  /// ## Example
209  ///
210  /// ```rust
211  /// use agnostic_mdns::{ServiceBuilder, Label};
212  ///
213  /// let builder = ServiceBuilder::new("hostname".into(), "_http._tcp".into());
214  /// assert_eq!(builder.instance(), &Label::from("hostname"));
215  /// ```
216  pub fn instance(&self) -> &Label<'a> {
217    &self.instance
218  }
219
220  /// Gets the current service name.
221  ///
222  /// ## Example
223  ///
224  /// ```rust
225  /// use agnostic_mdns::{ServiceBuilder, Label};
226  ///
227  /// let builder = ServiceBuilder::new("hostname".into(), "_http._tcp".into());
228  /// assert_eq!(builder.service(), &Label::from("_http._tcp"));
229  /// ```
230  pub fn service(&self) -> &Label<'a> {
231    &self.service
232  }
233
234  /// Gets the current domain.
235  ///
236  /// ## Example
237  ///
238  /// ```rust
239  /// use agnostic_mdns::ServiceBuilder;
240  ///
241  /// let builder = ServiceBuilder::new("hostname".into(), "_http._tcp".into());
242  ///
243  /// assert!(builder.domain().is_none());
244  /// ```
245  pub fn domain(&self) -> Option<&Label<'a>> {
246    self.domain.as_ref()
247  }
248
249  /// Sets the domain for the service.
250  ///
251  /// ## Example
252  ///
253  /// ```rust
254  /// use agnostic_mdns::{ServiceBuilder, Label};
255  ///
256  /// let builder = ServiceBuilder::new("hostname".into(), "_http._tcp".into())
257  ///   .with_domain("local.".into());
258  ///
259  /// assert_eq!(builder.domain().unwrap(), &Label::from("local."));
260  /// ```
261  pub fn with_domain(mut self, domain: Label<'a>) -> Self {
262    self.domain = Some(domain);
263    self
264  }
265
266  /// Gets the current host name.
267  ///
268  /// ## Example
269  ///
270  /// ```rust
271  /// use agnostic_mdns::{ServiceBuilder, Label};
272  ///
273  /// let builder = ServiceBuilder::new("hostname".into(), "_http._tcp".into())
274  ///   .with_hostname("testhost.".into());
275  ///
276  /// assert_eq!(builder.hostname().unwrap(), &Label::from("testhost."));
277  /// ```
278  pub fn hostname(&self) -> Option<&Label<'a>> {
279    self.hostname.as_ref()
280  }
281
282  /// Sets the host name for the service.
283  ///
284  /// ## Example
285  ///
286  /// ```rust
287  /// use agnostic_mdns::ServiceBuilder;
288  ///
289  /// let builder = ServiceBuilder::new("hostname".into(), "_http._tcp".into())
290  ///   .with_hostname("testhost.".into());
291  /// ```
292  pub fn with_hostname(mut self, hostname: Label<'a>) -> Self {
293    self.hostname = Some(hostname);
294    self
295  }
296
297  /// Gets the TTL.
298  ///
299  /// Defaults to `120` seconds.
300  ///
301  /// ## Example
302  ///
303  /// ```rust
304  /// use agnostic_mdns::ServiceBuilder;
305  ///
306  /// let builder = ServiceBuilder::new("hostname".into(), "_http._tcp".into());
307  /// assert_eq!(builder.ttl(), 120);
308  ///
309  /// let builder = builder.with_ttl(60);
310  /// assert_eq!(builder.ttl(), 60);
311  /// ```
312  pub fn ttl(&self) -> u32 {
313    self.ttl
314  }
315
316  /// Sets the TTL for the service.
317  ///
318  /// Defaults to `120` seconds.
319  ///
320  /// ## Example
321  ///
322  /// ```rust
323  /// use agnostic_mdns::ServiceBuilder;
324  ///
325  /// let builder = ServiceBuilder::new("hostname".into(), "_http._tcp".into())
326  ///   .with_ttl(60);
327  /// ```
328  pub fn with_ttl(mut self, ttl: u32) -> Self {
329    self.ttl = ttl;
330    self
331  }
332
333  /// Gets the priority for SRV records.
334  ///
335  /// Defaults to `10`.
336  ///
337  /// ## Example
338  ///
339  /// ```rust
340  /// use agnostic_mdns::ServiceBuilder;
341  ///
342  /// let builder = ServiceBuilder::new("hostname".into(), "_http._tcp".into());
343  /// assert_eq!(builder.srv_priority(), 10);
344  ///
345  /// let builder = builder.with_srv_priority(5);
346  /// assert_eq!(builder.srv_priority(), 5);
347  /// ```
348  pub fn srv_priority(&self) -> u16 {
349    self.srv_priority
350  }
351
352  /// Sets the priority for SRV records.
353  ///
354  /// Defaults to `10`.
355  ///
356  /// ## Example
357  ///
358  /// ```rust
359  /// use agnostic_mdns::ServiceBuilder;
360  ///
361  /// let builder = ServiceBuilder::new("hostname".into(), "_http._tcp".into())
362  ///   .with_srv_priority(5);
363  /// ```
364  pub fn with_srv_priority(mut self, priority: u16) -> Self {
365    self.srv_priority = priority;
366    self
367  }
368
369  /// Gets the weight for SRV records.
370  ///
371  /// Defaults to `1`.
372  ///
373  /// ## Example
374  ///
375  /// ```rust
376  /// use agnostic_mdns::ServiceBuilder;
377  ///
378  /// let builder = ServiceBuilder::new("hostname".into(), "_http._tcp".into());
379  /// assert_eq!(builder.srv_weight(), 1);
380  ///
381  /// let builder = builder.with_srv_weight(5);
382  /// assert_eq!(builder.srv_weight(), 5);
383  /// ```
384  pub fn srv_weight(&self) -> u16 {
385    self.srv_weight
386  }
387
388  /// Sets the weight for SRV records.
389  ///
390  /// Defaults to `1`.
391  ///
392  /// ## Example
393  ///
394  /// ```rust
395  /// use agnostic_mdns::ServiceBuilder;
396  ///
397  /// let builder = ServiceBuilder::new("hostname".into(), "_http._tcp".into())
398  ///   .with_srv_weight(5);
399  /// ```
400  pub fn with_srv_weight(mut self, weight: u16) -> Self {
401    self.srv_weight = weight;
402    self
403  }
404
405  /// Gets the current port.
406  ///
407  /// ## Example
408  ///
409  /// ```rust
410  /// use agnostic_mdns::ServiceBuilder;
411  ///
412  /// let builder = ServiceBuilder::new("hostname".into(), "_http._tcp".into());
413  /// assert!(builder.port().is_none());
414  /// ```
415  pub fn port(&self) -> Option<u16> {
416    self.port
417  }
418
419  /// Sets the port for the service.
420  ///
421  /// ## Example
422  ///
423  /// ```rust
424  /// use agnostic_mdns::ServiceBuilder;
425  ///
426  /// let builder = ServiceBuilder::new("hostname".into(), "_http._tcp".into())
427  ///   .with_port(80);
428  /// ```
429  pub fn with_port(mut self, port: u16) -> Self {
430    self.port = Some(port);
431    self
432  }
433
434  /// Gets the current IPv4 addresses.
435  ///
436  /// ## Example
437  ///
438  /// ```rust
439  /// use agnostic_mdns::ServiceBuilder;
440  /// use std::net::{IpAddr, Ipv4Addr};
441  ///
442  /// let builder = ServiceBuilder::new("hostname".into(), "_http._tcp".into());
443  /// assert!(builder.ipv4s().is_empty());
444  ///
445  /// let builder = builder.with_ip("192.168.0.1".parse::<IpAddr>().unwrap());
446  ///
447  /// assert_eq!(builder.ipv4s(), &["192.168.0.1".parse::<Ipv4Addr>().unwrap()]);
448  /// ```
449  pub fn ipv4s(&self) -> &[Ipv4Addr] {
450    &self.ipv4s
451  }
452
453  /// Gets the current IPv6 addresses.
454  ///
455  /// ## Example
456  ///
457  /// ```rust
458  /// use agnostic_mdns::ServiceBuilder;
459  /// use std::net::{IpAddr, Ipv6Addr};
460  ///
461  /// let builder = ServiceBuilder::new("hostname".into(), "_http._tcp".into());
462  /// assert!(builder.ipv6s().is_empty());
463  ///
464  /// let builder = builder.with_ip("::1".parse::<IpAddr>().unwrap());
465  ///
466  /// assert_eq!(builder.ipv6s(), &["::1".parse::<Ipv6Addr>().unwrap()]);
467  /// ```
468  pub fn ipv6s(&self) -> &[Ipv6Addr] {
469    &self.ipv6s
470  }
471
472  /// Sets the IPv4 addresses for the service.
473  ///
474  /// ## Example
475  ///
476  /// ```rust
477  /// use agnostic_mdns::ServiceBuilder;
478  ///
479  /// let builder = ServiceBuilder::new("hostname".into(), "_http._tcp".into())
480  ///   .with_ipv4s(["192.168.0.1".parse().unwrap()].into_iter().collect());
481  /// ```
482  pub fn with_ipv4s(mut self, ips: TinyVec<Ipv4Addr>) -> Self {
483    self.ipv4s = ips;
484    self
485  }
486
487  /// Sets the IPv6 addresses for the service.
488  ///
489  /// ## Example
490  ///
491  /// ```rust
492  /// use agnostic_mdns::ServiceBuilder;
493  ///
494  /// let builder = ServiceBuilder::new("hostname".into(), "_http._tcp".into())
495  ///   .with_ipv6s(["::1".parse().unwrap()].into_iter().collect());
496  /// ```
497  pub fn with_ipv6s(mut self, ips: TinyVec<Ipv6Addr>) -> Self {
498    self.ipv6s = ips;
499    self
500  }
501
502  /// Pushes an IP address to the list of IP addresses.
503  ///
504  /// ## Example
505  ///
506  /// ```rust
507  /// use agnostic_mdns::ServiceBuilder;
508  /// use std::net::IpAddr;
509  ///
510  /// let builder = ServiceBuilder::new("hostname".into(), "_http._tcp".into())
511  ///  .with_ip(IpAddr::V4("192.168.0.1".parse().unwrap()));
512  /// ```
513  pub fn with_ip(mut self, ip: IpAddr) -> Self {
514    match ip {
515      IpAddr::V4(ip) => self.ipv4s.push(ip),
516      IpAddr::V6(ip) => self.ipv6s.push(ip),
517    }
518    self
519  }
520
521  /// Gets the current TXT records.
522  ///
523  /// ## Example
524  ///
525  /// ```rust
526  /// use agnostic_mdns::{ServiceBuilder, SmolStr};
527  ///
528  /// let builder = ServiceBuilder::new("hostname".into(), "_http._tcp".into());
529  /// assert!(builder.txt_records().is_empty());
530  ///
531  /// let builder = builder.with_txt_record("info".into());
532  ///
533  /// assert_eq!(builder.txt_records(), &[SmolStr::new("info")]);
534  /// ```
535  pub fn txt_records(&self) -> &[SmolStr] {
536    &self.txt
537  }
538
539  /// Sets the TXT records for the service.
540  ///
541  /// ## Example
542  ///
543  /// ```rust
544  /// use agnostic_mdns::{ServiceBuilder, SmolStr};
545  ///
546  /// let builder = ServiceBuilder::new("hostname".into(), "_http._tcp".into())
547  ///   .with_txt_records([SmolStr::new("info")].into_iter().collect());
548  /// ```
549  pub fn with_txt_records(mut self, txt: TinyVec<SmolStr>) -> Self {
550    self.txt = txt;
551    self
552  }
553
554  /// Pushes a TXT record to the list of TXT records.
555  ///
556  /// ## Example
557  ///
558  /// ```rust
559  /// use agnostic_mdns::{ServiceBuilder, SmolStr};
560  ///
561  /// let builder = ServiceBuilder::new("hostname".into(), "_http._tcp".into())
562  ///  .with_txt_record("info".into());
563  /// ```
564  pub fn with_txt_record(mut self, txt: SmolStr) -> Self {
565    self.txt.push(txt);
566    self
567  }
568
569  /// Finalize the builder and try to create a new [`Service`].
570  // TODO(reddaly): This interface may need to change to account for "unique
571  // record" conflict rules of the mDNS protocol.  Upon startup, the server should
572  // check to ensure that the instance name does not conflict with other instance
573  // names, and, if required, select a new name.  There may also be conflicting
574  // hostName A/AAAA records.
575  pub fn finalize(self) -> io::Result<Service> {
576    let domain = self.domain.as_ref().map(|d| format_smolstr!("{}.", d));
577    let domain = match domain {
578      Some(domain) if !is_fqdn(domain.as_str()) => {
579        return Err(invalid_input_err(ServiceError::NotFQDN(domain)));
580      }
581      Some(domain) => domain,
582      None => "local".into(),
583    };
584
585    let hostname = self.hostname.as_ref().map(|h| format_smolstr!("{}.", h));
586    let hostname = match hostname {
587      Some(hostname) if !hostname.is_empty() => {
588        if !is_fqdn(hostname.as_str()) {
589          return Err(invalid_input_err(ServiceError::NotFQDN(hostname)));
590        }
591        hostname
592      }
593      _ => super::hostname_fqdn()?,
594    };
595
596    let port = match self.port {
597      None | Some(0) => return Err(invalid_input_err(ServiceError::PortNotFound)),
598      Some(port) => port,
599    };
600
601    let (ipv4s, ipv6s) = if self.ipv4s.is_empty() && self.ipv6s.is_empty() {
602      let tmp_hostname = format_smolstr!("{}.{}", hostname, domain);
603
604      let mut ipv4s = TinyVec::new();
605      let mut ipv6s = TinyVec::new();
606      tmp_hostname
607        .as_str()
608        .to_socket_addrs()
609        .map_err(|e| {
610          invalid_input_err(ServiceError::IpNotFound {
611            hostname: tmp_hostname,
612            error: e.into(),
613          })
614        })?
615        .for_each(|addr| match addr.ip() {
616          IpAddr::V4(ip) => ipv4s.push(ip),
617          IpAddr::V6(ip) => ipv6s.push(ip),
618        });
619
620      (ipv4s, ipv6s)
621    } else {
622      (self.ipv4s, self.ipv6s)
623    };
624
625    let service_addr = format_smolstr!("{}.{}.", self.service, domain.as_str().trim_matches('.'));
626    let instance_addr = format_smolstr!("{}.{}.{}.", self.instance, self.service, domain);
627    let enum_addr = format_smolstr!("_services._dns-sd._udp.{}.", domain);
628
629    let srv = SRV::new(self.srv_priority, self.srv_weight, port, hostname.clone())
630      .map_err(invalid_input_err)?;
631
632    Ok(Service {
633      instance: self.instance.to_smolstr(),
634      service: self.service.to_smolstr(),
635      domain,
636      hostname,
637      ipv4s: ipv4s.iter().map(|ip| A::from(*ip)).collect(),
638      ipv6s: ipv6s.iter().map(|ip| AAAA::from(*ip)).collect(),
639      ipv4s_origin: ipv4s,
640      ipv6s_origin: ipv6s,
641      txt: TXT::new(Arc::from_iter(self.txt)).map_err(invalid_input_err)?,
642      service_addr: PTR::new(service_addr).map_err(invalid_input_err)?,
643      instance_addr: PTR::new(instance_addr).map_err(invalid_input_err)?,
644      enum_addr: PTR::new(enum_addr).map_err(invalid_input_err)?,
645      ttl: AtomicU32::new(self.ttl),
646      srv,
647    })
648  }
649}
650
651/// Export a named service by implementing a [`Zone`].
652#[derive(Debug)]
653pub struct Service {
654  /// Instance name (e.g. "hostService name")
655  instance: SmolStr,
656  /// Service name (e.g. "_http._tcp.")
657  service: SmolStr,
658  /// If blank, assumes "local"
659  domain: SmolStr,
660  /// Host machine DNS name (e.g. "mymachine.net")
661  hostname: SmolStr,
662  /// IP addresses for the service's host
663  ipv4s_origin: TinyVec<Ipv4Addr>,
664  ipv6s_origin: TinyVec<Ipv6Addr>,
665
666  // TODO(al8n): remove the following two fields, when Ipv*Addr::as_octets is stabilized
667  ipv4s: TinyVec<A>,
668  ipv6s: TinyVec<AAAA>,
669
670  /// Service TXT records
671  txt: TXT,
672  /// Fully qualified service address
673  service_addr: PTR,
674  /// Fully qualified instance address
675  instance_addr: PTR,
676  /// _services._dns-sd._udp.<domain>
677  enum_addr: PTR,
678  ttl: AtomicU32,
679  srv: SRV,
680}
681
682impl Service {
683  /// Returns the instance of the service.
684  #[inline]
685  pub const fn instance(&self) -> &SmolStr {
686    &self.instance
687  }
688
689  /// Returns the service of the mdns service.
690  #[inline]
691  pub const fn service(&self) -> &SmolStr {
692    &self.service
693  }
694
695  /// Returns the domain of the mdns service.
696  #[inline]
697  pub const fn domain(&self) -> &SmolStr {
698    &self.domain
699  }
700
701  /// Returns the hostname of the mdns service.
702  #[inline]
703  pub const fn hostname(&self) -> &SmolStr {
704    &self.hostname
705  }
706
707  /// Returns the port of the mdns service.
708  #[inline]
709  pub fn port(&self) -> u16 {
710    self.srv.port()
711  }
712
713  /// Returns the TTL of the mdns service.
714  #[inline]
715  pub fn ttl(&self) -> u32 {
716    self.ttl.load(Ordering::Acquire)
717  }
718
719  /// Returns the IPv4 addresses of the mdns service.
720  #[inline]
721  pub fn ipv4s(&self) -> &[Ipv4Addr] {
722    &self.ipv4s_origin
723  }
724
725  /// Returns the IPv6 addresses of the mdns service.
726  #[inline]
727  pub fn ipv6s(&self) -> &[Ipv6Addr] {
728    &self.ipv6s_origin
729  }
730
731  /// Returns the TXT records of the mdns service.
732  #[inline]
733  pub fn txt_records(&self) -> &[SmolStr] {
734    self.txt.strings()
735  }
736
737  #[auto_enums::auto_enum(Iterator)]
738  pub(super) fn fetch_answers<'a>(
739    &'a self,
740    qn: Label<'a>,
741    rt: ResourceType,
742  ) -> impl Iterator<Item = ResourceRecord<'a>> + 'a {
743    let enum_addr_label = Label::from(self.enum_addr.name());
744    let service_addr_label = Label::from(self.service_addr.name());
745    let instance_addr_label = Label::from(self.instance_addr.name());
746    let hostname_label = Label::from(self.hostname.as_str());
747
748    match () {
749      () if enum_addr_label.eq(&qn) => self.service_enum(qn, rt),
750      () if service_addr_label.eq(&qn) => self.service_records(qn, rt),
751      () if instance_addr_label.eq(&qn) => self.instance_records(qn, rt),
752      () if hostname_label.eq(&qn) && matches!(rt, ResourceType::A | ResourceType::AAAA) => {
753        self.instance_records(qn, rt)
754      }
755      _ => core::iter::empty(),
756    }
757  }
758
759  #[auto_enums::auto_enum(Iterator)]
760  fn service_enum<'a>(
761    &'a self,
762    name: Label<'a>,
763    rt: ResourceType,
764  ) -> impl Iterator<Item = ResourceRecord<'a>> {
765    match rt {
766      ResourceType::Wildcard | ResourceType::Ptr => core::iter::once(ResourceRecord::new(
767        name,
768        ResourceType::Ptr,
769        DNS_CLASS_IN,
770        self.ttl(),
771        self.service_addr.data(),
772      )),
773      _ => core::iter::empty(),
774    }
775  }
776
777  #[auto_enums::auto_enum(Iterator)]
778  fn service_records<'a>(
779    &'a self,
780    name: Label<'a>,
781    rt: ResourceType,
782  ) -> impl Iterator<Item = ResourceRecord<'a>> {
783    match rt {
784      ResourceType::Wildcard | ResourceType::Ptr => {
785        // Get the instance records
786        core::iter::once(ResourceRecord::new(
787          name,
788          ResourceType::Ptr,
789          DNS_CLASS_IN,
790          self.ttl(),
791          self.instance_addr.data(),
792        ))
793        .chain(self.instance_records(self.instance_addr.name().into(), ResourceType::Wildcard))
794      }
795      _ => core::iter::empty(),
796    }
797  }
798
799  #[auto_enums::auto_enum(Iterator)]
800  fn instance_records<'a>(
801    &'a self,
802    name: Label<'a>,
803    rt: ResourceType,
804  ) -> impl Iterator<Item = ResourceRecord<'a>> {
805    match rt {
806      ResourceType::Wildcard => {
807        // Get the SRV, which includes A and AAAA
808        let recs = self.instance_records(self.instance_addr.name().into(), ResourceType::Srv);
809
810        // Add the TXT record
811        recs
812          .chain(self.instance_records(self.instance_addr.name().into(), ResourceType::Txt))
813          .collect::<SmallVec<_>>()
814          .into_iter()
815      }
816      ResourceType::A => self.ipv4s.iter().map(move |ip| {
817        ResourceRecord::new(name, ResourceType::A, DNS_CLASS_IN, self.ttl(), ip.data())
818      }),
819      ResourceType::AAAA => self.ipv6s.iter().map(move |ip| {
820        ResourceRecord::new(
821          name,
822          ResourceType::AAAA,
823          DNS_CLASS_IN,
824          self.ttl(),
825          ip.data(),
826        )
827      }),
828      ResourceType::Srv => {
829        // Create the SRV Record
830        let recs = core::iter::once(ResourceRecord::new(
831          name,
832          ResourceType::Srv,
833          DNS_CLASS_IN,
834          self.ttl(),
835          self.srv.data(),
836        ));
837        recs
838          // Add the A record
839          .chain(self.instance_records(self.instance_addr.name().into(), ResourceType::A))
840          // Add the AAAA record
841          .chain(self.instance_records(self.instance_addr.name().into(), ResourceType::AAAA))
842          .collect::<SmallVec<_>>()
843          .into_iter()
844      }
845      ResourceType::Txt => {
846        // Build a TXT response for the instance
847        core::iter::once(ResourceRecord::new(
848          name,
849          ResourceType::Txt,
850          DNS_CLASS_IN,
851          self.ttl(),
852          self.txt.data(),
853        ))
854      }
855      _ => core::iter::empty(),
856    }
857  }
858}