networkframework 0.9.1

Safe Rust bindings for Apple's Network.framework — modern, post-CFNetwork TCP / UDP / TLS / Bonjour networking on macOS
Documentation
//! Advertise-descriptor helpers.

#![allow(clippy::missing_errors_doc)]

use core::ffi::{c_char, c_int, c_void};
use std::ffi::{CStr, CString};

use crate::error::{from_status, NetworkError};
use crate::ffi;

pub struct AdvertiseDescriptor {
    handle: *mut c_void,
    bonjour_name: Option<String>,
    bonjour_type: Option<String>,
    bonjour_domain: Option<String>,
    application_service_name: Option<String>,
}

unsafe impl Send for AdvertiseDescriptor {}
unsafe impl Sync for AdvertiseDescriptor {}

pub struct Advertiser {
    handle: *mut c_void,
}

unsafe impl Send for Advertiser {}
unsafe impl Sync for Advertiser {}

fn to_cstring(value: &str, field: &str) -> Result<CString, NetworkError> {
    CString::new(value).map_err(|e| NetworkError::InvalidArgument(format!("{field} NUL byte: {e}")))
}

unsafe fn copied_string(ptr: *mut c_char) -> Option<String> {
    if ptr.is_null() {
        return None;
    }
    let value = unsafe { CStr::from_ptr(ptr) }
        .to_string_lossy()
        .into_owned();
    unsafe { ffi::nw_shim_free_buffer(ptr.cast()) };
    Some(value)
}

impl AdvertiseDescriptor {
    pub fn bonjour_service(
        name: Option<&str>,
        service_type: &str,
        domain: Option<&str>,
    ) -> Result<Self, NetworkError> {
        let service_type = to_cstring(service_type, "service_type")?;
        let name = match name {
            Some(value) => Some(to_cstring(value, "name")?),
            None => None,
        };
        let domain = match domain {
            Some(value) => Some(to_cstring(value, "domain")?),
            None => None,
        };
        let handle = unsafe {
            ffi::nw_shim_advertise_descriptor_create_bonjour_service(
                name.as_ref()
                    .map_or(core::ptr::null(), |value| value.as_ptr()),
                service_type.as_ptr(),
                domain
                    .as_ref()
                    .map_or(core::ptr::null(), |value| value.as_ptr()),
            )
        };
        if handle.is_null() {
            return Err(NetworkError::InvalidArgument(
                "failed to create advertise descriptor".into(),
            ));
        }
        Ok(Self {
            handle,
            bonjour_name: name.map(|value| value.to_string_lossy().into_owned()),
            bonjour_type: Some(service_type.to_string_lossy().into_owned()),
            bonjour_domain: domain.map(|value| value.to_string_lossy().into_owned()),
            application_service_name: None,
        })
    }

    pub fn application_service(application_service_name: &str) -> Result<Self, NetworkError> {
        let application_service_name =
            to_cstring(application_service_name, "application_service_name")?;
        let handle = unsafe {
            ffi::nw_shim_advertise_descriptor_create_application_service(
                application_service_name.as_ptr(),
            )
        };
        if handle.is_null() {
            return Err(NetworkError::InvalidArgument(
                "failed to create application-service advertise descriptor".into(),
            ));
        }
        Ok(Self {
            handle,
            bonjour_name: None,
            bonjour_type: None,
            bonjour_domain: None,
            application_service_name: Some(application_service_name.to_string_lossy().into_owned()),
        })
    }

    #[must_use]
    pub fn service_name(&self) -> Option<&str> {
        self.bonjour_name.as_deref()
    }

    #[must_use]
    pub fn service_type(&self) -> Option<&str> {
        self.bonjour_type.as_deref()
    }

    #[must_use]
    pub fn domain(&self) -> Option<&str> {
        self.bonjour_domain.as_deref()
    }

    #[must_use]
    pub fn application_service_name(&self) -> Option<String> {
        if self.application_service_name.is_some() {
            return self.application_service_name.clone();
        }
        unsafe {
            copied_string(
                ffi::nw_shim_advertise_descriptor_copy_application_service_name(self.handle),
            )
        }
    }

    pub fn set_txt_record(&mut self, txt_record: &[u8]) -> &mut Self {
        unsafe {
            ffi::nw_shim_advertise_descriptor_set_txt_record(
                self.handle,
                txt_record.as_ptr(),
                txt_record.len(),
            );
        };
        self
    }

    pub fn set_no_auto_rename(&mut self, no_auto_rename: bool) -> &mut Self {
        unsafe {
            ffi::nw_shim_advertise_descriptor_set_no_auto_rename(
                self.handle,
                c_int::from(no_auto_rename),
            );
        };
        self
    }

    #[must_use]
    pub fn no_auto_rename(&self) -> bool {
        unsafe { ffi::nw_shim_advertise_descriptor_get_no_auto_rename(self.handle) != 0 }
    }

    #[must_use]
    pub(crate) const fn as_ptr(&self) -> *mut c_void {
        self.handle
    }
}

pub fn advertise_with_descriptor(
    descriptor: &AdvertiseDescriptor,
    port: u16,
) -> Result<Advertiser, NetworkError> {
    let mut status = 0;
    let handle = if let (Some(service_type), Some(service_name)) = (
        descriptor.bonjour_type.as_deref(),
        descriptor.bonjour_name.as_deref(),
    ) {
        let service_type = to_cstring(service_type, "service_type")?;
        let service_name = to_cstring(service_name, "service_name")?;
        let domain = descriptor
            .bonjour_domain
            .as_deref()
            .map(|value| to_cstring(value, "domain"))
            .transpose()?;
        unsafe {
            ffi::nw_shim_bonjour_advertise_start(
                service_type.as_ptr(),
                service_name.as_ptr(),
                domain
                    .as_ref()
                    .map_or(core::ptr::null(), |value| value.as_ptr()),
                port,
                &mut status,
            )
        }
    } else {
        unsafe {
            ffi::nw_shim_bonjour_advertise_start_with_descriptor(
                descriptor.as_ptr(),
                port,
                &mut status,
            )
        }
    };
    if status != ffi::NW_OK || handle.is_null() {
        return Err(from_status(status));
    }
    Ok(Advertiser { handle })
}

impl Drop for Advertiser {
    fn drop(&mut self) {
        if !self.handle.is_null() {
            unsafe { ffi::nw_shim_bonjour_advertise_stop(self.handle) };
            self.handle = core::ptr::null_mut();
        }
    }
}

impl Clone for AdvertiseDescriptor {
    fn clone(&self) -> Self {
        let handle = unsafe { ffi::nw_shim_retain_object(self.handle) };
        Self {
            handle,
            bonjour_name: self.bonjour_name.clone(),
            bonjour_type: self.bonjour_type.clone(),
            bonjour_domain: self.bonjour_domain.clone(),
            application_service_name: self.application_service_name.clone(),
        }
    }
}

impl Drop for AdvertiseDescriptor {
    fn drop(&mut self) {
        if !self.handle.is_null() {
            unsafe { ffi::nw_shim_release_object(self.handle) };
            self.handle = core::ptr::null_mut();
        }
    }
}