#![expect(unsafe_code)]
use std::{
ffi::c_void,
sync::{Arc, Mutex, OnceLock, PoisonError},
};
use block2::{Block, RcBlock};
use cranpose_services::{
Connectivity, Metering, NetworkMonitor, NetworkStatus, set_platform_network_monitor,
};
use dispatch2::{DispatchQueue, DispatchRetained};
type NwObject = *mut c_void;
const NW_PATH_STATUS_SATISFIED: i32 = 1;
const NW_PATH_STATUS_UNSATISFIED: i32 = 2;
#[link(name = "Network", kind = "framework")]
unsafe extern "C" {
fn nw_path_monitor_create() -> NwObject;
fn nw_path_monitor_set_queue(monitor: NwObject, queue: *mut c_void);
fn nw_path_monitor_set_update_handler(monitor: NwObject, handler: &Block<dyn Fn(NwObject)>);
fn nw_path_monitor_start(monitor: NwObject);
fn nw_path_get_status(path: NwObject) -> i32;
fn nw_path_is_expensive(path: NwObject) -> bool;
fn nw_path_is_constrained(path: NwObject) -> bool;
}
struct PathMonitor {
status: Arc<Mutex<NetworkStatus>>,
_queue: DispatchRetained<DispatchQueue>,
}
struct AppleNetworkMonitor {
status: Arc<Mutex<NetworkStatus>>,
}
impl NetworkMonitor for AppleNetworkMonitor {
fn status(&self) -> NetworkStatus {
*self.status.lock().unwrap_or_else(PoisonError::into_inner)
}
fn is_alive(&self) -> bool {
true
}
fn reconnect(&self) {}
}
pub(crate) fn register() {
static MONITOR: OnceLock<PathMonitor> = OnceLock::new();
let monitor = MONITOR.get_or_init(start_path_monitor);
set_platform_network_monitor(Arc::new(AppleNetworkMonitor {
status: Arc::clone(&monitor.status),
}));
}
fn start_path_monitor() -> PathMonitor {
let status = Arc::new(Mutex::new(NetworkStatus::default()));
let observed = Arc::clone(&status);
let handler = RcBlock::new(move |path: NwObject| {
let reading = unsafe {
path_status(
nw_path_get_status(path),
nw_path_is_expensive(path),
nw_path_is_constrained(path),
)
};
*observed.lock().unwrap_or_else(PoisonError::into_inner) = reading;
});
let queue = DispatchQueue::new("dev.cranpose.network", None);
let queue_object = DispatchRetained::as_ptr(&queue).as_ptr().cast::<c_void>();
unsafe {
let monitor = nw_path_monitor_create();
if !monitor.is_null() {
nw_path_monitor_set_queue(monitor, queue_object);
nw_path_monitor_set_update_handler(monitor, &handler);
nw_path_monitor_start(monitor);
}
}
PathMonitor {
status,
_queue: queue,
}
}
fn path_status(status: i32, expensive: bool, constrained: bool) -> NetworkStatus {
let connectivity = match status {
NW_PATH_STATUS_SATISFIED => Connectivity::Online,
NW_PATH_STATUS_UNSATISFIED => Connectivity::Offline,
_ => Connectivity::Unknown,
};
let metering = match connectivity {
Connectivity::Online if expensive || constrained => Metering::Metered,
Connectivity::Online => Metering::Unmetered,
Connectivity::Offline | Connectivity::Unknown => Metering::Unknown,
};
NetworkStatus {
connectivity,
metering,
}
}
#[cfg(test)]
#[path = "tests/apple_network_tests.rs"]
mod tests;