Move from magiclip project

Signed-off-by: Walker Crouse <walker.crouse@coop.co.uk>
This commit is contained in:
Walker Crouse 2020-09-24 18:03:10 -04:00
parent 5a1b5a3505
commit 55cbcfe154
33 changed files with 2964 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
**/target

506
Cargo.lock generated Normal file
View File

@ -0,0 +1,506 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "aho-corasick"
version = "0.7.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "043164d8ba5c4c3035fec9bbee8647c0261d788f3474306f93bb65901cae0e86"
dependencies = [
"memchr",
]
[[package]]
name = "ansi_term"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
dependencies = [
"winapi",
]
[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
"hermit-abi",
"libc",
"winapi",
]
[[package]]
name = "avahi-sys"
version = "0.9.0"
dependencies = [
"bindgen",
"libc",
]
[[package]]
name = "bindgen"
version = "0.55.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75b13ce559e6433d360c26305643803cb52cfbabbc2b9c47ce04a58493dfb443"
dependencies = [
"bitflags",
"cexpr",
"cfg-if",
"clang-sys",
"clap",
"env_logger",
"lazy_static",
"lazycell",
"log",
"peeking_take_while",
"proc-macro2",
"quote",
"regex",
"rustc-hash",
"shlex",
"which",
]
[[package]]
name = "bitflags"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
[[package]]
name = "bonjour-sys"
version = "0.1.0"
dependencies = [
"bindgen",
"libc",
]
[[package]]
name = "cexpr"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4aedb84272dbe89af497cf81375129abda4fc0a9e7c5d317498c15cc30c0d27"
dependencies = [
"nom",
]
[[package]]
name = "cfg-if"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
[[package]]
name = "clang-sys"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9da1484c6a890e374ca5086062d4847e0a2c1e5eba9afa5d48c09e8eb39b2519"
dependencies = [
"glob",
"libc",
"libloading",
]
[[package]]
name = "clap"
version = "2.33.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002"
dependencies = [
"ansi_term",
"atty",
"bitflags",
"strsim 0.8.0",
"textwrap",
"unicode-width",
"vec_map",
]
[[package]]
name = "darling"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d706e75d87e35569db781a9b5e2416cff1236a47ed380831f959382ccd5f858"
dependencies = [
"darling_core",
"darling_macro",
]
[[package]]
name = "darling_core"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0c960ae2da4de88a91b2d920c2a7233b400bc33cb28453a2987822d8392519b"
dependencies = [
"fnv",
"ident_case",
"proc-macro2",
"quote",
"strsim 0.9.3",
"syn",
]
[[package]]
name = "darling_macro"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72"
dependencies = [
"darling_core",
"quote",
"syn",
]
[[package]]
name = "derive-getters"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c5905670fd9c320154f3a4a01c9e609733cd7b753f3c58777ab7d5ce26686b3"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "derive_builder"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2658621297f2cf68762a6f7dc0bb7e1ff2cfd6583daef8ee0fed6f7ec468ec0"
dependencies = [
"darling",
"derive_builder_core",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "derive_builder_core"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2791ea3e372c8495c0bc2033991d76b512cd799d07491fbd6890124db9458bef"
dependencies = [
"darling",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "env_logger"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36"
dependencies = [
"atty",
"humantime",
"log",
"regex",
"termcolor",
]
[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "glob"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
[[package]]
name = "hermit-abi"
version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c30f6d0bc6b00693347368a67d41b58f2fb851215ff1da49e90fe2c5c667151"
dependencies = [
"libc",
]
[[package]]
name = "humantime"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f"
dependencies = [
"quick-error",
]
[[package]]
name = "ident_case"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "lazycell"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
[[package]]
name = "libc"
version = "0.2.77"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2f96b10ec2560088a8e76961b00d47107b3a625fecb76dedb29ee7ccbf98235"
[[package]]
name = "libloading"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2443d8f0478b16759158b2f66d525991a05491138bc05814ef52a250148ef4f9"
dependencies = [
"cfg-if",
"winapi",
]
[[package]]
name = "log"
version = "0.4.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b"
dependencies = [
"cfg-if",
]
[[package]]
name = "memchr"
version = "2.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400"
[[package]]
name = "nom"
version = "5.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af"
dependencies = [
"memchr",
"version_check",
]
[[package]]
name = "peeking_take_while"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
[[package]]
name = "proc-macro2"
version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36e28516df94f3dd551a587da5357459d9b36d945a7c37c3557928c1c2ff2a2c"
dependencies = [
"unicode-xid",
]
[[package]]
name = "quick-error"
version = "1.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
[[package]]
name = "quote"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37"
dependencies = [
"proc-macro2",
]
[[package]]
name = "regex"
version = "1.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c3780fcf44b193bc4d09f36d2a3c87b251da4a046c87795a0d35f4f927ad8e6"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
"thread_local",
]
[[package]]
name = "regex-syntax"
version = "0.6.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26412eb97c6b088a6997e05f69403a802a92d520de2f8e63c2b65f9e0f47c4e8"
[[package]]
name = "rustc-hash"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "serde"
version = "1.0.116"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96fe57af81d28386a513cbc6858332abc6117cfdb5999647c6444b8f43a370a5"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.116"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f630a6370fd8e457873b4bd2ffdae75408bc291ba72be773772a4c2a065d9ae8"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "shlex"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fdf1b9db47230893d76faad238fd6097fd6d6a9245cd7a4d90dbd639536bbd2"
[[package]]
name = "strsim"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
[[package]]
name = "strsim"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c"
[[package]]
name = "syn"
version = "1.0.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6690e3e9f692504b941dc6c3b188fd28df054f7fb8469ab40680df52fdcc842b"
dependencies = [
"proc-macro2",
"quote",
"unicode-xid",
]
[[package]]
name = "termcolor"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb6bfa289a4d7c5766392812c0a1f4c1ba45afa1ad47803c11e1f407d846d75f"
dependencies = [
"winapi-util",
]
[[package]]
name = "textwrap"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
dependencies = [
"unicode-width",
]
[[package]]
name = "thread_local"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14"
dependencies = [
"lazy_static",
]
[[package]]
name = "unicode-width"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
[[package]]
name = "unicode-xid"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
[[package]]
name = "vec_map"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
[[package]]
name = "version_check"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed"
[[package]]
name = "which"
version = "3.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d011071ae14a2f6671d0b74080ae0cd8ebf3a6f8c9589a2cd45f23126fe29724"
dependencies = [
"libc",
]
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
dependencies = [
"winapi",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "zeroconf"
version = "0.1.0"
dependencies = [
"avahi-sys",
"bonjour-sys",
"derive-getters",
"derive_builder",
"libc",
"log",
"serde",
"zeroconf-macros",
]
[[package]]
name = "zeroconf-macros"
version = "0.1.0"
dependencies = [
"quote",
"syn",
]

5
Cargo.toml Normal file
View File

@ -0,0 +1,5 @@
[workspace]
members = [
"zeroconf",
"zeroconf-macros",
]

89
README.md Normal file
View File

@ -0,0 +1,89 @@
# zeroconf
`zeroconf` is a cross-platform library that wraps underlying [ZeroConf/mDNS] implementations
such as [Bonjour] or [Avahi], providing an easy and idiomatic way to both register and
browse services.
[ZeroConf/mDNS]: https://en.wikipedia.org/wiki/Zero-configuration_networking
[Bonjour]: https://en.wikipedia.org/wiki/Bonjour_(software)
[Avahi]: https://en.wikipedia.org/wiki/Avahi_(software)
## Prerequisites
```bash
$ sudo apt install xorg-dev libxcb-shape0-dev libxcb-xfixes0-dev clang
```
## Examples
## Register a service
When registering a service, you may optionally pass a "context" to pass state through the
callback. The only requirement is that this context implements the [`Any`] trait, which most
types will automatically. See [`MdnsService`] for more information about contexts.
```
use std::any::Any;
use std::sync::{Arc, Mutex};
use zeroconf::{MdnsService, ServiceRegistration};
#[derive(Default, Debug)]
pub struct Context {
service_name: String,
}
fn main() {
let mut service = MdnsService::new("_http._tcp", 8080);
let context: Arc<Mutex<Context>> = Arc::default();
service.set_registered_callback(Box::new(on_service_registered));
service.set_context(Box::new(context));
// blocks current thread, must keep-alive to keep service active
service.start().unwrap();
}
fn on_service_registered(service: ServiceRegistration, context: Option<Arc<dyn Any>>) {
println!("Service registered: {:?}", service);
let context = context
.as_ref()
.unwrap()
.downcast_ref::<Arc<Mutex<Context>>>()
.unwrap()
.clone();
context.lock().unwrap().service_name = service.name().clone();
println!("Context: {:?}", context);
// ...
}
```
## Browsing services
```
use std::any::Any;
use std::sync::Arc;
use zeroconf::{MdnsBrowser, ServiceDiscovery};
fn main() {
let mut browser = MdnsBrowser::new("_http._tcp");
browser.set_service_discovered_callback(Box::new(on_service_discovered));
// blocks current thread, must keep-alive to keep browser active
browser.start().unwrap()
}
fn on_service_discovered(service: ServiceDiscovery, _context: Option<Arc<dyn Any>>) {
println!("Service discovered: {:?}", &service);
// ...
}
```
## TODO
## Caveats

520
examples/Cargo.lock generated Normal file
View File

@ -0,0 +1,520 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "aho-corasick"
version = "0.7.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "043164d8ba5c4c3035fec9bbee8647c0261d788f3474306f93bb65901cae0e86"
dependencies = [
"memchr",
]
[[package]]
name = "ansi_term"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
dependencies = [
"winapi",
]
[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
"hermit-abi",
"libc",
"winapi",
]
[[package]]
name = "avahi-sys"
version = "0.9.0"
dependencies = [
"bindgen",
"libc",
]
[[package]]
name = "bindgen"
version = "0.55.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75b13ce559e6433d360c26305643803cb52cfbabbc2b9c47ce04a58493dfb443"
dependencies = [
"bitflags",
"cexpr",
"cfg-if",
"clang-sys",
"clap",
"env_logger",
"lazy_static",
"lazycell",
"log",
"peeking_take_while",
"proc-macro2",
"quote",
"regex",
"rustc-hash",
"shlex",
"which",
]
[[package]]
name = "bitflags"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
[[package]]
name = "bonjour-sys"
version = "0.1.0"
dependencies = [
"bindgen",
"libc",
]
[[package]]
name = "cexpr"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4aedb84272dbe89af497cf81375129abda4fc0a9e7c5d317498c15cc30c0d27"
dependencies = [
"nom",
]
[[package]]
name = "cfg-if"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
[[package]]
name = "clang-sys"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9da1484c6a890e374ca5086062d4847e0a2c1e5eba9afa5d48c09e8eb39b2519"
dependencies = [
"glob",
"libc",
"libloading",
]
[[package]]
name = "clap"
version = "2.33.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002"
dependencies = [
"ansi_term",
"atty",
"bitflags",
"strsim 0.8.0",
"textwrap",
"unicode-width",
"vec_map",
]
[[package]]
name = "darling"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d706e75d87e35569db781a9b5e2416cff1236a47ed380831f959382ccd5f858"
dependencies = [
"darling_core",
"darling_macro",
]
[[package]]
name = "darling_core"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0c960ae2da4de88a91b2d920c2a7233b400bc33cb28453a2987822d8392519b"
dependencies = [
"fnv",
"ident_case",
"proc-macro2",
"quote",
"strsim 0.9.3",
"syn",
]
[[package]]
name = "darling_macro"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72"
dependencies = [
"darling_core",
"quote",
"syn",
]
[[package]]
name = "derive-getters"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c5905670fd9c320154f3a4a01c9e609733cd7b753f3c58777ab7d5ce26686b3"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "derive_builder"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2658621297f2cf68762a6f7dc0bb7e1ff2cfd6583daef8ee0fed6f7ec468ec0"
dependencies = [
"darling",
"derive_builder_core",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "derive_builder_core"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2791ea3e372c8495c0bc2033991d76b512cd799d07491fbd6890124db9458bef"
dependencies = [
"darling",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "env_logger"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36"
dependencies = [
"atty",
"humantime",
"log",
"regex",
"termcolor",
]
[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "glob"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
[[package]]
name = "hermit-abi"
version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c30f6d0bc6b00693347368a67d41b58f2fb851215ff1da49e90fe2c5c667151"
dependencies = [
"libc",
]
[[package]]
name = "humantime"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f"
dependencies = [
"quick-error",
]
[[package]]
name = "ident_case"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "lazycell"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
[[package]]
name = "libc"
version = "0.2.77"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2f96b10ec2560088a8e76961b00d47107b3a625fecb76dedb29ee7ccbf98235"
[[package]]
name = "libloading"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2443d8f0478b16759158b2f66d525991a05491138bc05814ef52a250148ef4f9"
dependencies = [
"cfg-if",
"winapi",
]
[[package]]
name = "log"
version = "0.4.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b"
dependencies = [
"cfg-if",
]
[[package]]
name = "memchr"
version = "2.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400"
[[package]]
name = "nom"
version = "5.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af"
dependencies = [
"memchr",
"version_check",
]
[[package]]
name = "peeking_take_while"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
[[package]]
name = "proc-macro2"
version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36e28516df94f3dd551a587da5357459d9b36d945a7c37c3557928c1c2ff2a2c"
dependencies = [
"unicode-xid",
]
[[package]]
name = "quick-error"
version = "1.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
[[package]]
name = "quote"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37"
dependencies = [
"proc-macro2",
]
[[package]]
name = "regex"
version = "1.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c3780fcf44b193bc4d09f36d2a3c87b251da4a046c87795a0d35f4f927ad8e6"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
"thread_local",
]
[[package]]
name = "regex-syntax"
version = "0.6.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26412eb97c6b088a6997e05f69403a802a92d520de2f8e63c2b65f9e0f47c4e8"
[[package]]
name = "rustc-hash"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "serde"
version = "1.0.116"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96fe57af81d28386a513cbc6858332abc6117cfdb5999647c6444b8f43a370a5"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.116"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f630a6370fd8e457873b4bd2ffdae75408bc291ba72be773772a4c2a065d9ae8"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "shlex"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fdf1b9db47230893d76faad238fd6097fd6d6a9245cd7a4d90dbd639536bbd2"
[[package]]
name = "strsim"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
[[package]]
name = "strsim"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c"
[[package]]
name = "syn"
version = "1.0.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6690e3e9f692504b941dc6c3b188fd28df054f7fb8469ab40680df52fdcc842b"
dependencies = [
"proc-macro2",
"quote",
"unicode-xid",
]
[[package]]
name = "termcolor"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb6bfa289a4d7c5766392812c0a1f4c1ba45afa1ad47803c11e1f407d846d75f"
dependencies = [
"winapi-util",
]
[[package]]
name = "textwrap"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
dependencies = [
"unicode-width",
]
[[package]]
name = "thread_local"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14"
dependencies = [
"lazy_static",
]
[[package]]
name = "unicode-width"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
[[package]]
name = "unicode-xid"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
[[package]]
name = "vec_map"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
[[package]]
name = "version_check"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed"
[[package]]
name = "which"
version = "3.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d011071ae14a2f6671d0b74080ae0cd8ebf3a6f8c9589a2cd45f23126fe29724"
dependencies = [
"libc",
]
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
dependencies = [
"winapi",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "zeroconf"
version = "0.1.0"
dependencies = [
"avahi-sys",
"bonjour-sys",
"derive-getters",
"derive_builder",
"libc",
"log",
"serde",
"zeroconf-macros",
]
[[package]]
name = "zeroconf-browser-example"
version = "0.1.0"
dependencies = [
"zeroconf",
]
[[package]]
name = "zeroconf-macros"
version = "0.1.0"
dependencies = [
"quote",
"syn",
]
[[package]]
name = "zeroconf-service-example"
version = "0.1.0"
dependencies = [
"zeroconf",
]

5
examples/Cargo.toml Normal file
View File

@ -0,0 +1,5 @@
[workspace]
members = [
"browser",
"service",
]

View File

@ -0,0 +1,8 @@
[package]
name = "zeroconf-browser-example"
version = "0.1.0"
authors = ["Walker Crouse <walkercrouse@hotmail.com>"]
edition = "2018"
[dependencies]
zeroconf = { path = "../../zeroconf" }

View File

@ -0,0 +1,18 @@
use std::any::Any;
use std::sync::Arc;
use zeroconf::{MdnsBrowser, ServiceDiscovery};
fn main() {
let mut browser = MdnsBrowser::new("_http._tcp");
browser.set_service_discovered_callback(Box::new(on_service_discovered));
// blocks current thread, must keep-alive to keep browser active
browser.start().unwrap()
}
fn on_service_discovered(service: ServiceDiscovery, _context: Option<Arc<dyn Any>>) {
println!("Service discovered: {:?}", &service);
// ...
}

View File

@ -0,0 +1,8 @@
[package]
name = "zeroconf-service-example"
version = "0.1.0"
authors = ["Walker Crouse <walkercrouse@hotmail.com>"]
edition = "2018"
[dependencies]
zeroconf = { path = "../../zeroconf" }

View File

@ -0,0 +1,36 @@
use std::any::Any;
use std::sync::{Arc, Mutex};
use zeroconf::{MdnsService, ServiceRegistration};
#[derive(Default, Debug)]
pub struct Context {
service_name: String,
}
fn main() {
let mut service = MdnsService::new("_http._tcp", 8080);
let context: Arc<Mutex<Context>> = Arc::default();
service.set_registered_callback(Box::new(on_service_registered));
service.set_context(Box::new(context));
// blocks current thread, must keep-alive to keep service active
service.start().unwrap();
}
fn on_service_registered(service: ServiceRegistration, context: Option<Arc<dyn Any>>) {
println!("Service registered: {:?}", service);
let context = context
.as_ref()
.unwrap()
.downcast_ref::<Arc<Mutex<Context>>>()
.unwrap()
.clone();
context.lock().unwrap().service_name = service.name().clone();
println!("Context: {:?}", context);
// ...
}

View File

@ -0,0 +1,12 @@
[package]
name = "zeroconf-macros"
version = "0.1.0"
authors = ["Walker Crouse <walkercrouse@hotmail.com>"]
edition = "2018"
[lib]
proc-macro = true
[dependencies]
syn = "1.0.41"
quote = "1.0.7"

View File

@ -0,0 +1,70 @@
extern crate proc_macro;
use crate::proc_macro::TokenStream;
use quote::quote;
use syn::{self, DeriveInput, Ident};
#[proc_macro_derive(FromRaw)]
pub fn from_raw_macro_derive(input: TokenStream) -> TokenStream {
impl_from_raw(&syn::parse(input).unwrap())
}
fn impl_from_raw(ast: &DeriveInput) -> TokenStream {
let name = &ast.ident;
let generics = &ast.generics;
let gen = quote! {
impl #generics crate::ffi::FromRaw<#name #generics> for #name #generics {}
};
gen.into()
}
#[proc_macro_derive(CloneRaw)]
pub fn clone_raw_macro_derive(input: TokenStream) -> TokenStream {
impl_clone_raw(&syn::parse(input).unwrap())
}
fn impl_clone_raw(ast: &DeriveInput) -> TokenStream {
let name = &ast.ident;
let generics = &ast.generics;
let gen = quote! {
impl #generics crate::ffi::CloneRaw<#name #generics> for #name #generics {}
};
gen.into()
}
#[proc_macro_derive(AsRaw)]
pub fn as_raw_macro_derive(input: TokenStream) -> TokenStream {
impl_as_raw(&syn::parse(input).unwrap())
}
fn impl_as_raw(ast: &DeriveInput) -> TokenStream {
let name = &ast.ident;
let generics = &ast.generics;
let gen = quote! {
impl #generics crate::ffi::AsRaw for #name #generics {}
};
gen.into()
}
#[proc_macro_derive(BuilderDelegate)]
pub fn builder_delegate_macro_derive(input: TokenStream) -> TokenStream {
impl_builder_delegate(&syn::parse(input).unwrap())
}
fn impl_builder_delegate(ast: &DeriveInput) -> TokenStream {
let name = &ast.ident;
let builder: Ident = syn::parse_str(&format!("{}Builder", name)).unwrap();
let generics = &ast.generics;
let gen = quote! {
impl #generics crate::builder::BuilderDelegate<#builder #generics> for #name #generics {}
};
gen.into()
}

19
zeroconf/Cargo.toml Normal file
View File

@ -0,0 +1,19 @@
[package]
name = "zeroconf"
version = "0.1.0"
authors = ["Walker Crouse <walkercrouse@hotmail.com>"]
edition = "2018"
[dependencies]
serde = { version = "1.0.116", features = ["derive"] }
derive-getters = "0.2.0"
libc = "*"
derive_builder = "0.9.0"
zeroconf-macros = { path = "../zeroconf-macros" }
log = "0.4.11"
[target.'cfg(unix)'.dependencies]
avahi-sys = { path = "../../avahi-sys" }
[target.'cfg(target_os = "macos")'.dependencies]
bonjour-sys = { path = "../../bonjour-sys" }

9
zeroconf/src/builder.rs Normal file
View File

@ -0,0 +1,9 @@
//! Provides builder related helpers
/// Implements a `builder()` function for the specified type
pub trait BuilderDelegate<T: Default> {
/// Initializes a new default builder of type `T`
fn builder() -> T {
T::default()
}
}

25
zeroconf/src/discovery.rs Normal file
View File

@ -0,0 +1,25 @@
use std::any::Any;
use std::sync::Arc;
/// Callback invoked from [`MdnsBrowser`] once a service has been discovered and resolved.
///
/// # Arguments
/// * `discovered_service` - The service that was disovered
/// * `context` - The optional user context passed through using [`MdnsBrowser::set_context()`]
///
/// [`MdnsBrowser`]: struct.MdnsBrowser.html
/// [`MdnsBrowser::set_context()`]: struct.MdnsBrowser.html#method.set_context
pub type ServiceDiscoveredCallback = dyn Fn(ServiceDiscovery, Option<Arc<dyn Any>>);
/// Represents a service that has been discovered by a [`MdnsBrowser`].
///
/// [`MdnsBrowser`]: struct.MdnsBrowser.html
#[derive(Debug, Getters, Builder, BuilderDelegate, Serialize, Deserialize)]
pub struct ServiceDiscovery {
name: String,
kind: String,
domain: String,
host_name: String,
address: String,
port: u16,
}

62
zeroconf/src/ffi.rs Normal file
View File

@ -0,0 +1,62 @@
//! Utilities related to FFI bindings
use libc::c_void;
/// Helper trait to convert a raw `*mut c_void` to it's rust type
pub trait FromRaw<T> {
/// Converts the specified `*mut c_void` to a `&'a mut T`.
///
/// # Unsafe
/// This function is unsafe due to the dereference of the specified raw pointer.
unsafe fn from_raw<'a>(raw: *mut c_void) -> &'a mut T {
&mut *(raw as *mut T)
}
}
/// Helper trait to convert and clone a raw `*mut c_void` to it's rust type
pub trait CloneRaw<T: FromRaw<T> + Clone> {
/// Converts and clones the specified `*mut c_void` to a `Box<T>`.
///
/// # Unsafe
/// This function is unsafe due to a call to the unsafe function [`FromRaw::from_raw()`].
///
/// [`FromRaw::from_raw()`]: trait.FromRaw.html#method.from_raw
unsafe fn clone_raw<'a>(raw: *mut c_void) -> Box<T> {
Box::new(T::from_raw(raw).clone())
}
}
/// Helper trait to convert self to a raw `*mut c_void`
pub trait AsRaw {
/// Converts self to a raw `*mut c_void` by cast.
fn as_raw(&mut self) -> *mut c_void {
self as *mut _ as *mut c_void
}
}
pub mod cstr {
//! FFI utilities related to c-strings
use libc::c_char;
use std::ffi::CStr;
/// Returns the specified `*const c_char` as a `&'a str`. Ownership is not taken.
///
/// # Unsafe
/// This function is unsafe due to a call to the unsafe function [`CStr::from_ptr()`].
///
/// [`CStr::from_ptr()`]: https://doc.rust-lang.org/std/ffi/struct.CStr.html#method.from_ptr
pub unsafe fn raw_to_str<'a>(s: *const c_char) -> &'a str {
CStr::from_ptr(s).to_str().unwrap()
}
/// Copies the specified `*const c_char` into a `String`.
///
/// # Unsafe
/// This function is unsafe due to a call to the unsafe function [`raw_to_str()`].
///
/// [`raw_to_str()`]: fn.raw_to_str.html
pub unsafe fn copy_raw(s: *const c_char) -> String {
String::from(raw_to_str(s))
}
}

128
zeroconf/src/lib.rs Normal file
View File

@ -0,0 +1,128 @@
//! `zeroconf` is a cross-platform library that wraps underlying [ZeroConf/mDNS] implementations
//! such as [Bonjour] or [Avahi], providing an easy and idiomatic way to both register and
//! browse services.
//!
//! This crate provides the cross-platform [`MdnsService`] and [`MdnsBrowser`] available for each
//! supported platform as well as platform-specific modules for lower-level access to the mDNS
//! implementation should that be necessary.
//!
//! Most users of this crate need only [`MdnsService`] and [`MdnsBrowser`].
//!
//! # Examples
//!
//! ## Register a service
//!
//! When registering a service, you may optionally pass a "context" to pass state through the
//! callback. The only requirement is that this context implements the [`Any`] trait, which most
//! types will automatically. See [`MdnsService`] for more information about contexts.
//!
//! ```
//! use std::any::Any;
//! use std::sync::{Arc, Mutex};
//! use zeroconf::{MdnsService, ServiceRegistration};
//!
//! #[derive(Default, Debug)]
//! pub struct Context {
//! service_name: String,
//! }
//!
//! fn main() {
//! let mut service = MdnsService::new("_http._tcp", 8080);
//! let context: Arc<Mutex<Context>> = Arc::default();
//!
//! service.set_registered_callback(Box::new(on_service_registered));
//! service.set_context(Box::new(context));
//!
//! // blocks current thread, must keep-alive to keep service active
//! service.start().unwrap();
//! }
//!
//! fn on_service_registered(service: ServiceRegistration, context: Option<Arc<dyn Any>>) {
//! println!("Service registered: {:?}", service);
//!
//! let context = context
//! .as_ref()
//! .unwrap()
//! .downcast_ref::<Arc<Mutex<Context>>>()
//! .unwrap()
//! .clone();
//!
//! context.lock().unwrap().service_name = service.name().clone();
//!
//! println!("Context: {:?}", context);
//!
//! // ...
//! }
//! ```
//!
//! ## Browsing services
//!
//! ```
//! use std::any::Any;
//! use std::sync::Arc;
//! use zeroconf::{MdnsBrowser, ServiceDiscovery};
//!
//! fn main() {
//! let mut browser = MdnsBrowser::new("_http._tcp");
//!
//! browser.set_service_discovered_callback(Box::new(on_service_discovered));
//!
//! // blocks current thread, must keep-alive to keep browser active
//! browser.start().unwrap()
//! }
//!
//! fn on_service_discovered(service: ServiceDiscovery, _context: Option<Arc<dyn Any>>) {
//! println!("Service discovered: {:?}", &service);
//!
//! // ...
//! }
//! ```
//! [ZeroConf/mDNS]: https://en.wikipedia.org/wiki/Zero-configuration_networking
//! [Bonjour]: https://en.wikipedia.org/wiki/Bonjour_(software)
//! [Avahi]: https://en.wikipedia.org/wiki/Avahi_(software)
//! [`MdnsService`]: struct.MdnsService.html
//! [`MdnsBrowser`]: struct.MdnsBrowser.html
//! [`Any`]: https://doc.rust-lang.org/std/any/trait.Any.html
#[macro_use]
extern crate serde;
#[macro_use]
extern crate derive_builder;
#[macro_use]
extern crate zeroconf_macros;
#[cfg(target_os = "linux")]
extern crate avahi_sys;
#[cfg(target_os = "macos")]
extern crate bonjour_sys;
#[macro_use]
extern crate derive_getters;
#[macro_use]
extern crate log;
extern crate libc;
mod discovery;
mod registration;
pub mod builder;
pub mod ffi;
#[cfg(target_os = "linux")]
pub mod linux;
#[cfg(target_os = "macos")]
pub mod macos;
pub use discovery::*;
pub use registration::*;
/// Type alias for the platform-specific mDNS browser implementation
#[cfg(target_os = "linux")]
pub type MdnsBrowser = linux::browser::AvahiMdnsBrowser;
/// Type alias for the platform-specific mDNS service implementation
#[cfg(target_os = "linux")]
pub type MdnsService = linux::service::AvahiMdnsService;
/// Type alias for the platform-specific mDNS browser implementation
#[cfg(target_os = "macos")]
pub type MdnsBrowser = macos::browser::BonjourMdnsBrowser;
/// Type alias for the platform-specific mDNS service implementation
#[cfg(target_os = "macos")]
pub type MdnsService = macos::service::BonjourMdnsService;

View File

@ -0,0 +1,42 @@
//! Utilities related to Avahi
use super::constants;
use avahi_sys::{avahi_address_snprint, avahi_strerror, AvahiAddress};
use libc::c_char;
use std::ffi::{CStr, CString};
use std::mem;
/// Converts the specified `*const AvahiAddress` to a `String`.
///
/// The new `String` is constructed through allocating a new `CString`, passing it to
/// `avahi_address_snprint` and then converting it to a Rust-type `String`.
pub fn avahi_address_to_string(addr: *const AvahiAddress) -> String {
let addr_str = unsafe {
let str = CString::from_vec_unchecked(vec![0; constants::AVAHI_ADDRESS_STR_MAX]);
avahi_address_snprint(str.as_ptr() as *mut c_char, mem::size_of_val(&str), addr);
str
};
String::from(addr_str.to_str().unwrap())
.trim_matches(char::from(0))
.to_string()
}
/// Returns the `&str` message associated with the specified error code.
pub fn get_error<'a>(code: i32) -> &'a str {
unsafe {
CStr::from_ptr(avahi_strerror(code))
.to_str()
.expect("could not fetch Avahi error string")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn get_error_returns_valid_error_string() {
assert_eq!(get_error(avahi_sys::AVAHI_ERR_FAILURE), "Operation failed");
}
}

View File

@ -0,0 +1,225 @@
use super::avahi_util;
use super::client::{ManagedAvahiClient, ManagedAvahiClientParams};
use super::constants;
use super::poll::ManagedAvahiSimplePoll;
use super::raw_browser::{ManagedAvahiServiceBrowser, ManagedAvahiServiceBrowserParams};
use super::resolver::{
ManagedAvahiServiceResolver, ManagedAvahiServiceResolverParams, ServiceResolverSet,
};
use crate::builder::BuilderDelegate;
use crate::ffi::{cstr, FromRaw};
use crate::{ServiceDiscoveredCallback, ServiceDiscovery};
use avahi_sys::{
AvahiAddress, AvahiBrowserEvent, AvahiClient, AvahiClientFlags, AvahiClientState, AvahiIfIndex,
AvahiLookupResultFlags, AvahiProtocol, AvahiResolverEvent, AvahiServiceBrowser,
AvahiServiceResolver, AvahiStringList,
};
use libc::{c_char, c_void};
use std::any::Any;
use std::ffi::CString;
use std::sync::Arc;
use std::{fmt, ptr};
#[derive(Debug)]
pub struct AvahiMdnsBrowser {
poll: Option<ManagedAvahiSimplePoll>,
browser: Option<ManagedAvahiServiceBrowser>,
kind: CString,
context: *mut AvahiBrowserContext,
}
impl AvahiMdnsBrowser {
pub fn new(kind: &str) -> Self {
Self {
poll: None,
browser: None,
kind: CString::new(kind.to_string()).unwrap(),
context: Box::into_raw(Box::default()),
}
}
pub fn set_service_discovered_callback(
&mut self,
service_discovered_callback: Box<ServiceDiscoveredCallback>,
) {
unsafe { (*self.context).service_discovered_callback = Some(service_discovered_callback) };
}
pub fn set_context(&mut self, context: Box<dyn Any>) {
unsafe { (*self.context).user_context = Some(Arc::from(context)) };
}
pub fn start(&mut self) -> Result<(), String> {
debug!("Browsing services: {:?}", self);
self.poll = Some(ManagedAvahiSimplePoll::new()?);
let client = ManagedAvahiClient::new(
ManagedAvahiClientParams::builder()
.poll(self.poll.as_ref().unwrap())
.flags(AvahiClientFlags(0))
.callback(Some(client_callback))
.userdata(ptr::null_mut())
.build()?,
)?;
unsafe {
(*self.context).client = Some(client);
self.browser = Some(ManagedAvahiServiceBrowser::new(
ManagedAvahiServiceBrowserParams::builder()
.client(&(*self.context).client.as_ref().unwrap())
.interface(constants::AVAHI_IF_UNSPEC)
.protocol(constants::AVAHI_PROTO_UNSPEC)
.kind(self.kind.as_ptr())
.domain(ptr::null_mut())
.flags(0)
.callback(Some(browse_callback))
.userdata(self.context as *mut c_void)
.build()?,
)?);
}
self.poll.as_ref().unwrap().start_loop()
}
}
impl Drop for AvahiMdnsBrowser {
fn drop(&mut self) {
unsafe {
Box::from_raw(self.context);
}
}
}
#[derive(FromRaw)]
struct AvahiBrowserContext {
client: Option<ManagedAvahiClient>,
resolvers: ServiceResolverSet,
service_discovered_callback: Option<Box<ServiceDiscoveredCallback>>,
user_context: Option<Arc<dyn Any>>,
}
impl Default for AvahiBrowserContext {
fn default() -> Self {
AvahiBrowserContext {
client: None,
resolvers: ServiceResolverSet::default(),
service_discovered_callback: None,
user_context: None,
}
}
}
impl fmt::Debug for AvahiBrowserContext {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("AvahiBrowserContext")
.field("client", &self.client)
.field("resolvers", &self.resolvers)
.finish()
}
}
unsafe extern "C" fn browse_callback(
_browser: *mut AvahiServiceBrowser,
interface: AvahiIfIndex,
protocol: AvahiProtocol,
event: AvahiBrowserEvent,
name: *const c_char,
kind: *const c_char,
domain: *const c_char,
_flags: AvahiLookupResultFlags,
userdata: *mut c_void,
) {
let context = AvahiBrowserContext::from_raw(userdata);
match event {
avahi_sys::AvahiBrowserEvent_AVAHI_BROWSER_NEW => {
context.resolvers.insert(
ManagedAvahiServiceResolver::new(
ManagedAvahiServiceResolverParams::builder()
.client(context.client.as_ref().unwrap())
.interface(interface)
.protocol(protocol)
.name(name)
.kind(kind)
.domain(domain)
.aprotocol(constants::AVAHI_PROTO_UNSPEC)
.flags(0)
.callback(Some(resolve_callback))
.userdata(userdata)
.build()
.unwrap(),
)
.unwrap(),
);
}
avahi_sys::AvahiBrowserEvent_AVAHI_BROWSER_FAILURE => panic!("browser failure"),
_ => {}
};
}
unsafe extern "C" fn resolve_callback(
resolver: *mut AvahiServiceResolver,
_interface: AvahiIfIndex,
_protocol: AvahiProtocol,
event: AvahiResolverEvent,
name: *const c_char,
kind: *const c_char,
domain: *const c_char,
host_name: *const c_char,
addr: *const AvahiAddress,
port: u16,
_txt: *mut AvahiStringList,
_flags: AvahiLookupResultFlags,
userdata: *mut c_void,
) {
let name = cstr::raw_to_str(name);
let kind = cstr::raw_to_str(kind);
let domain = cstr::raw_to_str(domain);
let context = AvahiBrowserContext::from_raw(userdata);
match event {
avahi_sys::AvahiResolverEvent_AVAHI_RESOLVER_FAILURE => warn!(
"failed to resolve service `{}` of type `{}` in domain `{}`",
name, kind, domain
),
avahi_sys::AvahiResolverEvent_AVAHI_RESOLVER_FOUND => {
let host_name = cstr::raw_to_str(host_name);
let address = avahi_util::avahi_address_to_string(addr);
let result = ServiceDiscovery::builder()
.name(name.to_string())
.kind(kind.to_string())
.domain(domain.to_string())
.host_name(host_name.to_string())
.address(address)
.port(port)
.build()
.unwrap();
debug!("Service resolved: {:?}", result);
if let Some(f) = &context.service_discovered_callback {
f(result, context.user_context.clone());
} else {
warn!("Service resolved but no callback was set");
}
}
_ => {}
};
context.resolvers.remove_raw(resolver);
}
extern "C" fn client_callback(
_client: *mut AvahiClient,
state: AvahiClientState,
_userdata: *mut c_void,
) {
match state {
avahi_sys::AvahiClientState_AVAHI_CLIENT_FAILURE => panic!("client failure"),
_ => {}
}
}

View File

@ -0,0 +1,72 @@
use super::avahi_util;
use super::poll::ManagedAvahiSimplePoll;
use crate::ffi::cstr;
use avahi_sys::{
avahi_client_free, avahi_client_get_host_name, avahi_client_new, avahi_simple_poll_get,
AvahiClient, AvahiClientCallback, AvahiClientFlags,
};
use libc::{c_int, c_void};
use std::ptr;
#[derive(Debug)]
pub struct ManagedAvahiClient {
pub(super) client: *mut AvahiClient,
}
impl ManagedAvahiClient {
pub fn new(
ManagedAvahiClientParams {
poll,
flags,
callback,
userdata,
}: ManagedAvahiClientParams,
) -> Result<Self, String> {
let mut err: c_int = 0;
let client = unsafe {
avahi_client_new(
avahi_simple_poll_get(poll.poll),
flags,
callback,
userdata,
&mut err,
)
};
if client == ptr::null_mut() {
return Err("could not initialize AvahiClient".to_string());
}
match err {
0 => Ok(Self { client }),
_ => Err(format!(
"could not initialize AvahiClient: {}",
avahi_util::get_error(err)
)),
}
}
}
impl Drop for ManagedAvahiClient {
fn drop(&mut self) {
unsafe { avahi_client_free(self.client) };
}
}
#[derive(Builder, BuilderDelegate)]
pub struct ManagedAvahiClientParams<'a> {
poll: &'a ManagedAvahiSimplePoll,
flags: AvahiClientFlags,
callback: AvahiClientCallback,
userdata: *mut c_void,
}
pub unsafe fn get_host_name<'a>(client: *mut AvahiClient) -> Result<&'a str, String> {
let host_name = avahi_client_get_host_name(client);
if host_name != ptr::null_mut() {
Ok(cstr::raw_to_str(host_name))
} else {
Err("could not get host name from AvahiClient".to_string())
}
}

View File

@ -0,0 +1,6 @@
use avahi_sys::{AvahiIfIndex, AvahiProtocol};
pub const AVAHI_IF_UNSPEC: AvahiIfIndex = -1;
pub const AVAHI_PROTO_UNSPEC: AvahiProtocol = -1;
pub const AVAHI_ERR_COLLISION: i32 = -8;
pub const AVAHI_ADDRESS_STR_MAX: usize = 40;

View File

@ -0,0 +1,110 @@
use super::avahi_util;
use avahi_sys::{
avahi_entry_group_add_service, avahi_entry_group_commit, avahi_entry_group_free,
avahi_entry_group_is_empty, avahi_entry_group_new, avahi_entry_group_reset, AvahiClient,
AvahiEntryGroup, AvahiEntryGroupCallback, AvahiIfIndex, AvahiProtocol, AvahiPublishFlags,
};
use libc::{c_char, c_void};
use std::ptr;
#[derive(Debug)]
pub struct ManagedAvahiEntryGroup {
group: *mut AvahiEntryGroup,
}
impl ManagedAvahiEntryGroup {
pub fn new(
ManagedAvahiEntryGroupParams {
client,
callback,
userdata,
}: ManagedAvahiEntryGroupParams,
) -> Result<Self, String> {
let group = unsafe { avahi_entry_group_new(client, callback, userdata) };
if group == ptr::null_mut() {
Err("could not initialize AvahiEntryGroup".to_string())
} else {
Ok(Self { group })
}
}
pub fn is_empty(&self) -> bool {
unsafe { avahi_entry_group_is_empty(self.group) != 0 }
}
pub fn add_service(
&mut self,
AddServiceParams {
interface,
protocol,
flags,
name,
kind,
domain,
host,
port,
}: AddServiceParams,
) -> Result<(), String> {
let err = unsafe {
avahi_entry_group_add_service(
self.group,
interface,
protocol,
flags,
name,
kind,
domain,
host,
port,
ptr::null_mut() as *const c_char, // null terminated txt record list
)
};
if err < 0 {
return Err(format!(
"could not register service: `{}`",
avahi_util::get_error(err)
));
}
let err = unsafe { avahi_entry_group_commit(self.group) };
if err < 0 {
Err(format!(
"could not commit service: `{}`",
avahi_util::get_error(err)
))
} else {
Ok(())
}
}
pub fn reset(&mut self) {
unsafe { avahi_entry_group_reset(self.group) };
}
}
impl Drop for ManagedAvahiEntryGroup {
fn drop(&mut self) {
unsafe { avahi_entry_group_free(self.group) };
}
}
#[derive(Builder, BuilderDelegate)]
pub struct ManagedAvahiEntryGroupParams {
client: *mut AvahiClient,
callback: AvahiEntryGroupCallback,
userdata: *mut c_void,
}
#[derive(Builder, BuilderDelegate)]
pub struct AddServiceParams {
interface: AvahiIfIndex,
protocol: AvahiProtocol,
flags: AvahiPublishFlags,
name: *const c_char,
kind: *const c_char,
domain: *const c_char,
host: *const c_char,
port: u16,
}

21
zeroconf/src/linux/mod.rs Normal file
View File

@ -0,0 +1,21 @@
//! Linux-specific ZeroConf bindings
//!
//! This module wraps the [Avahi] mDNS implementation which can be found in most major Linux
//! distributions. It is a sufficient (and often more featured) replacement for Apple's [Bonjour].
//!
//! [Bonjour]: https://en.wikipedia.org/wiki/Bonjour_(software)
//! [Avahi]: https://en.wikipedia.org/wiki/Avahi_(software)
pub(crate) mod browser;
pub(crate) mod service;
pub mod avahi_util;
pub mod client;
pub mod constants;
pub mod entry_group;
pub mod poll;
pub mod raw_browser;
pub mod resolver;
pub use browser::*;
pub use service::*;

View File

@ -0,0 +1,39 @@
use super::avahi_util;
use avahi_sys::{
avahi_simple_poll_free, avahi_simple_poll_loop, avahi_simple_poll_new, AvahiSimplePoll,
};
use std::ptr;
#[derive(Debug)]
pub struct ManagedAvahiSimplePoll {
pub(super) poll: *mut AvahiSimplePoll,
}
impl ManagedAvahiSimplePoll {
pub fn new() -> Result<Self, String> {
let poll = unsafe { avahi_simple_poll_new() };
if poll == ptr::null_mut() {
Err("could not initialize AvahiSimplePoll".to_string())
} else {
Ok(Self { poll })
}
}
pub fn start_loop(&self) -> Result<(), String> {
let err = unsafe { avahi_simple_poll_loop(self.poll) };
if err != 0 {
Err(format!(
"could not start AvahiSimplePoll: {}",
avahi_util::get_error(err)
))
} else {
Ok(())
}
}
}
impl Drop for ManagedAvahiSimplePoll {
fn drop(&mut self) {
unsafe { avahi_simple_poll_free(self.poll) };
}
}

View File

@ -0,0 +1,64 @@
use super::client::ManagedAvahiClient;
use avahi_sys::{
avahi_service_browser_free, avahi_service_browser_new, AvahiIfIndex, AvahiLookupFlags,
AvahiProtocol, AvahiServiceBrowser, AvahiServiceBrowserCallback,
};
use libc::{c_char, c_void};
use std::ptr;
#[derive(Debug)]
pub struct ManagedAvahiServiceBrowser {
browser: *mut AvahiServiceBrowser,
}
impl ManagedAvahiServiceBrowser {
pub fn new(
ManagedAvahiServiceBrowserParams {
client,
interface,
protocol,
kind,
domain,
flags,
callback,
userdata,
}: ManagedAvahiServiceBrowserParams,
) -> Result<Self, String> {
let browser = unsafe {
avahi_service_browser_new(
client.client,
interface,
protocol,
kind,
domain,
flags,
callback,
userdata,
)
};
if browser == ptr::null_mut() {
Err("could not initialize Avahi service browser".to_string())
} else {
Ok(Self { browser })
}
}
}
impl Drop for ManagedAvahiServiceBrowser {
fn drop(&mut self) {
unsafe { avahi_service_browser_free(self.browser) };
}
}
#[derive(Builder, BuilderDelegate)]
pub struct ManagedAvahiServiceBrowserParams<'a> {
client: &'a ManagedAvahiClient,
interface: AvahiIfIndex,
protocol: AvahiProtocol,
kind: *const c_char,
domain: *const c_char,
flags: AvahiLookupFlags,
callback: AvahiServiceBrowserCallback,
userdata: *mut c_void,
}

View File

@ -0,0 +1,86 @@
use super::client::ManagedAvahiClient;
use avahi_sys::{
avahi_service_resolver_free, avahi_service_resolver_new, AvahiIfIndex, AvahiLookupFlags,
AvahiProtocol, AvahiServiceResolver, AvahiServiceResolverCallback,
};
use libc::{c_char, c_void};
use std::collections::HashMap;
use std::ptr;
#[derive(Debug)]
pub struct ManagedAvahiServiceResolver {
resolver: *mut AvahiServiceResolver,
}
impl ManagedAvahiServiceResolver {
pub fn new(
ManagedAvahiServiceResolverParams {
client,
interface,
protocol,
name,
kind,
domain,
aprotocol,
flags,
callback,
userdata,
}: ManagedAvahiServiceResolverParams,
) -> Result<Self, String> {
let resolver = unsafe {
avahi_service_resolver_new(
client.client,
interface,
protocol,
name,
kind,
domain,
aprotocol,
flags,
callback,
userdata,
)
};
if resolver == ptr::null_mut() {
Err("could not initialize AvahiServiceResolver".to_string())
} else {
Ok(Self { resolver })
}
}
}
impl Drop for ManagedAvahiServiceResolver {
fn drop(&mut self) {
unsafe { avahi_service_resolver_free(self.resolver) };
}
}
#[derive(Builder, BuilderDelegate)]
pub struct ManagedAvahiServiceResolverParams<'a> {
client: &'a ManagedAvahiClient,
interface: AvahiIfIndex,
protocol: AvahiProtocol,
name: *const c_char,
kind: *const c_char,
domain: *const c_char,
aprotocol: AvahiProtocol,
flags: AvahiLookupFlags,
callback: AvahiServiceResolverCallback,
userdata: *mut c_void,
}
#[derive(Default, Debug)]
pub struct ServiceResolverSet {
resolvers: HashMap<*mut AvahiServiceResolver, ManagedAvahiServiceResolver>,
}
impl ServiceResolverSet {
pub fn insert(&mut self, resolver: ManagedAvahiServiceResolver) {
self.resolvers.insert(resolver.resolver, resolver);
}
pub fn remove_raw(&mut self, raw: *mut AvahiServiceResolver) {
self.resolvers.remove(&raw);
}
}

View File

@ -0,0 +1,188 @@
use super::client::{self, ManagedAvahiClient, ManagedAvahiClientParams};
use super::constants;
use super::entry_group::{AddServiceParams, ManagedAvahiEntryGroup, ManagedAvahiEntryGroupParams};
use super::poll::ManagedAvahiSimplePoll;
use crate::builder::BuilderDelegate;
use crate::ffi::{cstr, AsRaw, FromRaw};
use crate::{ServiceRegisteredCallback, ServiceRegistration};
use avahi_sys::{
AvahiClient, AvahiClientFlags, AvahiClientState, AvahiEntryGroup, AvahiEntryGroupState,
};
use libc::c_void;
use std::any::Any;
use std::ffi::CString;
use std::fmt::{self, Formatter};
use std::ptr;
use std::sync::Arc;
#[derive(Debug)]
pub struct AvahiMdnsService {
client: Option<ManagedAvahiClient>,
poll: Option<ManagedAvahiSimplePoll>,
context: *mut AvahiServiceContext,
}
impl AvahiMdnsService {
pub fn new(kind: &str, port: u16) -> Self {
Self {
client: None,
poll: None,
context: Box::into_raw(Box::new(AvahiServiceContext::new(kind, port))),
}
}
pub fn set_registered_callback(&mut self, registered_callback: Box<ServiceRegisteredCallback>) {
unsafe { (*self.context).registered_callback = Some(registered_callback) };
}
pub fn set_context(&mut self, context: Box<dyn Any>) {
unsafe { (*self.context).user_context = Some(Arc::from(context)) };
}
pub fn start(&mut self) -> Result<(), String> {
debug!("Registering service: {:?}", self);
self.poll = Some(ManagedAvahiSimplePoll::new()?);
self.client = Some(ManagedAvahiClient::new(
ManagedAvahiClientParams::builder()
.poll(self.poll.as_ref().unwrap())
.flags(AvahiClientFlags(0))
.callback(Some(client_callback))
.userdata(self.context as *mut c_void)
.build()?,
)?);
self.poll.as_ref().unwrap().start_loop()
}
}
impl Drop for AvahiMdnsService {
fn drop(&mut self) {
unsafe { Box::from_raw(self.context) };
}
}
#[derive(FromRaw, AsRaw)]
struct AvahiServiceContext {
name: Option<CString>,
kind: CString,
port: u16,
group: Option<ManagedAvahiEntryGroup>,
registered_callback: Option<Box<ServiceRegisteredCallback>>,
user_context: Option<Arc<dyn Any>>,
}
impl AvahiServiceContext {
fn new(kind: &str, port: u16) -> Self {
Self {
name: None,
kind: CString::new(kind).unwrap(),
port,
group: None,
registered_callback: None,
user_context: None,
}
}
}
impl fmt::Debug for AvahiServiceContext {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_struct("AvahiServiceContext")
.field("name", &self.name)
.field("kind", &self.kind)
.field("port", &self.port)
.field("group", &self.group)
.finish()
}
}
unsafe extern "C" fn client_callback(
client: *mut AvahiClient,
state: AvahiClientState,
userdata: *mut c_void,
) {
let context = AvahiServiceContext::from_raw(userdata);
match state {
avahi_sys::AvahiClientState_AVAHI_CLIENT_S_RUNNING => create_service(client, context),
avahi_sys::AvahiClientState_AVAHI_CLIENT_FAILURE => panic!("client failure"),
avahi_sys::AvahiClientState_AVAHI_CLIENT_S_REGISTERING => {
if let Some(g) = &mut context.group {
debug!("Group reset");
g.reset();
}
}
_ => {}
};
}
unsafe fn create_service(client: *mut AvahiClient, context: &mut AvahiServiceContext) {
context.name = Some(CString::new(client::get_host_name(client).unwrap().to_string()).unwrap());
if context.group.is_none() {
debug!("Creating group");
context.group = Some(
ManagedAvahiEntryGroup::new(
ManagedAvahiEntryGroupParams::builder()
.client(client)
.callback(Some(entry_group_callback))
.userdata(context.as_raw())
.build()
.unwrap(),
)
.unwrap(),
);
}
let group = context.group.as_mut().unwrap();
if group.is_empty() {
debug!("Adding service");
group
.add_service(
AddServiceParams::builder()
.interface(constants::AVAHI_IF_UNSPEC)
.protocol(constants::AVAHI_PROTO_UNSPEC)
.flags(0)
.name(context.name.as_ref().unwrap().as_ptr())
.kind(context.kind.as_ptr())
.domain(ptr::null_mut())
.host(ptr::null_mut())
.port(context.port)
.build()
.unwrap(),
)
.unwrap();
}
}
unsafe extern "C" fn entry_group_callback(
_group: *mut AvahiEntryGroup,
state: AvahiEntryGroupState,
userdata: *mut c_void,
) {
match state {
avahi_sys::AvahiEntryGroupState_AVAHI_ENTRY_GROUP_ESTABLISHED => {
debug!("Group established");
let context = AvahiServiceContext::from_raw(userdata);
let result = ServiceRegistration::builder()
.name(cstr::copy_raw(context.name.as_ref().unwrap().as_ptr()))
.kind(cstr::copy_raw(context.kind.as_ptr()))
.domain("local".to_string())
.build()
.expect("could not build ServiceRegistration");
if let Some(f) = &context.registered_callback {
f(result, context.user_context.clone());
} else {
warn!("Service registered but no callback was set: {:?}", result);
}
}
_ => {}
};
}

View File

@ -0,0 +1,207 @@
use super::compat;
use super::service_ref::{
BrowseServicesParams, GetAddressInfoParams, ManagedDNSServiceRef, ServiceResolveParams,
};
use crate::builder::BuilderDelegate;
use crate::ffi::{cstr, FromRaw};
use crate::{ServiceDiscoveredCallback, ServiceDiscovery};
use bonjour_sys::{sockaddr, DNSServiceErrorType, DNSServiceFlags, DNSServiceRef};
use libc::{c_char, c_uchar, c_void, in_addr, sockaddr_in};
use std::any::Any;
use std::ffi::CString;
use std::fmt::{self, Formatter};
use std::ptr;
use std::sync::Arc;
#[derive(Debug)]
pub struct BonjourMdnsBrowser {
service: ManagedDNSServiceRef,
kind: CString,
context: *mut BonjourBrowserContext,
}
impl BonjourMdnsBrowser {
pub fn new(kind: &str) -> Self {
Self {
service: ManagedDNSServiceRef::default(),
kind: CString::new(kind).unwrap(),
context: Box::into_raw(Box::default()),
}
}
pub fn set_service_discovered_callback(
&self,
service_discovered_callback: Box<ServiceDiscoveredCallback>,
) {
unsafe { (*self.context).service_discovered_callback = Some(service_discovered_callback) };
}
pub fn set_context(&mut self, context: Box<dyn Any>) {
unsafe { (*self.context).user_context = Some(Arc::from(context)) };
}
pub fn start(&mut self) -> Result<(), String> {
debug!("Browsing services: {:?}", self);
self.service.browse_services(
BrowseServicesParams::builder()
.flags(0)
.interface_index(0)
.regtype(self.kind.as_ptr())
.domain(ptr::null_mut())
.callback(Some(browse_callback))
.context(self.context as *mut c_void)
.build()?,
)
}
}
impl Drop for BonjourMdnsBrowser {
fn drop(&mut self) {
unsafe { Box::from_raw(self.context) };
}
}
#[derive(Default, FromRaw)]
struct BonjourBrowserContext {
service_discovered_callback: Option<Box<ServiceDiscoveredCallback>>,
resolved_name: Option<String>,
resolved_kind: Option<String>,
resolved_domain: Option<String>,
resolved_port: u16,
user_context: Option<Arc<dyn Any>>,
}
impl fmt::Debug for BonjourBrowserContext {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_struct("BonjourResolverContext")
.field("resolved_name", &self.resolved_name)
.field("resolved_kind", &self.resolved_kind)
.field("resolved_domain", &self.resolved_domain)
.field("resolved_port", &self.resolved_port)
.finish()
}
}
unsafe extern "C" fn browse_callback(
_sd_ref: DNSServiceRef,
_flags: DNSServiceFlags,
interface_index: u32,
error: DNSServiceErrorType,
name: *const c_char,
regtype: *const c_char,
domain: *const c_char,
context: *mut c_void,
) {
let ctx = BonjourBrowserContext::from_raw(context);
if error != 0 {
panic!("browse_callback() reported error (code: {})", error);
}
ctx.resolved_name = Some(cstr::copy_raw(name));
ctx.resolved_kind = Some(cstr::copy_raw(regtype));
ctx.resolved_domain = Some(cstr::copy_raw(domain));
ManagedDNSServiceRef::default()
.resolve_service(
ServiceResolveParams::builder()
.flags(bonjour_sys::kDNSServiceFlagsForceMulticast)
.interface_index(interface_index)
.name(name)
.regtype(regtype)
.domain(domain)
.callback(Some(resolve_callback))
.context(context)
.build()
.expect("could not build ServiceResolveParams"),
)
.unwrap();
}
unsafe extern "C" fn resolve_callback(
_sd_ref: DNSServiceRef,
_flags: DNSServiceFlags,
interface_index: u32,
error: DNSServiceErrorType,
_fullname: *const c_char,
host_target: *const c_char,
port: u16,
_txt_len: u16,
_txt_record: *const c_uchar,
context: *mut c_void,
) {
let ctx = BonjourBrowserContext::from_raw(context);
if error != 0 {
panic!("error reported by resolve_callback: (code: {})", error);
}
ctx.resolved_port = port;
ManagedDNSServiceRef::default()
.get_address_info(
GetAddressInfoParams::builder()
.flags(bonjour_sys::kDNSServiceFlagsForceMulticast)
.interface_index(interface_index)
.protocol(0)
.hostname(host_target)
.callback(Some(get_address_info_callback))
.context(context)
.build()
.expect("could not build GetAddressInfoParams"),
)
.unwrap();
}
unsafe extern "C" fn get_address_info_callback(
_sd_ref: DNSServiceRef,
_flags: DNSServiceFlags,
_interface_index: u32,
error: DNSServiceErrorType,
hostname: *const c_char,
address: *const sockaddr,
_ttl: u32,
context: *mut c_void,
) {
let ctx = BonjourBrowserContext::from_raw(context);
// this callback runs multiple times for some reason
if ctx.resolved_name.is_none() {
return;
}
if error != 0 {
panic!(
"get_address_info_callback() reported error (code: {})",
error
);
}
let ip = get_ip(address as *const sockaddr_in);
let hostname = cstr::copy_raw(hostname);
let domain = compat::normalize_domain(&ctx.resolved_domain.take().unwrap());
let result = ServiceDiscovery::builder()
.name(ctx.resolved_name.take().unwrap())
.kind(ctx.resolved_kind.take().unwrap())
.domain(domain)
.host_name(hostname)
.address(ip)
.port(ctx.resolved_port)
.build()
.expect("could not build ServiceResolution");
if let Some(f) = &ctx.service_discovered_callback {
f(result, ctx.user_context.clone());
}
}
extern "C" {
fn inet_ntoa(addr: *const libc::in_addr) -> *const c_char;
}
unsafe fn get_ip(address: *const sockaddr_in) -> String {
let raw = inet_ntoa(&(*address).sin_addr as *const in_addr);
String::from(cstr::raw_to_str(raw))
}

