use std::ffi::c_void;
use std::sync::Arc;
use std::sync::mpsc::SyncSender;
use core_foundation::base::TCFType;
use core_foundation::dictionary::{CFDictionary, CFDictionaryRef};
use core_foundation::string::CFStringRef;
use super::cf_dict::to_proxy_dict;
use super::poll::{self, WakeSlot};
use super::proxy_dict;
use crate::config::{ProxyConfig, ProxyConfigSource};
use crate::error::Error;
use crate::mode::ProxyMode;
use crate::watch::{BackendHealth, Shared, WatchOptions};
#[link(name = "CFNetwork", kind = "framework")]
unsafe extern "C" {
fn CFNetworkCopySystemProxySettings() -> CFDictionaryRef;
}
pub(crate) fn read_config(_options: &WatchOptions) -> Result<ProxyConfig, Error> {
let settings = unsafe { CFNetworkCopySystemProxySettings() };
let mode = if settings.is_null() {
crate::trace::warning!(
"CFNetworkCopySystemProxySettings returned NULL; treating the settings as empty"
);
ProxyMode::Direct
} else {
let settings: CFDictionary = unsafe { CFDictionary::wrap_under_create_rule(settings) };
proxy_dict::mode_from_dict(&to_proxy_dict(&settings))?
};
Ok(ProxyConfig::from_source(
ProxyConfigSource::CfNetworkSystemSettings,
mode,
))
}
#[derive(Debug)]
pub(crate) struct Watch {
_hints: Hints,
poll: poll::Watch,
}
impl Watch {
pub(crate) fn armed(options: &WatchOptions) -> Result<Self, Error> {
let poll = poll::Watch::armed(options)?;
Ok(Self {
_hints: Hints::new(poll.waker()),
poll,
})
}
pub(crate) fn spawn(
&mut self,
options: &WatchOptions,
shared: Arc<Shared>,
) -> Result<(), Error> {
self.poll.spawn(options, shared)
}
pub(crate) fn health(&self) -> BackendHealth {
self.poll.health()
}
pub(crate) fn poll_now(&self) {
self.poll.poll_now();
}
}
type CFNotificationCenterRef = *mut c_void;
type CFNotificationCallback = extern "C" fn(
center: CFNotificationCenterRef,
observer: *mut c_void,
name: CFStringRef,
object: *const c_void,
user_info: CFDictionaryRef,
);
const DELIVER_IMMEDIATELY: isize = 4;
unsafe extern "C" {
fn CFNotificationCenterGetLocalCenter() -> CFNotificationCenterRef;
fn CFNotificationCenterAddObserver(
center: CFNotificationCenterRef,
observer: *const c_void,
callback: CFNotificationCallback,
name: CFStringRef,
object: *const c_void,
suspension_behavior: isize,
);
fn CFNotificationCenterRemoveObserver(
center: CFNotificationCenterRef,
observer: *const c_void,
name: CFStringRef,
object: *const c_void,
);
}
#[link(name = "UIKit", kind = "framework")]
unsafe extern "C" {
static UIApplicationDidBecomeActiveNotification: CFStringRef;
}
#[link(name = "Network", kind = "framework")]
unsafe extern "C" {
fn nw_path_monitor_create() -> *mut c_void;
fn nw_path_monitor_set_queue(monitor: *mut c_void, queue: *mut c_void);
fn nw_path_monitor_set_update_handler(monitor: *mut c_void, handler: *const PathBlock);
fn nw_path_monitor_start(monitor: *mut c_void);
fn nw_path_monitor_cancel(monitor: *mut c_void);
fn nw_release(object: *mut c_void);
}
unsafe extern "C" {
static _NSConcreteStackBlock: u8;
fn dispatch_get_global_queue(identifier: isize, flags: usize) -> *mut c_void;
}
const QOS_CLASS_UTILITY: isize = 0x11;
#[repr(C)]
struct PathBlock {
isa: *const c_void,
flags: i32,
reserved: i32,
invoke: unsafe extern "C" fn(block: *const PathBlock, path: *mut c_void),
descriptor: &'static BlockDescriptor,
id: i64,
}
#[repr(C)]
struct BlockDescriptor {
reserved: usize,
size: usize,
}
static PATH_BLOCK_DESCRIPTOR: BlockDescriptor = BlockDescriptor {
reserved: 0,
size: size_of::<PathBlock>(),
};
#[derive(Debug)]
struct Hints {
slot: WakeSlot,
monitor: *mut c_void,
}
unsafe impl Send for Hints {}
unsafe impl Sync for Hints {}
impl Hints {
fn new(wake: SyncSender<()>) -> Self {
let slot = WakeSlot::new(wake);
unsafe {
CFNotificationCenterAddObserver(
CFNotificationCenterGetLocalCenter(),
observer(slot.id()),
became_active,
UIApplicationDidBecomeActiveNotification,
std::ptr::null(),
DELIVER_IMMEDIATELY,
);
}
let monitor = unsafe { nw_path_monitor_create() };
if !monitor.is_null() {
let block = PathBlock {
isa: (&raw const _NSConcreteStackBlock).cast(),
flags: 0,
reserved: 0,
invoke: path_updated,
descriptor: &PATH_BLOCK_DESCRIPTOR,
id: slot.id(),
};
unsafe {
nw_path_monitor_set_queue(monitor, dispatch_get_global_queue(QOS_CLASS_UTILITY, 0));
nw_path_monitor_set_update_handler(monitor, &block);
nw_path_monitor_start(monitor);
}
}
Self { slot, monitor }
}
}
impl Drop for Hints {
fn drop(&mut self) {
unsafe {
CFNotificationCenterRemoveObserver(
CFNotificationCenterGetLocalCenter(),
observer(self.slot.id()),
UIApplicationDidBecomeActiveNotification,
std::ptr::null(),
);
}
if !self.monitor.is_null() {
unsafe {
nw_path_monitor_cancel(self.monitor);
nw_release(self.monitor);
}
}
}
}
fn observer(id: i64) -> *const c_void {
id as usize as *const c_void
}
extern "C" fn became_active(
_center: CFNotificationCenterRef,
observer: *mut c_void,
_name: CFStringRef,
_object: *const c_void,
_user_info: CFDictionaryRef,
) {
poll::wake(observer as usize as i64);
}
unsafe extern "C" fn path_updated(block: *const PathBlock, _path: *mut c_void) {
poll::wake(unsafe { (*block).id });
}