use objc2::ffi::{objc_allocateClassPair, objc_registerClassPair};
use objc2::runtime::{AnyClass, AnyObject, Bool, Sel};
use std::collections::HashMap;
use std::ffi::{CString, c_char, c_void};
use std::sync::{Mutex, OnceLock};
use thiserror::Error;
use tokio::sync::mpsc;
#[derive(Debug, Error)]
pub enum DelegateError {
#[error("failed to create delegate class")]
ClassCreationFailed,
#[error("failed to add ivar to delegate class")]
IvarAddFailed,
#[error("failed to add method to delegate class")]
MethodAddFailed,
#[error("failed to create delegate instance")]
InstanceCreationFailed,
#[error("delegate class not initialized")]
ClassNotInitialized,
}
unsafe extern "C" {
fn class_addMethod(
cls: *const AnyClass,
sel: Sel,
imp: *const c_void,
types: *const c_char,
) -> Bool;
fn class_addIvar(
cls: *const AnyClass,
name: *const c_char,
size: usize,
alignment: u8,
types: *const c_char,
) -> Bool;
fn object_getInstanceVariable(
obj: *mut AnyObject,
name: *const c_char,
outValue: *mut *mut c_void,
) -> *mut c_void;
fn object_setInstanceVariable(
obj: *mut AnyObject,
name: *const c_char,
value: *mut c_void,
) -> *mut c_void;
fn sel_registerName(name: *const c_char) -> Sel;
}
pub type ListenerHandle = u64;
pub struct IncomingConnection {
pub fd: i32,
pub source_port: u32,
pub destination_port: u32,
}
struct ListenerEntry {
sender: mpsc::UnboundedSender<IncomingConnection>,
}
static LISTENER_REGISTRY: OnceLock<Mutex<HashMap<ListenerHandle, ListenerEntry>>> = OnceLock::new();
static NEXT_HANDLE: OnceLock<Mutex<ListenerHandle>> = OnceLock::new();
fn get_registry() -> &'static Mutex<HashMap<ListenerHandle, ListenerEntry>> {
LISTENER_REGISTRY.get_or_init(|| Mutex::new(HashMap::new()))
}
fn get_next_handle() -> ListenerHandle {
let mut handle = NEXT_HANDLE.get_or_init(|| Mutex::new(1)).lock().unwrap();
let h = *handle;
*handle += 1;
h
}
pub fn register_listener(sender: mpsc::UnboundedSender<IncomingConnection>) -> ListenerHandle {
let handle = get_next_handle();
let mut registry = get_registry().lock().unwrap();
registry.insert(handle, ListenerEntry { sender });
handle
}
pub fn unregister_listener(handle: ListenerHandle) {
let mut registry = get_registry().lock().unwrap();
registry.remove(&handle);
}
fn send_connection(handle: ListenerHandle, conn: IncomingConnection) -> bool {
let registry = get_registry().lock().unwrap();
if let Some(entry) = registry.get(&handle) {
entry.sender.send(conn).is_ok()
} else {
tracing::warn!("Listener handle {} not found in registry", handle);
false
}
}
struct ClassResult(Result<*const AnyClass, DelegateError>);
unsafe impl Send for ClassResult {}
unsafe impl Sync for ClassResult {}
static DELEGATE_CLASS: OnceLock<ClassResult> = OnceLock::new();
const HANDLE_IVAR: &[u8] = b"_listenerHandle\0";
pub fn get_delegate_class() -> Result<*const AnyClass, DelegateError> {
let result = DELEGATE_CLASS.get_or_init(|| ClassResult(unsafe { create_delegate_class() }));
match &result.0 {
Ok(ptr) => Ok(*ptr),
Err(e) => Err(match e {
DelegateError::ClassCreationFailed => DelegateError::ClassCreationFailed,
DelegateError::IvarAddFailed => DelegateError::IvarAddFailed,
DelegateError::MethodAddFailed => DelegateError::MethodAddFailed,
_ => DelegateError::ClassNotInitialized,
}),
}
}
unsafe fn create_delegate_class() -> Result<*const AnyClass, DelegateError> {
unsafe {
let nsobj_name = CString::new("NSObject").unwrap();
let superclass = objc2::ffi::objc_getClass(nsobj_name.as_ptr()) as *const AnyClass;
let class_name = CString::new("ArcBoxSocketListenerDelegate").unwrap();
let new_class = objc_allocateClassPair(superclass, class_name.as_ptr(), 0);
if new_class.is_null() {
let existing = objc2::ffi::objc_getClass(class_name.as_ptr()) as *const AnyClass;
if !existing.is_null() {
tracing::debug!("Delegate class already exists, reusing");
return Ok(existing);
}
tracing::error!("Failed to create delegate class");
return Err(DelegateError::ClassCreationFailed);
}
let ivar_type = CString::new("Q").unwrap(); let added = class_addIvar(
new_class,
HANDLE_IVAR.as_ptr() as *const c_char,
std::mem::size_of::<ListenerHandle>(),
std::mem::align_of::<ListenerHandle>() as u8,
ivar_type.as_ptr(),
);
if !added.as_bool() {
tracing::error!("Failed to add handle ivar to delegate class");
return Err(DelegateError::IvarAddFailed);
}
let sel_name =
CString::new("listener:shouldAcceptNewConnection:fromSocketDevice:").unwrap();
let sel = sel_registerName(sel_name.as_ptr());
let method_types = CString::new("B@:@@@").unwrap();
let added = class_addMethod(
new_class,
sel,
should_accept_connection as *const c_void,
method_types.as_ptr(),
);
if !added.as_bool() {
tracing::error!("Failed to add delegate method");
return Err(DelegateError::MethodAddFailed);
}
objc_registerClassPair(new_class);
tracing::debug!("Created delegate class: ArcBoxSocketListenerDelegate");
Ok(new_class as *const AnyClass)
}
}
unsafe extern "C" fn should_accept_connection(
this: *mut AnyObject,
_cmd: Sel,
_listener: *mut AnyObject,
connection: *mut AnyObject,
_device: *mut AnyObject,
) -> Bool {
unsafe {
tracing::debug!("should_accept_connection called");
let mut handle_ptr: *mut c_void = std::ptr::null_mut();
object_getInstanceVariable(this, HANDLE_IVAR.as_ptr() as *const c_char, &mut handle_ptr);
let handle = handle_ptr as ListenerHandle;
if handle == 0 {
tracing::error!("Delegate has no handle set");
return Bool::NO;
}
if connection.is_null() {
tracing::error!("Connection is null");
return Bool::NO;
}
let original_fd: i32 = crate::msg_send_i32!(connection, fileDescriptor);
let fd = libc::dup(original_fd);
if fd < 0 {
tracing::error!("Failed to dup fd: {}", std::io::Error::last_os_error());
return Bool::NO;
}
let src_port: u32 = {
let sel = objc2::sel!(sourcePort);
let func: unsafe extern "C" fn(*const AnyObject, Sel) -> u32 =
std::mem::transmute(crate::ffi::runtime::objc_msgSend as *const c_void);
func(connection as *const AnyObject, sel)
};
let dst_port: u32 = {
let sel = objc2::sel!(destinationPort);
let func: unsafe extern "C" fn(*const AnyObject, Sel) -> u32 =
std::mem::transmute(crate::ffi::runtime::objc_msgSend as *const c_void);
func(connection as *const AnyObject, sel)
};
tracing::debug!(
"Incoming connection: fd={} (dup from {}), src_port={}, dst_port={}",
fd,
original_fd,
src_port,
dst_port
);
let conn = IncomingConnection {
fd,
source_port: src_port,
destination_port: dst_port,
};
if send_connection(handle, conn) {
Bool::YES
} else {
libc::close(fd);
Bool::NO
}
}
}
pub fn create_delegate_instance(handle: ListenerHandle) -> Result<*mut AnyObject, DelegateError> {
unsafe {
let cls = get_delegate_class()?;
let alloc_sel = objc2::sel!(alloc);
let init_sel = objc2::sel!(init);
let alloc_fn: unsafe extern "C" fn(*const AnyClass, Sel) -> *mut AnyObject =
std::mem::transmute(crate::ffi::runtime::objc_msgSend as *const c_void);
let instance = alloc_fn(cls, alloc_sel);
let init_fn: unsafe extern "C" fn(*mut AnyObject, Sel) -> *mut AnyObject =
std::mem::transmute(crate::ffi::runtime::objc_msgSend as *const c_void);
let instance = init_fn(instance, init_sel);
if instance.is_null() {
tracing::error!("Failed to create delegate instance");
return Err(DelegateError::InstanceCreationFailed);
}
object_setInstanceVariable(
instance,
HANDLE_IVAR.as_ptr() as *const c_char,
handle as *mut c_void,
);
tracing::debug!(
"Created delegate instance {:?} with handle {}",
instance,
handle
);
Ok(instance)
}
}