1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
//! A small and safe library for Multicast DNS-SD (Service Discovery).
//!
//! This library creates one new thread to run a mDNS daemon, and exposes
//! its API that interacts with the daemon via a
//! [`flume`](https://crates.io/crates/flume) channel. The channel supports
//! both `recv()` and `recv_async()`.
//!
//! For example, a client querying (browsing) a service behaves like this:
//!```text
//!  Client       <channel>       mDNS daemon thread
//!    |                             | starts its run-loop.
//!    |       --- Browse -->        |
//!    |                             | detects services
//!    |                             | finds service instance A
//!    |       <-- Found A --        |
//!    |           ...               | resolves service A
//!    |       <-- Resolved A --     |
//!    |           ...               |
//!```
//! All commands in the public API are sent to the daemon using the unblocking `try_send()`
//! so that the caller can use it with both sync and async code, with no dependency on any
//! particular async runtimes.
//!
//! # Usage
//!
//! The user starts with creating a daemon by calling [`ServiceDaemon::new()`].
//! Then as a mDNS querier, the user would call [`browse`](`ServiceDaemon::browse`) to
//! search for services, and/or as a mDNS responder, call [`register`](`ServiceDaemon::register`)
//! to publish (i.e. announce) its own service. And, the daemon type can be cloned and passed
//! around between threads.
//!
//! ## Example: a client querying for a service type.
//!
//! ```rust
//! use mdns_sd::{ServiceDaemon, ServiceEvent};
//!
//! // Create a daemon
//! let mdns = ServiceDaemon::new().expect("Failed to create daemon");
//!
//! // Browse for a service type.
//! let service_type = "_mdns-sd-my-test._udp.local.";
//! let receiver = mdns.browse(service_type).expect("Failed to browse");
//!
//! // Receive the browse events in sync or async. Here is
//! // an example of using a thread. Users can call `receiver.recv_async().await`
//! // if running in async environment.
//! std::thread::spawn(move || {
//!     while let Ok(event) = receiver.recv() {
//!         match event {
//!             ServiceEvent::ServiceResolved(info) => {
//!                 println!("Resolved a new service: {}", info.get_fullname());
//!             }
//!             other_event => {
//!                 println!("Received other event: {:?}", &other_event);
//!             }
//!         }
//!     }
//! });
//! ```
//!
//! ## Example: a server publishs a service and responds to queries.
//!
//! ```rust
//! use mdns_sd::{ServiceDaemon, ServiceInfo};
//! use std::collections::HashMap;
//!
//! // Create a daemon
//! let mdns = ServiceDaemon::new().expect("Failed to create daemon");
//!
//! // Create a service info.
//! let service_type = "_mdns-sd-my-test._udp.local.";
//! let instance_name = "my_instance";
//! let host_ipv4 = "192.168.1.12";
//! let host_name = "192.168.1.12.local.";
//! let port = 5200;
//! let properties = [("property_1", "test"), ("property_2", "1234")];
//!
//! let my_service = ServiceInfo::new(
//!     service_type,
//!     instance_name,
//!     host_name,
//!     host_ipv4,
//!     port,
//!     &properties[..],
//! ).unwrap();
//!
//! // Register with the daemon, which publishes the service.
//! mdns.register(my_service).expect("Failed to register our service");
//! ```
//!
//! # Limitations
//!
//! This implementation is based on the following RFCs:
//! - mDNS:   [RFC 6762](https://tools.ietf.org/html/rfc6762)
//! - DNS-SD: [RFC 6763](https://tools.ietf.org/html/rfc6763)
//! - DNS:    [RFC 1035](https://tools.ietf.org/html/rfc1035)
//!
//! We focus on the common use cases at first, and currently have the following limitations:
//! - Only support IPv4, not IPv6.
//! - Only support multicast, not unicast send/recv.
//! - Only support 32-bit or bigger platforms, not 16-bit platforms.

#![forbid(unsafe_code)]
#![allow(clippy::single_component_path_imports)]

// log for logging (optional).
#[cfg(feature = "logging")]
use log;

#[cfg(not(feature = "logging"))]
#[macro_use]
mod log {
    macro_rules! trace    ( ($($tt:tt)*) => {{}} );
    macro_rules! debug    ( ($($tt:tt)*) => {{}} );
    macro_rules! info     ( ($($tt:tt)*) => {{}} );
    macro_rules! warn     ( ($($tt:tt)*) => {{}} );
    macro_rules! error    ( ($($tt:tt)*) => {{}} );
}

mod dns_parser;
mod error;
mod service_daemon;
mod service_info;

pub use error::{Error, Result};
pub use service_daemon::{
    DaemonEvent, Metrics, ServiceDaemon, ServiceEvent, UnregisterStatus,
    SERVICE_NAME_LEN_MAX_DEFAULT,
};
pub use service_info::{AsIpv4Addrs, IntoTxtProperties, ServiceInfo, TxtProperties, TxtProperty};

/// A handler to receive messages from [ServiceDaemon]. Re-export from `flume` crate.
pub use flume::Receiver;