systemconfiguration-rs 0.2.1

Safe Rust bindings for Apple's SystemConfiguration framework via a Swift bridge on macOS
Documentation
use std::{ffi::c_void, sync::{Arc, Mutex}};

use crate::{
    bridge,
    error::Result,
    ffi,
    PropertyList, ReachabilityFlags,
};

pub type NetworkConnectionFlags = ReachabilityFlags;

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum NetworkConnectionStatus {
    Invalid,
    Disconnected,
    Connecting,
    Connected,
    Disconnecting,
    Unknown(i32),
}

impl NetworkConnectionStatus {
    pub const fn from_raw(raw: i32) -> Self {
        match raw {
            -1 => Self::Invalid,
            0 => Self::Disconnected,
            1 => Self::Connecting,
            2 => Self::Connected,
            3 => Self::Disconnecting,
            other => Self::Unknown(other),
        }
    }

    pub const fn raw_value(self) -> i32 {
        match self {
            Self::Invalid => -1,
            Self::Disconnected => 0,
            Self::Connecting => 1,
            Self::Connected => 2,
            Self::Disconnecting => 3,
            Self::Unknown(raw) => raw,
        }
    }
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum NetworkConnectionPppStatus {
    Disconnected,
    Initializing,
    ConnectingLink,
    DialOnTraffic,
    NegotiatingLink,
    Authenticating,
    WaitingForCallback,
    NegotiatingNetwork,
    Connected,
    Terminating,
    DisconnectingLink,
    HoldingLinkOff,
    Suspended,
    WaitingForRedial,
    Unknown(i32),
}

impl NetworkConnectionPppStatus {
    pub const fn from_raw(raw: i32) -> Self {
        match raw {
            0 => Self::Disconnected,
            1 => Self::Initializing,
            2 => Self::ConnectingLink,
            3 => Self::DialOnTraffic,
            4 => Self::NegotiatingLink,
            5 => Self::Authenticating,
            6 => Self::WaitingForCallback,
            7 => Self::NegotiatingNetwork,
            8 => Self::Connected,
            9 => Self::Terminating,
            10 => Self::DisconnectingLink,
            11 => Self::HoldingLinkOff,
            12 => Self::Suspended,
            13 => Self::WaitingForRedial,
            other => Self::Unknown(other),
        }
    }

    pub const fn raw_value(self) -> i32 {
        match self {
            Self::Disconnected => 0,
            Self::Initializing => 1,
            Self::ConnectingLink => 2,
            Self::DialOnTraffic => 3,
            Self::NegotiatingLink => 4,
            Self::Authenticating => 5,
            Self::WaitingForCallback => 6,
            Self::NegotiatingNetwork => 7,
            Self::Connected => 8,
            Self::Terminating => 9,
            Self::DisconnectingLink => 10,
            Self::HoldingLinkOff => 11,
            Self::Suspended => 12,
            Self::WaitingForRedial => 13,
            Self::Unknown(raw) => raw,
        }
    }
}

#[derive(Clone, Debug)]
pub struct NetworkConnectionUserPreferences {
    pub service_id: String,
    pub user_options: Option<PropertyList>,
}

struct CallbackState {
    callback: Box<dyn FnMut(NetworkConnectionStatus) + Send>,
}

unsafe extern "C" fn network_connection_callback(status: i32, info: *mut c_void) {
    if info.is_null() {
        return;
    }

    let mutex = &*info.cast::<Mutex<CallbackState>>();
    if let Ok(mut state) = mutex.lock() {
        (state.callback)(NetworkConnectionStatus::from_raw(status));
    }
}

#[derive(Clone)]
pub struct NetworkConnection {
    raw: bridge::OwnedHandle,
    _callback: Option<Arc<Mutex<CallbackState>>>,
}

impl std::fmt::Debug for NetworkConnection {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("NetworkConnection").finish_non_exhaustive()
    }
}

impl NetworkConnection {
    pub fn type_id() -> u64 {
        unsafe { ffi::network_connection::sc_network_connection_get_type_id() }
    }

    pub fn with_service_id(service_id: &str) -> Result<Self> {
        Self::create(service_id, None)
    }

    pub fn with_service_id_and_callback<F>(service_id: &str, callback: F) -> Result<Self>
    where
        F: FnMut(NetworkConnectionStatus) + Send + 'static,
    {
        let state = Arc::new(Mutex::new(CallbackState {
            callback: Box::new(callback),
        }));
        Self::create(service_id, Some(state))
    }

