zeroconf/
lib.rs

1//! `zeroconf` is a cross-platform library that wraps underlying [ZeroConf/mDNS] implementations
2//! such as [Bonjour] or [Avahi], providing an easy and idiomatic way to both register and
3//! browse services.
4//!
5//! This crate provides the cross-platform [`MdnsService`] and [`MdnsBrowser`] available for each
6//! supported platform as well as platform-specific modules for lower-level access to the mDNS
7//! implementation should that be necessary.
8//!
9//! Most users of this crate need only [`MdnsService`] and [`MdnsBrowser`].
10//!
11//! # Examples
12//!
13//! ## Register a service
14//!
15//! When registering a service, you may optionally pass a "context" to pass state through the
16//! callback. The only requirement is that this context implements the [`Any`] trait, which most
17//! types will automatically. See [`MdnsService`] for more information about contexts.
18//!
19//! ```no_run
20//! #[macro_use]
21//! extern crate log;
22//!
23//! use clap::Parser;
24//!
25//! use std::any::Any;
26//! use std::sync::{Arc, Mutex};
27//! use std::time::Duration;
28//! use zeroconf::prelude::*;
29//! use zeroconf::{MdnsService, ServiceRegistration, ServiceType, TxtRecord};
30//!
31//! #[derive(Parser, Debug)]
32//! #[command(author, version, about)]
33//! struct Args {
34//!     /// Name of the service type to register
35//!     #[clap(short, long, default_value = "http")]
36//!     name: String,
37//!
38//!     /// Protocol of the service type to register
39//!     #[clap(short, long, default_value = "tcp")]
40//!     protocol: String,
41//!
42//!     /// Sub-types of the service type to register
43//!     #[clap(short, long)]
44//!     sub_types: Vec<String>,
45//! }
46//!
47//! #[derive(Default, Debug)]
48//! pub struct Context {
49//!     service_name: String,
50//! }
51//!
52//! fn main() -> zeroconf::Result<()> {
53//!     env_logger::init();
54//!
55//!     let Args {
56//!         name,
57//!         protocol,
58//!         sub_types,
59//!     } = Args::parse();
60//!
61//!     let sub_types = sub_types.iter().map(|s| s.as_str()).collect::<Vec<_>>();
62//!     let service_type = ServiceType::with_sub_types(&name, &protocol, sub_types)?;
63//!     let mut service = MdnsService::new(service_type, 8080);
64//!     let mut txt_record = TxtRecord::new();
65//!     let context: Arc<Mutex<Context>> = Arc::default();
66//!
67//!     txt_record.insert("foo", "bar")?;
68//!
69//!     service.set_name("zeroconf_example_service");
70//!     service.set_registered_callback(Box::new(on_service_registered));
71//!     service.set_context(Box::new(context));
72//!     service.set_txt_record(txt_record);
73//!
74//!     let event_loop = service.register()?;
75//!
76//!     loop {
77//!         // calling `poll()` will keep this service alive
78//!         event_loop.poll(Duration::from_secs(0))?;
79//!     }
80//! }
81//!
82//! fn on_service_registered(
83//!     result: zeroconf::Result<ServiceRegistration>,
84//!     context: Option<Arc<dyn Any>>,
85//! ) {
86//!     let service = result.expect("failed to register service");
87//!
88//!     info!("Service registered: {:?}", service);
89//!
90//!     let context = context
91//!         .as_ref()
92//!         .expect("could not get context")
93//!         .downcast_ref::<Arc<Mutex<Context>>>()
94//!         .expect("error down-casting context")
95//!         .clone();
96//!
97//!     context
98//!         .lock()
99//!         .expect("failed to obtain context lock")
100//!         .service_name = service.name().clone();
101//!
102//!     info!("Context: {:?}", context);
103//!
104//!     // ...
105//! }
106//! ```
107//!
108//! ## Browsing services
109//! ```no_run
110//! #[macro_use]
111//! extern crate log;
112//!
113//! use clap::Parser;
114//!
115//! use std::any::Any;
116//! use std::sync::Arc;
117//! use std::time::Duration;
118//! use zeroconf::prelude::*;
119//! use zeroconf::{MdnsBrowser, ServiceDiscovery, ServiceType};
120//!
121//! /// Example of a simple mDNS browser
122//! #[derive(Parser, Debug)]
123//! #[command(author, version, about)]
124//! struct Args {
125//!     /// Name of the service type to browse
126//!     #[clap(short, long, default_value = "http")]
127//!     name: String,
128//!
129//!     /// Protocol of the service type to browse
130//!     #[clap(short, long, default_value = "tcp")]
131//!     protocol: String,
132//!
133//!     /// Sub-type of the service type to browse
134//!     #[clap(short, long)]
135//!     sub_type: Option<String>,
136//! }
137//!
138//! fn main() -> zeroconf::Result<()> {
139//!     env_logger::init();
140//!
141//!     let Args {
142//!         name,
143//!         protocol,
144//!         sub_type,
145//!     } = Args::parse();
146//!
147//!     let sub_types: Vec<&str> = match sub_type.as_ref() {
148//!         Some(sub_type) => vec![sub_type],
149//!         None => vec![],
150//!     };
151//!
152//!     let service_type =
153//!         ServiceType::with_sub_types(&name, &protocol, sub_types).expect("invalid service type");
154//!
155//!     let mut browser = MdnsBrowser::new(service_type);
156//!
157//!     browser.set_service_discovered_callback(Box::new(on_service_discovered));
158//!
159//!     let event_loop = browser.browse_services()?;
160//!
161//!     loop {
162//!         // calling `poll()` will keep this browser alive
163//!         event_loop.poll(Duration::from_secs(0))?;
164//!     }
165//! }
166//!
167//! fn on_service_discovered(
168//!     result: zeroconf::Result<ServiceDiscovery>,
169//!     _context: Option<Arc<dyn Any>>,
170//! ) {
171//!     info!(
172//!         "Service discovered: {:?}",
173//!         result.expect("service discovery failed")
174//!     );
175//!
176//!     // ...
177//! }
178//! ```
179//!
180//! [ZeroConf/mDNS]: https://en.wikipedia.org/wiki/Zero-configuration_networking
181//! [Bonjour]: https://en.wikipedia.org/wiki/Bonjour_(software)
182//! [Avahi]: https://en.wikipedia.org/wiki/Avahi_(software)
183//! [`MdnsService`]: type.MdnsService.html
184//! [`MdnsBrowser`]: type.MdnsBrowser.html
185//! [`Any`]: https://doc.rust-lang.org/std/any/trait.Any.html
186
187#![allow(clippy::needless_doctest_main)]
188#[macro_use]
189#[cfg(feature = "serde")]
190extern crate serde;
191#[macro_use]
192extern crate derive_builder;
193#[macro_use]
194extern crate zeroconf_macros;
195#[cfg(target_os = "linux")]
196extern crate avahi_sys;
197#[cfg(any(target_vendor = "apple", target_vendor = "pc"))]
198extern crate bonjour_sys;
199#[macro_use]
200extern crate derive_getters;
201#[macro_use]
202extern crate log;
203#[macro_use]
204extern crate derive_new;
205
206#[macro_use]
207#[cfg(test)]
208#[allow(unused_imports)]
209extern crate maplit;
210
211#[macro_use]
212mod macros;
213mod ffi;
214mod interface;
215mod service_type;
216#[cfg(test)]
217mod tests;
218
219pub mod browser;
220pub mod error;
221pub mod event_loop;
222pub mod prelude;
223pub mod service;
224pub mod txt_record;
225
226#[cfg(target_os = "linux")]
227pub mod avahi;
228#[cfg(any(target_vendor = "apple", target_vendor = "pc"))]
229pub mod bonjour;
230
231pub use browser::{ServiceDiscoveredCallback, ServiceDiscovery};
232pub use interface::*;
233pub use service::{ServiceRegisteredCallback, ServiceRegistration};
234pub use service_type::*;
235
236/// Type alias for the platform-specific mDNS browser implementation
237#[cfg(target_os = "linux")]
238pub type MdnsBrowser = avahi::browser::AvahiMdnsBrowser;
239/// Type alias for the platform-specific mDNS browser implementation
240#[cfg(any(target_vendor = "apple", target_vendor = "pc"))]
241pub type MdnsBrowser = bonjour::browser::BonjourMdnsBrowser;
242
243/// Type alias for the platform-specific mDNS service implementation
244#[cfg(target_os = "linux")]
245pub type MdnsService = avahi::service::AvahiMdnsService;
246/// Type alias for the platform-specific mDNS service implementation
247#[cfg(any(target_vendor = "apple", target_vendor = "pc"))]
248pub type MdnsService = bonjour::service::BonjourMdnsService;
249
250/// Type alias for the platform-specific structure responsible for polling the mDNS event loop
251#[cfg(target_os = "linux")]
252pub type EventLoop = avahi::event_loop::AvahiEventLoop;
253/// Type alias for the platform-specific structure responsible for polling the mDNS event loop
254#[cfg(any(target_vendor = "apple", target_vendor = "pc"))]
255pub type EventLoop = bonjour::event_loop::BonjourEventLoop;
256
257/// Type alias for the platform-specific structure responsible for storing and accessing TXT
258/// record data
259#[cfg(target_os = "linux")]
260pub type TxtRecord = avahi::txt_record::AvahiTxtRecord;
261/// Type alias for the platform-specific structure responsible for storing and accessing TXT
262/// record data
263#[cfg(any(target_vendor = "apple", target_vendor = "pc"))]
264pub type TxtRecord = bonjour::txt_record::BonjourTxtRecord;
265
266/// Result type for this library
267pub type Result<T> = std::result::Result<T, error::Error>;