use crate::ffi;
use crate::DNSServiceError;
use crate::Result;
use std::ffi::{c_void, CStr, CString};
use std::mem;
use std::net::{SocketAddr, ToSocketAddrs};
use std::os::raw::c_char;
use std::ptr;
macro_rules! mut_void_ptr {
($var:expr) => {
$var as *mut _ as *mut c_void
};
}
macro_rules! mut_raw_ptr {
($var:expr) => {
&mut $var as *mut _
};
}
#[derive(Debug)]
pub enum ServiceEventType {
Added,
Removed,
}
impl From<ffi::DNSServiceFlags> for ServiceEventType {
fn from(flags: ffi::DNSServiceFlags) -> Self {
if flags & ffi::kDNSServiceFlagsAdd as u32 != 0 {
ServiceEventType::Added
} else {
ServiceEventType::Removed
}
}
}
#[derive(Debug)]
pub struct Service {
pub name: String,
pub regtype: String,
pub interface_index: u32,
pub domain: String,
pub event_type: ServiceEventType,
}
#[derive(Debug)]
pub struct ResolvedService {
pub full_name: String,
pub hostname: String,
pub port: u16,
pub txt_record: Option<TXTHash>,
interface_index: u32,
}
impl ToSocketAddrs for ResolvedService {
type Iter = std::vec::IntoIter<SocketAddr>;
fn to_socket_addrs(&self) -> std::io::Result<Self::Iter> {
(self.hostname.as_str(), self.port).to_socket_addrs()
}
}
struct PendingResolution {
more_coming: bool,
results: Vec<ResolvedService>,
}
impl Default for PendingResolution {
fn default() -> Self {
PendingResolution {
more_coming: true, results: Vec::with_capacity(1),
}
}
}
impl Service {
pub fn resolve(&mut self) -> Result<Vec<ResolvedService>> {
let mut sdref: ffi::DNSServiceRef = unsafe { mem::zeroed() };
let regtype =
CString::new(self.regtype.as_str()).map_err(|_| DNSServiceError::InvalidString)?;
let name = CString::new(self.name.as_str()).map_err(|_| DNSServiceError::InvalidString)?;
let domain =
CString::new(self.domain.as_str()).map_err(|_| DNSServiceError::InvalidString)?;
let mut pending_resolution: PendingResolution = Default::default();
unsafe {
ffi::DNSServiceResolve(
mut_raw_ptr!(sdref),
0,
self.interface_index,
name.as_ptr(),
regtype.as_ptr(),
domain.as_ptr(),
Some(Self::resolve_callback),
mut_void_ptr!(&mut pending_resolution),
);
while pending_resolution.more_coming {
ffi::DNSServiceProcessResult(sdref);
}
ffi::DNSServiceRefDeallocate(sdref);
}
Ok(pending_resolution.results)
}
unsafe extern "C" fn resolve_callback(
_sd_ref: ffi::DNSServiceRef,
flags: ffi::DNSServiceFlags,
interface_index: u32,
error_code: ffi::DNSServiceErrorType,
full_name: *const c_char,
host_target: *const c_char,
port: u16, txt_len: u16,
txt_record: *const u8,
context: *mut c_void,
) {
let context: &mut PendingResolution = &mut *(context as *mut PendingResolution);
if error_code != ffi::kDNSServiceErr_NoError {
error!("Error resolving service: {}", error_code);
context.more_coming = false;
return;
}
context.more_coming = flags & ffi::kDNSServiceFlagsMoreComing as u32 != 0;
let process = || -> Result<(String, String)> {
let c_str: &CStr = CStr::from_ptr(full_name);
let full_name: &str = c_str
.to_str()
.map_err(|_| DNSServiceError::InternalInvalidString)?;
let c_str: &CStr = CStr::from_ptr(host_target);
let hostname: &str = c_str
.to_str()
.map_err(|_| DNSServiceError::InternalInvalidString)?;
Ok((full_name.to_owned(), hostname.to_owned()))
};
let txt_record = if txt_len > 0 {
let data = std::slice::from_raw_parts(txt_record, txt_len as usize).to_vec();
match TXTHash::new(data) {
Ok(hash) => {
trace!("Got TXT: {:?}", hash);
Some(hash)
}
Err(e) => {
error!("Failed to get TXT record: {:?}", e);
None
}
}
} else {
None
};
match process() {
Ok((full_name, hostname)) => {
let service = ResolvedService {
full_name,
hostname,
port: u16::from_be(port),
txt_record,
interface_index,
};
trace!(
"{} - {} service resolved",
service.full_name,
service.hostname
);
context.results.push(service);
}
Err(e) => {
error!("Error resolving service: {:?}", e);
}
}
}
}
#[derive(Debug)]
pub struct TXTHash {
data: Vec<u8>,
}
impl TXTHash {
pub fn new(data: Vec<u8>) -> Result<Self> {
Ok(TXTHash { data })
}
fn as_raw(&self) -> (u16, *const c_void) {
(self.data.len() as u16, self.data.as_ptr() as *const c_void)
}
pub fn contains(&self, key: &str) -> Result<bool> {
let key_c = CString::new(key).map_err(|_| DNSServiceError::InvalidString)?;
unsafe {
let (txt_len, txt_data) = self.as_raw();
let contains_key = ffi::TXTRecordContainsKey(txt_len, txt_data, key_c.as_ptr()) == 1;
Ok(contains_key)
}
}
pub fn get(&self, key: &str) -> Result<Option<Vec<u8>>> {
let key_c = CString::new(key).map_err(|_| DNSServiceError::InvalidString)?;
let mut value_len: u8 = 0;
let (txt_len, txt_data) = self.as_raw();
unsafe {
if !self.contains(key)? {
return Ok(None);
}
let data_ptr =
ffi::TXTRecordGetValuePtr(txt_len, txt_data, key_c.as_ptr(), &mut value_len);
let slice = std::slice::from_raw_parts(data_ptr as *const u8, value_len as usize);
Ok(Some(slice.to_vec()))
}
}
}
pub struct ServiceBrowserBuilder {
regtype: String,
domain: Option<String>,
}
impl ServiceBrowserBuilder {
pub fn new(regtype: &str) -> ServiceBrowserBuilder {
ServiceBrowserBuilder {
regtype: String::from(regtype),
domain: None,
}
}
pub fn with_domain(mut self, domain: &str) -> ServiceBrowserBuilder {
self.domain = Some(String::from(domain));
self
}
pub fn build(self) -> Result<DNSServiceBrowser> {
unsafe {
let service = DNSServiceBrowser {
regtype: self.regtype,
domain: self.domain,
raw: mem::zeroed(),
reply_callback: Box::new(|_| {}),
};
Ok(service)
}
}
}
pub struct DNSServiceBrowser {
pub regtype: String,
pub domain: Option<String>,
raw: ffi::DNSServiceRef,
reply_callback: Box<dyn Fn(Result<Service>) -> ()>,
}
impl DNSServiceBrowser {
unsafe extern "C" fn reply_callback(
_sd_ref: ffi::DNSServiceRef,
flags: ffi::DNSServiceFlags,
interface_index: u32,
error_code: ffi::DNSServiceErrorType,
service_name: *const c_char,
regtype: *const c_char,
reply_domain: *const c_char,
context: *mut c_void,
) {
let context: &mut DNSServiceBrowser = &mut *(context as *mut DNSServiceBrowser);
if error_code != 0 {
(context.reply_callback)(Err(DNSServiceError::ServiceError(error_code)));
return;
}
let process = || -> Result<(String, String, String)> {
let c_str: &CStr = CStr::from_ptr(service_name);
let service_name: &str = c_str
.to_str()
.map_err(|_| DNSServiceError::InternalInvalidString)?;
let c_str: &CStr = CStr::from_ptr(regtype);
let regtype: &str = c_str
.to_str()
.map_err(|_| DNSServiceError::InternalInvalidString)?;
let c_str: &CStr = CStr::from_ptr(reply_domain);
let reply_domain: &str = c_str
.to_str()
.map_err(|_| DNSServiceError::InternalInvalidString)?;
Ok((
service_name.to_owned(),
regtype.to_owned(),
reply_domain.to_owned(),
))
};
match process() {
Ok((name, regtype, domain)) => {
let service = Service {
name,
regtype,
interface_index,
domain,
event_type: flags.into(),
};
(context.reply_callback)(Ok(service));
}
Err(e) => {
(context.reply_callback)(Err(e));
}
}
}
pub fn socket(&self) -> i32 {
unsafe { ffi::DNSServiceRefSockFD(self.raw) }
}
pub fn process_result(&self) -> ffi::DNSServiceErrorType {
if self.raw.is_null() {
return ffi::kDNSServiceErr_Invalid;
}
unsafe { ffi::DNSServiceProcessResult(self.raw) }
}
pub fn start<F: 'static>(&mut self, callback: F) -> Result<()>
where
F: Fn(Result<Service>) -> (),
{
self.reply_callback = Box::new(callback);
unsafe {
let c_domain: Option<CString>;
if let Some(d) = &self.domain {
c_domain =
Some(CString::new(d.as_str()).map_err(|_| DNSServiceError::InvalidString)?);
} else {
c_domain = None;
}
let service_type =
CString::new(self.regtype.as_str()).map_err(|_| DNSServiceError::InvalidString)?;
let r = ffi::DNSServiceBrowse(
&mut self.raw as *mut _,
0,
0,
service_type.as_ptr(),
c_domain.map_or(ptr::null_mut(), |d| d.as_ptr()),
Some(DNSServiceBrowser::reply_callback),
mut_void_ptr!(self),
);
if r != ffi::kDNSServiceErr_NoError {
error!("DNSServiceBrowser error: {}", r);
return Err(DNSServiceError::ServiceError(r));
}
Ok(())
}
}
}
impl Drop for DNSServiceBrowser {
fn drop(&mut self) {
unsafe {
ffi::DNSServiceRefDeallocate(self.raw);
}
}
}