Add ServiceType

Signed-off-by: Walker Crouse <Walker.Crouse@bjss.com>
This commit is contained in:
Walker Crouse 2021-08-19 16:09:16 -04:00
parent 3c14883ac9
commit 9935ce280d
8 changed files with 166 additions and 15 deletions

1
.gitignore vendored
View File

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

4
examples/Cargo.lock generated
View File

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

View File

@ -1,8 +1,8 @@
use std::any::Any; use std::any::Any;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use std::time::Duration; use std::time::Duration;
use zeroconf::{MdnsService, ServiceRegistration, TxtRecord};
use zeroconf::prelude::*; use zeroconf::prelude::*;
use zeroconf::{MdnsService, ServiceRegistration, ServiceType, TxtRecord};
#[derive(Default, Debug)] #[derive(Default, Debug)]
pub struct Context { pub struct Context {
@ -10,7 +10,7 @@ pub struct Context {
} }
fn main() { 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 mut txt_record = TxtRecord::new();
let context: Arc<Mutex<Context>> = Arc::default(); let context: Arc<Mutex<Context>> = Arc::default();

View File

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

View File

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

View File

@ -1,14 +1,14 @@
//! Trait definition for cross-platform service. //! 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::any::Any;
use std::sync::Arc; use std::sync::Arc;
/// Interface for interacting with underlying mDNS service implementation registration /// Interface for interacting with underlying mDNS service implementation registration
/// capabilities. /// capabilities.
pub trait TMdnsService { pub trait TMdnsService {
/// Creates a new `MdnsService` with the specified `kind` (e.g. `_http._tcp`) and `port`. /// Creates a new `MdnsService` with the specified `ServiceType` (e.g. `_http._tcp`) and `port`.
fn new(kind: &str, port: u16) -> Self; fn new(service_type: ServiceType, port: u16) -> Self;
/// Sets the name to register this service under. /// Sets the name to register this service under.
fn set_name(&mut self, name: &str); 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)] #[derive(Builder, BuilderDelegate, Debug, Getters, Clone, Default, PartialEq, Eq)]
pub struct ServiceRegistration { pub struct ServiceRegistration {
name: String, name: String,
kind: String, service_type: ServiceType,
domain: String, 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::prelude::*;
use crate::{MdnsBrowser, MdnsService, TxtRecord}; use crate::{MdnsBrowser, MdnsService, ServiceType, TxtRecord};
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use std::time::Duration; use std::time::Duration;
@ -14,7 +14,7 @@ fn service_register_is_browsable() {
} }
static SERVICE_NAME: &str = "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 context: Arc<Mutex<Context>> = Arc::default();
let mut txt = TxtRecord::new(); let mut txt = TxtRecord::new();