Move from magiclip project
Signed-off-by: Walker Crouse <walker.crouse@coop.co.uk>
This commit is contained in:
parent
5a1b5a3505
commit
55cbcfe154
|
@ -0,0 +1 @@
|
|||
**/target
|
|
@ -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",
|
||||
]
|
|
@ -0,0 +1,5 @@
|
|||
[workspace]
|
||||
members = [
|
||||
"zeroconf",
|
||||
"zeroconf-macros",
|
||||
]
|
|
@ -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
|
|
@ -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",
|
||||
]
|
|
@ -0,0 +1,5 @@
|
|||
[workspace]
|
||||
members = [
|
||||
"browser",
|
||||
"service",
|
||||
]
|
|
@ -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" }
|
|
@ -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);
|
||||
|
||||
// ...
|
||||
}
|
|
@ -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" }
|
|
@ -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);
|
||||
|
||||
// ...
|
||||
}
|
|
@ -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"
|
|
@ -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()
|
||||
}
|
|
@ -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" }
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
}
|
|
@ -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))
|
||||
}
|
||||
}
|
|
@ -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;
|
|
@ -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");
|
||||
}
|
||||
}
|
|
@ -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"),
|
||||
_ => {}
|
||||
}
|
||||
}
|
|
@ -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())
|
||||
}
|
||||
}
|
|
@ -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;
|
|
@ -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,
|
||||
}
|
|
@ -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::*;
|
|
@ -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) };
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
}
|
|
@ -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))
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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::*;
|
|
@ -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");
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
}
|
Loading…
Reference in New Issue