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}