mdns_sd/
service_info.rs

1//! Define `ServiceInfo` to represent a service and its operations.
2
3#[cfg(feature = "logging")]
4use crate::log::{debug, trace};
5use crate::{
6    dns_parser::{DnsRecordBox, DnsRecordExt, DnsSrv, RRType, ScopedIp},
7    Error, IfKind, InterfaceId, Result,
8};
9use if_addrs::{IfAddr, Interface};
10use std::net::Ipv6Addr;
11use std::{
12    cmp,
13    collections::{HashMap, HashSet},
14    convert::TryInto,
15    fmt,
16    net::{IpAddr, Ipv4Addr},
17    str::FromStr,
18};
19
20/// Default TTL values in seconds
21const DNS_HOST_TTL: u32 = 120; // 2 minutes for host records (A, SRV etc) per RFC6762
22const DNS_OTHER_TTL: u32 = 4500; // 75 minutes for non-host records (PTR, TXT etc) per RFC6762
23
24/// Represents a network interface.
25#[derive(Debug)]
26pub(crate) struct MyIntf {
27    /// The name of the interface.
28    pub(crate) name: String,
29
30    /// Unique index assigned by the OS. Used by IPv6 for its scope_id.
31    pub(crate) index: u32,
32
33    /// One interface can have multiple IPv4 addresses and/or multiple IPv6 addresses.
34    pub(crate) addrs: HashSet<IfAddr>,
35}
36
37impl MyIntf {
38    pub(crate) fn next_ifaddr_v4(&self) -> Option<&IfAddr> {
39        self.addrs.iter().find(|a| a.ip().is_ipv4())
40    }
41
42    pub(crate) fn next_ifaddr_v6(&self) -> Option<&IfAddr> {
43        self.addrs.iter().find(|a| a.ip().is_ipv6())
44    }
45}
46
47impl From<&MyIntf> for InterfaceId {
48    fn from(my_intf: &MyIntf) -> Self {
49        InterfaceId {
50            name: my_intf.name.clone(),
51            index: my_intf.index,
52        }
53    }
54}
55
56/// Complete info about a Service Instance.
57///
58/// We can construct some PTR, one SRV and one TXT record from this info,
59/// as well as A (IPv4 Address) and AAAA (IPv6 Address) records.
60#[derive(Debug, Clone)]
61pub struct ServiceInfo {
62    ty_domain: String, // <service>.<domain>
63
64    /// See RFC6763 section 7.1 about "Subtypes":
65    /// <https://datatracker.ietf.org/doc/html/rfc6763#section-7.1>
66    sub_domain: Option<String>, // <subservice>._sub.<service>.<domain>
67
68    fullname: String, // <instance>.<service>.<domain>
69    server: String,   // fully qualified name for service host
70    addresses: HashSet<IpAddr>,
71    port: u16,
72    host_ttl: u32,  // used for SRV and Address records
73    other_ttl: u32, // used for PTR and TXT records
74    priority: u16,
75    weight: u16,
76    txt_properties: TxtProperties,
77    addr_auto: bool, // Let the system update addresses automatically.
78
79    status: HashMap<u32, ServiceStatus>, // keyed by interface index.
80
81    /// Whether we need to probe names before announcing this service.
82    requires_probe: bool,
83
84    /// If set, the service is only exposed on these interfaces
85    supported_intfs: Vec<IfKind>,
86
87    /// If true, only link-local addresses are published.
88    is_link_local_only: bool,
89}
90
91#[derive(Debug, Clone, PartialEq, Eq)]
92pub(crate) enum ServiceStatus {
93    Probing,
94    Announced,
95    Unknown,
96}
97
98impl ServiceInfo {
99    /// Creates a new service info.
100    ///
101    /// `ty_domain` is the service type and the domain label, for example
102    /// "_my-service._udp.local.".
103    ///
104    /// `my_name` is the instance name, without the service type suffix.
105    ///
106    /// `host_name` is the "host" in the context of DNS. It is used as the "name"
107    /// in the address records (i.e. TYPE_A and TYPE_AAAA records). It means that
108    /// for the same hostname in the same local network, the service resolves in
109    /// the same addresses. Be sure to check it if you see unexpected addresses resolved.
110    ///
111    /// `properties` can be `None` or key/value string pairs, in a type that
112    /// implements [`IntoTxtProperties`] trait. It supports:
113    /// - `HashMap<String, String>`
114    /// - `Option<HashMap<String, String>>`
115    /// - slice of tuple: `&[(K, V)]` where `K` and `V` are [`std::string::ToString`].
116    ///
117    /// Note: The maximum length of a single property string is `255`, Property that exceed the length are truncated.
118    /// > `len(key + value) < u8::MAX`
119    ///
120    /// `ip` can be one or more IP addresses, in a type that implements
121    /// [`AsIpAddrs`] trait. It supports:
122    ///
123    /// - Single IPv4: `"192.168.0.1"`
124    /// - Single IPv6: `"2001:0db8::7334"`
125    /// - Multiple IPv4 separated by comma: `"192.168.0.1,192.168.0.2"`
126    /// - Multiple IPv6 separated by comma: `"2001:0db8::7334,2001:0db8::7335"`
127    /// - A slice of IPv4: `&["192.168.0.1", "192.168.0.2"]`
128    /// - A slice of IPv6: `&["2001:0db8::7334", "2001:0db8::7335"]`
129    /// - A mix of IPv4 and IPv6: `"192.168.0.1,2001:0db8::7334"`
130    /// - All the above formats with [IpAddr] or `String` instead of `&str`.
131    ///
132    /// The host TTL and other TTL are set to default values.
133    pub fn new<Ip: AsIpAddrs, P: IntoTxtProperties>(
134        ty_domain: &str,
135        my_name: &str,
136        host_name: &str,
137        ip: Ip,
138        port: u16,
139        properties: P,
140    ) -> Result<Self> {
141        let (ty_domain, sub_domain) = split_sub_domain(ty_domain);
142
143        let fullname = format!("{my_name}.{ty_domain}");
144        let ty_domain = ty_domain.to_string();
145        let sub_domain = sub_domain.map(str::to_string);
146        let server = normalize_hostname(host_name.to_string());
147        let addresses = ip.as_ip_addrs()?;
148        let txt_properties = properties.into_txt_properties();
149
150        // RFC6763 section 6.4: https://www.rfc-editor.org/rfc/rfc6763#section-6.4
151        // The characters of a key MUST be printable US-ASCII values (0x20-0x7E)
152        // [RFC20], excluding '=' (0x3D).
153        for prop in txt_properties.iter() {
154            let key = prop.key();
155            if !key.is_ascii() {
156                return Err(Error::Msg(format!(
157                    "TXT property key {} is not ASCII",
158                    prop.key()
159                )));
160            }
161            if key.contains('=') {
162                return Err(Error::Msg(format!(
163                    "TXT property key {} contains '='",
164                    prop.key()
165                )));
166            }
167        }
168
169        let this = Self {
170            ty_domain,
171            sub_domain,
172            fullname,
173            server,
174            addresses,
175            port,
176            host_ttl: DNS_HOST_TTL,
177            other_ttl: DNS_OTHER_TTL,
178            priority: 0,
179            weight: 0,
180            txt_properties,
181            addr_auto: false,
182            status: HashMap::new(),
183            requires_probe: true,
184            is_link_local_only: false,
185            supported_intfs: vec![IfKind::All],
186        };
187
188        Ok(this)
189    }
190
191    /// Indicates that the library should automatically
192    /// update the addresses of this service, when IP
193    /// address(es) are added or removed on the host.
194    pub const fn enable_addr_auto(mut self) -> Self {
195        self.addr_auto = true;
196        self
197    }
198
199    /// Returns if the service's addresses will be updated
200    /// automatically when the host IP addrs change.
201    pub const fn is_addr_auto(&self) -> bool {
202        self.addr_auto
203    }
204
205    /// Set whether this service info requires name probing for potential name conflicts.
206    ///
207    /// By default, it is true (i.e. requires probing) for every service info. You
208    /// set it to `false` only when you are sure there are no conflicts, or for testing purposes.
209    pub fn set_requires_probe(&mut self, enable: bool) {
210        self.requires_probe = enable;
211    }
212
213    /// Set whether the service is restricted to link-local addresses.
214    ///
215    /// By default, it is false.
216    pub fn set_link_local_only(&mut self, is_link_local_only: bool) {
217        self.is_link_local_only = is_link_local_only;
218    }
219
220    /// Set the supported interfaces for this service.
221    ///
222    /// The service will be advertised on the provided interfaces only. When ips are auto-detected
223    /// (via 'enable_addr_auto') only addresses on these interfaces will be considered.
224    pub fn set_interfaces(&mut self, intfs: Vec<IfKind>) {
225        self.supported_intfs = intfs;
226    }
227
228    /// Returns whether this service info requires name probing for potential name conflicts.
229    ///
230    /// By default, it returns true for every service info.
231    pub const fn requires_probe(&self) -> bool {
232        self.requires_probe
233    }
234
235    /// Returns the service type including the domain label.
236    ///
237    /// For example: "_my-service._udp.local.".
238    #[inline]
239    pub fn get_type(&self) -> &str {
240        &self.ty_domain
241    }
242
243    /// Returns the service subtype including the domain label,
244    /// if subtype has been defined.
245    ///
246    /// For example: "_printer._sub._http._tcp.local.".
247    #[inline]
248    pub const fn get_subtype(&self) -> &Option<String> {
249        &self.sub_domain
250    }
251
252    /// Returns a reference of the service fullname.
253    ///
254    /// This is useful, for example, in unregister.
255    #[inline]
256    pub fn get_fullname(&self) -> &str {
257        &self.fullname
258    }
259
260    /// Returns the properties from TXT records.
261    #[inline]
262    pub const fn get_properties(&self) -> &TxtProperties {
263        &self.txt_properties
264    }
265
266    /// Returns a property for a given `key`, where `key` is
267    /// case insensitive.
268    ///
269    /// Returns `None` if `key` does not exist.
270    pub fn get_property(&self, key: &str) -> Option<&TxtProperty> {
271        self.txt_properties.get(key)
272    }
273
274    /// Returns a property value for a given `key`, where `key` is
275    /// case insensitive.
276    ///
277    /// Returns `None` if `key` does not exist.
278    pub fn get_property_val(&self, key: &str) -> Option<Option<&[u8]>> {
279        self.txt_properties.get_property_val(key)
280    }
281
282    /// Returns a property value string for a given `key`, where `key` is
283    /// case insensitive.
284    ///
285    /// Returns `None` if `key` does not exist.
286    pub fn get_property_val_str(&self, key: &str) -> Option<&str> {
287        self.txt_properties.get_property_val_str(key)
288    }
289
290    /// Returns the service's hostname.
291    #[inline]
292    pub fn get_hostname(&self) -> &str {
293        &self.server
294    }
295
296    /// Returns the service's port.
297    #[inline]
298    pub const fn get_port(&self) -> u16 {
299        self.port
300    }
301
302    /// Returns the service's addresses
303    #[inline]
304    pub const fn get_addresses(&self) -> &HashSet<IpAddr> {
305        &self.addresses
306    }
307
308    /// Returns the service's IPv4 addresses only.
309    pub fn get_addresses_v4(&self) -> HashSet<&Ipv4Addr> {
310        let mut ipv4_addresses = HashSet::new();
311
312        for ip in &self.addresses {
313            if let IpAddr::V4(ipv4) = ip {
314                ipv4_addresses.insert(ipv4);
315            }
316        }
317
318        ipv4_addresses
319    }
320
321    /// Returns the service's TTL used for SRV and Address records.
322    #[inline]
323    pub const fn get_host_ttl(&self) -> u32 {
324        self.host_ttl
325    }
326
327    /// Returns the service's TTL used for PTR and TXT records.
328    #[inline]
329    pub const fn get_other_ttl(&self) -> u32 {
330        self.other_ttl
331    }
332
333    /// Returns the service's priority used in SRV records.
334    #[inline]
335    pub const fn get_priority(&self) -> u16 {
336        self.priority
337    }
338
339    /// Returns the service's weight used in SRV records.
340    #[inline]
341    pub const fn get_weight(&self) -> u16 {
342        self.weight
343    }
344
345    /// Returns all addresses published
346    pub(crate) fn get_addrs_on_my_intf_v4(&self, my_intf: &MyIntf) -> Vec<IpAddr> {
347        self.addresses
348            .iter()
349            .filter(|a| a.is_ipv4() && my_intf.addrs.iter().any(|x| valid_ip_on_intf(a, x)))
350            .copied()
351            .collect()
352    }
353
354    pub(crate) fn get_addrs_on_my_intf_v6(&self, my_intf: &MyIntf) -> Vec<IpAddr> {
355        self.addresses
356            .iter()
357            .filter(|a| a.is_ipv6() && my_intf.addrs.iter().any(|x| valid_ip_on_intf(a, x)))
358            .copied()
359            .collect()
360    }
361
362    /// Returns whether the service info is ready to be resolved.
363    pub(crate) fn _is_ready(&self) -> bool {
364        let some_missing = self.ty_domain.is_empty()
365            || self.fullname.is_empty()
366            || self.server.is_empty()
367            || self.addresses.is_empty();
368        !some_missing
369    }
370
371    /// Insert `addr` into service info addresses.
372    pub(crate) fn insert_ipaddr(&mut self, intf: &Interface) {
373        if self.is_address_supported(intf) {
374            self.addresses.insert(intf.addr.ip());
375        } else {
376            trace!(
377                "skipping unsupported address {} for service {}",
378                intf.addr.ip(),
379                self.fullname
380            );
381        }
382    }
383
384    pub(crate) fn remove_ipaddr(&mut self, addr: &IpAddr) {
385        self.addresses.remove(addr);
386    }
387
388    pub(crate) fn generate_txt(&self) -> Vec<u8> {
389        encode_txt(self.get_properties().iter())
390    }
391
392    pub(crate) fn _set_port(&mut self, port: u16) {
393        self.port = port;
394    }
395
396    pub(crate) fn _set_hostname(&mut self, hostname: String) {
397        self.server = normalize_hostname(hostname);
398    }
399
400    /// Returns true if properties are updated.
401    pub(crate) fn _set_properties_from_txt(&mut self, txt: &[u8]) -> bool {
402        let properties = decode_txt_unique(txt);
403        if self.txt_properties.properties != properties {
404            self.txt_properties = TxtProperties { properties };
405            true
406        } else {
407            false
408        }
409    }
410
411    pub(crate) fn _set_subtype(&mut self, subtype: String) {
412        self.sub_domain = Some(subtype);
413    }
414
415    /// host_ttl is for SRV and address records
416    /// currently only used for testing.
417    pub(crate) fn _set_host_ttl(&mut self, ttl: u32) {
418        self.host_ttl = ttl;
419    }
420
421    /// other_ttl is for PTR and TXT records.
422    pub(crate) fn _set_other_ttl(&mut self, ttl: u32) {
423        self.other_ttl = ttl;
424    }
425
426    pub(crate) fn set_status(&mut self, if_index: u32, status: ServiceStatus) {
427        match self.status.get_mut(&if_index) {
428            Some(service_status) => {
429                *service_status = status;
430            }
431            None => {
432                self.status.entry(if_index).or_insert(status);
433            }
434        }
435    }
436
437    pub(crate) fn get_status(&self, intf: u32) -> ServiceStatus {
438        self.status
439            .get(&intf)
440            .cloned()
441            .unwrap_or(ServiceStatus::Unknown)
442    }
443
444    /// Consumes self and returns a resolved service, i.e. a lite version of `ServiceInfo`.
445    pub fn as_resolved_service(self) -> ResolvedService {
446        let addresses: HashSet<ScopedIp> = self.addresses.into_iter().map(|a| a.into()).collect();
447        ResolvedService {
448            ty_domain: self.ty_domain,
449            sub_ty_domain: self.sub_domain,
450            fullname: self.fullname,
451            host: self.server,
452            port: self.port,
453            addresses,
454            txt_properties: self.txt_properties,
455        }
456    }
457
458    fn is_address_supported(&self, intf: &Interface) -> bool {
459        let addr = intf.ip();
460        let interface_supported = self.supported_intfs.iter().any(|i| match i {
461            IfKind::Name(name) => *name == intf.name,
462            IfKind::IPv4 => addr.is_ipv4(),
463            IfKind::IPv6 => addr.is_ipv6(),
464            IfKind::Addr(a) => *a == addr,
465            IfKind::LoopbackV4 => matches!(addr, IpAddr::V4(ipv4) if ipv4.is_loopback()),
466            IfKind::LoopbackV6 => matches!(addr, IpAddr::V6(ipv6) if ipv6.is_loopback()),
467            IfKind::All => true,
468        });
469
470        let passes_link_local = !self.is_link_local_only
471            || match &addr {
472                IpAddr::V4(ipv4) => ipv4.is_link_local(),
473                IpAddr::V6(ipv6) => is_unicast_link_local(ipv6),
474            };
475        debug!(
476            "matching inserted address {} on intf {}: passes_link_local={}, interface_supported={}",
477            addr, addr, passes_link_local, interface_supported
478        );
479        interface_supported && passes_link_local
480    }
481}
482
483/// Removes potentially duplicated ".local." at the end of "hostname".
484fn normalize_hostname(mut hostname: String) -> String {
485    if hostname.ends_with(".local.local.") {
486        let new_len = hostname.len() - "local.".len();
487        hostname.truncate(new_len);
488    }
489    hostname
490}
491
492/// This trait allows for parsing an input into a set of one or multiple [`Ipv4Addr`].
493pub trait AsIpAddrs {
494    fn as_ip_addrs(&self) -> Result<HashSet<IpAddr>>;
495}
496
497impl<T: AsIpAddrs> AsIpAddrs for &T {
498    fn as_ip_addrs(&self) -> Result<HashSet<IpAddr>> {
499        (*self).as_ip_addrs()
500    }
501}
502
503/// Supports one address or multiple addresses separated by `,`.
504/// For example: "127.0.0.1,127.0.0.2".
505///
506/// If the string is empty, will return an empty set.
507impl AsIpAddrs for &str {
508    fn as_ip_addrs(&self) -> Result<HashSet<IpAddr>> {
509        let mut addrs = HashSet::new();
510
511        if !self.is_empty() {
512            let iter = self.split(',').map(str::trim).map(IpAddr::from_str);
513            for addr in iter {
514                let addr = addr.map_err(|err| Error::ParseIpAddr(err.to_string()))?;
515                addrs.insert(addr);
516            }
517        }
518
519        Ok(addrs)
520    }
521}
522
523impl AsIpAddrs for String {
524    fn as_ip_addrs(&self) -> Result<HashSet<IpAddr>> {
525        self.as_str().as_ip_addrs()
526    }
527}
528
529/// Support slice. Example: &["127.0.0.1", "127.0.0.2"]
530impl<I: AsIpAddrs> AsIpAddrs for &[I] {
531    fn as_ip_addrs(&self) -> Result<HashSet<IpAddr>> {
532        let mut addrs = HashSet::new();
533
534        for result in self.iter().map(I::as_ip_addrs) {
535            addrs.extend(result?);
536        }
537
538        Ok(addrs)
539    }
540}
541
542/// Optimization for zero sized/empty values, as `()` will never take up any space or evaluate to
543/// anything, helpful in contexts where we just want an empty value.
544impl AsIpAddrs for () {
545    fn as_ip_addrs(&self) -> Result<HashSet<IpAddr>> {
546        Ok(HashSet::new())
547    }
548}
549
550impl AsIpAddrs for std::net::IpAddr {
551    fn as_ip_addrs(&self) -> Result<HashSet<IpAddr>> {
552        let mut ips = HashSet::new();
553        ips.insert(*self);
554
555        Ok(ips)
556    }
557}
558
559impl AsIpAddrs for Box<dyn AsIpAddrs> {
560    fn as_ip_addrs(&self) -> Result<HashSet<IpAddr>> {
561        self.as_ref().as_ip_addrs()
562    }
563}
564
565/// Represents properties in a TXT record.
566///
567/// The key string of a property is case insensitive, and only
568/// one [`TxtProperty`] is stored for the same key.
569///
570/// [RFC 6763](https://www.rfc-editor.org/rfc/rfc6763#section-6.4):
571/// "A given key SHOULD NOT appear more than once in a TXT record."
572#[derive(Debug, Clone, PartialEq, Eq)]
573pub struct TxtProperties {
574    // Use `Vec` instead of `HashMap` to keep the order of insertions.
575    properties: Vec<TxtProperty>,
576}
577
578impl Default for TxtProperties {
579    fn default() -> Self {
580        TxtProperties::new()
581    }
582}
583
584impl TxtProperties {
585    pub fn new() -> Self {
586        TxtProperties {
587            properties: Vec::new(),
588        }
589    }
590
591    /// Returns an iterator for all properties.
592    pub fn iter(&self) -> impl Iterator<Item = &TxtProperty> {
593        self.properties.iter()
594    }
595
596    /// Returns the number of properties.
597    pub fn len(&self) -> usize {
598        self.properties.len()
599    }
600
601    /// Returns if the properties are empty.
602    pub fn is_empty(&self) -> bool {
603        self.properties.is_empty()
604    }
605
606    /// Returns a property for a given `key`, where `key` is
607    /// case insensitive.
608    pub fn get(&self, key: &str) -> Option<&TxtProperty> {
609        let key = key.to_lowercase();
610        self.properties
611            .iter()
612            .find(|&prop| prop.key.to_lowercase() == key)
613    }
614
615    /// Returns a property value for a given `key`, where `key` is
616    /// case insensitive.
617    ///
618    /// Returns `None` if `key` does not exist.
619    /// Returns `Some(Option<&u8>)` for its value.
620    pub fn get_property_val(&self, key: &str) -> Option<Option<&[u8]>> {
621        self.get(key).map(|x| x.val())
622    }
623
624    /// Returns a property value string for a given `key`, where `key` is
625    /// case insensitive.
626    ///
627    /// Returns `None` if `key` does not exist.
628    /// Returns `Some("")` if its value is `None` or is empty.
629    pub fn get_property_val_str(&self, key: &str) -> Option<&str> {
630        self.get(key).map(|x| x.val_str())
631    }
632
633    /// Consumes properties and returns a hashmap, where the keys are the properties keys.
634    ///
635    /// If a property value is empty, return an empty string (because RFC 6763 allows empty values).
636    /// If a property value is non-empty but not valid UTF-8, skip the property and log a message.
637    pub fn into_property_map_str(self) -> HashMap<String, String> {
638        self.properties
639            .into_iter()
640            .filter_map(|property| {
641                let val_string = property.val.map_or(Some(String::new()), |val| {
642                    String::from_utf8(val)
643                        .map_err(|e| {
644                            debug!("Property value contains invalid UTF-8: {e}");
645                        })
646                        .ok()
647                })?;
648                Some((property.key, val_string))
649            })
650            .collect()
651    }
652}
653
654impl fmt::Display for TxtProperties {
655    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
656        let delimiter = ", ";
657        let props: Vec<String> = self.properties.iter().map(|p| p.to_string()).collect();
658        write!(f, "({})", props.join(delimiter))
659    }
660}
661
662impl From<&[u8]> for TxtProperties {
663    fn from(txt: &[u8]) -> Self {
664        let properties = decode_txt_unique(txt);
665        TxtProperties { properties }
666    }
667}
668
669/// Represents a property in a TXT record.
670#[derive(Clone, PartialEq, Eq)]
671pub struct TxtProperty {
672    /// The name of the property. The original cases are kept.
673    key: String,
674
675    /// RFC 6763 says values are bytes, not necessarily UTF-8.
676    /// It is also possible that there is no value, in which case
677    /// the key is a boolean key.
678    val: Option<Vec<u8>>,
679}
680
681impl TxtProperty {
682    /// Returns the key of a property.
683    pub fn key(&self) -> &str {
684        &self.key
685    }
686
687    /// Returns the value of a property, which could be `None`.
688    ///
689    /// To obtain a `&str` of the value, use `val_str()` instead.
690    pub fn val(&self) -> Option<&[u8]> {
691        self.val.as_deref()
692    }
693
694    /// Returns the value of a property as str.
695    pub fn val_str(&self) -> &str {
696        self.val
697            .as_ref()
698            .map_or("", |v| std::str::from_utf8(&v[..]).unwrap_or_default())
699    }
700}
701
702/// Supports constructing from a tuple.
703impl<K, V> From<&(K, V)> for TxtProperty
704where
705    K: ToString,
706    V: ToString,
707{
708    fn from(prop: &(K, V)) -> Self {
709        Self {
710            key: prop.0.to_string(),
711            val: Some(prop.1.to_string().into_bytes()),
712        }
713    }
714}
715
716impl<K, V> From<(K, V)> for TxtProperty
717where
718    K: ToString,
719    V: AsRef<[u8]>,
720{
721    fn from(prop: (K, V)) -> Self {
722        Self {
723            key: prop.0.to_string(),
724            val: Some(prop.1.as_ref().into()),
725        }
726    }
727}
728
729/// Support a property that has no value.
730impl From<&str> for TxtProperty {
731    fn from(key: &str) -> Self {
732        Self {
733            key: key.to_string(),
734            val: None,
735        }
736    }
737}
738
739impl fmt::Display for TxtProperty {
740    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
741        write!(f, "{}={}", self.key, self.val_str())
742    }
743}
744
745/// Mimic the default debug output for a struct, with a twist:
746/// - If self.var is UTF-8, will output it as a string in double quotes.
747/// - If self.var is not UTF-8, will output its bytes as in hex.
748impl fmt::Debug for TxtProperty {
749    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
750        let val_string = self.val.as_ref().map_or_else(
751            || "None".to_string(),
752            |v| {
753                std::str::from_utf8(&v[..]).map_or_else(
754                    |_| format!("Some({})", u8_slice_to_hex(&v[..])),
755                    |s| format!("Some(\"{s}\")"),
756                )
757            },
758        );
759
760        write!(
761            f,
762            "TxtProperty {{key: \"{}\", val: {}}}",
763            &self.key, &val_string,
764        )
765    }
766}
767
768const HEX_TABLE: [char; 16] = [
769    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f',
770];
771
772/// Create a hex string from `slice`, with a "0x" prefix.
773///
774/// For example, [1u8, 2u8] -> "0x0102"
775fn u8_slice_to_hex(slice: &[u8]) -> String {
776    let mut hex = String::with_capacity(slice.len() * 2 + 2);
777    hex.push_str("0x");
778    for b in slice {
779        hex.push(HEX_TABLE[(b >> 4) as usize]);
780        hex.push(HEX_TABLE[(b & 0x0F) as usize]);
781    }
782    hex
783}
784
785/// This trait allows for converting inputs into [`TxtProperties`].
786pub trait IntoTxtProperties {
787    fn into_txt_properties(self) -> TxtProperties;
788}
789
790impl IntoTxtProperties for HashMap<String, String> {
791    fn into_txt_properties(mut self) -> TxtProperties {
792        let properties = self
793            .drain()
794            .map(|(key, val)| TxtProperty {
795                key,
796                val: Some(val.into_bytes()),
797            })
798            .collect();
799        TxtProperties { properties }
800    }
801}
802
803/// Mainly for backward compatibility.
804impl IntoTxtProperties for Option<HashMap<String, String>> {
805    fn into_txt_properties(self) -> TxtProperties {
806        self.map_or_else(
807            || TxtProperties {
808                properties: Vec::new(),
809            },
810            |h| h.into_txt_properties(),
811        )
812    }
813}
814
815/// Support Vec like `[("k1", "v1"), ("k2", "v2")]`.
816impl<'a, T: 'a> IntoTxtProperties for &'a [T]
817where
818    TxtProperty: From<&'a T>,
819{
820    fn into_txt_properties(self) -> TxtProperties {
821        let mut properties = Vec::new();
822        let mut keys = HashSet::new();
823        for t in self.iter() {
824            let prop = TxtProperty::from(t);
825            let key = prop.key.to_lowercase();
826            if keys.insert(key) {
827                // Only push a new entry if the key did not exist.
828                //
829                // RFC 6763: https://www.rfc-editor.org/rfc/rfc6763#section-6.4
830                //
831                // "If a client receives a TXT record containing the same key more than
832                //    once, then the client MUST silently ignore all but the first
833                //    occurrence of that attribute. "
834                properties.push(prop);
835            }
836        }
837        TxtProperties { properties }
838    }
839}
840
841impl IntoTxtProperties for Vec<TxtProperty> {
842    fn into_txt_properties(self) -> TxtProperties {
843        TxtProperties { properties: self }
844    }
845}
846
847// Convert from properties key/value pairs to DNS TXT record content
848fn encode_txt<'a>(properties: impl Iterator<Item = &'a TxtProperty>) -> Vec<u8> {
849    let mut bytes = Vec::new();
850    for prop in properties {
851        let mut s = prop.key.clone().into_bytes();
852        if let Some(v) = &prop.val {
853            s.extend(b"=");
854            s.extend(v);
855        }
856
857        // Property that exceed the length are truncated
858        let sz: u8 = s.len().try_into().unwrap_or_else(|_| {
859            debug!("Property {} is too long, truncating to 255 bytes", prop.key);
860            s.resize(u8::MAX as usize, 0);
861            u8::MAX
862        });
863
864        // TXT uses (Length,Value) format for each property,
865        // i.e. the first byte is the length.
866        bytes.push(sz);
867        bytes.extend(s);
868    }
869    if bytes.is_empty() {
870        bytes.push(0);
871    }
872    bytes
873}
874
875// Convert from DNS TXT record content to key/value pairs
876pub(crate) fn decode_txt(txt: &[u8]) -> Vec<TxtProperty> {
877    let mut properties = Vec::new();
878    let mut offset = 0;
879    while offset < txt.len() {
880        let length = txt[offset] as usize;
881        if length == 0 {
882            break; // reached the end
883        }
884        offset += 1; // move over the length byte
885
886        let offset_end = offset + length;
887        if offset_end > txt.len() {
888            debug!("DNS TXT record contains invalid data: Size given for property would be out of range. (offset={}, length={}, offset_end={}, record length={})", offset, length, offset_end, txt.len());
889            break; // Skipping the rest of the record content, as the size for this property would already be out of range.
890        }
891        let kv_bytes = &txt[offset..offset_end];
892
893        // split key and val using the first `=`
894        let (k, v) = kv_bytes.iter().position(|&x| x == b'=').map_or_else(
895            || (kv_bytes.to_vec(), None),
896            |idx| (kv_bytes[..idx].to_vec(), Some(kv_bytes[idx + 1..].to_vec())),
897        );
898
899        // Make sure the key can be stored in UTF-8.
900        match String::from_utf8(k) {
901            Ok(k_string) => {
902                properties.push(TxtProperty {
903                    key: k_string,
904                    val: v,
905                });
906            }
907            Err(e) => debug!("failed to convert to String from key: {}", e),
908        }
909
910        offset += length;
911    }
912
913    properties
914}
915
916fn decode_txt_unique(txt: &[u8]) -> Vec<TxtProperty> {
917    let mut properties = decode_txt(txt);
918
919    // Remove duplicated keys and retain only the first appearance
920    // of each key.
921    let mut keys = HashSet::new();
922    properties.retain(|p| {
923        let key = p.key().to_lowercase();
924        keys.insert(key) // returns True if key is new.
925    });
926    properties
927}
928
929/// Returns true if `addr` is in the same network of `intf`.
930pub(crate) fn valid_ip_on_intf(addr: &IpAddr, if_addr: &IfAddr) -> bool {
931    match (addr, if_addr) {
932        (IpAddr::V4(addr), IfAddr::V4(if_v4)) => {
933            let netmask = u32::from(if_v4.netmask);
934            let intf_net = u32::from(if_v4.ip) & netmask;
935            let addr_net = u32::from(*addr) & netmask;
936            addr_net == intf_net
937        }
938        (IpAddr::V6(addr), IfAddr::V6(if_v6)) => {
939            let netmask = u128::from(if_v6.netmask);
940            let intf_net = u128::from(if_v6.ip) & netmask;
941            let addr_net = u128::from(*addr) & netmask;
942            addr_net == intf_net
943        }
944        _ => false,
945    }
946}
947
948/// A probing for a particular name.
949#[derive(Debug)]
950pub(crate) struct Probe {
951    /// All records probing for the same name.
952    pub(crate) records: Vec<DnsRecordBox>,
953
954    /// The fullnames of services that are probing these records.
955    /// These are the original service names, will not change per conflicts.
956    pub(crate) waiting_services: HashSet<String>,
957
958    /// The time (T) to send the first query .
959    pub(crate) start_time: u64,
960
961    /// The time to send the next (including the first) query.
962    pub(crate) next_send: u64,
963}
964
965impl Probe {
966    pub(crate) fn new(start_time: u64) -> Self {
967        // RFC 6762: https://datatracker.ietf.org/doc/html/rfc6762#section-8.1:
968        //
969        // "250 ms after the first query, the host should send a second; then,
970        //   250 ms after that, a third.  If, by 250 ms after the third probe, no
971        //   conflicting Multicast DNS responses have been received, the host may
972        //   move to the next step, announcing. "
973        let next_send = start_time;
974
975        Self {
976            records: Vec::new(),
977            waiting_services: HashSet::new(),
978            start_time,
979            next_send,
980        }
981    }
982
983    /// Add a new record with the same probing name in a sorted order.
984    pub(crate) fn insert_record(&mut self, record: DnsRecordBox) {
985        /*
986        RFC 6762: https://datatracker.ietf.org/doc/html/rfc6762#section-8.2.1
987
988        " The records are sorted using the same lexicographical order as
989        described above, that is, if the record classes differ, the record
990        with the lower class number comes first.  If the classes are the same
991        but the rrtypes differ, the record with the lower rrtype number comes
992        first."
993         */
994        let insert_position = self
995            .records
996            .binary_search_by(
997                |existing| match existing.get_class().cmp(&record.get_class()) {
998                    std::cmp::Ordering::Equal => existing.get_type().cmp(&record.get_type()),
999                    other => other,
1000                },
1001            )
1002            .unwrap_or_else(|pos| pos);
1003
1004        self.records.insert(insert_position, record);
1005    }
1006
1007    /// Compares with `incoming` records. Postpone probe and retry if we yield.
1008    pub(crate) fn tiebreaking(&mut self, incoming: &[&DnsRecordBox], now: u64, probe_name: &str) {
1009        /*
1010        RFC 6762 section 8.2: https://datatracker.ietf.org/doc/html/rfc6762#section-8.2
1011        ...
1012        if the host finds that its own data is lexicographically later, it
1013        simply ignores the other host's probe.  If the host finds that its
1014        own data is lexicographically earlier, then it defers to the winning
1015        host by waiting one second, and then begins probing for this record
1016        again.
1017        */
1018        let min_len = self.records.len().min(incoming.len());
1019
1020        // Compare elements up to the length of the shorter vector
1021        let mut cmp_result = cmp::Ordering::Equal;
1022        for (i, incoming_record) in incoming.iter().enumerate().take(min_len) {
1023            match self.records[i].compare(incoming_record.as_ref()) {
1024                cmp::Ordering::Equal => continue,
1025                other => {
1026                    cmp_result = other;
1027                    break; // exit loop on first difference
1028                }
1029            }
1030        }
1031
1032        if cmp_result == cmp::Ordering::Equal {
1033            // If all compared records are equal, compare the lengths of the records.
1034            cmp_result = self.records.len().cmp(&incoming.len());
1035        }
1036
1037        match cmp_result {
1038            cmp::Ordering::Less => {
1039                debug!("tiebreaking '{probe_name}': LOST, will wait for one second",);
1040                self.start_time = now + 1000; // wait and restart.
1041                self.next_send = now + 1000;
1042            }
1043            ordering => {
1044                debug!("tiebreaking '{probe_name}': {:?}", ordering);
1045            }
1046        }
1047    }
1048
1049    pub(crate) fn update_next_send(&mut self, now: u64) {
1050        self.next_send = now + 250;
1051    }
1052
1053    /// Returns whether this probe is finished.
1054    pub(crate) fn expired(&self, now: u64) -> bool {
1055        // The 2nd query is T + 250ms, the 3rd query is T + 500ms,
1056        // The expire time is T + 750ms
1057        now >= self.start_time + 750
1058    }
1059}
1060
1061/// DNS records of all the registered services.
1062pub(crate) struct DnsRegistry {
1063    /// keyed by the name of all related records.
1064    /*
1065     When a host is probing for a group of related records with the same
1066    name (e.g., the SRV and TXT record describing a DNS-SD service), only
1067    a single question need be placed in the Question Section, since query
1068    type "ANY" (255) is used, which will elicit answers for all records
1069    with that name.  However, for tiebreaking to work correctly in all
1070    cases, the Authority Section must contain *all* the records and
1071    proposed rdata being probed for uniqueness.
1072     */
1073    pub(crate) probing: HashMap<String, Probe>,
1074
1075    /// Already done probing, or no need to probe.
1076    pub(crate) active: HashMap<String, Vec<DnsRecordBox>>,
1077
1078    /// timers of the newly added probes.
1079    pub(crate) new_timers: Vec<u64>,
1080
1081    /// Mapping from original names to new names.
1082    pub(crate) name_changes: HashMap<String, String>,
1083}
1084
1085impl DnsRegistry {
1086    pub(crate) fn new() -> Self {
1087        Self {
1088            probing: HashMap::new(),
1089            active: HashMap::new(),
1090            new_timers: Vec::new(),
1091            name_changes: HashMap::new(),
1092        }
1093    }
1094
1095    pub(crate) fn is_probing_done<T>(
1096        &mut self,
1097        answer: &T,
1098        service_name: &str,
1099        start_time: u64,
1100    ) -> bool
1101    where
1102        T: DnsRecordExt + Send + 'static,
1103    {
1104        if let Some(active_records) = self.active.get(answer.get_name()) {
1105            for record in active_records.iter() {
1106                if answer.matches(record.as_ref()) {
1107                    debug!(
1108                        "found active record {} {}",
1109                        answer.get_type(),
1110                        answer.get_name(),
1111                    );
1112                    return true;
1113                }
1114            }
1115        }
1116
1117        let probe = self
1118            .probing
1119            .entry(answer.get_name().to_string())
1120            .or_insert_with(|| {
1121                debug!("new probe of {}", answer.get_name());
1122                Probe::new(start_time)
1123            });
1124
1125        self.new_timers.push(probe.next_send);
1126
1127        for record in probe.records.iter() {
1128            if answer.matches(record.as_ref()) {
1129                debug!(
1130                    "found existing record {} in probe of '{}'",
1131                    answer.get_type(),
1132                    answer.get_name(),
1133                );
1134                probe.waiting_services.insert(service_name.to_string());
1135                return false; // Found existing probe for the same record.
1136            }
1137        }
1138
1139        debug!(
1140            "insert record {} into probe of {}",
1141            answer.get_type(),
1142            answer.get_name(),
1143        );
1144        probe.insert_record(answer.clone_box());
1145        probe.waiting_services.insert(service_name.to_string());
1146
1147        false
1148    }
1149
1150    /// check all records in "probing" and "active":
1151    /// if the record is SRV, and hostname is set to original, remove it.
1152    /// and create a new SRV with "host" set to "new_name" and put into "probing".
1153    pub(crate) fn update_hostname(
1154        &mut self,
1155        original: &str,
1156        new_name: &str,
1157        probe_time: u64,
1158    ) -> bool {
1159        let mut found_records = Vec::new();
1160        let mut new_timer_added = false;
1161
1162        for (_name, probe) in self.probing.iter_mut() {
1163            probe.records.retain(|record| {
1164                if record.get_type() == RRType::SRV {
1165                    if let Some(srv) = record.any().downcast_ref::<DnsSrv>() {
1166                        if srv.host() == original {
1167                            let mut new_record = srv.clone();
1168                            new_record.set_host(new_name.to_string());
1169                            found_records.push(new_record);
1170                            return false;
1171                        }
1172                    }
1173                }
1174                true
1175            });
1176        }
1177
1178        for (_name, records) in self.active.iter_mut() {
1179            records.retain(|record| {
1180                if record.get_type() == RRType::SRV {
1181                    if let Some(srv) = record.any().downcast_ref::<DnsSrv>() {
1182                        if srv.host() == original {
1183                            let mut new_record = srv.clone();
1184                            new_record.set_host(new_name.to_string());
1185                            found_records.push(new_record);
1186                            return false;
1187                        }
1188                    }
1189                }
1190                true
1191            });
1192        }
1193
1194        for record in found_records {
1195            let probe = match self.probing.get_mut(record.get_name()) {
1196                Some(p) => {
1197                    p.start_time = probe_time; // restart this probe.
1198                    p
1199                }
1200                None => {
1201                    let new_probe = self
1202                        .probing
1203                        .entry(record.get_name().to_string())
1204                        .or_insert_with(|| Probe::new(probe_time));
1205                    new_timer_added = true;
1206                    new_probe
1207                }
1208            };
1209
1210            debug!(
1211                "insert record {} with new hostname {new_name} into probe for: {}",
1212                record.get_type(),
1213                record.get_name()
1214            );
1215            probe.insert_record(record.boxed());
1216        }
1217
1218        new_timer_added
1219    }
1220}
1221
1222/// Returns a tuple of (service_type_domain, optional_sub_domain)
1223pub(crate) fn split_sub_domain(domain: &str) -> (&str, Option<&str>) {
1224    if let Some((_, ty_domain)) = domain.rsplit_once("._sub.") {
1225        (ty_domain, Some(domain))
1226    } else {
1227        (domain, None)
1228    }
1229}
1230
1231/// Returns true if `addr` is a unicast link-local IPv6 address.
1232/// Replicates the logic from `std::net::Ipv6Addr::is_unicast_link_local()`, which is not
1233/// stable on the current mdns-sd Rust version (1.71.0).
1234///
1235/// https://github.com/rust-lang/rust/blob/9fc6b43126469e3858e2fe86cafb4f0fd5068869/library/core/src/net/ip_addr.rs#L1684
1236fn is_unicast_link_local(addr: &Ipv6Addr) -> bool {
1237    (addr.segments()[0] & 0xffc0) == 0xfe80
1238}
1239
1240/// Represents a resolved service as a plain data struct.
1241/// This is from a client (i.e. querier) point of view.
1242#[derive(Clone, Debug)]
1243#[non_exhaustive]
1244pub struct ResolvedService {
1245    /// Service type and domain. For example, "_http._tcp.local."
1246    pub ty_domain: String,
1247
1248    /// Optional service subtype and domain.
1249    ///
1250    /// See RFC6763 section 7.1 about "Subtypes":
1251    /// <https://datatracker.ietf.org/doc/html/rfc6763#section-7.1>
1252    /// For example, "_printer._sub._http._tcp.local."
1253    pub sub_ty_domain: Option<String>,
1254
1255    /// Full name of the service. For example, "my-service._http._tcp.local."
1256    pub fullname: String,
1257
1258    /// Host name of the service. For example, "my-server1.local."
1259    pub host: String,
1260
1261    /// Port of the service. I.e. TCP or UDP port.
1262    pub port: u16,
1263
1264    /// Addresses of the service. IPv4 or IPv6 addresses.
1265    pub addresses: HashSet<ScopedIp>,
1266
1267    /// Properties of the service, decoded from TXT record.
1268    pub txt_properties: TxtProperties,
1269}
1270
1271impl ResolvedService {
1272    /// Returns true if the service data is valid, i.e. ready to be used.
1273    pub fn is_valid(&self) -> bool {
1274        let some_missing = self.ty_domain.is_empty()
1275            || self.fullname.is_empty()
1276            || self.host.is_empty()
1277            || self.addresses.is_empty();
1278        !some_missing
1279    }
1280
1281    #[inline]
1282    pub const fn get_subtype(&self) -> &Option<String> {
1283        &self.sub_ty_domain
1284    }
1285
1286    #[inline]
1287    pub fn get_fullname(&self) -> &str {
1288        &self.fullname
1289    }
1290
1291    #[inline]
1292    pub fn get_hostname(&self) -> &str {
1293        &self.host
1294    }
1295
1296    #[inline]
1297    pub fn get_port(&self) -> u16 {
1298        self.port
1299    }
1300
1301    #[inline]
1302    pub fn get_addresses(&self) -> &HashSet<ScopedIp> {
1303        &self.addresses
1304    }
1305
1306    pub fn get_addresses_v4(&self) -> HashSet<Ipv4Addr> {
1307        self.addresses
1308            .iter()
1309            .filter_map(|ip| match ip {
1310                ScopedIp::V4(ipv4) => Some(*ipv4.addr()),
1311                _ => None,
1312            })
1313            .collect()
1314    }
1315
1316    #[inline]
1317    pub fn get_properties(&self) -> &TxtProperties {
1318        &self.txt_properties
1319    }
1320
1321    #[inline]
1322    pub fn get_property(&self, key: &str) -> Option<&TxtProperty> {
1323        self.txt_properties.get(key)
1324    }
1325
1326    pub fn get_property_val(&self, key: &str) -> Option<Option<&[u8]>> {
1327        self.txt_properties.get_property_val(key)
1328    }
1329
1330    pub fn get_property_val_str(&self, key: &str) -> Option<&str> {
1331        self.txt_properties.get_property_val_str(key)
1332    }
1333}
1334
1335#[cfg(test)]
1336mod tests {
1337    use super::{decode_txt, encode_txt, u8_slice_to_hex, ServiceInfo, TxtProperty};
1338    use crate::IfKind;
1339    use if_addrs::{IfAddr, IfOperStatus, Ifv4Addr, Ifv6Addr, Interface};
1340    use std::net::{Ipv4Addr, Ipv6Addr};
1341
1342    #[test]
1343    fn test_txt_encode_decode() {
1344        let properties = [
1345            TxtProperty::from(&("key1", "value1")),
1346            TxtProperty::from(&("key2", "value2")),
1347        ];
1348
1349        // test encode
1350        let property_count = properties.len();
1351        let encoded = encode_txt(properties.iter());
1352        assert_eq!(
1353            encoded.len(),
1354            "key1=value1".len() + "key2=value2".len() + property_count
1355        );
1356        assert_eq!(encoded[0] as usize, "key1=value1".len());
1357
1358        // test decode
1359        let decoded = decode_txt(&encoded);
1360        assert!(properties[..] == decoded[..]);
1361
1362        // test empty value
1363        let properties = vec![TxtProperty::from(&("key3", ""))];
1364        let property_count = properties.len();
1365        let encoded = encode_txt(properties.iter());
1366        assert_eq!(encoded.len(), "key3=".len() + property_count);
1367
1368        let decoded = decode_txt(&encoded);
1369        assert_eq!(properties, decoded);
1370
1371        // test non-string value
1372        let binary_val: Vec<u8> = vec![123, 234, 0];
1373        let binary_len = binary_val.len();
1374        let properties = vec![TxtProperty::from(("key4", binary_val))];
1375        let property_count = properties.len();
1376        let encoded = encode_txt(properties.iter());
1377        assert_eq!(encoded.len(), "key4=".len() + binary_len + property_count);
1378
1379        let decoded = decode_txt(&encoded);
1380        assert_eq!(properties, decoded);
1381
1382        // test value that contains '='
1383        let properties = vec![TxtProperty::from(("key5", "val=5"))];
1384        let property_count = properties.len();
1385        let encoded = encode_txt(properties.iter());
1386        assert_eq!(
1387            encoded.len(),
1388            "key5=".len() + "val=5".len() + property_count
1389        );
1390
1391        let decoded = decode_txt(&encoded);
1392        assert_eq!(properties, decoded);
1393
1394        // test a property that has no value.
1395        let properties = vec![TxtProperty::from("key6")];
1396        let property_count = properties.len();
1397        let encoded = encode_txt(properties.iter());
1398        assert_eq!(encoded.len(), "key6".len() + property_count);
1399        let decoded = decode_txt(&encoded);
1400        assert_eq!(properties, decoded);
1401
1402        // test very long property.
1403        let properties = [TxtProperty::from(
1404            String::from_utf8(vec![0x30; 1024]).unwrap().as_str(), // A long string of 0 char
1405        )];
1406        let property_count = properties.len();
1407        let encoded = encode_txt(properties.iter());
1408        assert_eq!(encoded.len(), 255 + property_count);
1409        let decoded = decode_txt(&encoded);
1410        assert_eq!(
1411            vec![TxtProperty::from(
1412                String::from_utf8(vec![0x30; 255]).unwrap().as_str()
1413            )],
1414            decoded
1415        );
1416    }
1417
1418    #[test]
1419    fn test_set_properties_from_txt() {
1420        // Three duplicated keys.
1421        let properties = [
1422            TxtProperty::from(&("one", "1")),
1423            TxtProperty::from(&("ONE", "2")),
1424            TxtProperty::from(&("One", "3")),
1425        ];
1426        let encoded = encode_txt(properties.iter());
1427
1428        // Simple decode does not remove duplicated keys.
1429        let decoded = decode_txt(&encoded);
1430        assert_eq!(decoded.len(), 3);
1431
1432        // ServiceInfo removes duplicated keys and keeps only the first one.
1433        let mut service_info =
1434            ServiceInfo::new("_test._tcp", "prop_test", "localhost", "", 1234, None).unwrap();
1435        service_info._set_properties_from_txt(&encoded);
1436        assert_eq!(service_info.get_properties().len(), 1);
1437
1438        // Verify the only one property.
1439        let prop = service_info.get_properties().iter().next().unwrap();
1440        assert_eq!(prop.key, "one");
1441        assert_eq!(prop.val_str(), "1");
1442    }
1443
1444    #[test]
1445    fn test_u8_slice_to_hex() {
1446        let bytes = [0x01u8, 0x02u8, 0x03u8];
1447        let hex = u8_slice_to_hex(&bytes);
1448        assert_eq!(hex.as_str(), "0x010203");
1449
1450        let slice = "abcdefghijklmnopqrstuvwxyz";
1451        let hex = u8_slice_to_hex(slice.as_bytes());
1452        assert_eq!(hex.len(), slice.len() * 2 + 2);
1453        assert_eq!(
1454            hex.as_str(),
1455            "0x6162636465666768696a6b6c6d6e6f707172737475767778797a"
1456        );
1457    }
1458
1459    #[test]
1460    fn test_txt_property_debug() {
1461        // Test UTF-8 property value.
1462        let prop_1 = TxtProperty {
1463            key: "key1".to_string(),
1464            val: Some("val1".to_string().into()),
1465        };
1466        let prop_1_debug = format!("{:?}", &prop_1);
1467        assert_eq!(
1468            prop_1_debug,
1469            "TxtProperty {key: \"key1\", val: Some(\"val1\")}"
1470        );
1471
1472        // Test non-UTF-8 property value.
1473        let prop_2 = TxtProperty {
1474            key: "key2".to_string(),
1475            val: Some(vec![150u8, 151u8, 152u8]),
1476        };
1477        let prop_2_debug = format!("{:?}", &prop_2);
1478        assert_eq!(
1479            prop_2_debug,
1480            "TxtProperty {key: \"key2\", val: Some(0x969798)}"
1481        );
1482    }
1483
1484    #[test]
1485    fn test_txt_decode_property_size_out_of_bounds() {
1486        // Construct a TXT record with an invalid property length that would be out of bounds.
1487        let encoded: Vec<u8> = vec![
1488            0x0b, // Length 11
1489            b'k', b'e', b'y', b'1', b'=', b'v', b'a', b'l', b'u', b'e',
1490            b'1', // key1=value1 (Length 11)
1491            0x10, // Length 16 (Would be out of bounds)
1492            b'k', b'e', b'y', b'2', b'=', b'v', b'a', b'l', b'u', b'e',
1493            b'2', // key2=value2 (Length 11)
1494        ];
1495        // Decode the record content
1496        let decoded = decode_txt(&encoded);
1497        // We expect the out of bounds length for the second property to have caused the rest of the record content to be skipped.
1498        // Test that we only parsed the first property.
1499        assert_eq!(decoded.len(), 1);
1500        // Test that the key of the property we parsed is "key1"
1501        assert_eq!(decoded[0].key, "key1");
1502    }
1503
1504    #[test]
1505    fn test_is_address_supported() {
1506        let mut service_info =
1507            ServiceInfo::new("_test._tcp", "prop_test", "testhost", "", 1234, None).unwrap();
1508
1509        let intf_v6 = Interface {
1510            name: "foo".to_string(),
1511            index: Some(1),
1512            addr: IfAddr::V6(Ifv6Addr {
1513                ip: Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0x1234, 0, 0, 1),
1514                netmask: Ipv6Addr::new(0xffff, 0xffff, 0xffff, 0xffff, 0, 0, 0, 0),
1515                broadcast: None,
1516                prefixlen: 16,
1517            }),
1518            oper_status: IfOperStatus::Up,
1519            #[cfg(windows)]
1520            adapter_name: String::new(),
1521        };
1522
1523        let intf_v4 = Interface {
1524            name: "bar".to_string(),
1525            index: Some(1),
1526            addr: IfAddr::V4(Ifv4Addr {
1527                ip: Ipv4Addr::new(192, 1, 2, 3),
1528                netmask: Ipv4Addr::new(255, 255, 0, 0),
1529                broadcast: None,
1530                prefixlen: 16,
1531            }),
1532            oper_status: IfOperStatus::Up,
1533            #[cfg(windows)]
1534            adapter_name: String::new(),
1535        };
1536
1537        let intf_baz = Interface {
1538            name: "baz".to_string(),
1539            index: Some(1),
1540            addr: IfAddr::V6(Ifv6Addr {
1541                ip: Ipv6Addr::new(0x2003, 0xdb8, 0, 0, 0x1234, 0, 0, 1),
1542                netmask: Ipv6Addr::new(0xffff, 0xffff, 0xffff, 0xffff, 0, 0, 0, 0),
1543                broadcast: None,
1544                prefixlen: 16,
1545            }),
1546            oper_status: IfOperStatus::Up,
1547            #[cfg(windows)]
1548            adapter_name: String::new(),
1549        };
1550
1551        let intf_loopback_v4 = Interface {
1552            name: "foo".to_string(),
1553            index: Some(1),
1554            addr: IfAddr::V4(Ifv4Addr {
1555                ip: Ipv4Addr::new(127, 0, 0, 1),
1556                netmask: Ipv4Addr::new(255, 255, 255, 255),
1557                broadcast: None,
1558                prefixlen: 16,
1559            }),
1560            oper_status: IfOperStatus::Up,
1561            #[cfg(windows)]
1562            adapter_name: String::new(),
1563        };
1564
1565        let intf_loopback_v6 = Interface {
1566            name: "foo".to_string(),
1567            index: Some(1),
1568            addr: IfAddr::V6(Ifv6Addr {
1569                ip: Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1),
1570                netmask: Ipv6Addr::new(
1571                    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,
1572                ),
1573                broadcast: None,
1574                prefixlen: 16,
1575            }),
1576            oper_status: IfOperStatus::Up,
1577            #[cfg(windows)]
1578            adapter_name: String::new(),
1579        };
1580
1581        let intf_link_local_v4 = Interface {
1582            name: "foo".to_string(),
1583            index: Some(1),
1584            addr: IfAddr::V4(Ifv4Addr {
1585                ip: Ipv4Addr::new(169, 254, 0, 1),
1586                netmask: Ipv4Addr::new(255, 255, 0, 0),
1587                broadcast: None,
1588                prefixlen: 16,
1589            }),
1590            oper_status: IfOperStatus::Up,
1591            #[cfg(windows)]
1592            adapter_name: String::new(),
1593        };
1594
1595        let intf_link_local_v6 = Interface {
1596            name: "foo".to_string(),
1597            index: Some(1),
1598            addr: IfAddr::V6(Ifv6Addr {
1599                ip: Ipv6Addr::new(0xfe80, 0, 0, 0, 0x1234, 0, 0, 1),
1600                netmask: Ipv6Addr::new(0xffff, 0xffff, 0xffff, 0xffff, 0, 0, 0, 0),
1601                broadcast: None,
1602                prefixlen: 16,
1603            }),
1604            oper_status: IfOperStatus::Up,
1605            #[cfg(windows)]
1606            adapter_name: String::new(),
1607        };
1608
1609        // supported addresses not specified
1610        assert!(service_info.is_address_supported(&intf_v6));
1611
1612        // Interface not supported
1613        service_info.set_interfaces(vec![
1614            IfKind::Name("foo".to_string()),
1615            IfKind::Name("bar".to_string()),
1616        ]);
1617        assert!(!service_info.is_address_supported(&intf_baz));
1618
1619        // link-local only
1620        service_info.set_link_local_only(true);
1621        assert!(!service_info.is_address_supported(&intf_v4));
1622        assert!(!service_info.is_address_supported(&intf_v6));
1623        assert!(service_info.is_address_supported(&intf_link_local_v4));
1624        assert!(service_info.is_address_supported(&intf_link_local_v6));
1625        service_info.set_link_local_only(false);
1626
1627        // supported interfaces: IfKing::All
1628        service_info.set_interfaces(vec![IfKind::All]);
1629        assert!(service_info.is_address_supported(&intf_v6));
1630        assert!(service_info.is_address_supported(&intf_v4));
1631
1632        // supported interfaces: IfKind::IPv6
1633        service_info.set_interfaces(vec![IfKind::IPv6]);
1634        assert!(service_info.is_address_supported(&intf_v6));
1635        assert!(!service_info.is_address_supported(&intf_v4));
1636
1637        // supported interfaces: IfKind::IPv4
1638        service_info.set_interfaces(vec![IfKind::IPv4]);
1639        assert!(service_info.is_address_supported(&intf_v4));
1640        assert!(!service_info.is_address_supported(&intf_v6));
1641
1642        // supported interfaces: IfKind::Addr
1643        service_info.set_interfaces(vec![IfKind::Addr(intf_v6.ip())]);
1644        assert!(service_info.is_address_supported(&intf_v6));
1645        assert!(!service_info.is_address_supported(&intf_v4));
1646
1647        // supported interfaces: IfKind::LoopbackV4
1648        service_info.set_interfaces(vec![IfKind::LoopbackV4]);
1649        assert!(service_info.is_address_supported(&intf_loopback_v4));
1650        assert!(!service_info.is_address_supported(&intf_loopback_v6));
1651
1652        // supported interfaces: IfKind::LoopbackV6
1653        service_info.set_interfaces(vec![IfKind::LoopbackV6]);
1654        assert!(!service_info.is_address_supported(&intf_loopback_v4));
1655        assert!(service_info.is_address_supported(&intf_loopback_v6));
1656    }
1657}