Merge pull request #12 from windy1/feature/name-check

Add ServiceType to enforce naming format and avoid ambiguous errors
This commit is contained in:
Walker Crouse 2021-08-19 17:47:29 -04:00 committed by GitHub
commit e5c2979a5e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 190 additions and 25 deletions

1
.gitignore vendored
View File

@ -1 +1,2 @@
**/target
.vscode

View File

@ -29,8 +29,8 @@ types will automatically. See `MdnsService` for more information about contexts.
use std::any::Any;
use std::sync::{Arc, Mutex};
use std::time::Duration;
use zeroconf::{MdnsService, ServiceRegistration, TxtRecord};
use zeroconf::prelude::*;
use zeroconf::{MdnsService, ServiceRegistration, ServiceType, TxtRecord};
#[derive(Default, Debug)]
pub struct Context {
@ -38,7 +38,7 @@ pub struct Context {
}
fn main() {
let mut service = MdnsService::new("_http._tcp", 8080);
let mut service = MdnsService::new(ServiceType::new("http", "tcp").unwrap(), 8080);
let mut txt_record = TxtRecord::new();
let context: Arc<Mutex<Context>> = Arc::default();
@ -111,7 +111,14 @@ fn on_service_discovered(
}
```
## Resources
* [Avahi docs]
* [Bonjour docs]
[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)
[`Any`]: https://doc.rust-lang.org/std/any/trait.Any.html
[Avahi docs]: https://avahi.org/doxygen/html/
[Bonjour docs]: https://developer.apple.com/documentation/dnssd/dns_service_discovery_c

4
examples/Cargo.lock generated
View File

@ -1,5 +1,7 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "aho-corasick"
version = "0.7.13"
@ -500,7 +502,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "zeroconf"
version = "0.7.0"
version = "0.7.2"
dependencies = [
"avahi-sys",
"bonjour-sys",

View File

@ -1,8 +1,8 @@
use std::any::Any;
use std::sync::{Arc, Mutex};
use std::time::Duration;
use zeroconf::{MdnsService, ServiceRegistration, TxtRecord};
use zeroconf::prelude::*;
use zeroconf::{MdnsService, ServiceRegistration, ServiceType, TxtRecord};
#[derive(Default, Debug)]
pub struct Context {
@ -10,7 +10,7 @@ pub struct Context {
}
fn main() {
let mut service = MdnsService::new("_http._tcp", 8080);
let mut service = MdnsService::new(ServiceType::new("http", "tcp").unwrap(), 8080);
let mut txt_record = TxtRecord::new();
let context: Arc<Mutex<Context>> = Arc::default();

View File

@ -44,7 +44,9 @@ pub type ServiceDiscoveredCallback = dyn Fn(Result<ServiceDiscovery>, Option<Arc
/// Represents a service that has been discovered by a [`MdnsBrowser`].
///
/// [`MdnsBrowser`]: type.MdnsBrowser.html
#[derive(Debug, Getters, Builder, BuilderDelegate, Serialize, Deserialize, Clone, PartialEq, Eq)]
#[derive(
Debug, Getters, Builder, BuilderDelegate, Serialize, Deserialize, Clone, PartialEq, Eq,
)]
pub struct ServiceDiscovery {
name: String,
kind: String,

View File

@ -3,7 +3,7 @@
use crate::Result;
#[cfg(target_os = "linux")]
use libc::{c_char, in_addr, sockaddr_in};
use libc::{c_void, fd_set, timeval, time_t, suseconds_t};
use libc::{c_void, fd_set, suseconds_t, time_t, timeval};
use std::time::Duration;
use std::{mem, ptr};

View File

@ -20,8 +20,8 @@
//! use std::any::Any;
//! use std::sync::{Arc, Mutex};
//! use std::time::Duration;
//! use zeroconf::{MdnsService, ServiceRegistration, TxtRecord};
//! use zeroconf::prelude::*;
//! use zeroconf::{MdnsService, ServiceRegistration, ServiceType, TxtRecord};
//!
//! #[derive(Default, Debug)]
//! pub struct Context {
@ -29,7 +29,7 @@
//! }
//!
//! fn main() {
//! let mut service = MdnsService::new("_http._tcp", 8080);
//! let mut service = MdnsService::new(ServiceType::new("http", "tcp").unwrap(), 8080);
//! let mut txt_record = TxtRecord::new();
//! let context: Arc<Mutex<Context>> = Arc::default();
//!
@ -143,6 +143,7 @@ pub mod event_loop;
pub mod ffi;
pub mod prelude;
pub mod service;
pub mod service_type;
pub mod txt_record;
#[cfg(target_os = "linux")]
@ -153,6 +154,7 @@ pub mod macos;
pub use browser::{ServiceDiscoveredCallback, ServiceDiscovery};
pub use interface::*;
pub use service::{ServiceRegisteredCallback, ServiceRegistration};
pub use service_type::*;
/// Type alias for the platform-specific mDNS browser implementation
#[cfg(target_os = "linux")]

View File

@ -92,7 +92,10 @@ mod tests {
};
unsafe {
assert_eq!(avahi_address_to_string(&ipv6_addr), "fe80::1234:5678:9abc:def0");
assert_eq!(
avahi_address_to_string(&ipv6_addr),
"fe80::1234:5678:9abc:def0"
);
}
}
}

View File

@ -8,7 +8,8 @@ use super::poll::ManagedAvahiSimplePoll;
use crate::ffi::{c_str, AsRaw, FromRaw, UnwrapOrNull};
use crate::prelude::*;
use crate::{
EventLoop, NetworkInterface, Result, ServiceRegisteredCallback, ServiceRegistration, TxtRecord,
EventLoop, NetworkInterface, Result, ServiceRegisteredCallback, ServiceRegistration,
ServiceType, TxtRecord,
};
use avahi_sys::{
AvahiClient, AvahiClientFlags, AvahiClientState, AvahiEntryGroup, AvahiEntryGroupState,
@ -18,6 +19,7 @@ use libc::c_void;
use std::any::Any;
use std::ffi::CString;
use std::fmt::{self, Formatter};
use std::str::FromStr;
use std::sync::Arc;
#[derive(Debug)]
@ -28,11 +30,14 @@ pub struct AvahiMdnsService {
}
impl TMdnsService for AvahiMdnsService {
fn new(kind: &str, port: u16) -> Self {
fn new(service_type: ServiceType, port: u16) -> Self {
Self {
client: None,
poll: None,
context: Box::into_raw(Box::new(AvahiServiceContext::new(kind, port))),
context: Box::into_raw(Box::new(AvahiServiceContext::new(
&service_type.to_string(),
port,
))),
}
}
@ -231,7 +236,9 @@ unsafe fn handle_group_established(context: &AvahiServiceContext) -> Result<()>
let result = ServiceRegistration::builder()
.name(c_str::copy_raw(context.name.as_ref().unwrap().as_ptr()))
.kind(c_str::copy_raw(context.kind.as_ptr()))
.service_type(ServiceType::from_str(&c_str::copy_raw(
context.kind.as_ptr(),
))?)
.domain("local".to_string())
.build()?;

View File

@ -3,9 +3,9 @@
use super::service_ref::ManagedDNSServiceRef;
use crate::event_loop::TEventLoop;
use crate::{ffi, Result};
use std::marker::PhantomData;
use std::sync::{Arc, Mutex};
use std::time::Duration;
use std::marker::PhantomData;
#[derive(new)]
pub struct BonjourEventLoop<'a> {

View File

@ -6,12 +6,14 @@ use crate::ffi::c_str::{self, AsCChars};
use crate::ffi::{FromRaw, UnwrapOrNull};
use crate::prelude::*;
use crate::{
EventLoop, NetworkInterface, Result, ServiceRegisteredCallback, ServiceRegistration, TxtRecord,
EventLoop, NetworkInterface, Result, ServiceRegisteredCallback, ServiceRegistration,
ServiceType, TxtRecord,
};
use bonjour_sys::{DNSServiceErrorType, DNSServiceFlags, DNSServiceRef};
use libc::{c_char, c_void};
use std::any::Any;
use std::ffi::CString;
use std::str::FromStr;
use std::sync::{Arc, Mutex};
#[derive(Debug)]
@ -28,10 +30,10 @@ pub struct BonjourMdnsService {
}
impl TMdnsService for BonjourMdnsService {
fn new(kind: &str, port: u16) -> Self {
fn new(service_type: ServiceType, port: u16) -> Self {
Self {
service: Arc::default(),
kind: c_string!(kind),
kind: c_string!(service_type.to_string()),
port,
name: None,
domain: None,
@ -159,7 +161,7 @@ unsafe fn handle_register(
let result = ServiceRegistration::builder()
.name(c_str::copy_raw(name))
.kind(c_str::copy_raw(regtype))
.service_type(ServiceType::from_str(&c_str::copy_raw(regtype))?)
.domain(domain)
.build()
.expect("could not build ServiceRegistration");

View File

@ -1,14 +1,14 @@
//! Trait definition for cross-platform service.
use crate::{EventLoop, NetworkInterface, Result, TxtRecord};
use crate::{EventLoop, NetworkInterface, Result, ServiceType, TxtRecord};
use std::any::Any;
use std::sync::Arc;
/// Interface for interacting with underlying mDNS service implementation registration
/// capabilities.
pub trait TMdnsService {
/// Creates a new `MdnsService` with the specified `kind` (e.g. `_http._tcp`) and `port`.
fn new(kind: &str, port: u16) -> Self;
/// Creates a new `MdnsService` with the specified `ServiceType` (e.g. `_http._tcp`) and `port`.
fn new(service_type: ServiceType, port: u16) -> Self;
/// Sets the name to register this service under.
fn set_name(&mut self, name: &str);
@ -64,6 +64,6 @@ pub type ServiceRegisteredCallback = dyn Fn(Result<ServiceRegistration>, Option<
#[derive(Builder, BuilderDelegate, Debug, Getters, Clone, Default, PartialEq, Eq)]
pub struct ServiceRegistration {
name: String,
kind: String,
service_type: ServiceType,
domain: String,
}

View File

@ -0,0 +1,139 @@
//! Data type for constructing a service type
use crate::Result;
use std::str::FromStr;
/// Data type for constructing a service type to register as an mDNS service.
#[derive(Default, Debug, Getters, Serialize, Deserialize, Clone, PartialEq, Eq)]
pub struct ServiceType {
name: String,
protocol: String,
sub_types: Vec<String>,
}
impl ServiceType {
/// Creates a new `ServiceType` with the specified name (e.g. `http`) and protocol (e.g. `tcp`)
pub fn new(name: &str, protocol: &str) -> Result<Self> {
Ok(Self {
name: Self::check_part(name)?.to_string(),
protocol: Self::check_part(protocol)?.to_string(),
sub_types: vec![],
})
}
/// Creates a new `ServiceType` with the specified name (e.g. `http`) and protocol (e.g. `tcp`)
/// and sub-types.
pub fn with_sub_types(name: &str, protocol: &str, sub_types: Vec<&str>) -> Result<Self> {
for sub_type in &sub_types {
Self::check_part(sub_type)?;
}
Ok(Self {
name: name.to_string(),
protocol: protocol.to_string(),
sub_types: sub_types.iter().map(|s| s.to_string()).collect(),
})
}
fn check_part(part: &str) -> Result<&str> {
if part.contains(".") {
Err("invalid character: .".into())
} else if part.contains(",") {
Err("invalid character: ,".into())
} else {
Ok(part)
}
}
}
impl ToString for ServiceType {
fn to_string(&self) -> String {
format!("_{}._{}{}", self.name, self.protocol, {
if !self.sub_types.is_empty() {
format!(",_{}", self.sub_types.join(",_"))
} else {
"".to_string()
}
})
}
}
impl FromStr for ServiceType {
type Err = crate::error::Error;
fn from_str(s: &str) -> Result<Self> {
let parts: Vec<&str> = s.split(",").collect();
if parts.is_empty() {
return Err("could not parse ServiceType from string".into());
}
let head: Vec<&str> = parts[0].split(".").collect();
let mut name = head[0];
if name.starts_with("_") {
name = &name[1..];
}
let mut protocol = head[1];
if protocol.starts_with("_") {
protocol = &protocol[1..];
}
let mut sub_types: Vec<&str> = vec![];
if parts.len() > 1 {
for i in 1..parts.len() {
let mut sub_type = parts[i];
if sub_type.starts_with("_") {
sub_type = &sub_type[1..];
}
sub_types.push(sub_type);
}
}
Ok(ServiceType::with_sub_types(name, protocol, sub_types)?)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn new_invalid() {
ServiceType::new(".http", "tcp").expect_err("invalid character: .".into());
ServiceType::new("http", ".tcp").expect_err("invalid character: .".into());
ServiceType::new(",http", "tcp").expect_err("invalid character: ,".into());
ServiceType::new("http", ",tcp").expect_err("invalid character: ,".into());
}
#[test]
fn to_string_success() {
assert_eq!(
ServiceType::new("http", "tcp").unwrap().to_string(),
"_http._tcp"
);
}
#[test]
fn to_string_with_sub_types_success() {
assert_eq!(
ServiceType::with_sub_types("http", "tcp", vec!["api-v1", "api-v2"])
.unwrap()
.to_string(),
"_http._tcp,_api-v1,_api-v2"
);
}
#[test]
fn from_str_success() {
assert_eq!(
ServiceType::from_str("_http._tcp").unwrap(),
ServiceType::new("http", "tcp").unwrap()
);
}
#[test]
fn from_str_with_sub_types_success() {
assert_eq!(
ServiceType::from_str("_http._tcp,api-v1,api-v2").unwrap(),
ServiceType::with_sub_types("http", "tcp", vec!["api-v1", "api-v2"]).unwrap()
);
}
}

View File

@ -1,5 +1,5 @@
use crate::prelude::*;
use crate::{MdnsBrowser, MdnsService, TxtRecord};
use crate::{MdnsBrowser, MdnsService, ServiceType, TxtRecord};
use std::sync::{Arc, Mutex};
use std::time::Duration;
@ -14,7 +14,7 @@ fn service_register_is_browsable() {
}
static SERVICE_NAME: &str = "service_register_is_browsable";
let mut service = MdnsService::new("_http._tcp", 8080);
let mut service = MdnsService::new(ServiceType::new("http", "tcp").unwrap(), 8080);
let context: Arc<Mutex<Context>> = Arc::default();
let mut txt = TxtRecord::new();