diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 0000000..17616dd --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,7 @@ +FROM ubuntu:jammy + +RUN apt-get update && apt-get install -y curl git + +RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | bash -s -- -y + +RUN apt-get install -y xorg-dev libxcb-shape0-dev libxcb-xfixes0-dev clang avahi-daemon libavahi-client-dev diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..97e9929 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,18 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the +// README at: https://github.com/devcontainers/templates/tree/main/src/ubuntu +{ + "name": "Ubuntu", + "build": { + "dockerfile": "Dockerfile" + } + // Features to add to the dev container. More info: https://containers.dev/features. + // "features": {}, + // Use 'forwardPorts' to make a list of ports inside the container available locally. + // "forwardPorts": [], + // Use 'postCreateCommand' to run commands after the container is created. + // "postCreateCommand": "uname -a", + // Configure tool-specific properties. + // "customizations": {}, + // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. + // "remoteUser": "root" +} \ No newline at end of file diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 63ba9ca..ea21844 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -4,7 +4,7 @@ on: push: branches: [master] pull_request: - branches: [master] + branches: [master, release/**] env: CARGO_TERM_COLOR: always diff --git a/.gitignore b/.gitignore index 4c7b3d0..b60de5b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1 @@ **/target -.vscode diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..6c0f15a --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,30 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in library 'zeroconf'", + "cargo": { + "args": [ + "test", + "--no-run", + "--lib", + "--package=zeroconf" + ], + "filter": { + "name": "zeroconf", + "kind": "lib" + } + }, + "args": [], + "cwd": "${workspaceFolder}", + "env": { + "RUST_LOG": "debug" + } + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..c10c4b4 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,9 @@ +{ + "editor.formatOnSave": true, + "cSpell.words": [ + "aprotocol", + "errno", + "strlst", + "userdata" + ] +} \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 84de6ce..ad8b401 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -531,7 +531,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "zeroconf" -version = "0.11.0" +version = "0.11.1" dependencies = [ "avahi-sys", "bonjour-sys", diff --git a/examples/Cargo.lock b/examples/Cargo.lock index 558e85f..7b57375 100644 --- a/examples/Cargo.lock +++ b/examples/Cargo.lock @@ -502,7 +502,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "zeroconf" -version = "0.10.3" +version = "0.11.1" dependencies = [ "avahi-sys", "bonjour-sys", diff --git a/scripts/checkfmt.sh b/scripts/checkfmt.sh index f772a61..7aa10d8 100755 --- a/scripts/checkfmt.sh +++ b/scripts/checkfmt.sh @@ -2,4 +2,11 @@ set -e -find examples/browser/src examples/service/src zeroconf/src zeroconf-macros/src -type f -name *.rs -print0 | xargs -0 -n1 rustfmt --check --verbose +find \ + examples/browser/src \ + examples/service/src \ + zeroconf/src \ + zeroconf-macros/src \ + -type f \ + -name *.rs \ + -print0 | xargs -0 -n1 rustfmt --check --verbose diff --git a/zeroconf/Cargo.toml b/zeroconf/Cargo.toml index ae33b5e..223dcd1 100644 --- a/zeroconf/Cargo.toml +++ b/zeroconf/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "zeroconf" -version = "0.11.0" +version = "0.11.1" authors = ["Walker Crouse "] edition = "2018" description = "cross-platform library that wraps ZeroConf/mDNS implementations like Bonjour or Avahi" @@ -9,7 +9,13 @@ homepage = "https://github.com/windy1/zeroconf-rs" repository = "https://github.com/windy1/zeroconf-rs" license-file = "../LICENSE" keywords = ["zeroconf", "mdns", "avahi", "bonjour", "dnssd"] -categories = ["api-bindings", "network-programming", "os", "os::linux-apis", "os::macos-apis"] +categories = [ + "api-bindings", + "network-programming", + "os", + "os::linux-apis", + "os::macos-apis", +] documentation = "https://docs.rs/zeroconf" [dependencies] diff --git a/zeroconf/src/linux/avahi_util.rs b/zeroconf/src/linux/avahi_util.rs index 2b6e9d7..a7b085d 100644 --- a/zeroconf/src/linux/avahi_util.rs +++ b/zeroconf/src/linux/avahi_util.rs @@ -47,6 +47,24 @@ pub fn interface_index(interface: NetworkInterface) -> i32 { } } +/// Executes the specified closure and returns a formatted `Result` +pub fn sys_exec i32>(func: F, message: &str) -> crate::Result<()> { + let err = func(); + + if err < 0 { + crate::Result::Err( + format!( + "{}: `{}`", + message, + crate::linux::avahi_util::get_error(err) + ) + .into(), + ) + } else { + crate::Result::Ok(()) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/zeroconf/src/linux/browser.rs b/zeroconf/src/linux/browser.rs index 3589bd1..8bc1347 100644 --- a/zeroconf/src/linux/browser.rs +++ b/zeroconf/src/linux/browser.rs @@ -73,7 +73,7 @@ impl TMdnsBrowser for AvahiMdnsBrowser { self.client = Some(Arc::new(ManagedAvahiClient::new( ManagedAvahiClientParams::builder() - .poll(self.poll.as_ref().unwrap()) + .poll(Arc::clone(self.poll.as_ref().unwrap())) .flags(AvahiClientFlags(0)) .callback(Some(client_callback)) .userdata(ptr::null_mut()) @@ -91,7 +91,7 @@ impl TMdnsBrowser for AvahiMdnsBrowser { .flags(0) .callback(Some(browse_callback)) .userdata(self.context.as_raw()) - .client(self.context.client.as_ref().unwrap()) + .client(Arc::clone(self.context.client.as_ref().unwrap())) .build()?, )?); @@ -182,7 +182,7 @@ fn handle_browser_new( let raw_context = context.as_raw(); context.resolvers.insert(ManagedAvahiServiceResolver::new( ManagedAvahiServiceResolverParams::builder() - .client(context.client.as_ref().unwrap()) + .client(Arc::clone(context.client.as_ref().unwrap())) .interface(interface) .protocol(protocol) .name(name) diff --git a/zeroconf/src/linux/client.rs b/zeroconf/src/linux/client.rs index c16fddb..e0d6f76 100644 --- a/zeroconf/src/linux/client.rs +++ b/zeroconf/src/linux/client.rs @@ -1,5 +1,7 @@ //! Rust friendly `AvahiClient` wrappers/helpers +use std::sync::Arc; + use super::avahi_util; use super::poll::ManagedAvahiSimplePoll; use crate::ffi::c_str; @@ -15,7 +17,10 @@ use libc::{c_int, c_void}; /// This struct allocates a new `*mut AvahiClient` when `ManagedAvahiClient::new()` is invoked and /// calls the Avahi function responsible for freeing the client on `trait Drop`. #[derive(Debug)] -pub struct ManagedAvahiClient(*mut AvahiClient); +pub struct ManagedAvahiClient { + pub(crate) inner: *mut AvahiClient, + _poll: Arc, +} impl ManagedAvahiClient { /// Initializes the underlying `*mut AvahiClient` and verifies it was created; returning @@ -30,7 +35,7 @@ impl ManagedAvahiClient { ) -> Result { let mut err: c_int = 0; - let client = unsafe { + let inner = unsafe { avahi_client_new( avahi_simple_poll_get(poll.inner()), flags, @@ -40,12 +45,12 @@ impl ManagedAvahiClient { ) }; - if client.is_null() { + if inner.is_null() { return Err("could not initialize AvahiClient".into()); } match err { - 0 => Ok(Self(client)), + 0 => Ok(Self { inner, _poll: poll }), _ => Err(format!( "could not initialize AvahiClient: {}", avahi_util::get_error(err) @@ -58,17 +63,13 @@ impl ManagedAvahiClient { /// /// [`avahi_client_get_host_name()`]: https://avahi.org/doxygen/html/client_8h.html#a89378618c3c592a255551c308ba300bf pub fn host_name<'a>(&self) -> Result<&'a str> { - unsafe { get_host_name(self.0) } - } - - pub(super) fn inner(&self) -> *mut AvahiClient { - self.0 + unsafe { get_host_name(self.inner) } } } impl Drop for ManagedAvahiClient { fn drop(&mut self) { - unsafe { avahi_client_free(self.0) }; + unsafe { avahi_client_free(self.inner) }; } } @@ -78,8 +79,8 @@ impl Drop for ManagedAvahiClient { /// /// [`avahi_client_new()`]: https://avahi.org/doxygen/html/client_8h.html#a07b2a33a3e7cbb18a0eb9d00eade6ae6 #[derive(Builder, BuilderDelegate)] -pub struct ManagedAvahiClientParams<'a> { - poll: &'a ManagedAvahiSimplePoll, +pub struct ManagedAvahiClientParams { + poll: Arc, flags: AvahiClientFlags, callback: AvahiClientCallback, userdata: *mut c_void, diff --git a/zeroconf/src/linux/entry_group.rs b/zeroconf/src/linux/entry_group.rs index 02fac6c..7be8cc0 100644 --- a/zeroconf/src/linux/entry_group.rs +++ b/zeroconf/src/linux/entry_group.rs @@ -1,13 +1,16 @@ //! Rust friendly `AvahiEntryGroup` wrappers/helpers -use super::string_list::ManagedAvahiStringList; +use std::sync::Arc; + +use super::{client::ManagedAvahiClient, string_list::ManagedAvahiStringList}; use crate::ffi::UnwrapMutOrNull; -use crate::Result; use crate::linux::avahi_util; +use crate::Result; use avahi_sys::{ - avahi_entry_group_add_service_strlst, avahi_entry_group_commit, avahi_entry_group_free, - avahi_entry_group_is_empty, avahi_entry_group_new, avahi_entry_group_reset, avahi_client_errno, - AvahiClient, AvahiEntryGroup, AvahiEntryGroupCallback, AvahiIfIndex, AvahiProtocol, AvahiPublishFlags, + avahi_client_errno, avahi_entry_group_add_service_strlst, avahi_entry_group_commit, + avahi_entry_group_free, avahi_entry_group_is_empty, avahi_entry_group_new, + avahi_entry_group_reset, AvahiEntryGroup, AvahiEntryGroupCallback, AvahiIfIndex, AvahiProtocol, + AvahiPublishFlags, }; use libc::{c_char, c_void}; @@ -16,10 +19,13 @@ use libc::{c_char, c_void}; /// This struct allocates a new `*mut AvahiEntryGroup` when `ManagedAvahiEntryGroup::new()` is /// invoked and calls the Avahi function responsible for freeing the group on `trait Drop`. #[derive(Debug)] -pub struct ManagedAvahiEntryGroup(*mut AvahiEntryGroup); +pub struct ManagedAvahiEntryGroup { + inner: *mut AvahiEntryGroup, + _client: Arc, +} impl ManagedAvahiEntryGroup { - /// Intiializes the underlying `*mut AvahiEntryGroup` and verifies it was created; returning + /// Initializes the underlying `*mut AvahiEntryGroup` and verifies it was created; returning /// `Err(String)` if unsuccessful. pub fn new( ManagedAvahiEntryGroupParams { @@ -28,12 +34,16 @@ impl ManagedAvahiEntryGroup { userdata, }: ManagedAvahiEntryGroupParams, ) -> Result { - let group = unsafe { avahi_entry_group_new(client, callback, userdata) }; - if group.is_null() { - let err = avahi_util::get_error(unsafe { avahi_client_errno(client) }); + let inner = unsafe { avahi_entry_group_new(client.inner, callback, userdata) }; + + if inner.is_null() { + let err = avahi_util::get_error(unsafe { avahi_client_errno(client.inner) }); Err(format!("could not initialize AvahiEntryGroup: {}", err).into()) } else { - Ok(Self(group)) + Ok(Self { + inner, + _client: client, + }) } } @@ -41,10 +51,10 @@ impl ManagedAvahiEntryGroup { /// /// [`avahi_entry_group_is_empty()`]: https://avahi.org/doxygen/html/publish_8h.html#af5a78ee1fda6678970536889d459d85c pub fn is_empty(&self) -> bool { - unsafe { avahi_entry_group_is_empty(self.0) != 0 } + unsafe { avahi_entry_group_is_empty(self.inner) != 0 } } - /// Delgate function for [`avahi_entry_group_add_service()`]. + /// Delegate function for [`avahi_entry_group_add_service()`]. /// /// Also propagates any error returned into a `Result`. /// @@ -63,36 +73,41 @@ impl ManagedAvahiEntryGroup { txt, }: AddServiceParams, ) -> Result<()> { - avahi!( - avahi_entry_group_add_service_strlst( - self.0, - interface, - protocol, - flags, - name, - kind, - domain, - host, - port, - txt.map(|t| t.inner()).unwrap_mut_or_null() - ), - "could not register service" + avahi_util::sys_exec( + || unsafe { + avahi_entry_group_add_service_strlst( + self.inner, + interface, + protocol, + flags, + name, + kind, + domain, + host, + port, + txt.map(|t| t.inner()).unwrap_mut_or_null(), + ) + }, + "could not register service", )?; - avahi!(avahi_entry_group_commit(self.0), "could not commit service") + avahi_util::sys_exec( + || unsafe { avahi_entry_group_commit(self.inner) }, + "could not commit service", + ) } /// Delegate function for [`avahi_entry_group_reset()`]. /// /// [`avahi_entry_group_reset()`]: https://avahi.org/doxygen/html/publish_8h.html#a1293bbccf878dbeb9916660022bc71b2 pub fn reset(&mut self) { - unsafe { avahi_entry_group_reset(self.0) }; + unsafe { avahi_entry_group_reset(self.inner) }; } } impl Drop for ManagedAvahiEntryGroup { fn drop(&mut self) { - unsafe { avahi_entry_group_free(self.0) }; + unsafe { avahi_entry_group_free(self.inner) }; } } @@ -104,7 +119,7 @@ impl Drop for ManagedAvahiEntryGroup { /// [avahi_entry_group_new()]: https://avahi.org/doxygen/html/publish_8h.html#abb17598f2b6ec3c3f69defdd488d568c #[derive(Builder, BuilderDelegate)] pub struct ManagedAvahiEntryGroupParams { - client: *mut AvahiClient, + client: Arc, callback: AvahiEntryGroupCallback, userdata: *mut c_void, } diff --git a/zeroconf/src/linux/poll.rs b/zeroconf/src/linux/poll.rs index 355cb38..62e4050 100644 --- a/zeroconf/src/linux/poll.rs +++ b/zeroconf/src/linux/poll.rs @@ -1,7 +1,7 @@ //! Rust friendly `AvahiSimplePoll` wrappers/helpers -use crate::error::Error; use crate::Result; +use crate::{error::Error, linux::avahi_util}; use avahi_sys::{ avahi_simple_poll_free, avahi_simple_poll_iterate, avahi_simple_poll_loop, avahi_simple_poll_new, AvahiSimplePoll, @@ -31,9 +31,9 @@ impl ManagedAvahiSimplePoll { /// /// [`avahi_simple_poll_loop()`]: https://avahi.org/doxygen/html/simple-watch_8h.html#a14b4cb29832e8c3de609d4c4e5611985 pub fn start_loop(&self) -> Result<()> { - avahi!( - avahi_simple_poll_loop(self.0), - "could not start AvahiSimplePoll" + avahi_util::sys_exec( + || unsafe { avahi_simple_poll_loop(self.0) }, + "could not start AvahiSimplePoll", ) } diff --git a/zeroconf/src/linux/raw_browser.rs b/zeroconf/src/linux/raw_browser.rs index e8ed068..6b6cd92 100644 --- a/zeroconf/src/linux/raw_browser.rs +++ b/zeroconf/src/linux/raw_browser.rs @@ -1,5 +1,7 @@ //! Rust friendly `AvahiServiceBrowser` wrappers/helpers +use std::sync::Arc; + use super::client::ManagedAvahiClient; use crate::Result; use avahi_sys::{ @@ -13,10 +15,13 @@ use libc::{c_char, c_void}; /// This struct allocates a new `*mut AvahiServiceBrowser` when `ManagedAvahiServiceBrowser::new()` /// is invoked and calls the Avahi function responsible for freeing the client on `trait Drop`. #[derive(Debug)] -pub struct ManagedAvahiServiceBrowser(*mut AvahiServiceBrowser); +pub struct ManagedAvahiServiceBrowser { + inner: *mut AvahiServiceBrowser, + _client: Arc, +} impl ManagedAvahiServiceBrowser { - /// Intializes the underlying `*mut AvahiClient` and verifies it was created; returning + /// Initializes the underlying `*mut AvahiClient` and verifies it was created; returning /// `Err(String)` if unsuccessful. pub fn new( ManagedAvahiServiceBrowserParams { @@ -30,9 +35,9 @@ impl ManagedAvahiServiceBrowser { userdata, }: ManagedAvahiServiceBrowserParams, ) -> Result { - let browser = unsafe { + let inner = unsafe { avahi_service_browser_new( - client.inner(), + client.inner, interface, protocol, kind, @@ -43,17 +48,20 @@ impl ManagedAvahiServiceBrowser { ) }; - if browser.is_null() { + if inner.is_null() { Err("could not initialize Avahi service browser".into()) } else { - Ok(Self(browser)) + Ok(Self { + inner, + _client: client, + }) } } } impl Drop for ManagedAvahiServiceBrowser { fn drop(&mut self) { - unsafe { avahi_service_browser_free(self.0) }; + unsafe { avahi_service_browser_free(self.inner) }; } } @@ -64,8 +72,8 @@ impl Drop for ManagedAvahiServiceBrowser { /// /// [`avahi_service_browser_new()`]: https://avahi.org/doxygen/html/lookup_8h.html#a52d55a5156a7943012d03e6700880d2b #[derive(Builder, BuilderDelegate)] -pub struct ManagedAvahiServiceBrowserParams<'a> { - client: &'a ManagedAvahiClient, +pub struct ManagedAvahiServiceBrowserParams { + client: Arc, interface: AvahiIfIndex, protocol: AvahiProtocol, kind: *const c_char, diff --git a/zeroconf/src/linux/resolver.rs b/zeroconf/src/linux/resolver.rs index 95c57dc..014c209 100644 --- a/zeroconf/src/linux/resolver.rs +++ b/zeroconf/src/linux/resolver.rs @@ -7,7 +7,7 @@ use avahi_sys::{ AvahiProtocol, AvahiServiceResolver, AvahiServiceResolverCallback, }; use libc::{c_char, c_void}; -use std::collections::HashMap; +use std::{collections::HashMap, sync::Arc}; /// Wraps the `AvahiServiceResolver` type from the raw Avahi bindings. /// @@ -15,10 +15,13 @@ use std::collections::HashMap; /// `ManagedAvahiServiceResolver::new()` is invoked and calls the Avahi function responsible for /// freeing the client on `trait Drop`. #[derive(Debug)] -pub struct ManagedAvahiServiceResolver(*mut AvahiServiceResolver); +pub struct ManagedAvahiServiceResolver { + inner: *mut AvahiServiceResolver, + _client: Arc, +} impl ManagedAvahiServiceResolver { - /// Intializes the underlying `*mut AvahiServiceResolver` and verifies it was created; + /// Initializes the underlying `*mut AvahiServiceResolver` and verifies it was created; /// returning `Err(String)` if unsuccessful. pub fn new( ManagedAvahiServiceResolverParams { @@ -34,9 +37,9 @@ impl ManagedAvahiServiceResolver { userdata, }: ManagedAvahiServiceResolverParams, ) -> Result { - let resolver = unsafe { + let inner = unsafe { avahi_service_resolver_new( - client.inner(), + client.inner, interface, protocol, name, @@ -49,17 +52,20 @@ impl ManagedAvahiServiceResolver { ) }; - if resolver.is_null() { + if inner.is_null() { Err("could not initialize AvahiServiceResolver".into()) } else { - Ok(Self(resolver)) + Ok(Self { + inner, + _client: client, + }) } } } impl Drop for ManagedAvahiServiceResolver { fn drop(&mut self) { - unsafe { avahi_service_resolver_free(self.0) }; + unsafe { avahi_service_resolver_free(self.inner) }; } } @@ -70,8 +76,8 @@ impl Drop for ManagedAvahiServiceResolver { /// /// [`avahi_service_resolver_new()`]: https://avahi.org/doxygen/html/lookup_8h.html#a904611a4134ceb5919f6bb637df84124 #[derive(Builder, BuilderDelegate)] -pub struct ManagedAvahiServiceResolverParams<'a> { - client: &'a ManagedAvahiClient, +pub struct ManagedAvahiServiceResolverParams { + client: Arc, interface: AvahiIfIndex, protocol: AvahiProtocol, name: *const c_char, @@ -90,7 +96,7 @@ pub(crate) struct ServiceResolverSet { impl ServiceResolverSet { pub fn insert(&mut self, resolver: ManagedAvahiServiceResolver) { - self.resolvers.insert(resolver.0, resolver); + self.resolvers.insert(resolver.inner, resolver); } pub fn remove_raw(&mut self, raw: *mut AvahiServiceResolver) { diff --git a/zeroconf/src/linux/service.rs b/zeroconf/src/linux/service.rs index cd03139..a8ca6e0 100644 --- a/zeroconf/src/linux/service.rs +++ b/zeroconf/src/linux/service.rs @@ -23,7 +23,7 @@ use std::sync::Arc; #[derive(Debug)] pub struct AvahiMdnsService { - client: Option, + client: Option>, poll: Option>, context: Box, } @@ -33,10 +33,7 @@ impl TMdnsService for AvahiMdnsService { Self { client: None, poll: None, - context: Box::new(AvahiServiceContext::new( - &service_type.to_string(), - port, - )), + context: Box::new(AvahiServiceContext::new(&service_type.to_string(), port)), } } @@ -79,14 +76,18 @@ impl TMdnsService for AvahiMdnsService { self.poll = Some(Arc::new(ManagedAvahiSimplePoll::new()?)); - self.client = Some(ManagedAvahiClient::new( + self.client = Some(Arc::new(ManagedAvahiClient::new( ManagedAvahiClientParams::builder() - .poll(self.poll.as_ref().unwrap()) + .poll(Arc::clone(self.poll.as_ref().unwrap())) .flags(AvahiClientFlags(0)) .callback(Some(client_callback)) .userdata(self.context.as_raw()) .build()?, - )?); + )?)); + + self.context.client = self.client.clone(); + + unsafe { create_service(&mut self.context) }?; Ok(EventLoop::new(self.poll.as_ref().unwrap().clone())) } @@ -94,6 +95,7 @@ impl TMdnsService for AvahiMdnsService { #[derive(FromRaw, AsRaw)] struct AvahiServiceContext { + client: Option>, name: Option, kind: CString, port: u16, @@ -109,6 +111,7 @@ struct AvahiServiceContext { impl AvahiServiceContext { fn new(kind: &str, port: u16) -> Self { Self { + client: None, name: None, kind: c_string!(kind), port, @@ -142,38 +145,12 @@ impl fmt::Debug for AvahiServiceContext { } } -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 => { - if let Err(e) = create_service(client, context) { - context.invoke_callback(Err(e)); - } - } - avahi_sys::AvahiClientState_AVAHI_CLIENT_FAILURE => { - context.invoke_callback(Err("client failure".into())) - } - 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, -) -> Result<()> { +unsafe fn create_service(context: &mut AvahiServiceContext) -> Result<()> { if context.name.is_none() { - context.name = Some(c_string!(client::get_host_name(client)?.to_string())); + context.name = Some(c_string!(client::get_host_name( + context.client.as_ref().unwrap().inner + )? + .to_string())); } if context.group.is_none() { @@ -181,7 +158,7 @@ unsafe fn create_service( context.group = Some(ManagedAvahiEntryGroup::new( ManagedAvahiEntryGroupParams::builder() - .client(client) + .client(Arc::clone(context.client.as_ref().unwrap())) .callback(Some(entry_group_callback)) .userdata(context.as_raw()) .build()?, @@ -239,3 +216,14 @@ unsafe fn handle_group_established(context: &AvahiServiceContext) -> Result<()> Ok(()) } + +unsafe extern "C" fn client_callback( + _client: *mut AvahiClient, + state: AvahiClientState, + _userdata: *mut c_void, +) { + // TODO: handle this better + if let avahi_sys::AvahiClientState_AVAHI_CLIENT_FAILURE = state { + panic!("client failure"); + } +} diff --git a/zeroconf/src/linux/txt_record.rs b/zeroconf/src/linux/txt_record.rs index 3464642..d121309 100644 --- a/zeroconf/src/linux/txt_record.rs +++ b/zeroconf/src/linux/txt_record.rs @@ -14,19 +14,23 @@ impl TTxtRecord for AvahiTxtRecord { } fn insert(&mut self, key: &str, value: &str) -> Result<()> { + let c_key = c_string!(key); + let c_value = c_string!(value); + unsafe { self.inner_mut().add_pair( - c_string!(key).as_ptr() as *const c_char, - c_string!(value).as_ptr() as *const c_char, + c_key.as_ptr() as *const c_char, + c_value.as_ptr() as *const c_char, ); } Ok(()) } fn get(&self, key: &str) -> Option { + let c_str = c_string!(key); unsafe { self.inner_mut() - .find(c_string!(key).as_ptr() as *const c_char)? + .find(c_str.as_ptr() as *const c_char)? .get_pair() .value() .as_str() @@ -41,10 +45,13 @@ impl TTxtRecord for AvahiTxtRecord { map.remove(key); for (key, value) in map { + let c_key = c_string!(key); + let c_value = c_string!(value); + unsafe { list.add_pair( - c_string!(key).as_ptr() as *const c_char, - c_string!(value).as_ptr() as *const c_char, + c_key.as_ptr() as *const c_char, + c_value.as_ptr() as *const c_char, ); } } @@ -55,9 +62,10 @@ impl TTxtRecord for AvahiTxtRecord { } fn contains_key(&self, key: &str) -> bool { + let c_str = c_string!(key); unsafe { self.inner_mut() - .find(c_string!(key).as_ptr() as *const c_char) + .find(c_str.as_ptr() as *const c_char) .is_some() } } diff --git a/zeroconf/src/macos/bonjour_util.rs b/zeroconf/src/macos/bonjour_util.rs index 139f32e..196c0f4 100644 --- a/zeroconf/src/macos/bonjour_util.rs +++ b/zeroconf/src/macos/bonjour_util.rs @@ -2,6 +2,7 @@ use super::constants; use crate::NetworkInterface; +use bonjour_sys::DNSServiceErrorType; /// Normalizes the specified domain `&str` to conform to a standard enforced by this crate. /// @@ -24,3 +25,14 @@ pub fn interface_index(interface: NetworkInterface) -> u32 { NetworkInterface::AtIndex(i) => i, } } + +/// Executes the specified closure and returns a formatted `Result` +pub fn sys_exec DNSServiceErrorType>(func: F, message: &str) -> crate::Result<()> { + let err = func(); + + if err < 0 { + crate::Result::Err(format!("{} (code: {})", message, err).into()) + } else { + crate::Result::Ok(()) + } +} diff --git a/zeroconf/src/macos/browser.rs b/zeroconf/src/macos/browser.rs index 7a54b5c..560aa0c 100644 --- a/zeroconf/src/macos/browser.rs +++ b/zeroconf/src/macos/browser.rs @@ -203,7 +203,7 @@ unsafe fn handle_resolve( GetAddressInfoParams::builder() .flags(bonjour_sys::kDNSServiceFlagsForceMulticast) .interface_index(interface_index) - .protocol(1) + .protocol(0) .hostname(host_target) .callback(Some(get_address_info_callback)) .context(ctx.as_raw()) diff --git a/zeroconf/src/macos/service.rs b/zeroconf/src/macos/service.rs index 7743baa..aafaba9 100644 --- a/zeroconf/src/macos/service.rs +++ b/zeroconf/src/macos/service.rs @@ -3,7 +3,7 @@ use super::service_ref::{ManagedDNSServiceRef, RegisterServiceParams}; use super::{bonjour_util, constants}; use crate::ffi::c_str::{self, AsCChars}; -use crate::ffi::{FromRaw, UnwrapOrNull, AsRaw}; +use crate::ffi::{AsRaw, FromRaw, UnwrapOrNull}; use crate::prelude::*; use crate::{ EventLoop, NetworkInterface, Result, ServiceRegisteredCallback, ServiceRegistration, @@ -117,7 +117,9 @@ struct BonjourServiceContext { // Necessary for BonjourMdnsService, cant be `derive`d because of registered_callback impl std::fmt::Debug for BonjourServiceContext { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("BonjourServiceContext").field("user_context", &self.user_context).finish() + f.debug_struct("BonjourServiceContext") + .field("user_context", &self.user_context) + .finish() } } diff --git a/zeroconf/src/macos/service_ref.rs b/zeroconf/src/macos/service_ref.rs index 4d3540f..413a48d 100644 --- a/zeroconf/src/macos/service_ref.rs +++ b/zeroconf/src/macos/service_ref.rs @@ -1,6 +1,6 @@ //! Low level interface for interacting with `DNSserviceRef` -use crate::Result; +use crate::{macos::bonjour_util, Result}; use bonjour_sys::{ DNSServiceBrowse, DNSServiceBrowseReply, DNSServiceFlags, DNSServiceGetAddrInfo, DNSServiceGetAddrInfoReply, DNSServiceProcessResult, DNSServiceProtocol, DNSServiceRef, @@ -12,11 +12,11 @@ use std::ptr; /// Wraps the `DNSServiceRef` type from the raw Bonjour bindings. /// -/// This struct allocates a new `DNSServiceRef` when any of the delgate functions is invoked and +/// This struct allocates a new `DNSServiceRef` when any of the delegate functions is invoked and /// calls the Bonjour function responsible for freeing the client on `trait Drop`. /// /// # Note -/// This wrapper is meant for one-off calls to underlying Bonjour functions. The behaviour for +/// This wrapper is meant for one-off calls to underlying Bonjour functions. The behavior for /// using an already initialized `DNSServiceRef` in one of these functions is undefined. Therefore, /// it is preferable to only call one delegate function per-instance. #[derive(Debug)] @@ -47,22 +47,24 @@ impl ManagedDNSServiceRef { context, }: RegisterServiceParams, ) -> Result<()> { - bonjour!( - DNSServiceRegister( - &mut self.0 as *mut DNSServiceRef, - flags, - interface_index, - name, - regtype, - domain, - host, - port.to_be(), - txt_len, - txt_record, - callback, - context, - ), - "could not register service" + bonjour_util::sys_exec( + || unsafe { + DNSServiceRegister( + &mut self.0 as *mut DNSServiceRef, + flags, + interface_index, + name, + regtype, + domain, + host, + port.to_be(), + txt_len, + txt_record, + callback, + context, + ) + }, + "could not register service", ) } @@ -80,17 +82,19 @@ impl ManagedDNSServiceRef { context, }: BrowseServicesParams, ) -> Result<()> { - bonjour!( - DNSServiceBrowse( - &mut self.0 as *mut DNSServiceRef, - flags, - interface_index, - regtype, - domain, - callback, - context, - ), - "could not browse services" + bonjour_util::sys_exec( + || unsafe { + DNSServiceBrowse( + &mut self.0 as *mut DNSServiceRef, + flags, + interface_index, + regtype, + domain, + callback, + context, + ) + }, + "could not browse services", ) } @@ -109,18 +113,20 @@ impl ManagedDNSServiceRef { context, }: ServiceResolveParams, ) -> Result<()> { - bonjour!( - DNSServiceResolve( - &mut self.0 as *mut DNSServiceRef, - flags, - interface_index, - name, - regtype, - domain, - callback, - context, - ), - "DNSServiceResolve() reported error" + bonjour_util::sys_exec( + || unsafe { + DNSServiceResolve( + &mut self.0 as *mut DNSServiceRef, + flags, + interface_index, + name, + regtype, + domain, + callback, + context, + ) + }, + "DNSServiceResolve() reported error", )?; self.process_result() @@ -140,17 +146,19 @@ impl ManagedDNSServiceRef { context, }: GetAddressInfoParams, ) -> Result<()> { - bonjour!( - DNSServiceGetAddrInfo( - &mut self.0 as *mut DNSServiceRef, - flags, - interface_index, - protocol, - hostname, - callback, - context, - ), - "DNSServiceGetAddrInfo() reported error" + bonjour_util::sys_exec( + || unsafe { + DNSServiceGetAddrInfo( + &mut self.0 as *mut DNSServiceRef, + flags, + interface_index, + protocol, + hostname, + callback, + context, + ) + }, + "DNSServiceGetAddrInfo() reported error", )?; self.process_result() @@ -160,9 +168,9 @@ impl ManagedDNSServiceRef { /// /// [`DNSServiceProcessResult`]: https://developer.apple.com/documentation/dnssd/1804696-dnsserviceprocessresult?language=objc pub fn process_result(&self) -> Result<()> { - bonjour!( - DNSServiceProcessResult(self.0), - "could not process service result" + bonjour_util::sys_exec( + || unsafe { DNSServiceProcessResult(self.0) }, + "could not process service result", ) } diff --git a/zeroconf/src/macos/txt_record.rs b/zeroconf/src/macos/txt_record.rs index efdedc0..e9f8a40 100644 --- a/zeroconf/src/macos/txt_record.rs +++ b/zeroconf/src/macos/txt_record.rs @@ -1,14 +1,13 @@ //! Bonjour implementation for cross-platform TXT record. use super::txt_record_ref::ManagedTXTRecordRef; -use crate::ffi::c_str; use crate::txt_record::TTxtRecord; use crate::Result; use libc::{c_char, c_void}; use std::ffi::CString; -use std::ptr; +use std::{ptr, slice}; -/// Interface for interfacting with Bonjour's TXT record capabilities. +/// Interface for interfacing with Bonjour's TXT record capabilities. #[derive(Clone)] pub struct BonjourTxtRecord(ManagedTXTRecordRef); @@ -20,8 +19,8 @@ impl TTxtRecord for BonjourTxtRecord { fn insert(&mut self, key: &str, value: &str) -> Result<()> { let key = c_string!(key); let value = c_string!(value); - // let value_size = mem::size_of_val(&value) as u8; let value_size = value.as_bytes().len(); + unsafe { self.0.set_value( key.as_ptr() as *const c_char, @@ -34,30 +33,28 @@ impl TTxtRecord for BonjourTxtRecord { fn get(&self, key: &str) -> Option { let mut value_len: u8 = 0; + let c_str = c_string!(key); + let value_raw = unsafe { self.0 - .get_value_ptr(c_string!(key).as_ptr() as *const c_char, &mut value_len) + .get_value_ptr(c_str.as_ptr() as *const c_char, &mut value_len) }; if value_raw.is_null() { None } else { - Some(unsafe { c_str::raw_to_str(value_raw as *const c_char).to_string() }) + Some(unsafe { read_value(value_raw, value_len) }) } } fn remove(&mut self, key: &str) -> Result<()> { - unsafe { - self.0 - .remove_value(c_string!(key).as_ptr() as *const c_char) - } + let c_str = c_string!(key); + unsafe { self.0.remove_value(c_str.as_ptr() as *const c_char) } } fn contains_key(&self, key: &str) -> bool { - unsafe { - self.0 - .contains_key(c_string!(key).as_ptr() as *const c_char) - } + let c_str = c_string!(key); + unsafe { self.0.contains_key(c_str.as_ptr() as *const c_char) } } fn len(&self) -> usize { @@ -138,7 +135,7 @@ impl Iterator for Iter<'_> { .trim_matches(char::from(0)) .to_string(); - let value = unsafe { c_str::raw_to_str(value as *const c_char).to_string() }; + let value = unsafe { read_value(value, value_len) }; self.index += 1; @@ -167,3 +164,9 @@ impl<'a> Iterator for Values<'a> { self.0.next().map(|e| e.1) } } + +unsafe fn read_value(value: *const c_void, value_len: u8) -> String { + let value_len = value_len as usize; + let value_raw = slice::from_raw_parts(value as *const u8, value_len); + String::from_utf8(value_raw.to_vec()).unwrap() +} diff --git a/zeroconf/src/macos/txt_record_ref.rs b/zeroconf/src/macos/txt_record_ref.rs index 14c9328..a49e3ef 100644 --- a/zeroconf/src/macos/txt_record_ref.rs +++ b/zeroconf/src/macos/txt_record_ref.rs @@ -10,6 +10,8 @@ use libc::{c_char, c_uchar, c_void}; use std::ffi::CString; use std::{fmt, mem, ptr}; +use super::bonjour_util; + /// Wraps the `ManagedTXTRecordRef` type from the raw Bonjour bindings. /// /// `zeroconf::TxtRecord` provides the cross-platform bindings for this functionality. @@ -49,9 +51,9 @@ impl ManagedTXTRecordRef { /// /// [`TXTRecordRemoveValue()`]: https://developer.apple.com/documentation/dnssd/1804721-txtrecordremovevalue?language=objc pub unsafe fn remove_value(&mut self, key: *const c_char) -> Result<()> { - bonjour!( - TXTRecordRemoveValue(&mut self.0, key), - "could not remove TXT record value" + bonjour_util::sys_exec( + || unsafe { TXTRecordRemoveValue(&mut self.0, key) }, + "could not remove TXT record value", ) } @@ -68,9 +70,9 @@ impl ManagedTXTRecordRef { value_size: u8, value: *const c_void, ) -> Result<()> { - bonjour!( - TXTRecordSetValue(&mut self.0, key, value_size, value), - "could not set TXT record value" + bonjour_util::sys_exec( + || unsafe { TXTRecordSetValue(&mut self.0, key, value_size, value) }, + "could not set TXT record value", ) } @@ -95,7 +97,7 @@ impl ManagedTXTRecordRef { /// Delegate function for [`TXTRecordGetItemAtIndex`]. /// /// # Safety - /// This function is unsafe because it makes no guarantees about it's rew pointer arguments + /// This function is unsafe because it makes no guarantees about it's raw pointer arguments /// that are dereferenced. /// /// [`TXTRecordGetItemAtIndex`]: https://developer.apple.com/documentation/dnssd/1804708-txtrecordgetitematindex?language=objc @@ -200,9 +202,11 @@ fn _get_item_at_index( value_len: *mut u8, value: *mut *const c_void, ) -> Result<()> { - bonjour!( - TXTRecordGetItemAtIndex(length, data, item_index, key_buf_len, key, value_len, value), - "could get item at index for TXT record" + bonjour_util::sys_exec( + || unsafe { + TXTRecordGetItemAtIndex(length, data, item_index, key_buf_len, key, value_len, value) + }, + "could get item at index for TXT record", ) } diff --git a/zeroconf/src/macros.rs b/zeroconf/src/macros.rs index 1d2e04f..31f6b97 100644 --- a/zeroconf/src/macros.rs +++ b/zeroconf/src/macros.rs @@ -13,38 +13,6 @@ macro_rules! c_string { }; } -#[cfg(target_vendor = "apple")] -macro_rules! bonjour { - ($call:expr, $msg:expr) => {{ - #[allow(unused_unsafe)] - let err = unsafe { $call }; - if err != 0 { - crate::Result::Err(format!("{}", format!("{} (code: {})", $msg, err)).into()) - } else { - crate::Result::Ok(()) - } - }}; -} - -#[cfg(target_os = "linux")] -macro_rules! avahi { - ($call:expr, $msg:expr) => {{ - #[allow(unused_unsafe)] - let err = unsafe { $call }; - if err < 0 { - crate::Result::Err( - format!( - "{}", - format!("{}: `{}`", $msg, crate::linux::avahi_util::get_error(err)) - ) - .into(), - ) - } else { - crate::Result::Ok(()) - } - }}; -} - #[cfg(test)] mod tests { use libc::c_char; @@ -53,7 +21,8 @@ mod tests { #[test] fn assert_not_null_non_null_success() { - assert_not_null!(c_string!("foo").as_ptr()); + let c_str = c_string!("foo"); + assert_not_null!(c_str.as_ptr()); } #[test] diff --git a/zeroconf/src/tests/txt_record_test.rs b/zeroconf/src/tests/txt_record_test.rs index e1ca2cc..29e47b5 100644 --- a/zeroconf/src/tests/txt_record_test.rs +++ b/zeroconf/src/tests/txt_record_test.rs @@ -38,7 +38,6 @@ fn len_success() { } #[test] -#[ignore] fn iter_success() { super::setup(); @@ -55,7 +54,6 @@ fn iter_success() { } #[test] -#[ignore] fn keys_success() { super::setup(); @@ -70,7 +68,6 @@ fn keys_success() { } #[test] -#[ignore] fn values_success() { super::setup();