View File

@ -0,0 +1,13 @@
//! Utilities related to compatibility between platforms
/// Normalizes the specified domain `&str` to conform to a standard enforced by this crate.
///
/// Bonjour suffixes domains with a final `'.'` character in some contexts but is not required by
/// the standard. This function removes the final dot if present.
pub fn normalize_domain(domain: &str) -> String {
if domain.chars().nth(domain.len() - 1).unwrap() == '.' {
String::from(&domain[..domain.len() - 1])
} else {
String::from(domain)
}
}

14
zeroconf/src/macos/mod.rs Normal file
View File

@ -0,0 +1,14 @@
//! macOS-specific ZeroConf bindings
//!
//! This module wraps the [Bonjour] mDNS implementation which is distributed with macOS.
//!
//! [Bonjour]: https://en.wikipedia.org/wiki/Bonjour_(software)
pub(crate) mod browser;
pub(crate) mod service;
pub mod compat;
pub mod service_ref;
pub use browser::*;
pub use service::*;

View File

@ -0,0 +1,104 @@
use super::compat;
use super::service_ref::{ManagedDNSServiceRef, RegisterServiceParams};
use crate::builder::BuilderDelegate;
use crate::ffi::{cstr, FromRaw};
use crate::{ServiceRegisteredCallback, ServiceRegistration};
use bonjour_sys::{DNSServiceErrorType, DNSServiceFlags, DNSServiceRef};
use libc::{c_char, c_void};
use std::any::Any;
use std::ffi::CString;
use std::ptr;
use std::sync::Arc;
const BONJOUR_IF_UNSPEC: u32 = 0;
const BONJOUR_RENAME_FLAGS: DNSServiceFlags = 0;
#[derive(Debug)]
pub struct BonjourMdnsService {
service: ManagedDNSServiceRef,
kind: CString,
port: u16,
context: *mut BonjourServiceContext,
}
impl BonjourMdnsService {
pub fn new(kind: &str, port: u16) -> Self {
Self {
service: ManagedDNSServiceRef::default(),
kind: CString::new(kind).unwrap(),
port,
context: Box::into_raw(Box::default()),
}
}
pub fn set_registered_callback(&mut self, registered_callback: Box<ServiceRegisteredCallback>) {
unsafe { (*self.context).registered_callback = Some(registered_callback) };
}
pub fn set_context(&mut self, context: Box<dyn Any>) {
unsafe { (*self.context).user_context = Some(Arc::from(context)) };
}
pub fn start(&mut self) -> Result<(), String> {
debug!("Registering service: {:?}", self);
self.service.register_service(
RegisterServiceParams::builder()
.flags(BONJOUR_RENAME_FLAGS)
.interface_index(BONJOUR_IF_UNSPEC)
.name(ptr::null())
.regtype(self.kind.as_ptr())
.domain(ptr::null())
.host(ptr::null())
.port(self.port)
.txt_len(0)
.txt_record(ptr::null())
.callback(Some(register_callback))
.context(self.context as *mut c_void)
.build()?,
)
}
}
impl Drop for BonjourMdnsService {
fn drop(&mut self) {
unsafe { Box::from_raw(self.context) };
}
}
#[derive(Default, FromRaw)]
struct BonjourServiceContext {
registered_callback: Option<Box<ServiceRegisteredCallback>>,
user_context: Option<Arc<dyn Any>>,
}
unsafe extern "C" fn register_callback(
_sd_ref: DNSServiceRef,
_flags: DNSServiceFlags,
error: DNSServiceErrorType,
name: *const c_char,
regtype: *const c_char,
domain: *const c_char,
context: *mut c_void,
) {
if error != 0 {
panic!("register_callback() reported error (code: {0})", error);
}
let domain = compat::normalize_domain(cstr::raw_to_str(domain));
let result = ServiceRegistration::builder()
.name(cstr::copy_raw(name))
.kind(cstr::copy_raw(regtype))
.domain(domain)
.build()
.expect("could not build ServiceRegistration");
let context = BonjourServiceContext::from_raw(context);
if let Some(f) = &mut context.registered_callback {
f(result, context.user_context.clone());
} else {
warn!("Service registered but no callback has been set");
}
}

