use core::ffi::c_void;
use core::ptr;
use std::sync::Arc;
use std::sync::Mutex;
use doom_fish_utils::panic_safe::catch_user_panic;
use crate::ffi;
use crate::interface::{list_interfaces_for_monitor, NetworkInterface};
pub use crate::interface::InterfaceType;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PathUpdate {
pub satisfied: bool,
pub interface: InterfaceType,
}
type PathCb = Mutex<Box<dyn FnMut(PathUpdate) + Send + 'static>>;
type CancelCb = Mutex<Box<dyn FnMut() + Send + 'static>>;
#[allow(clippy::type_complexity)]
pub struct PathMonitor {
handle: *mut c_void,
callback_raw: *const PathCb,
cancel_raw: *const CancelCb,
}
unsafe impl Send for PathMonitor {}
unsafe impl Sync for PathMonitor {}
fn reclaim_arc_raw<T>(raw: &mut *const T) {
if !raw.is_null() {
unsafe {
drop(Arc::from_raw(*raw));
}
*raw = ptr::null();
}
}
impl PathMonitor {
#[must_use]
pub fn list_interfaces(&self) -> Vec<NetworkInterface> {
list_interfaces_for_monitor(self.handle)
}
#[must_use]
pub fn current_path(&self) -> Option<crate::path::Path> {
let handle = unsafe { ffi::nw_shim_path_monitor_copy_latest_path(self.handle) };
if handle.is_null() {
None
} else {
Some(unsafe { crate::path::Path::from_raw(handle) })
}
}
#[must_use]
pub(crate) const fn as_ptr(&self) -> *mut c_void {
self.handle
}
pub fn prohibit_interface_type(&mut self, interface_type: InterfaceType) -> &mut Self {
unsafe {
ffi::nw_shim_path_monitor_prohibit_interface_type(self.handle, interface_type.as_raw());
}
self
}
pub fn set_cancel_handler<F>(&mut self, callback: F)
where
F: FnMut() + Send + 'static,
{
if !self.cancel_raw.is_null() {
if !self.handle.is_null() {
unsafe {
ffi::nw_shim_path_monitor_set_cancel_handler(
self.handle,
None,
ptr::null_mut(),
);
ffi::nw_shim_path_monitor_drain_queue(self.handle);
}
}
reclaim_arc_raw(&mut self.cancel_raw);
}
let callback: Box<dyn FnMut() + Send + 'static> = Box::new(callback);
let cancel_raw = Arc::into_raw(Arc::new(Mutex::new(callback)));
if self.handle.is_null() {
self.cancel_raw = cancel_raw;
return;
}
unsafe {
ffi::nw_shim_path_monitor_set_cancel_handler(
self.handle,
Some(cancel_trampoline),
cancel_raw.cast::<c_void>().cast_mut(),
);
}
self.cancel_raw = cancel_raw;
}
}
impl Drop for PathMonitor {
fn drop(&mut self) {
if !self.handle.is_null() {
unsafe {
ffi::nw_shim_path_monitor_stop(self.handle);
}
self.handle = ptr::null_mut();
}
reclaim_arc_raw(&mut self.callback_raw);
reclaim_arc_raw(&mut self.cancel_raw);
}
}
unsafe extern "C" fn trampoline(satisfied: i32, interface_type: i32, user_info: *mut c_void) {
if user_info.is_null() {
return;
}
let callback = unsafe { &*user_info.cast::<PathCb>() };
let Ok(mut guard) = callback.lock() else {
return;
};
catch_user_panic("path_monitor_trampoline", || {
guard(PathUpdate {
satisfied: satisfied != 0,
interface: InterfaceType::from_raw(interface_type),
});
});
}
unsafe extern "C" fn cancel_trampoline(user_info: *mut c_void) {
if user_info.is_null() {
return;
}
let callback = unsafe { &*user_info.cast::<CancelCb>() };
let Ok(mut guard) = callback.lock() else {
return;
};
catch_user_panic("path_monitor_cancel_trampoline", &mut *guard);
}
#[must_use]
pub fn start_path_monitor<F>(callback: F) -> PathMonitor
where
F: FnMut(PathUpdate) + Send + 'static,
{
let boxed: Box<dyn FnMut(PathUpdate) + Send + 'static> = Box::new(callback);
let callback_raw = Arc::into_raw(Arc::new(Mutex::new(boxed)));
let handle = unsafe {
ffi::nw_shim_path_monitor_start(trampoline, callback_raw.cast::<c_void>().cast_mut())
};
PathMonitor {
handle,
callback_raw,
cancel_raw: ptr::null(),
}
}
#[must_use]
pub fn start_path_monitor_with_type<F>(interface_type: InterfaceType, callback: F) -> PathMonitor
where
F: FnMut(PathUpdate) + Send + 'static,
{
let boxed: Box<dyn FnMut(PathUpdate) + Send + 'static> = Box::new(callback);
let callback_raw = Arc::into_raw(Arc::new(Mutex::new(boxed)));
let handle = unsafe {
ffi::nw_shim_path_monitor_start_with_type(
interface_type.as_raw(),
trampoline,
callback_raw.cast::<c_void>().cast_mut(),
)
};
PathMonitor {
handle,
callback_raw,
cancel_raw: ptr::null(),
}
}
#[must_use]
pub fn start_path_monitor_for_ethernet_channel<F>(callback: F) -> PathMonitor
where
F: FnMut(PathUpdate) + Send + 'static,
{
let boxed: Box<dyn FnMut(PathUpdate) + Send + 'static> = Box::new(callback);
let callback_raw = Arc::into_raw(Arc::new(Mutex::new(boxed)));
let handle = unsafe {
ffi::nw_shim_path_monitor_start_for_ethernet_channel(
trampoline,
callback_raw.cast::<c_void>().cast_mut(),
)
};
PathMonitor {
handle,
callback_raw,
cancel_raw: ptr::null(),
}
}