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>;