agnostic_mdns/
lib.rs

1#![doc = include_str!("../README.md")]
2#![forbid(unsafe_code)]
3#![deny(missing_docs)]
4#![allow(unexpected_cfgs)]
5#![cfg_attr(docsrs, feature(doc_cfg))]
6#![cfg_attr(docsrs, allow(unused_attributes))]
7#![allow(clippy::needless_return)]
8#![allow(unreachable_code)]
9
10#[cfg(test)]
11mod tests;
12
13use std::{
14  io,
15  net::{Ipv4Addr, Ipv6Addr},
16  time::Duration,
17};
18
19const IPV4_MDNS: Ipv4Addr = Ipv4Addr::new(224, 0, 0, 251);
20const IPV6_MDNS: Ipv6Addr = Ipv6Addr::new(0xff02, 0, 0, 0, 0, 0, 0, 0xfb);
21const IPV4_SIZE: usize = core::mem::size_of::<Ipv4Addr>();
22const IPV6_SIZE: usize = core::mem::size_of::<Ipv6Addr>();
23const MDNS_PORT: u16 = 5353;
24const MAX_INLINE_PACKET_SIZE: usize = 1500;
25
26pub use mdns_proto::{error, proto::Label};
27
28/// synchronous mDNS server implementation
29pub mod sync;
30
31/// Generic asynchronous mDNS implementation for work stealing runtimes
32#[cfg(feature = "worksteal")]
33#[cfg_attr(docsrs, doc(cfg(feature = "worksteal")))]
34pub mod worksteal;
35
36/// A builtin service that can be used with the mDNS server
37pub mod service;
38
39pub use iprobe as netprobe;
40pub use service::{Service, ServiceBuilder};
41pub use smol_str::{SmolStr, format_smolstr};
42
43/// The options for [`Server`].
44#[derive(Clone, Debug)]
45pub struct ServerOptions {
46  pub(crate) ipv4_interface: Option<Ipv4Addr>,
47  pub(crate) ipv6_interface: Option<u32>,
48  pub(crate) log_empty_responses: bool,
49  pub(crate) max_payload_size: usize,
50}
51
52impl Default for ServerOptions {
53  #[inline]
54  fn default() -> Self {
55    Self::new()
56  }
57}
58
59impl ServerOptions {
60  /// Returns a new instance of [`ServerOptions`].
61  #[inline]
62  pub const fn new() -> Self {
63    Self {
64      ipv4_interface: None,
65      ipv6_interface: None,
66      log_empty_responses: false,
67      max_payload_size: 1500,
68    }
69  }
70
71  /// Returns the Ipv4 interface to bind the multicast listener to.
72  ///
73  /// ## Example
74  ///
75  /// ```rust
76  /// use agnostic_mdns::ServerOptions;
77  /// use std::net::Ipv4Addr;
78  ///
79  /// let opts = ServerOptions::new().with_ipv4_interface(Ipv4Addr::new(192, 168, 1, 1));
80  /// assert_eq!(opts.ipv4_interface(), Some(&Ipv4Addr::new(192, 168, 1, 1)));
81  /// ```
82  #[inline]
83  pub const fn ipv4_interface(&self) -> Option<&Ipv4Addr> {
84    self.ipv4_interface.as_ref()
85  }
86
87  /// Sets the IPv4 interface to bind the multicast listener to.
88  ///
89  /// ## Example
90  ///
91  /// ```rust
92  /// use agnostic_mdns::ServerOptions;
93  /// use std::net::Ipv4Addr;
94  ///
95  /// let opts = ServerOptions::new().with_ipv4_interface(Ipv4Addr::new(192, 168, 1, 1));
96  /// ```
97  #[inline]
98  pub fn with_ipv4_interface(mut self, iface: Ipv4Addr) -> Self {
99    self.ipv4_interface = Some(iface);
100    self
101  }
102
103  /// Returns the Ipv6 interface to bind the multicast listener to.
104  ///
105  /// ## Example
106  ///
107  /// ```rust
108  /// use agnostic_mdns::ServerOptions;
109  ///
110  /// let opts = ServerOptions::new().with_ipv6_interface(1);
111  /// assert_eq!(opts.ipv6_interface(), Some(1));
112  /// ```
113  #[inline]
114  pub const fn ipv6_interface(&self) -> Option<u32> {
115    self.ipv6_interface
116  }
117
118  /// Sets the IPv6 interface to bind the multicast listener to.
119  ///
120  /// ## Example
121  ///
122  /// ```rust
123  /// use agnostic_mdns::ServerOptions;
124  ///
125  /// let opts = ServerOptions::new().with_ipv6_interface(1);
126  /// ```
127  #[inline]
128  pub fn with_ipv6_interface(mut self, index: u32) -> Self {
129    self.ipv6_interface = Some(index);
130    self
131  }
132
133  /// Sets whether the server should print an informative message
134  /// when there is an mDNS query for which the server has no response.
135  ///
136  /// Default is `false`.
137  ///
138  /// ## Example
139  ///
140  /// ```rust
141  /// use agnostic_mdns::ServerOptions;
142  ///
143  /// let opts = ServerOptions::new().with_log_empty_responses(true);
144  /// assert_eq!(opts.log_empty_responses(), true);
145  /// ```
146  #[inline]
147  pub fn with_log_empty_responses(mut self, log_empty_responses: bool) -> Self {
148    self.log_empty_responses = log_empty_responses;
149    self
150  }
151
152  /// Returns whether the server should print an informative message
153  /// when there is an mDNS query for which the server has no response.
154  ///
155  /// ## Example
156  ///
157  /// ```rust
158  /// use agnostic_mdns::ServerOptions;
159  ///
160  /// let opts = ServerOptions::new().with_log_empty_responses(true);
161  /// assert_eq!(opts.log_empty_responses(), true);
162  /// ```
163  #[inline]
164  pub const fn log_empty_responses(&self) -> bool {
165    self.log_empty_responses
166  }
167
168  /// Returns the configured maximum payload size for mDNS message packets.
169  ///
170  /// This value limits how large each mDNS packet can be when sending queries or
171  /// receiving responses.
172  ///
173  /// Smaller values may be necessary on networks with MTU constraints or when
174  /// working with devices that have limited buffer sizes.
175  ///
176  /// Default is `1500` bytes.
177  ///
178  /// ## Example
179  ///
180  /// ```rust
181  /// use agnostic_mdns::ServerOptions;
182  ///
183  /// let opts = ServerOptions::new().with_max_payload_size(1500);
184  /// ```
185  #[inline]
186  pub const fn max_payload_size(&self) -> usize {
187    self.max_payload_size
188  }
189
190  /// Sets the maximum payload size for mDNS message packets.
191  ///
192  /// This controls how large each mDNS packet can be when sending queries or
193  /// receiving responses.
194  ///
195  /// You might want to adjust this value to:
196  /// - Reduce the size to avoid IP fragmentation on networks with lower MTUs
197  /// - Match device-specific constraints in IoT environments
198  /// - Optimize for specific network conditions
199  ///
200  /// Default is `1500` bytes.
201  ///
202  /// ## Example
203  ///
204  /// ```rust
205  /// use agnostic_mdns::ServerOptions;
206  ///
207  /// let opts = ServerOptions::new().with_max_payload_size(1500);
208  ///
209  /// assert_eq!(opts.max_payload_size(), 1500);
210  /// ```
211  #[inline]
212  pub const fn with_max_payload_size(mut self, max_payload_size: usize) -> Self {
213    self.max_payload_size = max_payload_size;
214    self
215  }
216}
217
218/// How a lookup is performed.
219#[derive(Clone, Debug)]
220pub struct QueryParam<'a> {
221  service: Label<'a>,
222  domain: Label<'a>,
223  timeout: Duration,
224  ipv4_interface: Option<Ipv4Addr>,
225  ipv6_interface: Option<u32>,
226  cap: Option<usize>,
227  want_unicast_response: bool, // Unicast response desired, as per 5.4 in RFC
228  // Whether to disable usage of IPv4 for MDNS operations. Does not affect discovered addresses.
229  disable_ipv4: bool,
230  // Whether to disable usage of IPv6 for MDNS operations. Does not affect discovered addresses.
231  disable_ipv6: bool,
232  max_payload_size: usize,
233}
234
235impl<'a> QueryParam<'a> {
236  /// Creates a new query parameter with default values.
237  #[inline]
238  pub fn new(service: Label<'a>) -> Self {
239    Self {
240      service,
241      domain: Label::from("local"),
242      timeout: Duration::from_secs(1),
243      ipv4_interface: None,
244      ipv6_interface: None,
245      want_unicast_response: false,
246      disable_ipv4: false,
247      disable_ipv6: false,
248      cap: None,
249      max_payload_size: 1500,
250    }
251  }
252
253  /// Sets the domain to search in.
254  ///
255  /// ## Example
256  ///
257  /// ```rust
258  /// use agnostic_mdns::QueryParam;
259  ///
260  /// let params = QueryParam::new("service._tcp".into())
261  ///   .with_domain("local.".into());
262  /// ```
263  pub fn with_domain(mut self, domain: Label<'a>) -> Self {
264    self.domain = domain;
265    self
266  }
267
268  /// Returns the domain to search in.
269  ///
270  /// ## Example
271  ///
272  /// ```rust
273  /// use agnostic_mdns::{QueryParam, Label};
274  ///
275  /// let params = QueryParam::new("service._tcp".into())
276  ///   .with_domain("local.".into());
277  ///
278  /// assert_eq!(params.domain(), &Label::from("local"));
279  pub const fn domain(&self) -> &Label<'a> {
280    &self.domain
281  }
282
283  /// Sets the service to search for.
284  ///
285  /// ## Example
286  ///
287  /// ```rust
288  /// use agnostic_mdns::QueryParam;
289  ///
290  /// let params = QueryParam::new("service._tcp".into())
291  ///   .with_service("service._udp".into());
292  /// ```
293  pub fn with_service(mut self, service: Label<'a>) -> Self {
294    self.service = service;
295    self
296  }
297
298  /// Returns the service to search for.
299  ///
300  /// ## Example
301  ///
302  /// ```rust
303  /// use agnostic_mdns::{QueryParam, Label};
304  ///
305  /// let params = QueryParam::new("service._tcp".into())
306  ///   .with_service("service._udp".into());
307  ///
308  /// assert_eq!(params.service(), &Label::from("service._udp"));
309  pub const fn service(&self) -> &Label<'a> {
310    &self.service
311  }
312
313  /// Sets the timeout for the query.
314  ///
315  /// ## Example
316  ///
317  /// ```rust
318  /// use agnostic_mdns::QueryParam;
319  ///
320  /// let params = QueryParam::new("service._tcp".into())
321  ///   .with_timeout(std::time::Duration::from_secs(1));
322  /// ```
323  pub fn with_timeout(mut self, timeout: Duration) -> Self {
324    self.timeout = timeout;
325    self
326  }
327
328  /// Returns the timeout for the query.
329  ///
330  /// ## Example
331  ///
332  /// ```rust
333  /// use agnostic_mdns::QueryParam;
334  ///
335  /// let params = QueryParam::new("service._tcp".into())
336  ///   .with_timeout(std::time::Duration::from_secs(1));
337  ///
338  /// assert_eq!(params.timeout(), std::time::Duration::from_secs(1));
339  /// ```
340  pub const fn timeout(&self) -> Duration {
341    self.timeout
342  }
343
344  /// Sets the IPv4 interface to use for queries.
345  ///
346  /// ## Example
347  ///
348  /// ```rust
349  /// use agnostic_mdns::QueryParam;
350  ///
351  /// let params = QueryParam::new("service._tcp".into())
352  ///   .with_ipv4_interface("0.0.0.0".parse().unwrap());
353  /// ```
354  pub fn with_ipv4_interface(mut self, ipv4_interface: Ipv4Addr) -> Self {
355    self.ipv4_interface = Some(ipv4_interface);
356    self
357  }
358
359  /// Returns the IPv4 interface to use for queries.
360  ///
361  /// ## Example
362  ///
363  /// ```rust
364  /// use agnostic_mdns::QueryParam;
365  ///
366  /// let params = QueryParam::new("service._tcp".into())
367  ///  .with_ipv4_interface("0.0.0.0".parse().unwrap());
368  ///
369  /// assert_eq!(params.ipv4_interface().unwrap(), &"0.0.0.0".parse::<std::net::Ipv4Addr>().unwrap());
370  /// ```
371  pub const fn ipv4_interface(&self) -> Option<&Ipv4Addr> {
372    self.ipv4_interface.as_ref()
373  }
374
375  /// Sets the IPv6 interface to use for queries.
376  ///
377  /// ## Example
378  ///
379  /// ```rust
380  /// use agnostic_mdns::QueryParam;
381  ///
382  /// let params = QueryParam::new("service._tcp".into())
383  ///   .with_ipv6_interface(1);
384  /// ```
385  pub fn with_ipv6_interface(mut self, ipv6_interface: u32) -> Self {
386    self.ipv6_interface = Some(ipv6_interface);
387    self
388  }
389
390  /// Returns the IPv6 interface to use for queries.
391  ///
392  /// ## Example
393  ///
394  /// ```rust
395  /// use agnostic_mdns::QueryParam;
396  ///
397  /// let params = QueryParam::new("service._tcp".into())
398  ///   .with_ipv6_interface(1);
399  /// assert_eq!(params.ipv6_interface().unwrap(), 1);
400  /// ```
401  pub const fn ipv6_interface(&self) -> Option<u32> {
402    self.ipv6_interface
403  }
404
405  /// Sets whether to request unicast responses.
406  ///
407  /// ## Example
408  ///
409  /// ```rust
410  /// use agnostic_mdns::QueryParam;
411  ///
412  /// let params = QueryParam::new("service._tcp".into())
413  ///   .with_unicast_response(true);
414  /// ```
415  pub fn with_unicast_response(mut self, want_unicast_response: bool) -> Self {
416    self.want_unicast_response = want_unicast_response;
417    self
418  }
419
420  /// Returns whether to request unicast responses.
421  ///
422  /// ## Example
423  ///
424  /// ```rust
425  /// use agnostic_mdns::QueryParam;
426  ///
427  /// let params = QueryParam::new("service._tcp".into())
428  ///   .with_unicast_response(true);
429  ///
430  /// assert_eq!(params.want_unicast_response(), true);
431  /// ```
432  pub const fn want_unicast_response(&self) -> bool {
433    self.want_unicast_response
434  }
435
436  /// Sets whether to disable IPv4 for MDNS operations.
437  ///
438  /// ## Example
439  ///
440  /// ```rust
441  /// use agnostic_mdns::QueryParam;
442  ///
443  /// let params = QueryParam::new("service._tcp".into())
444  ///   .with_disable_ipv4(true);
445  /// ```
446  pub fn with_disable_ipv4(mut self, disable_ipv4: bool) -> Self {
447    self.disable_ipv4 = disable_ipv4;
448    self
449  }
450
451  /// Returns whether to disable IPv4 for MDNS operations.
452  ///
453  /// ## Example
454  ///
455  /// ```rust
456  /// use agnostic_mdns::QueryParam;
457  ///
458  /// let params = QueryParam::new("service._tcp".into())
459  ///   .with_disable_ipv4(true);
460  ///
461  /// assert_eq!(params.disable_ipv4(), true);
462  /// ```
463  pub const fn disable_ipv4(&self) -> bool {
464    self.disable_ipv4
465  }
466
467  /// Sets whether to disable IPv6 for MDNS operations.
468  ///
469  /// ## Example
470  ///
471  /// ```rust
472  /// use agnostic_mdns::QueryParam;
473  ///
474  /// let params = QueryParam::new("service._tcp".into())
475  ///   .with_disable_ipv6(true);
476  /// ```
477  pub fn with_disable_ipv6(mut self, disable_ipv6: bool) -> Self {
478    self.disable_ipv6 = disable_ipv6;
479    self
480  }
481
482  /// Returns whether to disable IPv6 for MDNS operations.
483  ///
484  /// ## Example
485  ///
486  /// ```rust
487  /// use agnostic_mdns::QueryParam;
488  ///
489  /// let params = QueryParam::new("service._tcp".into())
490  ///   .with_disable_ipv6(true);
491  ///
492  /// assert_eq!(params.disable_ipv6(), true);
493  /// ```
494  pub const fn disable_ipv6(&self) -> bool {
495    self.disable_ipv6
496  }
497
498  /// Returns the configured maximum payload size for mDNS message packets.
499  ///
500  /// This value limits how large each mDNS packet can be when sending queries or
501  /// receiving responses.
502  ///
503  /// Smaller values may be necessary on networks with MTU constraints or when
504  /// working with devices that have limited buffer sizes.
505  ///
506  /// Default is `1500` bytes.
507  ///
508  /// ## Example
509  ///
510  /// ```rust
511  /// use agnostic_mdns::QueryParam;
512  ///
513  /// let params = QueryParam::new("service._tcp".into())
514  ///  .with_max_payload_size(1500);
515  /// ```
516  #[inline]
517  pub const fn max_payload_size(&self) -> usize {
518    self.max_payload_size
519  }
520
521  /// Sets the maximum payload size for mDNS message packets.
522  ///
523  /// This controls how large each mDNS packet can be when sending queries or
524  /// receiving responses.
525  ///
526  /// You might want to adjust this value to:
527  /// - Reduce the size to avoid IP fragmentation on networks with lower MTUs
528  /// - Match device-specific constraints in IoT environments
529  /// - Optimize for specific network conditions
530  ///
531  /// Default is `1500` bytes.
532  ///
533  /// ## Example
534  ///
535  /// ```rust
536  /// use agnostic_mdns::QueryParam;
537  ///
538  /// let params = QueryParam::new("service._tcp".into())
539  /// .with_max_payload_size(1500);
540  ///
541  /// assert_eq!(params.max_payload_size(), 1500);
542  /// ```
543  #[inline]
544  pub const fn with_max_payload_size(mut self, max_payload_size: usize) -> Self {
545    self.max_payload_size = max_payload_size;
546    self
547  }
548
549  /// Returns the channel capacity for the [`Lookup`] stream.
550  ///
551  /// If `None`, the channel is unbounded.
552  ///
553  /// Default is `None`.
554  ///
555  /// ## Example
556  ///
557  /// ```rust
558  /// use agnostic_mdns::QueryParam;
559  ///
560  /// let params = QueryParam::new("service._tcp".into())
561  ///   .with_capacity(Some(10));
562  ///
563  /// assert_eq!(params.capacity().unwrap(), 10);
564  /// ```
565  #[inline]
566  pub const fn capacity(&self) -> Option<usize> {
567    self.cap
568  }
569
570  /// Sets the channel capacity for the [`Lookup`] stream.
571  ///
572  /// If `None`, the channel is unbounded.
573  ///
574  /// Default is `None`.
575  ///
576  /// ## Example
577  ///
578  /// ```rust
579  /// use agnostic_mdns::QueryParam;
580  ///
581  /// let params = QueryParam::new("service._tcp".into())
582  ///  .with_capacity(Some(10));
583  /// ```
584  #[inline]
585  pub fn with_capacity(mut self, cap: Option<usize>) -> Self {
586    self.cap = cap;
587    self
588  }
589}
590
591/// Types for `tokio` runtime
592#[cfg(feature = "tokio")]
593#[cfg_attr(docsrs, doc(cfg(feature = "tokio")))]
594pub mod tokio {
595  use std::io;
596
597  use super::{QueryParam, service::Service, worksteal::ServiceEntry};
598  use mdns_proto::proto::Label;
599
600  pub use agnostic_net::{runtime::tokio::TokioRuntime as Runtime, tokio::Net};
601  pub use async_channel as channel;
602
603  /// A server that can be used with `tokio` runtime
604  pub type Server = super::worksteal::Server<Net, Service>;
605
606  /// Looks up a given service, in a domain, waiting at most
607  /// for a timeout before finishing the query. The results are streamed
608  /// to a channel. Sends will not block, so clients should make sure to
609  /// either read or buffer. This method will attempt to stop the query
610  /// on cancellation.
611  #[inline]
612  pub async fn query(params: QueryParam<'_>, tx: channel::Sender<ServiceEntry>) -> io::Result<()> {
613    super::worksteal::query::<Net>(params, tx).await
614  }
615
616  /// Similar to [`query`], however it uses all the default parameters
617  #[inline]
618  pub async fn lookup(service: Label<'_>, tx: channel::Sender<ServiceEntry>) -> io::Result<()> {
619    super::worksteal::lookup::<Net>(service, tx).await
620  }
621}
622
623/// Types for `smol` runtime
624#[cfg(feature = "smol")]
625#[cfg_attr(docsrs, doc(cfg(feature = "smol")))]
626pub mod smol {
627  use super::{Label, QueryParam, service::Service, worksteal::ServiceEntry};
628  use std::io;
629
630  pub use agnostic_net::{runtime::smol::SmolRuntime as Runtime, smol::Net};
631  pub use async_channel as channel;
632
633  /// A server that can be used with `smol` runtime
634  pub type Server = super::worksteal::Server<Net, Service>;
635
636  /// Looks up a given service, in a domain, waiting at most
637  /// for a timeout before finishing the query. The results are streamed
638  /// to a channel. Sends will not block, so clients should make sure to
639  /// either read or buffer. This method will attempt to stop the query
640  /// on cancellation.
641  #[inline]
642  pub async fn query(params: QueryParam<'_>, tx: channel::Sender<ServiceEntry>) -> io::Result<()> {
643    super::worksteal::query::<Net>(params, tx).await
644  }
645
646  /// Similar to [`query`], however it uses all the default parameters
647  #[inline]
648  pub async fn lookup(service: Label<'_>, tx: channel::Sender<ServiceEntry>) -> io::Result<()> {
649    super::worksteal::lookup::<Net>(service, tx).await
650  }
651}
652
653/// Types for `async-std` runtime
654#[cfg(feature = "async-std")]
655#[cfg_attr(docsrs, doc(cfg(feature = "async-std")))]
656pub mod async_std {
657  use super::{Label, QueryParam, service::Service, worksteal::ServiceEntry};
658  use std::io;
659
660  pub use agnostic_net::{async_std::Net, runtime::async_std::AsyncStdRuntime as Runtime};
661  pub use async_channel as channel;
662
663  /// A server that can be used with `async-std` runtime
664  pub type Server = super::worksteal::Server<Net, Service>;
665
666  /// Looks up a given service, in a domain, waiting at most
667  /// for a timeout before finishing the query. The results are streamed
668  /// to a channel. Sends will not block, so clients should make sure to
669  /// either read or buffer. This method will attempt to stop the query
670  /// on cancellation.
671  #[inline]
672  pub async fn query(params: QueryParam<'_>, tx: channel::Sender<ServiceEntry>) -> io::Result<()> {
673    super::worksteal::query::<Net>(params, tx).await
674  }
675
676  /// Similar to [`query`], however it uses all the default parameters
677  #[inline]
678  pub async fn lookup(service: Label<'_>, tx: channel::Sender<ServiceEntry>) -> io::Result<()> {
679    super::worksteal::lookup::<Net>(service, tx).await
680  }
681}
682
683mod utils;
684
685/// Returns `true` if a domain name is fully qualified domain name
686#[inline]
687pub fn is_fqdn(s: &str) -> bool {
688  let len = s.len();
689  if s.is_empty() || !s.ends_with('.') {
690    return false;
691  }
692
693  let s = &s[..len - 1];
694
695  if s.is_empty() || !s.ends_with('\\') {
696    return true;
697  }
698
699  // Count backslashes at the end
700  let last_non_backslash = s.rfind(|c| c != '\\').unwrap_or(0);
701
702  (len - last_non_backslash) % 2 == 0
703}
704
705/// Returns the hostname of the current machine.
706///
707/// On wasm target, this function always returns `None`.
708///
709/// ## Examples
710///
711/// ```
712/// use agnostic_mdns::hostname;
713///
714/// let hostname = hostname();
715/// println!("hostname: {hostname:?}");
716/// ```
717#[allow(unreachable_code)]
718pub fn hostname() -> io::Result<SmolStr> {
719  #[cfg(not(any(windows, target_os = "wasi")))]
720  return {
721    let name = rustix::system::uname();
722    let name = name.nodename().to_string_lossy();
723    Ok(SmolStr::from(name.as_ref()))
724  };
725
726  #[cfg(windows)]
727  return {
728    match ::hostname::get() {
729      Ok(name) => {
730        let name = name.to_string_lossy();
731        Ok(SmolStr::from(name.as_ref()))
732      }
733      Err(e) => Err(e),
734    }
735  };
736
737  Err(io::Error::new(
738    io::ErrorKind::Unsupported,
739    "hostname is not supported on this platform",
740  ))
741}
742
743fn hostname_fqdn() -> io::Result<SmolStr> {
744  #[cfg(not(any(windows, target_os = "wasi")))]
745  return {
746    let name = rustix::system::uname();
747    let name = name.nodename().to_string_lossy();
748    Ok(format_smolstr!("{}.", Label::from(name.as_ref())))
749  };
750
751  #[cfg(windows)]
752  return {
753    match ::hostname::get() {
754      Ok(name) => {
755        let name = name.to_string_lossy();
756        Ok(format_smolstr!("{}.", Label::from(name.as_ref())))
757      }
758      Err(e) => Err(e),
759    }
760  };
761
762  Err(io::Error::new(
763    io::ErrorKind::Unsupported,
764    "hostname is not supported on this platform",
765  ))
766}
767
768fn invalid_input_err<E>(e: E) -> io::Error
769where
770  E: Into<Box<dyn std::error::Error + Send + Sync>>,
771{
772  io::Error::new(io::ErrorKind::InvalidInput, e)
773}
774
775#[allow(clippy::large_enum_variant)]
776#[derive(Clone)]
777enum Buffer {
778  Heap(Vec<u8>),
779  Stack([u8; MAX_INLINE_PACKET_SIZE]),
780}
781
782impl Buffer {
783  fn zerod(cap: usize) -> Self {
784    if cap <= MAX_INLINE_PACKET_SIZE {
785      Buffer::Stack([0; MAX_INLINE_PACKET_SIZE])
786    } else {
787      Buffer::Heap(vec![0; cap])
788    }
789  }
790}
791
792impl From<usize> for Buffer {
793  fn from(size: usize) -> Self {
794    if size <= MAX_INLINE_PACKET_SIZE {
795      Buffer::Stack([0; MAX_INLINE_PACKET_SIZE])
796    } else {
797      Buffer::Heap(vec![0; size])
798    }
799  }
800}
801
802impl core::ops::Deref for Buffer {
803  type Target = [u8];
804
805  fn deref(&self) -> &[u8] {
806    match self {
807      Buffer::Heap(v) => v,
808      Buffer::Stack(v) => v,
809    }
810  }
811}
812
813impl core::ops::DerefMut for Buffer {
814  fn deref_mut(&mut self) -> &mut [u8] {
815    match self {
816      Buffer::Heap(v) => v,
817      Buffer::Stack(v) => v,
818    }
819  }
820}