    fn create(service_id: &str, callback: Option<Arc<Mutex<CallbackState>>>) -> Result<Self> {
        let service_id = bridge::cstring(service_id, "sc_network_connection_create_with_service_id")?;
        let raw = unsafe {
            ffi::network_connection::sc_network_connection_create_with_service_id(
                service_id.as_ptr(),
                callback.as_ref().map(|_| network_connection_callback as unsafe extern "C" fn(i32, *mut c_void)),
                callback
                    .as_ref()
                    .map_or(std::ptr::null_mut(), |state| Arc::as_ptr(state).cast_mut().cast::<c_void>()),
            )
        };
        let raw = bridge::owned_handle_or_last("sc_network_connection_create_with_service_id", raw)?;
        Ok(Self {
            raw,
            _callback: callback,
        })
    }

    pub fn copy_user_preferences() -> Result<NetworkConnectionUserPreferences> {
        let service_id = bridge::take_optional_string(unsafe {
            ffi::network_connection::sc_network_connection_copy_user_preferences_service_id()
        })
        .ok_or_else(|| {
            crate::SystemConfigurationError::last("sc_network_connection_copy_user_preferences_service_id")
        })?;
        let user_options = unsafe {
            bridge::OwnedHandle::from_raw(
                ffi::network_connection::sc_network_connection_copy_user_preferences_user_options(),
            )
        }
        .map(PropertyList::from_owned_handle);
        Ok(NetworkConnectionUserPreferences {
            service_id,
            user_options,
        })
    }

    pub fn service_id(&self) -> Result<Option<String>> {
        Ok(bridge::take_optional_string(unsafe {
            ffi::network_connection::sc_network_connection_copy_service_id(self.raw.as_ptr())
        }))
    }

    pub fn status(&self) -> NetworkConnectionStatus {
        NetworkConnectionStatus::from_raw(unsafe {
            ffi::network_connection::sc_network_connection_get_status(self.raw.as_ptr())
        })
    }

    pub fn extended_status(&self) -> Option<PropertyList> {
        unsafe {
            bridge::OwnedHandle::from_raw(ffi::network_connection::sc_network_connection_copy_extended_status(
                self.raw.as_ptr(),
            ))
        }
        .map(PropertyList::from_owned_handle)
    }

    pub fn statistics(&self) -> Option<PropertyList> {
        unsafe {
            bridge::OwnedHandle::from_raw(ffi::network_connection::sc_network_connection_copy_statistics(
                self.raw.as_ptr(),
            ))
        }
        .map(PropertyList::from_owned_handle)
    }

    pub fn user_options(&self) -> Option<PropertyList> {
        unsafe {
            bridge::OwnedHandle::from_raw(ffi::network_connection::sc_network_connection_copy_user_options(
                self.raw.as_ptr(),
            ))
        }
        .map(PropertyList::from_owned_handle)
    }

    pub fn start(&self, user_options: Option<&PropertyList>, linger: bool) -> Result<()> {
        let ok = unsafe {
            ffi::network_connection::sc_network_connection_start(
                self.raw.as_ptr(),
                user_options.map_or(std::ptr::null_mut(), PropertyList::as_ptr),
                u8::from(linger),
            )
        };
        bridge::bool_result("sc_network_connection_start", ok)
    }

    pub fn stop(&self, force_disconnect: bool) -> Result<()> {
        let ok = unsafe {
            ffi::network_connection::sc_network_connection_stop(self.raw.as_ptr(), u8::from(force_disconnect))
        };
        bridge::bool_result("sc_network_connection_stop", ok)
    }

    pub fn schedule_with_run_loop_current(&self) -> Result<()> {
        let ok = unsafe {
            ffi::network_connection::sc_network_connection_schedule_with_run_loop_current(self.raw.as_ptr())
        };
        bridge::bool_result("sc_network_connection_schedule_with_run_loop_current", ok)
    }

    pub fn unschedule_from_run_loop_current(&self) -> Result<()> {
        let ok = unsafe {
            ffi::network_connection::sc_network_connection_unschedule_from_run_loop_current(self.raw.as_ptr())
        };
        bridge::bool_result("sc_network_connection_unschedule_from_run_loop_current", ok)
    }

    pub fn set_dispatch_queue_global(&self) -> Result<()> {
        let ok = unsafe {
            ffi::network_connection::sc_network_connection_set_dispatch_queue_global(self.raw.as_ptr())
        };
        bridge::bool_result("sc_network_connection_set_dispatch_queue_global", ok)
    }

    pub fn clear_dispatch_queue(&self) -> Result<()> {
        let ok = unsafe {
            ffi::network_connection::sc_network_connection_clear_dispatch_queue(self.raw.as_ptr())
        };
        bridge::bool_result("sc_network_connection_clear_dispatch_queue", ok)
    }
}