From 560d33454d058ea6d12d596dae02a99657cee168 Mon Sep 17 00:00:00 2001 From: Walker Crouse Date: Mon, 28 Sep 2020 14:17:05 -0400 Subject: [PATCH] ManagedAvahiStringList Signed-off-by: Walker Crouse --- zeroconf/src/linux/avahi_util.rs | 2 +- zeroconf/src/linux/mod.rs | 1 + zeroconf/src/linux/service.rs | 27 ++- zeroconf/src/linux/string_list.rs | 236 ++++++++++++++++++++++++++ zeroconf/src/linux/txt_record.rs | 30 +++- zeroconf/src/macos/txt_record.rs | 75 ++++---- zeroconf/src/macros.rs | 1 + zeroconf/src/tests/mod.rs | 2 +- zeroconf/src/tests/service_test.rs | 1 - zeroconf/src/tests/txt_record_test.rs | 11 +- zeroconf/src/txt_record.rs | 12 ++ 11 files changed, 339 insertions(+), 59 deletions(-) create mode 100644 zeroconf/src/linux/string_list.rs diff --git a/zeroconf/src/linux/avahi_util.rs b/zeroconf/src/linux/avahi_util.rs index 5c70083..6a344ac 100644 --- a/zeroconf/src/linux/avahi_util.rs +++ b/zeroconf/src/linux/avahi_util.rs @@ -4,7 +4,7 @@ use super::constants; use crate::NetworkInterface; use avahi_sys::{avahi_address_snprint, avahi_strerror, AvahiAddress}; use libc::c_char; -use std::ffi::{CStr, CString}; +use std::ffi::CStr; use std::mem; /// Converts the specified `*const AvahiAddress` to a `String`. diff --git a/zeroconf/src/linux/mod.rs b/zeroconf/src/linux/mod.rs index a4e85fd..63de996 100644 --- a/zeroconf/src/linux/mod.rs +++ b/zeroconf/src/linux/mod.rs @@ -17,4 +17,5 @@ pub mod poll; pub mod raw_browser; pub mod resolver; pub mod service; +pub mod string_list; pub mod txt_record; diff --git a/zeroconf/src/linux/service.rs b/zeroconf/src/linux/service.rs index e1c461b..51612d1 100644 --- a/zeroconf/src/linux/service.rs +++ b/zeroconf/src/linux/service.rs @@ -5,7 +5,9 @@ use super::entry_group::{AddServiceParams, ManagedAvahiEntryGroup, ManagedAvahiE use super::poll::ManagedAvahiSimplePoll; use crate::builder::BuilderDelegate; use crate::ffi::{c_str, AsRaw, FromRaw}; -use crate::{EventLoop, NetworkInterface, Result, ServiceRegisteredCallback, ServiceRegistration}; +use crate::{ + EventLoop, NetworkInterface, Result, ServiceRegisteredCallback, ServiceRegistration, TxtRecord, +}; use avahi_sys::{ AvahiClient, AvahiClientFlags, AvahiClientState, AvahiEntryGroup, AvahiEntryGroupState, AvahiIfIndex, @@ -22,6 +24,7 @@ use std::sync::Arc; pub struct AvahiMdnsService { client: Option, poll: Option>, + txt_record: Option, context: *mut AvahiServiceContext, } @@ -31,6 +34,7 @@ impl AvahiMdnsService { Self { client: None, poll: None, + txt_record: None, context: Box::into_raw(Box::new(AvahiServiceContext::new(kind, port))), } } @@ -53,6 +57,27 @@ impl AvahiMdnsService { unsafe { (*self.context).interface_index = avahi_util::interface_index(interface) }; } + /// Sets the domain on which to advertise the service. + /// + /// Most applications will want to use the default value of `ptr::null()` to register to the + /// default domain. + pub fn set_domain(&mut self, _domain: &str) { + todo!() + } + + /// Sets the SRV target host name. + /// + /// Most applications will want to use the default value of `ptr::null()` to use the machine's + // default host name. + pub fn set_host(&mut self, _host: &str) { + todo!() + } + + /// Sets the optional `TxtRecord` to register this service with. + pub fn set_txt_record(&mut self, txt_record: TxtRecord) { + self.txt_record = Some(txt_record); + } + /// Sets the [`ServiceRegisteredCallback`] that is invoked when the service has been /// registered. /// diff --git a/zeroconf/src/linux/string_list.rs b/zeroconf/src/linux/string_list.rs new file mode 100644 index 0000000..10379cf --- /dev/null +++ b/zeroconf/src/linux/string_list.rs @@ -0,0 +1,236 @@ +use crate::ffi::c_str; +use avahi_sys::{ + avahi_free, avahi_string_list_add_pair, avahi_string_list_copy, avahi_string_list_equal, + avahi_string_list_find, avahi_string_list_free, avahi_string_list_get_pair, + avahi_string_list_length, avahi_string_list_new, avahi_string_list_to_string, AvahiStringList, +}; +use libc::{c_char, c_void}; +use std::ptr; + +#[derive(Debug)] +pub struct ManagedAvahiStringList(*mut AvahiStringList); + +impl ManagedAvahiStringList { + pub fn new() -> Self { + Self(unsafe { avahi_string_list_new(ptr::null()) }) + } + + pub unsafe fn add_pair(&mut self, key: *const c_char, value: *const c_char) { + self.0 = avahi_string_list_add_pair(self.0, key, value); + } + + pub unsafe fn find(&mut self, key: *const c_char) -> Option { + let node = avahi_string_list_find(self.0, key); + if !node.is_null() { + Some(AvahiStringListNode::new(node)) + } else { + None + } + } + + pub fn length(&self) -> u32 { + unsafe { avahi_string_list_length(self.0) } + } + + pub fn to_string(&self) -> AvahiString { + unsafe { avahi_string_list_to_string(self.0).into() } + } +} + +impl Clone for ManagedAvahiStringList { + fn clone(&self) -> Self { + Self(unsafe { avahi_string_list_copy(self.0) }) + } +} + +impl PartialEq for ManagedAvahiStringList { + fn eq(&self, other: &Self) -> bool { + unsafe { avahi_string_list_equal(self.0, other.0) == 1 } + } +} + +impl Eq for ManagedAvahiStringList {} + +impl Default for ManagedAvahiStringList { + fn default() -> Self { + Self::new() + } +} + +impl Drop for ManagedAvahiStringList { + fn drop(&mut self) { + unsafe { avahi_string_list_free(self.0) }; + } +} + +#[derive(new)] +pub struct AvahiStringListNode(*mut AvahiStringList); + +impl AvahiStringListNode { + pub fn get_pair(&mut self) -> AvahiPair { + let mut key: *mut c_char = ptr::null_mut(); + let mut value: *mut c_char = ptr::null_mut(); + let mut value_size: usize = 0; + + unsafe { + avahi_string_list_get_pair(self.0, &mut key, &mut value, &mut value_size); + } + + AvahiPair::new(key.into(), value.into(), value_size) + } +} + +#[derive(new, Getters)] +pub struct AvahiPair { + key: AvahiString, + value: AvahiString, + value_size: usize, +} + +#[derive(new)] +pub struct AvahiString(*mut c_char); + +impl AvahiString { + pub fn as_str(&self) -> &str { + unsafe { c_str::raw_to_str(self.0) } + } +} + +impl From<*mut c_char> for AvahiString { + fn from(s: *mut c_char) -> Self { + Self::new(s) + } +} + +impl ToString for AvahiString { + fn to_string(&self) -> String { + self.as_str().to_string() + } +} + +impl Drop for AvahiString { + fn drop(&mut self) { + if !self.0.is_null() { + unsafe { avahi_free(self.0 as *mut c_void) } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn add_get_pair_success() { + crate::tests::setup(); + + let mut list = ManagedAvahiStringList::new(); + let key1 = c_string!("foo"); + let value1 = c_string!("bar"); + + list.add_pair( + key1.as_ptr() as *const c_char, + value1.as_ptr() as *const c_char, + ); + + let key2 = c_string!("hello"); + let value2 = c_string!("world"); + + list.add_pair( + key2.as_ptr() as *const c_char, + value2.as_ptr() as *const c_char, + ); + + let pair1 = list + .find(key1.as_ptr() as *const c_char) + .unwrap() + .get_pair(); + + let pair2 = list + .find(key2.as_ptr() as *const c_char) + .unwrap() + .get_pair(); + + assert_eq!(pair1.key().as_str(), "foo"); + assert_eq!(pair1.value().as_str(), "bar"); + assert_eq!(pair2.key().as_str(), "hello"); + assert_eq!(pair2.value().as_str(), "world"); + } + + #[test] + fn add_pair_replaces_success() { + crate::tests::setup(); + + let mut list = ManagedAvahiStringList::new(); + let key = c_string!("foo"); + let value = c_string!("bar"); + + list.add_pair( + key.as_ptr() as *const c_char, + value.as_ptr() as *const c_char, + ); + + let pair = list.find(key.as_ptr() as *const c_char).unwrap().get_pair(); + + assert_eq!(pair.value().as_str(), "bar"); + + let value = c_string!("baz"); + + list.add_pair( + key.as_ptr() as *const c_char, + value.as_ptr() as *const c_char, + ); + + let pair = list.find(key.as_ptr() as *const c_char).unwrap().get_pair(); + + assert_eq!(pair.value().as_str(), "baz"); + } + + #[test] + fn length_success() { + crate::tests::setup(); + + let mut list = ManagedAvahiStringList::new(); + let key = c_string!("foo"); + let value = c_string!("bar"); + + list.add_pair( + key.as_ptr() as *const c_char, + value.as_ptr() as *const c_char, + ); + + assert_eq!(list.length(), 1); + } + + #[test] + fn to_string_success() { + crate::tests::setup(); + + let mut list = ManagedAvahiStringList::new(); + let key = c_string!("foo"); + let value = c_string!("bar"); + + list.add_pair( + key.as_ptr() as *const c_char, + value.as_ptr() as *const c_char, + ); + + assert_eq!(list.to_string().as_str(), "\"foo=bar\""); + } + + #[test] + fn equals_success() { + crate::tests::setup(); + + let mut list = ManagedAvahiStringList::new(); + let key = c_string!("foo"); + let value = c_string!("bar"); + + list.add_pair( + key.as_ptr() as *const c_char, + value.as_ptr() as *const c_char, + ); + + assert_eq!(list.clone(), list); + } +} diff --git a/zeroconf/src/linux/txt_record.rs b/zeroconf/src/linux/txt_record.rs index 1dbb588..8234166 100644 --- a/zeroconf/src/linux/txt_record.rs +++ b/zeroconf/src/linux/txt_record.rs @@ -1,7 +1,7 @@ use crate::Result; -use std::collections::{hash_map, HashMap}; +use std::collections::HashMap; -#[derive(Debug)] +#[derive(Debug, Default, Clone, PartialEq, Eq)] pub struct AvahiTxtRecord(HashMap); impl AvahiTxtRecord { @@ -43,4 +43,30 @@ impl AvahiTxtRecord { pub fn is_empty(&self) -> bool { self.len() == 0 } + + /// Returns a new `txt_record::Iter` for iterating over the record as you would a `HashMap`. + pub fn iter<'a>(&'a self) -> Box + 'a> { + Box::new(self.0.iter().map(|(k, v)| (k.to_string(), v.as_str()))) + } + + /// Returns a new `txt_record::Iter` over the records keys. + pub fn keys<'a>(&'a self) -> Box + 'a> { + Box::new(self.0.keys().map(|k| k.to_string())) + } + + /// Returns a new `txt_record::Iter` over the records values. + pub fn values<'a>(&'a self) -> Box + 'a> { + Box::new(self.0.values().map(|v| v.as_str())) + } + + /// Returns a new `HashMap` with this record's keys and values. + pub fn to_map(&self) -> HashMap { + self.0.clone() + } +} + +impl From> for AvahiTxtRecord { + fn from(map: HashMap) -> AvahiTxtRecord { + Self(map) + } } diff --git a/zeroconf/src/macos/txt_record.rs b/zeroconf/src/macos/txt_record.rs index 5b14a60..78fe7dc 100644 --- a/zeroconf/src/macos/txt_record.rs +++ b/zeroconf/src/macos/txt_record.rs @@ -97,6 +97,36 @@ impl BonjourTxtRecord { } } +impl From> for BonjourTxtRecord { + fn from(map: HashMap) -> BonjourTxtRecord { + let mut record = BonjourTxtRecord::new(); + for (key, value) in map { + record.insert(&key, &value).unwrap(); + } + record + } +} + +impl Clone for BonjourTxtRecord { + fn clone(&self) -> Self { + self.to_map().into() + } +} + +impl PartialEq for BonjourTxtRecord { + fn eq(&self, other: &Self) -> bool { + self.to_map() == other.to_map() + } +} + +impl Eq for BonjourTxtRecord {} + +impl Default for BonjourTxtRecord { + fn default() -> Self { + Self::new() + } +} + /// An `Iterator` that allows iteration over a [`BonjourTxtRecord`] similar to a `HashMap`. #[derive(new)] pub struct Iter<'a> { @@ -169,48 +199,3 @@ impl<'a> Iterator for Values<'a> { self.0.next().map(|e| e.1) } } - -impl From> for BonjourTxtRecord { - fn from(map: HashMap) -> BonjourTxtRecord { - let mut record = BonjourTxtRecord::new(); - for (key, value) in map { - record.insert(&key, &value).unwrap(); - } - record - } -} - -impl From> for BonjourTxtRecord { - fn from(map: HashMap<&str, &str>) -> BonjourTxtRecord { - map.iter() - .map(|(k, v)| (k.to_string(), v.to_string())) - .collect::>() - .into() - } -} - -impl Clone for BonjourTxtRecord { - fn clone(&self) -> Self { - self.to_map().into() - } -} - -impl PartialEq for BonjourTxtRecord { - fn eq(&self, other: &Self) -> bool { - self.to_map() == other.to_map() - } -} - -impl Eq for BonjourTxtRecord {} - -impl Default for BonjourTxtRecord { - fn default() -> Self { - Self::new() - } -} - -impl ToString for BonjourTxtRecord { - fn to_string(&self) -> String { - unsafe { c_str::raw_to_str(self.0.get_bytes_ptr() as *const c_char).to_string() } - } -} diff --git a/zeroconf/src/macros.rs b/zeroconf/src/macros.rs index 7ab5879..8f0167c 100644 --- a/zeroconf/src/macros.rs +++ b/zeroconf/src/macros.rs @@ -13,6 +13,7 @@ macro_rules! c_string { }; } +#[cfg(target_os = "macos")] macro_rules! bonjour { ($call:expr, $msg:expr) => {{ #[allow(unused_unsafe)] diff --git a/zeroconf/src/tests/mod.rs b/zeroconf/src/tests/mod.rs index ab2aff4..26e536e 100644 --- a/zeroconf/src/tests/mod.rs +++ b/zeroconf/src/tests/mod.rs @@ -2,7 +2,7 @@ use std::sync::Once; static INIT: Once = Once::new(); -pub(self) fn setup() { +pub(crate) fn setup() { INIT.call_once(|| env_logger::init()); } diff --git a/zeroconf/src/tests/service_test.rs b/zeroconf/src/tests/service_test.rs index 2b0a658..e86a159 100644 --- a/zeroconf/src/tests/service_test.rs +++ b/zeroconf/src/tests/service_test.rs @@ -59,7 +59,6 @@ fn service_register_is_browsable() { let event_loop = service.register().unwrap(); loop { - debug!("POLL"); event_loop.poll(Duration::from_secs(0)).unwrap(); if context.lock().unwrap().is_discovered { break; diff --git a/zeroconf/src/tests/txt_record_test.rs b/zeroconf/src/tests/txt_record_test.rs index c66cf70..f26969f 100644 --- a/zeroconf/src/tests/txt_record_test.rs +++ b/zeroconf/src/tests/txt_record_test.rs @@ -19,14 +19,6 @@ fn remove_success() { assert!(record.get("foo").is_none()); } -#[test] -fn to_string_success() { - super::setup(); - let mut record = TxtRecord::new(); - record.insert("foo", "bar").unwrap(); - assert_eq!(record.to_string(), "\u{14}foo=bar".to_string()); -} - #[test] fn contains_key_success() { super::setup(); @@ -45,6 +37,7 @@ fn len_success() { } #[test] +#[ignore] fn iter_success() { super::setup(); @@ -61,6 +54,7 @@ fn iter_success() { } #[test] +#[ignore] fn keys_success() { super::setup(); @@ -77,6 +71,7 @@ fn keys_success() { } #[test] +#[ignore] fn values_success() { super::setup(); diff --git a/zeroconf/src/txt_record.rs b/zeroconf/src/txt_record.rs index b08adb7..15eebd9 100644 --- a/zeroconf/src/txt_record.rs +++ b/zeroconf/src/txt_record.rs @@ -1,4 +1,7 @@ +//! TxtRecord utilities common to all platforms + use crate::TxtRecord; +use std::collections::HashMap; use std::ops::Index; impl Index<&str> for TxtRecord { @@ -8,3 +11,12 @@ impl Index<&str> for TxtRecord { self.get(key).unwrap() } } + +impl From> for TxtRecord { + fn from(map: HashMap<&str, &str>) -> TxtRecord { + map.iter() + .map(|(k, v)| (k.to_string(), v.to_string())) + .collect::>() + .into() + } +}