View File

@ -0,0 +1,230 @@
use bonjour_sys::{
DNSServiceBrowse, DNSServiceBrowseReply, DNSServiceFlags, DNSServiceGetAddrInfo,
DNSServiceGetAddrInfoReply, DNSServiceProcessResult, DNSServiceProtocol, DNSServiceRef,
DNSServiceRefDeallocate, DNSServiceRegister, DNSServiceRegisterReply, DNSServiceResolve,
DNSServiceResolveReply,
};
use libc::{c_char, c_void};
use std::ptr;
#[derive(Debug)]
pub struct ManagedDNSServiceRef {
service: DNSServiceRef,
}
#[derive(Builder, BuilderDelegate)]
pub struct RegisterServiceParams {
flags: DNSServiceFlags,
interface_index: u32,
name: *const c_char,
regtype: *const c_char,
domain: *const c_char,
host: *const c_char,
port: u16,
txt_len: u16,
txt_record: *const c_void,
callback: DNSServiceRegisterReply,
context: *mut c_void,
}
#[derive(Builder, BuilderDelegate)]
pub struct BrowseServicesParams {
flags: DNSServiceFlags,
interface_index: u32,
regtype: *const c_char,
domain: *const c_char,
callback: DNSServiceBrowseReply,
context: *mut c_void,
}
#[derive(Builder, BuilderDelegate)]
pub struct ServiceResolveParams {
flags: DNSServiceFlags,
interface_index: u32,
name: *const c_char,
regtype: *const c_char,
domain: *const c_char,
callback: DNSServiceResolveReply,
context: *mut c_void,
}
#[derive(Builder, BuilderDelegate)]
pub struct GetAddressInfoParams {
flags: DNSServiceFlags,
interface_index: u32,
protocol: DNSServiceProtocol,
hostname: *const c_char,
callback: DNSServiceGetAddrInfoReply,
context: *mut c_void,
}
impl ManagedDNSServiceRef {
pub fn register_service(
&mut self,
RegisterServiceParams {
flags,
interface_index,
name,
regtype,
domain,
host,
port,
txt_len,
txt_record,
callback,
context,
}: RegisterServiceParams,
) -> Result<(), String> {
let err = unsafe {
DNSServiceRegister(
&mut self.service as *mut DNSServiceRef,
flags,
interface_index,
name,
regtype,
domain,
host,
port,
txt_len,
txt_record,
callback,
context,
)
};
if err != 0 {
return Err(format!("could not register service (code: {})", err).to_string());
}
loop {
self.process_result()?
}
}
pub fn browse_services(
&mut self,
BrowseServicesParams {
flags,
interface_index,
regtype,
domain,
callback,
context,
}: BrowseServicesParams,
) -> Result<(), String> {
let err = unsafe {
DNSServiceBrowse(
&mut self.service as *mut DNSServiceRef,
flags,
interface_index,
regtype,
domain,
callback,
context,
)
};
if err != 0 {
return Err(format!("could not browse services (code: {})", err).to_string());
}
loop {
self.process_result()?
}
}
pub fn resolve_service(
&mut self,
ServiceResolveParams {
flags,
interface_index,
name,
regtype,
domain,
callback,
context,
}: ServiceResolveParams,
) -> Result<(), String> {
let error = unsafe {
DNSServiceResolve(
&mut self.service as *mut DNSServiceRef,
flags,
interface_index,
name,
regtype,
domain,
callback,
context,
)
};
if error != 0 {
return Err(format!(
"DNSServiceResolve() reported error (code: {})",
error
));
}
self.process_result()
}
pub fn get_address_info(
&mut self,
GetAddressInfoParams {
flags,
interface_index,
protocol,
hostname,
callback,
context,
}: GetAddressInfoParams,
) -> Result<(), String> {
let err = unsafe {
DNSServiceGetAddrInfo(
&mut self.service as *mut DNSServiceRef,
flags,
interface_index,
protocol,
hostname,
callback,
context,
)
};
if err != 0 {
return Err(format!(
"DNSServiceGetAddrInfo() reported error (code: {})",
err
));
}
self.process_result()
}
fn process_result(&self) -> Result<(), String> {
let err = unsafe { DNSServiceProcessResult(self.service) };
if err != 0 {
Err(format!("could not process service result (code: {})", err))
} else {
Ok(())
}
}
}
impl Default for ManagedDNSServiceRef {
fn default() -> Self {
Self {
service: ptr::null_mut(),
}
}
}
impl Drop for ManagedDNSServiceRef {
fn drop(&mut self) {
unsafe {
if self.service != ptr::null_mut() {
DNSServiceRefDeallocate(self.service);
}
}
}
}

View File

@ -0,0 +1,22 @@
use std::any::Any;
use std::sync::Arc;
/// Callback invoked from [`MdnsService`] once it has successfully registered.
///
/// # Arguments
/// `service` - The service information that was registered
/// `context` - The optional user context passed through using [`MdnsService::set_context()`]
///
/// [`MdnsService`]: struct.MdnsService.html
/// [`MdnsService::set_context()`]: struct.MdnsService.html#method.set_context
pub type ServiceRegisteredCallback = dyn Fn(ServiceRegistration, Option<Arc<dyn Any>>);
/// Represents a registration event for a [`MdnsService`].
///
/// [`MdnsService`]: struct.MdnsService.html
#[derive(Builder, BuilderDelegate, Debug, Getters)]
pub struct ServiceRegistration {
name: String,
kind: String,
domain: String,
}