astro_dnssd/os/apple/
register.rs

1//! Registration of dns-sd services
2
3// use super::txt::TXTRecord;
4use crate::{
5    ffi::apple::{
6        kDNSServiceErr_NoError, kDNSServiceErr_ServiceNotRunning, DNSServiceErrorType,
7        DNSServiceFlags, DNSServiceProcessResult, DNSServiceRef, DNSServiceRefDeallocate,
8        DNSServiceRefSockFD, DNSServiceRegister,
9    },
10    os::apple::txt::TXTRecord,
11    register::Result,
12    DNSServiceBuilder,
13};
14use std::{
15    ffi::{c_void, CStr, CString},
16    fmt,
17    os::raw::c_char,
18    ptr::{null, null_mut},
19    sync::{
20        atomic::{AtomicBool, Ordering},
21        mpsc::{sync_channel, SyncSender},
22        Arc,
23    },
24    time::Duration,
25};
26use thiserror::Error;
27
28const CALLBACK_TIMEOUT: Duration = Duration::from_secs(10);
29
30/// Common error for DNS-SD service
31#[derive(Debug, Error, Copy, Clone, PartialEq, Eq)]
32pub enum RegistrationError {
33    /// Invalid input string
34    #[error("Invalid string argument, must be C string compatible")]
35    InvalidString,
36    /// Unexpected invalid strings from C API
37    #[error("DNSSD API returned invalid UTF-8 string")]
38    InternalInvalidString,
39    /// Error from DNSSD service
40    #[error("DNSSD Error: {0}")]
41    ServiceError(i32),
42}
43
44unsafe extern "C" fn register_reply(
45    _sd_ref: DNSServiceRef,
46    _flags: DNSServiceFlags,
47    error_code: DNSServiceErrorType,
48    name: *const c_char,
49    regtype: *const c_char,
50    domain: *const c_char,
51    context: *mut c_void,
52) {
53    info!("Got reply");
54    // let context: &mut RegisteredDnsService = &mut *(context as *mut RegisteredDnsService);
55    let process = || -> Result<(String, String, String)> {
56        let c_str: &CStr = CStr::from_ptr(name);
57        let service_name: &str = c_str
58            .to_str()
59            .map_err(|_| RegistrationError::InternalInvalidString)?;
60        let c_str: &CStr = CStr::from_ptr(regtype);
61        let regtype: &str = c_str
62            .to_str()
63            .map_err(|_| RegistrationError::InternalInvalidString)?;
64        let c_str: &CStr = CStr::from_ptr(domain);
65        let reply_domain: &str = c_str
66            .to_str()
67            .map_err(|_| RegistrationError::InternalInvalidString)?;
68        Ok((
69            service_name.to_owned(),
70            regtype.to_owned(),
71            reply_domain.to_owned(),
72        ))
73    };
74    if !context.is_null() {
75        let tx_ptr: *mut SyncSender<Result<DNSServiceRegisterReply>> = context as _;
76        let tx = &*tx_ptr;
77        trace!("Registration replied");
78        match process() {
79            Ok((name, regtype, domain)) => {
80                if error_code == kDNSServiceErr_NoError {
81                    let reply = DNSServiceRegisterReply {
82                        regtype,
83                        name,
84                        domain,
85                    };
86                    tx.send(Ok(reply)).unwrap();
87                    info!("Reply info sent");
88                } else {
89                    error!("Error in reply: {}", error_code);
90                    tx.send(Err(RegistrationError::ServiceError(error_code)))
91                        .unwrap();
92                }
93            }
94            Err(e) => {
95                error!("Error in reply: {:?}", e);
96                tx.send(Err(e)).unwrap();
97            }
98        }
99    }
100}
101
102/// DNS-SD Service for registration use
103pub struct RegisteredDnsService {
104    shutdown_flag: Arc<AtomicBool>,
105}
106impl fmt::Debug for RegisteredDnsService {
107    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
108        write!(f, "RegisteredDnsService")
109    }
110}
111
112/// Reply information upon successful registration
113#[derive(Debug)]
114#[allow(dead_code)] // TODO: in future look to see if we should use these values?
115pub struct DNSServiceRegisterReply {
116    /// Service type of successfully registered service
117    pub regtype: String,
118    /// Name of service
119    pub name: String,
120    /// Domain used for successful registration
121    pub domain: String,
122}
123
124/// Service ref to encapsulate DNSServiceRef to send to a thread & cleanup on drop
125struct ServiceRef {
126    raw: DNSServiceRef,
127    context: *mut c_void,
128}
129impl ServiceRef {
130    fn new(raw: DNSServiceRef, context: *mut c_void) -> Self {
131        ServiceRef { raw, context }
132    }
133}
134unsafe impl Send for ServiceRef {}
135impl Drop for ServiceRef {
136    fn drop(&mut self) {
137        unsafe {
138            trace!("Dropping service");
139            if !self.raw.is_null() {
140                trace!("Deallocating DNSServiceRef");
141                DNSServiceRefDeallocate(self.raw);
142                self.raw = null_mut();
143                _ = Box::from_raw(self.context);
144            }
145        }
146    }
147}
148
149impl RegisteredDnsService {}
150
151// Signal the background thread via an atomic flag
152impl Drop for RegisteredDnsService {
153    fn drop(&mut self) {
154        trace!("Signaling thread to exit...");
155        self.shutdown_flag.store(true, Ordering::Relaxed);
156    }
157}
158fn run_thread_with_poll(service: ServiceRef, shutdown_flag: Arc<AtomicBool>) {
159    let socket = unsafe { DNSServiceRefSockFD(service.raw) };
160
161    std::thread::spawn(move || {
162        loop {
163            // Check a shutdown flag
164            if shutdown_flag.load(Ordering::Relaxed) {
165                trace!("Shutdown requested, exiting DNS-SD processing thread");
166                break;
167            }
168
169            unsafe {
170                let mut poll_fd = libc::pollfd {
171                    fd: socket,
172                    events: libc::POLLIN,
173                    revents: 0,
174                };
175
176                // 100 ms timeout
177                let result = libc::poll(&mut poll_fd, 1, 100);
178
179                match result {
180                    -1 => {
181                        // Portable errno retrieval
182                        let errno = std::io::Error::last_os_error().raw_os_error().unwrap_or(0);
183                        if errno == libc::EINTR {
184                            continue;
185                        } else if errno == libc::EBADF {
186                            trace!("DNS-SD socket closed, exiting thread");
187                            break;
188                        } else {
189                            error!("poll() error: {}, exiting thread", errno);
190                            break;
191                        }
192                    }
193                    0 => {
194                        // Timeout - continue to check the shutdown flag
195                        continue;
196                    }
197                    _ => {
198                        // Check what events occurred
199                        if poll_fd.revents & libc::POLLIN != 0 {
200                            // Data ready to read
201                            trace!("Processing DNS-SD result...");
202                            let r = DNSServiceProcessResult(service.raw);
203                            if r != kDNSServiceErr_NoError {
204                                if r == kDNSServiceErr_ServiceNotRunning {
205                                    trace!("DNS-SD service stopped, exiting thread");
206                                } else {
207                                    error!("Error processing DNS-SD result: {}, exiting thread", r);
208                                }
209                                break;
210                            }
211                        } else if poll_fd.revents & (libc::POLLHUP | libc::POLLERR | libc::POLLNVAL)
212                            != 0
213                        {
214                            // Socket error or hangup
215                            trace!(
216                                "DNS-SD socket error/hangup (revents: {}), exiting thread",
217                                poll_fd.revents
218                            );
219                            break;
220                        }
221                    }
222                }
223            }
224        }
225
226        trace!("DNS-SD processing thread exited");
227    });
228}
229
230pub fn register_service(service: DNSServiceBuilder) -> Result<RegisteredDnsService> {
231    unsafe {
232        let c_name: Option<CString>;
233        if let Some(n) = &service.name {
234            c_name = Some(CString::new(n.as_str()).map_err(|_| RegistrationError::InvalidString)?);
235        } else {
236            c_name = None;
237        }
238        let c_name = c_name.as_ref();
239        let service_type =
240            CString::new(service.regtype.as_str()).map_err(|_| RegistrationError::InvalidString)?;
241        let txt = service.txt.map(TXTRecord::from);
242        let (txt_record, txt_len) = match &txt {
243            Some(txt) => (txt.raw_bytes_ptr(), txt.raw_bytes_len()),
244            None => (null(), 0),
245        };
246
247        let (tx, rx) = sync_channel::<Result<DNSServiceRegisterReply>>(4);
248        let tx = Box::into_raw(Box::new(tx));
249
250        let mut raw: DNSServiceRef = null_mut();
251        let result = DNSServiceRegister(
252            &mut raw,
253            0,
254            0,
255            c_name.map_or(null_mut(), |c| c.as_ptr()),
256            service_type.as_ptr(),
257            null(),
258            null(),
259            service.port.to_be(),
260            txt_len,
261            txt_record,
262            Some(register_reply),
263            tx as _,
264        );
265        if result == kDNSServiceErr_NoError {
266            // process callback
267            let shutdown_flag = Arc::new(AtomicBool::new(false));
268            let raw_service = ServiceRef::new(raw, tx as _);
269
270            // spin a thread that keeps the registration working
271            run_thread_with_poll(raw_service, shutdown_flag.clone());
272
273            let service = RegisteredDnsService { shutdown_flag };
274
275            match rx.recv_timeout(CALLBACK_TIMEOUT) {
276                Ok(Ok(_reply)) => Ok(service),
277                Ok(Err(e)) => Err(e),
278                Err(e) => {
279                    error!("Error waiting for callback: {:?}", e);
280                    Err(RegistrationError::ServiceError(0))
281                }
282            }
283        } else {
284            Err(RegistrationError::ServiceError(result))
285        }
286    }
287}