1#[cfg(feature = "logging")]
4use crate::log::{debug, trace};
5use crate::{
6 dns_parser::{DnsIncoming, 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 fmt,
15 net::{IpAddr, Ipv4Addr},
16 str::FromStr,
17};
18
19#[cfg(feature = "serde")]
20use serde::{Deserialize, Serialize};
21
22const DNS_HOST_TTL: u32 = 120; const DNS_OTHER_TTL: u32 = 4500; #[derive(Debug)]
28pub(crate) struct MyIntf {
29 pub(crate) name: String,
31
32 pub(crate) index: u32,
34
35 pub(crate) addrs: HashSet<IfAddr>,
37}
38
39impl MyIntf {
40 pub(crate) fn next_ifaddr_v4(&self) -> Option<&IfAddr> {
41 self.addrs.iter().find(|a| a.ip().is_ipv4())
42 }
43
44 pub(crate) fn next_ifaddr_v6(&self) -> Option<&IfAddr> {
45 self.addrs.iter().find(|a| a.ip().is_ipv6())
46 }
47}
48
49impl From<&MyIntf> for InterfaceId {
50 fn from(my_intf: &MyIntf) -> Self {
51 InterfaceId {
52 name: my_intf.name.clone(),
53 index: my_intf.index,
54 }
55 }
56}
57
58fn escape_instance_name(name: &str) -> String {
67 let mut result = String::with_capacity(name.len() + 10); for ch in name.chars() {
70 match ch {
71 '.' => {
72 result.push('\\');
73 result.push('.');
74 }
75 '\\' => {
76 result.push('\\');
77 result.push('\\');
78 }
79 _ => result.push(ch),
80 }
81 }
82
83 result
84}
85
86#[derive(Debug, Clone)]
91pub struct ServiceInfo {
92 ty_domain: String,
96
97 sub_domain: Option<String>, fullname: String, server: String, addresses: HashSet<IpAddr>,
104 port: u16,
105 host_ttl: u32, other_ttl: u32, priority: u16,
108 weight: u16,
109 txt_properties: TxtProperties,
110 addr_auto: bool, status: HashMap<u32, ServiceStatus>, requires_probe: bool,
116
117 supported_intfs: Vec<IfKind>,
119
120 is_link_local_only: bool,
122}
123
124#[derive(Debug, Clone, PartialEq, Eq)]
125pub(crate) enum ServiceStatus {
126 Probing,
127 Announced,
128 Unknown,
129}
130
131impl ServiceInfo {
132 pub fn new<Ip: AsIpAddrs, P: IntoTxtProperties>(
168 ty_domain: &str,
169 my_name: &str,
170 host_name: &str,
171 ip: Ip,
172 port: u16,
173 properties: P,
174 ) -> Result<Self> {
175 let (ty_domain, sub_domain) = split_sub_domain(ty_domain);
176
177 let escaped_name = escape_instance_name(my_name);
178 let fullname = format!("{escaped_name}.{ty_domain}");
179 let ty_domain = ty_domain.to_string();
180 let sub_domain = sub_domain.map(str::to_string);
181 let server = normalize_hostname(host_name.to_string());
182 let addresses = ip.as_ip_addrs()?;
183 let txt_properties = properties.into_txt_properties();
184
185 for prop in txt_properties.iter() {
189 let key = prop.key();
190 if !key.is_ascii() {
191 return Err(Error::Msg(format!(
192 "TXT property key {} is not ASCII",
193 prop.key()
194 )));
195 }
196 if key.contains('=') {
197 return Err(Error::Msg(format!(
198 "TXT property key {} contains '='",
199 prop.key()
200 )));
201 }
202
203 let prop_len = key.len() + prop.val().map_or(0, |v| v.len() + 1);
206 if prop_len > u8::MAX as usize {
207 return Err(Error::Msg(format!(
208 "TXT property '{}' has length {} bytes, exceeding the 255-byte limit",
209 key, prop_len
210 )));
211 }
212 }
213
214 let this = Self {
215 ty_domain,
216 sub_domain,
217 fullname,
218 server,
219 addresses,
220 port,
221 host_ttl: DNS_HOST_TTL,
222 other_ttl: DNS_OTHER_TTL,
223 priority: 0,
224 weight: 0,
225 txt_properties,
226 addr_auto: false,
227 status: HashMap::new(),
228 requires_probe: true,
229 is_link_local_only: false,
230 supported_intfs: vec![IfKind::All],
231 };
232
233 Ok(this)
234 }
235
236 pub const fn enable_addr_auto(mut self) -> Self {
240 self.addr_auto = true;
241 self
242 }
243
244 pub const fn is_addr_auto(&self) -> bool {
247 self.addr_auto
248 }
249
250 pub fn set_requires_probe(&mut self, enable: bool) {
255 self.requires_probe = enable;
256 }
257
258 pub fn set_link_local_only(&mut self, is_link_local_only: bool) {
262 self.is_link_local_only = is_link_local_only;
263 }
264
265 pub fn set_interfaces(&mut self, intfs: Vec<IfKind>) {
270 self.supported_intfs = intfs;
271 }
272
273 pub const fn requires_probe(&self) -> bool {
277 self.requires_probe
278 }
279
280 #[inline]
284 pub fn get_type(&self) -> &str {
285 &self.ty_domain
286 }
287
288 #[inline]
293 pub const fn get_subtype(&self) -> &Option<String> {
294 &self.sub_domain
295 }
296
297 pub(crate) fn matches_type_or_subtype(&self, name: &str) -> bool {
299 name == self.get_type() || self.get_subtype().as_ref().is_some_and(|v| v == name)
300 }
301
302 #[inline]
306 pub fn get_fullname(&self) -> &str {
307 &self.fullname
308 }
309
310 #[inline]
312 pub const fn get_properties(&self) -> &TxtProperties {
313 &self.txt_properties
314 }
315
316 pub fn get_property(&self, key: &str) -> Option<&TxtProperty> {
321 self.txt_properties.get(key)
322 }
323
324 pub fn get_property_val(&self, key: &str) -> Option<Option<&[u8]>> {
329 self.txt_properties.get_property_val(key)
330 }
331
332 pub fn get_property_val_str(&self, key: &str) -> Option<&str> {
337 self.txt_properties.get_property_val_str(key)
338 }
339
340 #[inline]
342 pub fn get_hostname(&self) -> &str {
343 &self.server
344 }
345
346 #[inline]
348 pub const fn get_port(&self) -> u16 {
349 self.port
350 }
351
352 #[inline]
354 pub const fn get_addresses(&self) -> &HashSet<IpAddr> {
355 &self.addresses
356 }
357
358 pub fn get_addresses_v4(&self) -> HashSet<&Ipv4Addr> {
360 let mut ipv4_addresses = HashSet::new();
361
362 for ip in &self.addresses {
363 if let IpAddr::V4(ipv4) = ip {
364 ipv4_addresses.insert(ipv4);
365 }
366 }
367
368 ipv4_addresses
369 }
370
371 #[inline]
373 pub const fn get_host_ttl(&self) -> u32 {
374 self.host_ttl
375 }
376
377 #[inline]
379 pub const fn get_other_ttl(&self) -> u32 {
380 self.other_ttl
381 }
382
383 #[inline]
385 pub const fn get_priority(&self) -> u16 {
386 self.priority
387 }
388
389 #[inline]
391 pub const fn get_weight(&self) -> u16 {
392 self.weight
393 }
394
395 pub(crate) fn get_addrs_on_my_intf_v4(&self, my_intf: &MyIntf) -> Vec<IpAddr> {
397 self.addresses
398 .iter()
399 .filter(|a| a.is_ipv4() && my_intf.addrs.iter().any(|x| valid_ip_on_intf(a, x)))
400 .copied()
401 .collect()
402 }
403
404 pub(crate) fn get_addrs_on_my_intf_v6(&self, my_intf: &MyIntf) -> Vec<IpAddr> {
405 self.addresses
406 .iter()
407 .filter(|a| a.is_ipv6() && my_intf.addrs.iter().any(|x| valid_ip_on_intf(a, x)))
408 .copied()
409 .collect()
410 }
411
412 pub(crate) fn _is_ready(&self) -> bool {
414 let some_missing = self.ty_domain.is_empty()
415 || self.fullname.is_empty()
416 || self.server.is_empty()
417 || self.addresses.is_empty();
418 !some_missing
419 }
420
421 pub(crate) fn insert_ipaddr(&mut self, intf: &Interface) {
423 if self.is_address_supported(intf) {
424 self.addresses.insert(intf.addr.ip());
425 } else {
426 trace!(
427 "skipping unsupported address {} for service {}",
428 intf.addr.ip(),
429 self.fullname
430 );
431 }
432 }
433
434 pub(crate) fn remove_ipaddr(&mut self, addr: &IpAddr) {
435 self.addresses.remove(addr);
436 }
437
438 pub(crate) fn generate_txt(&self) -> Vec<u8> {
439 encode_txt(self.get_properties().iter())
440 }
441
442 pub(crate) fn _set_port(&mut self, port: u16) {
443 self.port = port;
444 }
445
446 pub(crate) fn _set_hostname(&mut self, hostname: String) {
447 self.server = normalize_hostname(hostname);
448 }
449
450 pub(crate) fn _set_properties_from_txt(&mut self, txt: &[u8]) -> bool {
452 let properties = decode_txt_unique(txt);
453 if self.txt_properties.properties != properties {
454 self.txt_properties = TxtProperties { properties };
455 true
456 } else {
457 false
458 }
459 }
460
461 pub(crate) fn _set_subtype(&mut self, subtype: String) {
462 self.sub_domain = Some(subtype);
463 }
464
465 pub(crate) fn _set_host_ttl(&mut self, ttl: u32) {
468 self.host_ttl = ttl;
469 }
470
471 pub(crate) fn _set_other_ttl(&mut self, ttl: u32) {
473 self.other_ttl = ttl;
474 }
475
476 pub(crate) fn set_status(&mut self, if_index: u32, status: ServiceStatus) {
477 match self.status.get_mut(&if_index) {
478 Some(service_status) => {
479 *service_status = status;
480 }
481 None => {
482 self.status.entry(if_index).or_insert(status);
483 }
484 }
485 }
486
487 pub(crate) fn get_status(&self, intf: u32) -> ServiceStatus {
488 self.status
489 .get(&intf)
490 .cloned()
491 .unwrap_or(ServiceStatus::Unknown)
492 }
493
494 pub fn as_resolved_service(self) -> ResolvedService {
496 let addresses: HashSet<ScopedIp> = self.addresses.into_iter().map(|a| a.into()).collect();
497 ResolvedService {
498 ty_domain: self.ty_domain,
499 sub_ty_domain: self.sub_domain,
500 fullname: self.fullname,
501 host: self.server,
502 port: self.port,
503 addresses,
504 txt_properties: self.txt_properties,
505 }
506 }
507
508 fn is_address_supported(&self, intf: &Interface) -> bool {
509 let addr = intf.ip();
510 let interface_supported = self.supported_intfs.iter().any(|i| match i {
511 IfKind::Name(name) => *name == intf.name,
512 IfKind::IPv4 => addr.is_ipv4(),
513 IfKind::IPv6 => addr.is_ipv6(),
514 IfKind::Addr(a) => *a == addr,
515 IfKind::LoopbackV4 => matches!(addr, IpAddr::V4(ipv4) if ipv4.is_loopback()),
516 IfKind::LoopbackV6 => matches!(addr, IpAddr::V6(ipv6) if ipv6.is_loopback()),
517 IfKind::IndexV4(idx) => intf.index == Some(*idx) && addr.is_ipv4(),
518 IfKind::IndexV6(idx) => intf.index == Some(*idx) && addr.is_ipv6(),
519 IfKind::All => true,
520 });
521
522 let passes_link_local = !self.is_link_local_only
523 || match &addr {
524 IpAddr::V4(ipv4) => ipv4.is_link_local(),
525 IpAddr::V6(ipv6) => is_unicast_link_local(ipv6),
526 };
527 debug!(
528 "matching inserted address {} on intf {}: passes_link_local={}, interface_supported={}",
529 addr, addr, passes_link_local, interface_supported
530 );
531 interface_supported && passes_link_local
532 }
533}
534
535fn normalize_hostname(mut hostname: String) -> String {
537 if hostname.ends_with(".local.local.") {
538 let new_len = hostname.len() - "local.".len();
539 hostname.truncate(new_len);
540 }
541 hostname
542}
543
544pub trait AsIpAddrs {
546 fn as_ip_addrs(&self) -> Result<HashSet<IpAddr>>;
547}
548
549impl<T: AsIpAddrs> AsIpAddrs for &T {
550 fn as_ip_addrs(&self) -> Result<HashSet<IpAddr>> {
551 (*self).as_ip_addrs()
552 }
553}
554
555impl AsIpAddrs for &str {
560 fn as_ip_addrs(&self) -> Result<HashSet<IpAddr>> {
561 let mut addrs = HashSet::new();
562
563 if !self.is_empty() {
564 let iter = self.split(',').map(str::trim).map(IpAddr::from_str);
565 for addr in iter {
566 let addr = addr.map_err(|err| Error::ParseIpAddr(err.to_string()))?;
567 addrs.insert(addr);
568 }
569 }
570
571 Ok(addrs)
572 }
573}
574
575impl AsIpAddrs for String {
576 fn as_ip_addrs(&self) -> Result<HashSet<IpAddr>> {
577 self.as_str().as_ip_addrs()
578 }
579}
580
581impl<I: AsIpAddrs> AsIpAddrs for &[I] {
583 fn as_ip_addrs(&self) -> Result<HashSet<IpAddr>> {
584 let mut addrs = HashSet::new();
585
586 for result in self.iter().map(I::as_ip_addrs) {
587 addrs.extend(result?);
588 }
589
590 Ok(addrs)
591 }
592}
593
594impl AsIpAddrs for () {
597 fn as_ip_addrs(&self) -> Result<HashSet<IpAddr>> {
598 Ok(HashSet::new())
599 }
600}
601
602impl AsIpAddrs for std::net::IpAddr {
603 fn as_ip_addrs(&self) -> Result<HashSet<IpAddr>> {
604 let mut ips = HashSet::new();
605 ips.insert(*self);
606
607 Ok(ips)
608 }
609}
610
611impl AsIpAddrs for Box<dyn AsIpAddrs> {
612 fn as_ip_addrs(&self) -> Result<HashSet<IpAddr>> {
613 self.as_ref().as_ip_addrs()
614 }
615}
616
617#[derive(Debug, Clone, PartialEq, Eq)]
625#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
626#[cfg_attr(feature = "serde", serde(transparent))]
627pub struct TxtProperties {
628 properties: Vec<TxtProperty>,
630}
631
632impl Default for TxtProperties {
633 fn default() -> Self {
634 TxtProperties::new()
635 }
636}
637
638impl TxtProperties {
639 pub fn new() -> Self {
640 TxtProperties {
641 properties: Vec::new(),
642 }
643 }
644
645 pub fn iter(&self) -> impl Iterator<Item = &TxtProperty> {
647 self.properties.iter()
648 }
649
650 pub fn len(&self) -> usize {
652 self.properties.len()
653 }
654
655 pub fn is_empty(&self) -> bool {
657 self.properties.is_empty()
658 }
659
660 pub fn get(&self, key: &str) -> Option<&TxtProperty> {
663 let key = key.to_lowercase();
664 self.properties
665 .iter()
666 .find(|&prop| prop.key.to_lowercase() == key)
667 }
668
669 pub fn get_property_val(&self, key: &str) -> Option<Option<&[u8]>> {
675 self.get(key).map(|x| x.val())
676 }
677
678 pub fn get_property_val_str(&self, key: &str) -> Option<&str> {
684 self.get(key).map(|x| x.val_str())
685 }
686
687 pub fn into_property_map_str(self) -> HashMap<String, String> {
692 self.properties
693 .into_iter()
694 .filter_map(|property| {
695 let val_string = property.val.map_or(Some(String::new()), |val| {
696 String::from_utf8(val)
697 .map_err(|e| {
698 debug!("Property value contains invalid UTF-8: {e}");
699 })
700 .ok()
701 })?;
702 Some((property.key, val_string))
703 })
704 .collect()
705 }
706}
707
708impl fmt::Display for TxtProperties {
709 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
710 let delimiter = ", ";
711 let props: Vec<String> = self.properties.iter().map(|p| p.to_string()).collect();
712 write!(f, "({})", props.join(delimiter))
713 }
714}
715
716impl From<&[u8]> for TxtProperties {
717 fn from(txt: &[u8]) -> Self {
718 let properties = decode_txt_unique(txt);
719 TxtProperties { properties }
720 }
721}
722
723#[derive(Clone, PartialEq, Eq)]
725#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
726pub struct TxtProperty {
727 key: String,
729
730 #[cfg_attr(feature = "serde", serde(rename = "value"))]
734 val: Option<Vec<u8>>,
735}
736
737impl TxtProperty {
738 pub fn key(&self) -> &str {
740 &self.key
741 }
742
743 pub fn val(&self) -> Option<&[u8]> {
747 self.val.as_deref()
748 }
749
750 pub fn val_str(&self) -> &str {
752 self.val
753 .as_ref()
754 .map_or("", |v| std::str::from_utf8(&v[..]).unwrap_or_default())
755 }
756}
757
758impl<K, V> From<&(K, V)> for TxtProperty
760where
761 K: ToString,
762 V: ToString,
763{
764 fn from(prop: &(K, V)) -> Self {
765 Self {
766 key: prop.0.to_string(),
767 val: Some(prop.1.to_string().into_bytes()),
768 }
769 }
770}
771
772impl<K, V> From<(K, V)> for TxtProperty
773where
774 K: ToString,
775 V: AsRef<[u8]>,
776{
777 fn from(prop: (K, V)) -> Self {
778 Self {
779 key: prop.0.to_string(),
780 val: Some(prop.1.as_ref().into()),
781 }
782 }
783}
784
785impl From<&str> for TxtProperty {
787 fn from(key: &str) -> Self {
788 Self {
789 key: key.to_string(),
790 val: None,
791 }
792 }
793}
794
795impl fmt::Display for TxtProperty {
796 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
797 write!(f, "{}={}", self.key, self.val_str())
798 }
799}
800
801impl fmt::Debug for TxtProperty {
805 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
806 let val_string = self.val.as_ref().map_or_else(
807 || "None".to_string(),
808 |v| {
809 std::str::from_utf8(&v[..]).map_or_else(
810 |_| format!("Some({})", u8_slice_to_hex(&v[..])),
811 |s| format!("Some(\"{s}\")"),
812 )
813 },
814 );
815
816 write!(
817 f,
818 "TxtProperty {{key: \"{}\", val: {}}}",
819 &self.key, &val_string,
820 )
821 }
822}
823
824const HEX_TABLE: [char; 16] = [
825 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f',
826];
827
828fn u8_slice_to_hex(slice: &[u8]) -> String {
832 let mut hex = String::with_capacity(slice.len() * 2 + 2);
833 hex.push_str("0x");
834 for b in slice {
835 hex.push(HEX_TABLE[(b >> 4) as usize]);
836 hex.push(HEX_TABLE[(b & 0x0F) as usize]);
837 }
838 hex
839}
840
841pub trait IntoTxtProperties {
843 fn into_txt_properties(self) -> TxtProperties;
844}
845
846impl IntoTxtProperties for HashMap<String, String> {
847 fn into_txt_properties(mut self) -> TxtProperties {
848 let properties = self
849 .drain()
850 .map(|(key, val)| TxtProperty {
851 key,
852 val: Some(val.into_bytes()),
853 })
854 .collect();
855 TxtProperties { properties }
856 }
857}
858
859impl IntoTxtProperties for Option<HashMap<String, String>> {
861 fn into_txt_properties(self) -> TxtProperties {
862 self.map_or_else(
863 || TxtProperties {
864 properties: Vec::new(),
865 },
866 |h| h.into_txt_properties(),
867 )
868 }
869}
870
871impl<'a, T: 'a> IntoTxtProperties for &'a [T]
873where
874 TxtProperty: From<&'a T>,
875{
876 fn into_txt_properties(self) -> TxtProperties {
877 let mut properties = Vec::new();
878 let mut keys = HashSet::new();
879 for t in self.iter() {
880 let prop = TxtProperty::from(t);
881 let key = prop.key.to_lowercase();
882 if keys.insert(key) {
883 properties.push(prop);
891 }
892 }
893 TxtProperties { properties }
894 }
895}
896
897impl IntoTxtProperties for Vec<TxtProperty> {
898 fn into_txt_properties(self) -> TxtProperties {
899 TxtProperties { properties: self }
900 }
901}
902
903fn encode_txt<'a>(properties: impl Iterator<Item = &'a TxtProperty>) -> Vec<u8> {
905 let mut bytes = Vec::new();
906 for prop in properties {
907 let mut s = prop.key.clone().into_bytes();
908 if let Some(v) = &prop.val {
909 s.extend(b"=");
910 s.extend(v);
911 }
912
913 debug_assert!(
914 s.len() <= u8::MAX as usize,
915 "TXT property '{}' exceeds 255 bytes; should have been validated in ServiceInfo::new()",
916 prop.key
917 );
918 s.truncate(u8::MAX as usize);
919 let sz: u8 = s.len() as u8;
920
921 bytes.push(sz);
924 bytes.extend(s);
925 }
926 if bytes.is_empty() {
927 bytes.push(0);
928 }
929 bytes
930}
931
932pub(crate) fn decode_txt(txt: &[u8]) -> Vec<TxtProperty> {
934 let mut properties = Vec::new();
935 let mut offset = 0;
936 while offset < txt.len() {
937 let length = txt[offset] as usize;
938 if length == 0 {
939 break; }
941 offset += 1; let offset_end = offset + length;
944 if offset_end > txt.len() {
945 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());
946 break; }
948 let kv_bytes = &txt[offset..offset_end];
949
950 let (k, v) = kv_bytes.iter().position(|&x| x == b'=').map_or_else(
952 || (kv_bytes.to_vec(), None),
953 |idx| (kv_bytes[..idx].to_vec(), Some(kv_bytes[idx + 1..].to_vec())),
954 );
955
956 match String::from_utf8(k) {
958 Ok(k_string) => {
959 properties.push(TxtProperty {
960 key: k_string,
961 val: v,
962 });
963 }
964 Err(e) => debug!("failed to convert to String from key: {}", e),
965 }
966
967 offset += length;
968 }
969
970 properties
971}
972
973fn decode_txt_unique(txt: &[u8]) -> Vec<TxtProperty> {
974 let mut properties = decode_txt(txt);
975
976 let mut keys = HashSet::new();
979 properties.retain(|p| {
980 let key = p.key().to_lowercase();
981 keys.insert(key) });
983 properties
984}
985
986pub(crate) fn valid_ip_on_intf(addr: &IpAddr, if_addr: &IfAddr) -> bool {
988 match (addr, if_addr) {
989 (IpAddr::V4(addr), IfAddr::V4(if_v4)) => {
990 let netmask = u32::from(if_v4.netmask);
991 let intf_net = u32::from(if_v4.ip) & netmask;
992 let addr_net = u32::from(*addr) & netmask;
993 addr_net == intf_net
994 }
995 (IpAddr::V6(addr), IfAddr::V6(if_v6)) => {
996 let netmask = u128::from(if_v6.netmask);
997 let intf_net = u128::from(if_v6.ip) & netmask;
998 let addr_net = u128::from(*addr) & netmask;
999 addr_net == intf_net
1000 }
1001 _ => false,
1002 }
1003}
1004
1005#[derive(Debug)]
1007pub(crate) struct Probe {
1008 pub(crate) records: Vec<DnsRecordBox>,
1010
1011 pub(crate) waiting_services: HashSet<String>,
1014
1015 pub(crate) start_time: u64,
1017
1018 pub(crate) next_send: u64,
1020}
1021
1022impl Probe {
1023 pub(crate) fn new(start_time: u64) -> Self {
1024 let next_send = start_time;
1031
1032 Self {
1033 records: Vec::new(),
1034 waiting_services: HashSet::new(),
1035 start_time,
1036 next_send,
1037 }
1038 }
1039
1040 pub(crate) fn insert_record(&mut self, record: DnsRecordBox) {
1042 let insert_position = self
1052 .records
1053 .binary_search_by(
1054 |existing| match existing.get_class().cmp(&record.get_class()) {
1055 std::cmp::Ordering::Equal => existing.get_type().cmp(&record.get_type()),
1056 other => other,
1057 },
1058 )
1059 .unwrap_or_else(|pos| pos);
1060
1061 self.records.insert(insert_position, record);
1062 }
1063
1064 pub(crate) fn tiebreaking(&mut self, msg: &DnsIncoming, probe_name: &str) {
1066 let now = crate::current_time_millis();
1067
1068 if self.start_time >= now {
1072 return;
1073 }
1074
1075 let incoming: Vec<_> = msg
1076 .authorities()
1077 .iter()
1078 .filter(|r| r.get_name() == probe_name)
1079 .collect();
1080 let min_len = self.records.len().min(incoming.len());
1090
1091 let mut cmp_result = cmp::Ordering::Equal;
1093 for (i, incoming_record) in incoming.iter().enumerate().take(min_len) {
1094 match self.records[i].compare(incoming_record.as_ref()) {
1095 cmp::Ordering::Equal => continue,
1096 other => {
1097 cmp_result = other;
1098 break; }
1100 }
1101 }
1102
1103 if cmp_result == cmp::Ordering::Equal {
1104 cmp_result = self.records.len().cmp(&incoming.len());
1106 }
1107
1108 match cmp_result {
1109 cmp::Ordering::Less => {
1110 debug!("tiebreaking '{probe_name}': LOST, will wait for one second",);
1111 self.start_time = now + 1000; self.next_send = now + 1000;
1113 }
1114 ordering => {
1115 debug!("tiebreaking '{probe_name}': {:?}", ordering);
1116 }
1117 }
1118 }
1119
1120 pub(crate) fn update_next_send(&mut self, now: u64) {
1121 self.next_send = now + 250;
1122 }
1123
1124 pub(crate) fn expired(&self, now: u64) -> bool {
1126 now >= self.start_time + 750
1129 }
1130}
1131
1132pub(crate) struct DnsRegistry {
1134 pub(crate) probing: HashMap<String, Probe>,
1145
1146 pub(crate) active: HashMap<String, Vec<DnsRecordBox>>,
1149
1150 pub(crate) new_timers: Vec<u64>,
1152
1153 pub(crate) name_changes: HashMap<String, String>,
1155}
1156
1157impl DnsRegistry {
1158 pub(crate) fn new() -> Self {
1159 Self {
1160 probing: HashMap::new(),
1161 active: HashMap::new(),
1162 new_timers: Vec::new(),
1163 name_changes: HashMap::new(),
1164 }
1165 }
1166
1167 pub(crate) fn resolve_name<'a>(&'a self, name: &'a str) -> &'a str {
1169 match self.name_changes.get(name) {
1170 Some(new_name) => new_name,
1171 None => name,
1172 }
1173 }
1174
1175 pub(crate) fn is_probing_done<T>(
1176 &mut self,
1177 answer: &T,
1178 service_name: &str,
1179 start_time: u64,
1180 ) -> bool
1181 where
1182 T: DnsRecordExt + Send + 'static,
1183 {
1184 if let Some(active_records) = self.active.get(answer.get_name()) {
1185 for record in active_records.iter() {
1186 if answer.matches(record.as_ref()) {
1187 debug!(
1188 "found active record {} {}",
1189 answer.get_type(),
1190 answer.get_name(),
1191 );
1192 return true;
1193 }
1194 }
1195 }
1196
1197 let probe = self
1198 .probing
1199 .entry(answer.get_name().to_string())
1200 .or_insert_with(|| {
1201 debug!("new probe of {}", answer.get_name());
1202 Probe::new(start_time)
1203 });
1204
1205 self.new_timers.push(probe.next_send);
1206
1207 for record in probe.records.iter() {
1208 if answer.matches(record.as_ref()) {
1209 debug!(
1210 "found existing record {} in probe of '{}'",
1211 answer.get_type(),
1212 answer.get_name(),
1213 );
1214 probe.waiting_services.insert(service_name.to_string());
1215 return false; }
1217 }
1218
1219 debug!(
1220 "insert record {} into probe of {}",
1221 answer.get_type(),
1222 answer.get_name(),
1223 );
1224 probe.insert_record(answer.clone_box());
1225 probe.waiting_services.insert(service_name.to_string());
1226
1227 false
1228 }
1229
1230 pub(crate) fn update_hostname(
1234 &mut self,
1235 original: &str,
1236 new_name: &str,
1237 probe_time: u64,
1238 ) -> bool {
1239 let mut found_records = Vec::new();
1240 let mut new_timer_added = false;
1241
1242 for (_name, probe) in self.probing.iter_mut() {
1243 probe.records.retain(|record| {
1244 if record.get_type() == RRType::SRV {
1245 if let Some(srv) = record.any().downcast_ref::<DnsSrv>() {
1246 if srv.host() == original {
1247 let mut new_record = srv.clone();
1248 new_record.set_host(new_name.to_string());
1249 found_records.push(new_record);
1250 return false;
1251 }
1252 }
1253 }
1254 true
1255 });
1256 }
1257
1258 for (_name, records) in self.active.iter_mut() {
1259 records.retain(|record| {
1260 if record.get_type() == RRType::SRV {
1261 if let Some(srv) = record.any().downcast_ref::<DnsSrv>() {
1262 if srv.host() == original {
1263 let mut new_record = srv.clone();
1264 new_record.set_host(new_name.to_string());
1265 found_records.push(new_record);
1266 return false;
1267 }
1268 }
1269 }
1270 true
1271 });
1272 }
1273
1274 for record in found_records {
1275 let probe = match self.probing.get_mut(record.get_name()) {
1276 Some(p) => {
1277 p.start_time = probe_time; p
1279 }
1280 None => {
1281 let new_probe = self
1282 .probing
1283 .entry(record.get_name().to_string())
1284 .or_insert_with(|| Probe::new(probe_time));
1285 new_timer_added = true;
1286 new_probe
1287 }
1288 };
1289
1290 debug!(
1291 "insert record {} with new hostname {new_name} into probe for: {}",
1292 record.get_type(),
1293 record.get_name()
1294 );
1295 probe.insert_record(record.boxed());
1296 }
1297
1298 new_timer_added
1299 }
1300}
1301
1302pub(crate) fn split_sub_domain(domain: &str) -> (&str, Option<&str>) {
1304 if let Some((_, ty_domain)) = domain.rsplit_once("._sub.") {
1305 (ty_domain, Some(domain))
1306 } else {
1307 (domain, None)
1308 }
1309}
1310
1311pub(crate) fn is_unicast_link_local(addr: &Ipv6Addr) -> bool {
1317 (addr.segments()[0] & 0xffc0) == 0xfe80
1318}
1319
1320#[derive(Clone, Debug)]
1323#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
1324#[non_exhaustive]
1325pub struct ResolvedService {
1326 pub ty_domain: String,
1328
1329 pub sub_ty_domain: Option<String>,
1335
1336 pub fullname: String,
1338
1339 pub host: String,
1341
1342 pub port: u16,
1344
1345 pub addresses: HashSet<ScopedIp>,
1347
1348 pub txt_properties: TxtProperties,
1350}
1351
1352impl ResolvedService {
1353 pub fn is_valid(&self) -> bool {
1355 let some_missing = self.ty_domain.is_empty()
1356 || self.fullname.is_empty()
1357 || self.host.is_empty()
1358 || self.addresses.is_empty();
1359 !some_missing
1360 }
1361
1362 #[inline]
1363 pub const fn get_subtype(&self) -> &Option<String> {
1364 &self.sub_ty_domain
1365 }
1366
1367 #[inline]
1368 pub fn get_fullname(&self) -> &str {
1369 &self.fullname
1370 }
1371
1372 #[inline]
1373 pub fn get_hostname(&self) -> &str {
1374 &self.host
1375 }
1376
1377 #[inline]
1378 pub fn get_port(&self) -> u16 {
1379 self.port
1380 }
1381
1382 #[inline]
1383 pub fn get_addresses(&self) -> &HashSet<ScopedIp> {
1384 &self.addresses
1385 }
1386
1387 pub fn get_addresses_v4(&self) -> HashSet<Ipv4Addr> {
1388 self.addresses
1389 .iter()
1390 .filter_map(|ip| match ip {
1391 ScopedIp::V4(ipv4) => Some(*ipv4.addr()),
1392 _ => None,
1393 })
1394 .collect()
1395 }
1396
1397 #[inline]
1398 pub fn get_properties(&self) -> &TxtProperties {
1399 &self.txt_properties
1400 }
1401
1402 #[inline]
1403 pub fn get_property(&self, key: &str) -> Option<&TxtProperty> {
1404 self.txt_properties.get(key)
1405 }
1406
1407 pub fn get_property_val(&self, key: &str) -> Option<Option<&[u8]>> {
1408 self.txt_properties.get_property_val(key)
1409 }
1410
1411 pub fn get_property_val_str(&self, key: &str) -> Option<&str> {
1412 self.txt_properties.get_property_val_str(key)
1413 }
1414}
1415
1416#[cfg(test)]
1417mod tests {
1418 use super::{decode_txt, encode_txt, u8_slice_to_hex, ServiceInfo, TxtProperty};
1419 use crate::IfKind;
1420 use if_addrs::{IfAddr, IfOperStatus, Ifv4Addr, Ifv6Addr, Interface};
1421 use std::net::{Ipv4Addr, Ipv6Addr};
1422
1423 #[test]
1424 fn test_txt_encode_decode() {
1425 let properties = [
1426 TxtProperty::from(&("key1", "value1")),
1427 TxtProperty::from(&("key2", "value2")),
1428 ];
1429
1430 let property_count = properties.len();
1432 let encoded = encode_txt(properties.iter());
1433 assert_eq!(
1434 encoded.len(),
1435 "key1=value1".len() + "key2=value2".len() + property_count
1436 );
1437 assert_eq!(encoded[0] as usize, "key1=value1".len());
1438
1439 let decoded = decode_txt(&encoded);
1441 assert!(properties[..] == decoded[..]);
1442
1443 let properties = vec![TxtProperty::from(&("key3", ""))];
1445 let property_count = properties.len();
1446 let encoded = encode_txt(properties.iter());
1447 assert_eq!(encoded.len(), "key3=".len() + property_count);
1448
1449 let decoded = decode_txt(&encoded);
1450 assert_eq!(properties, decoded);
1451
1452 let binary_val: Vec<u8> = vec![123, 234, 0];
1454 let binary_len = binary_val.len();
1455 let properties = vec![TxtProperty::from(("key4", binary_val))];
1456 let property_count = properties.len();
1457 let encoded = encode_txt(properties.iter());
1458 assert_eq!(encoded.len(), "key4=".len() + binary_len + property_count);
1459
1460 let decoded = decode_txt(&encoded);
1461 assert_eq!(properties, decoded);
1462
1463 let properties = vec![TxtProperty::from(("key5", "val=5"))];
1465 let property_count = properties.len();
1466 let encoded = encode_txt(properties.iter());
1467 assert_eq!(
1468 encoded.len(),
1469 "key5=".len() + "val=5".len() + property_count
1470 );
1471
1472 let decoded = decode_txt(&encoded);
1473 assert_eq!(properties, decoded);
1474
1475 let properties = vec![TxtProperty::from("key6")];
1477 let property_count = properties.len();
1478 let encoded = encode_txt(properties.iter());
1479 assert_eq!(encoded.len(), "key6".len() + property_count);
1480 let decoded = decode_txt(&encoded);
1481 assert_eq!(properties, decoded);
1482
1483 let properties = [TxtProperty::from(
1485 String::from_utf8(vec![0x30; 255]).unwrap().as_str(),
1486 )];
1487 let property_count = properties.len();
1488 let encoded = encode_txt(properties.iter());
1489 assert_eq!(encoded.len(), 255 + property_count);
1491 let decoded = decode_txt(&encoded);
1492 assert_eq!(properties.to_vec(), decoded);
1493 }
1494
1495 #[test]
1496 fn test_txt_property_exceeds_255_bytes() {
1497 let long_key = String::from_utf8(vec![0x30; 256]).unwrap();
1498 let result = ServiceInfo::new(
1499 "_test._tcp.local.",
1500 "test",
1501 "host",
1502 "",
1503 1234,
1504 &[(long_key.as_str(), "")][..],
1505 );
1506 assert!(result.is_err());
1507 assert!(result
1508 .unwrap_err()
1509 .to_string()
1510 .contains("exceeding the 255-byte limit"));
1511
1512 let key_at_limit = String::from_utf8(vec![0x30; 250]).unwrap();
1515 let result = ServiceInfo::new(
1516 "_test._tcp.local.",
1517 "test",
1518 "host",
1519 "",
1520 1234,
1521 &[(key_at_limit.as_str(), "abcd")][..],
1522 );
1523 assert!(result.is_ok());
1524 }
1525
1526 #[test]
1527 fn test_set_properties_from_txt() {
1528 let properties = [
1530 TxtProperty::from(&("one", "1")),
1531 TxtProperty::from(&("ONE", "2")),
1532 TxtProperty::from(&("One", "3")),
1533 ];
1534 let encoded = encode_txt(properties.iter());
1535
1536 let decoded = decode_txt(&encoded);
1538 assert_eq!(decoded.len(), 3);
1539
1540 let mut service_info =
1542 ServiceInfo::new("_test._tcp", "prop_test", "localhost", "", 1234, None).unwrap();
1543 service_info._set_properties_from_txt(&encoded);
1544 assert_eq!(service_info.get_properties().len(), 1);
1545
1546 let prop = service_info.get_properties().iter().next().unwrap();
1548 assert_eq!(prop.key, "one");
1549 assert_eq!(prop.val_str(), "1");
1550 }
1551
1552 #[test]
1553 fn test_u8_slice_to_hex() {
1554 let bytes = [0x01u8, 0x02u8, 0x03u8];
1555 let hex = u8_slice_to_hex(&bytes);
1556 assert_eq!(hex.as_str(), "0x010203");
1557
1558 let slice = "abcdefghijklmnopqrstuvwxyz";
1559 let hex = u8_slice_to_hex(slice.as_bytes());
1560 assert_eq!(hex.len(), slice.len() * 2 + 2);
1561 assert_eq!(
1562 hex.as_str(),
1563 "0x6162636465666768696a6b6c6d6e6f707172737475767778797a"
1564 );
1565 }
1566
1567 #[test]
1568 fn test_txt_property_debug() {
1569 let prop_1 = TxtProperty {
1571 key: "key1".to_string(),
1572 val: Some("val1".to_string().into()),
1573 };
1574 let prop_1_debug = format!("{:?}", &prop_1);
1575 assert_eq!(
1576 prop_1_debug,
1577 "TxtProperty {key: \"key1\", val: Some(\"val1\")}"
1578 );
1579
1580 let prop_2 = TxtProperty {
1582 key: "key2".to_string(),
1583 val: Some(vec![150u8, 151u8, 152u8]),
1584 };
1585 let prop_2_debug = format!("{:?}", &prop_2);
1586 assert_eq!(
1587 prop_2_debug,
1588 "TxtProperty {key: \"key2\", val: Some(0x969798)}"
1589 );
1590 }
1591
1592 #[test]
1593 fn test_txt_decode_property_size_out_of_bounds() {
1594 let encoded: Vec<u8> = vec![
1596 0x0b, b'k', b'e', b'y', b'1', b'=', b'v', b'a', b'l', b'u', b'e',
1598 b'1', 0x10, b'k', b'e', b'y', b'2', b'=', b'v', b'a', b'l', b'u', b'e',
1601 b'2', ];
1603 let decoded = decode_txt(&encoded);
1605 assert_eq!(decoded.len(), 1);
1608 assert_eq!(decoded[0].key, "key1");
1610 }
1611
1612 #[test]
1613 fn test_is_address_supported() {
1614 let mut service_info =
1615 ServiceInfo::new("_test._tcp", "prop_test", "testhost", "", 1234, None).unwrap();
1616
1617 let intf_v6 = Interface {
1618 name: "foo".to_string(),
1619 index: Some(1),
1620 addr: IfAddr::V6(Ifv6Addr {
1621 ip: Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0x1234, 0, 0, 1),
1622 netmask: Ipv6Addr::new(0xffff, 0xffff, 0xffff, 0xffff, 0, 0, 0, 0),
1623 broadcast: None,
1624 prefixlen: 16,
1625 }),
1626 oper_status: IfOperStatus::Up,
1627 is_p2p: false,
1628 #[cfg(windows)]
1629 adapter_name: String::new(),
1630 };
1631
1632 let intf_v4 = Interface {
1633 name: "bar".to_string(),
1634 index: Some(1),
1635 addr: IfAddr::V4(Ifv4Addr {
1636 ip: Ipv4Addr::new(192, 1, 2, 3),
1637 netmask: Ipv4Addr::new(255, 255, 0, 0),
1638 broadcast: None,
1639 prefixlen: 16,
1640 }),
1641 oper_status: IfOperStatus::Up,
1642 is_p2p: false,
1643 #[cfg(windows)]
1644 adapter_name: String::new(),
1645 };
1646
1647 let intf_baz = Interface {
1648 name: "baz".to_string(),
1649 index: Some(1),
1650 addr: IfAddr::V6(Ifv6Addr {
1651 ip: Ipv6Addr::new(0x2003, 0xdb8, 0, 0, 0x1234, 0, 0, 1),
1652 netmask: Ipv6Addr::new(0xffff, 0xffff, 0xffff, 0xffff, 0, 0, 0, 0),
1653 broadcast: None,
1654 prefixlen: 16,
1655 }),
1656 oper_status: IfOperStatus::Up,
1657 is_p2p: false,
1658 #[cfg(windows)]
1659 adapter_name: String::new(),
1660 };
1661
1662 let intf_loopback_v4 = Interface {
1663 name: "foo".to_string(),
1664 index: Some(1),
1665 addr: IfAddr::V4(Ifv4Addr {
1666 ip: Ipv4Addr::new(127, 0, 0, 1),
1667 netmask: Ipv4Addr::new(255, 255, 255, 255),
1668 broadcast: None,
1669 prefixlen: 16,
1670 }),
1671 oper_status: IfOperStatus::Up,
1672 is_p2p: false,
1673 #[cfg(windows)]
1674 adapter_name: String::new(),
1675 };
1676
1677 let intf_loopback_v6 = Interface {
1678 name: "foo".to_string(),
1679 index: Some(1),
1680 addr: IfAddr::V6(Ifv6Addr {
1681 ip: Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1),
1682 netmask: Ipv6Addr::new(
1683 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,
1684 ),
1685 broadcast: None,
1686 prefixlen: 16,
1687 }),
1688 oper_status: IfOperStatus::Up,
1689 is_p2p: false,
1690 #[cfg(windows)]
1691 adapter_name: String::new(),
1692 };
1693
1694 let intf_link_local_v4 = Interface {
1695 name: "foo".to_string(),
1696 index: Some(1),
1697 addr: IfAddr::V4(Ifv4Addr {
1698 ip: Ipv4Addr::new(169, 254, 0, 1),
1699 netmask: Ipv4Addr::new(255, 255, 0, 0),
1700 broadcast: None,
1701 prefixlen: 16,
1702 }),
1703 oper_status: IfOperStatus::Up,
1704 is_p2p: false,
1705 #[cfg(windows)]
1706 adapter_name: String::new(),
1707 };
1708
1709 let intf_link_local_v6 = Interface {
1710 name: "foo".to_string(),
1711 index: Some(1),
1712 addr: IfAddr::V6(Ifv6Addr {
1713 ip: Ipv6Addr::new(0xfe80, 0, 0, 0, 0x1234, 0, 0, 1),
1714 netmask: Ipv6Addr::new(0xffff, 0xffff, 0xffff, 0xffff, 0, 0, 0, 0),
1715 broadcast: None,
1716 prefixlen: 16,
1717 }),
1718 oper_status: IfOperStatus::Up,
1719 is_p2p: false,
1720 #[cfg(windows)]
1721 adapter_name: String::new(),
1722 };
1723
1724 assert!(service_info.is_address_supported(&intf_v6));
1726
1727 service_info.set_interfaces(vec![
1729 IfKind::Name("foo".to_string()),
1730 IfKind::Name("bar".to_string()),
1731 ]);
1732 assert!(!service_info.is_address_supported(&intf_baz));
1733
1734 service_info.set_link_local_only(true);
1736 assert!(!service_info.is_address_supported(&intf_v4));
1737 assert!(!service_info.is_address_supported(&intf_v6));
1738 assert!(service_info.is_address_supported(&intf_link_local_v4));
1739 assert!(service_info.is_address_supported(&intf_link_local_v6));
1740 service_info.set_link_local_only(false);
1741
1742 service_info.set_interfaces(vec![IfKind::All]);
1744 assert!(service_info.is_address_supported(&intf_v6));
1745 assert!(service_info.is_address_supported(&intf_v4));
1746
1747 service_info.set_interfaces(vec![IfKind::IPv6]);
1749 assert!(service_info.is_address_supported(&intf_v6));
1750 assert!(!service_info.is_address_supported(&intf_v4));
1751
1752 service_info.set_interfaces(vec![IfKind::IPv4]);
1754 assert!(service_info.is_address_supported(&intf_v4));
1755 assert!(!service_info.is_address_supported(&intf_v6));
1756
1757 service_info.set_interfaces(vec![IfKind::Addr(intf_v6.ip())]);
1759 assert!(service_info.is_address_supported(&intf_v6));
1760 assert!(!service_info.is_address_supported(&intf_v4));
1761
1762 service_info.set_interfaces(vec![IfKind::LoopbackV4]);
1764 assert!(service_info.is_address_supported(&intf_loopback_v4));
1765 assert!(!service_info.is_address_supported(&intf_loopback_v6));
1766
1767 service_info.set_interfaces(vec![IfKind::LoopbackV6]);
1769 assert!(!service_info.is_address_supported(&intf_loopback_v4));
1770 assert!(service_info.is_address_supported(&intf_loopback_v6));
1771 }
1772
1773 #[test]
1774 fn test_scoped_ip_set_detects_interface_id_change() {
1775 use crate::{InterfaceId, ScopedIp, ScopedIpV4};
1776 use std::collections::HashSet;
1777
1778 let intf1 = InterfaceId {
1779 name: "en0".to_string(),
1780 index: 1,
1781 };
1782 let intf2 = InterfaceId {
1783 name: "en1".to_string(),
1784 index: 2,
1785 };
1786 let addr = Ipv4Addr::new(192, 168, 1, 100);
1787
1788 let scoped_v4_one_intf = ScopedIpV4::new(addr, intf1);
1789 let mut scoped_v4_two_intfs = scoped_v4_one_intf.clone();
1790 scoped_v4_two_intfs.add_interface_id(intf2);
1791
1792 assert_ne!(scoped_v4_one_intf, scoped_v4_two_intfs);
1793
1794 let set_old: HashSet<ScopedIp> = HashSet::from([ScopedIp::V4(scoped_v4_one_intf)]);
1795 let set_new: HashSet<ScopedIp> = HashSet::from([ScopedIp::V4(scoped_v4_two_intfs)]);
1796
1797 assert_ne!(set_old, set_new);
1798 }
1799
1800 #[cfg(test)]
1801 #[cfg(feature = "serde")]
1802 mod serde {
1803 use super::{Ipv4Addr, Ipv6Addr};
1804 use crate::{ResolvedService, ScopedIp, TxtProperties};
1805
1806 use std::collections::HashSet;
1807 use std::net::IpAddr;
1808
1809 #[test]
1810 fn test_deserialize_serialize() -> Result<(), Box<dyn std::error::Error>> {
1811 let addresses = HashSet::from([
1812 ScopedIp::from(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))),
1813 ScopedIp::from(IpAddr::V6(Ipv6Addr::new(
1814 0xfe80, 0x2001, 0x0db8, 0x85a3, 0x0000, 0x8a2e, 0x0370, 0x7334,
1815 ))),
1816 ]);
1817
1818 let service = ResolvedService {
1819 ty_domain: "_http._tcp.local.".to_owned(),
1820 sub_ty_domain: None,
1821 fullname: "example._http._tcp.local.".to_owned(),
1822 host: "example.local.".to_owned(),
1823 port: 1234,
1824 addresses,
1825 txt_properties: TxtProperties::new(),
1826 };
1827
1828 let json = serde_json::to_value(&service)?;
1829
1830 let parsed: ResolvedService = serde_json::from_value(json)?;
1831
1832 assert!(compare(&service, &parsed));
1833
1834 Ok(())
1835 }
1836
1837 fn compare(service: &ResolvedService, other: &ResolvedService) -> bool {
1838 service.ty_domain == other.ty_domain
1839 && service.sub_ty_domain == other.sub_ty_domain
1840 && service.fullname == other.fullname
1841 && service.host == other.host
1842 && service.port == other.port
1843 && service.addresses == other.addresses
1844 && service.txt_properties == other.txt_properties
1845 }
1846 }
1847}