Merge pull request #12 from windy1/feature/name-check
Add ServiceType to enforce naming format and avoid ambiguous errors
This commit is contained in:
commit
e5c2979a5e
|
@ -1 +1,2 @@
|
|||
**/target
|
||||
.vscode
|
||||
|
|
11
README.md
11
README.md
|
@ -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
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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};
|
||||
|
||||
|
|
|
@ -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")]
|
||||
|
|
|
@ -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"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()?;
|
||||
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
|
|
Loading…
Reference in New Issue