From 618500fd0c0a6c2a680121e5188707e9ca8d0b2e Mon Sep 17 00:00:00 2001 From: Jackson Coxson Date: Tue, 12 Aug 2025 11:35:13 -0600 Subject: [PATCH] Use platform-independent socket for FFI Windows is truly awful Remove config.toml --- Cargo.lock | 1 + ffi/Cargo.toml | 3 + ffi/build.rs | 27 ++++++- ffi/src/lib.rs | 22 ++--- ffi/src/provider.rs | 32 ++++---- ffi/src/usbmuxd.rs | 50 +++++++----- ffi/src/util.rs | 190 ++++++++++++++++++++++++++++++++++---------- 7 files changed, 235 insertions(+), 90 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fa93e66..4ba49d9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1154,6 +1154,7 @@ dependencies = [ "simplelog", "tokio", "ureq", + "windows-sys 0.60.2", ] [[package]] diff --git a/ffi/Cargo.toml b/ffi/Cargo.toml index f9fc7a9..f1ca3f9 100644 --- a/ffi/Cargo.toml +++ b/ffi/Cargo.toml @@ -14,6 +14,9 @@ libc = "0.2.171" plist = "1.7.1" plist_ffi = "0.1.3" +[target.'cfg(windows)'.dependencies] +windows-sys = { version = "0.60", features = ["Win32_Networking_WinSock"] } + [features] aws-lc = ["idevice/aws-lc"] ring = ["idevice/ring"] diff --git a/ffi/build.rs b/ffi/build.rs index e92a349..d26e3b8 100644 --- a/ffi/build.rs +++ b/ffi/build.rs @@ -2,16 +2,35 @@ use std::{env, fs::OpenOptions, io::Write}; +const HEADER: &str = r#"// Jackson Coxson +// Bindings to idevice - https://github.com/jkcoxson/idevice + +#ifdef _WIN32 + #ifndef WIN32_LEAN_AND_MEAN + #define WIN32_LEAN_AND_MEAN + #endif + #include + #include + typedef int idevice_socklen_t; + typedef struct sockaddr idevice_sockaddr; +#else + #include + #include + typedef socklen_t idevice_socklen_t; + typedef struct sockaddr idevice_sockaddr; +#endif +"#; + fn main() { let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); cbindgen::Builder::new() .with_crate(crate_dir) - .with_header( - "// Jackson Coxson\n// Bindings to idevice - https://github.com/jkcoxson/idevice", - ) + .with_header(HEADER) .with_language(cbindgen::Language::C) - .with_sys_include("sys/socket.h") + .with_include_guard("IDEVICE_H") + .exclude_item("idevice_socklen_t") + .exclude_item("idevice_sockaddr") .generate() .expect("Unable to generate bindings") .write_to_file("idevice.h"); diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index 65c32ab..33f04af 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -136,22 +136,23 @@ pub unsafe extern "C" fn idevice_new( /// `addr` must be a valid sockaddr /// `label` must be a valid null-terminated C string /// `idevice` must be a valid, non-null pointer to a location where the handle will be stored +#[cfg(unix)] #[unsafe(no_mangle)] pub unsafe extern "C" fn idevice_new_tcp_socket( - addr: *const libc::sockaddr, - addr_len: libc::socklen_t, + addr: *const idevice_sockaddr, + addr_len: idevice_socklen_t, label: *const c_char, idevice: *mut *mut IdeviceHandle, ) -> *mut IdeviceFfiError { - if addr.is_null() { - log::error!("socket addr null pointer"); + if addr.is_null() || label.is_null() || idevice.is_null() { + log::error!("null pointer(s) to idevice_new_tcp_socket"); return ffi_err!(IdeviceError::FfiInvalidArg); } + let addr = addr as *const SockAddr; - // Convert C string to Rust string let label = match unsafe { CStr::from_ptr(label).to_str() } { Ok(s) => s, - Err(_) => return ffi_err!(IdeviceError::FfiInvalidArg), + Err(_) => return ffi_err!(IdeviceError::FfiInvalidString), }; let addr = match util::c_socket_to_rust(addr, addr_len) { @@ -159,9 +160,10 @@ pub unsafe extern "C" fn idevice_new_tcp_socket( Err(e) => return ffi_err!(e), }; - let device: Result = RUNTIME.block_on(async move { - Ok(idevice::Idevice::new( - Box::new(tokio::net::TcpStream::connect(addr).await?), + let device = RUNTIME.block_on(async move { + let stream = tokio::net::TcpStream::connect(addr).await?; + Ok::(idevice::Idevice::new( + Box::new(stream), label, )) }); @@ -170,7 +172,7 @@ pub unsafe extern "C" fn idevice_new_tcp_socket( Ok(dev) => { let boxed = Box::new(IdeviceHandle(dev)); unsafe { *idevice = Box::into_raw(boxed) }; - null_mut() + std::ptr::null_mut() } Err(e) => ffi_err!(e), } diff --git a/ffi/src/provider.rs b/ffi/src/provider.rs index f6cb00a..95fd169 100644 --- a/ffi/src/provider.rs +++ b/ffi/src/provider.rs @@ -1,9 +1,11 @@ // Jackson Coxson use idevice::provider::{IdeviceProvider, TcpProvider, UsbmuxdProvider}; +use std::net::IpAddr; use std::os::raw::c_char; use std::{ffi::CStr, ptr::null_mut}; +use crate::util::{SockAddr, idevice_sockaddr}; use crate::{IdeviceFfiError, ffi_err, usbmuxd::UsbmuxdAddrHandle, util}; pub struct IdeviceProviderHandle(pub Box); @@ -24,33 +26,27 @@ pub struct IdeviceProviderHandle(pub Box); /// `pairing_file` is consumed must never be used again /// `label` must be a valid Cstr /// `provider` must be a valid, non-null pointer to a location where the handle will be stored -#[cfg(feature = "tcp")] #[unsafe(no_mangle)] pub unsafe extern "C" fn idevice_tcp_provider_new( - ip: *const libc::sockaddr, + ip: *const idevice_sockaddr, pairing_file: *mut crate::pairing_file::IdevicePairingFile, label: *const c_char, provider: *mut *mut IdeviceProviderHandle, ) -> *mut IdeviceFfiError { - if ip.is_null() || label.is_null() || provider.is_null() { - return ffi_err!(IdeviceError::FfiInvalidArg); - } - - let addr = match util::c_addr_to_rust(ip) { + let ip = ip as *const SockAddr; + let addr: IpAddr = match util::c_addr_to_rust(ip) { Ok(i) => i, - Err(e) => { - return ffi_err!(e); - } - }; - let label = match unsafe { CStr::from_ptr(label) }.to_str() { - Ok(l) => l.to_string(), - Err(e) => { - log::error!("Invalid label string: {e:?}"); - return ffi_err!(IdeviceError::FfiInvalidString); - } + Err(e) => return ffi_err!(e), }; + let label = match unsafe { CStr::from_ptr(label).to_str() } { + Ok(s) => s.to_string(), + Err(_) => return ffi_err!(IdeviceError::FfiInvalidString), + }; + + // consume the pairing file on success let pairing_file = unsafe { Box::from_raw(pairing_file) }; + let t = TcpProvider { addr, pairing_file: pairing_file.0, @@ -59,7 +55,7 @@ pub unsafe extern "C" fn idevice_tcp_provider_new( let boxed = Box::new(IdeviceProviderHandle(Box::new(t))); unsafe { *provider = Box::into_raw(boxed) }; - null_mut() + std::ptr::null_mut() } /// Frees an IdeviceProvider handle diff --git a/ffi/src/usbmuxd.rs b/ffi/src/usbmuxd.rs index f7e163d..56da89e 100644 --- a/ffi/src/usbmuxd.rs +++ b/ffi/src/usbmuxd.rs @@ -6,7 +6,8 @@ use std::{ }; use crate::{ - IdeviceFfiError, IdeviceHandle, IdevicePairingFile, RUNTIME, ffi_err, util::c_socket_to_rust, + IdeviceFfiError, IdeviceHandle, IdevicePairingFile, RUNTIME, ffi_err, + util::{SockAddr, c_socket_to_rust, idevice_sockaddr, idevice_socklen_t}, }; use idevice::{ IdeviceError, @@ -32,28 +33,33 @@ pub struct UsbmuxdDeviceHandle(pub UsbmuxdDevice); /// # Safety /// `addr` must be a valid sockaddr /// `usbmuxd_connection` must be a valid, non-null pointer to a location where the handle will be stored -#[unsafe(no_mangle)] pub unsafe extern "C" fn idevice_usbmuxd_new_tcp_connection( - addr: *const libc::sockaddr, - addr_len: libc::socklen_t, + addr: *const idevice_sockaddr, + addr_len: idevice_socklen_t, tag: u32, - usbmuxd_connection: *mut *mut UsbmuxdConnectionHandle, + out: *mut *mut UsbmuxdConnectionHandle, ) -> *mut IdeviceFfiError { - let addr = match c_socket_to_rust(addr, addr_len) { + if addr.is_null() || out.is_null() { + return ffi_err!(IdeviceError::FfiInvalidArg); + } + + // Reinterpret as the real platform sockaddr for parsing + let addr = addr as *const SockAddr; + + let addr = match c_socket_to_rust(addr, addr_len as _) { Ok(a) => a, Err(e) => return ffi_err!(e), }; - let res: Result = RUNTIME.block_on(async move { + let res = RUNTIME.block_on(async move { let stream = tokio::net::TcpStream::connect(addr).await?; - Ok(UsbmuxdConnection::new(Box::new(stream), tag)) + Ok::<_, IdeviceError>(UsbmuxdConnection::new(Box::new(stream), tag)) }); match res { - Ok(r) => { - let boxed = Box::new(UsbmuxdConnectionHandle(r)); - unsafe { *usbmuxd_connection = Box::into_raw(boxed) }; - null_mut() + Ok(conn) => { + unsafe { *out = Box::into_raw(Box::new(UsbmuxdConnectionHandle(conn))) }; + std::ptr::null_mut() } Err(e) => ffi_err!(e), } @@ -367,20 +373,28 @@ pub unsafe extern "C" fn idevice_usbmuxd_connection_free( /// `usbmuxd_Addr` must be a valid, non-null pointer to a location where the handle will be stored #[unsafe(no_mangle)] pub unsafe extern "C" fn idevice_usbmuxd_tcp_addr_new( - addr: *const libc::sockaddr, - addr_len: libc::socklen_t, + addr: *const idevice_sockaddr, // <- portable + addr_len: idevice_socklen_t, usbmuxd_addr: *mut *mut UsbmuxdAddrHandle, ) -> *mut IdeviceFfiError { - let addr = match c_socket_to_rust(addr, addr_len) { + if addr.is_null() || usbmuxd_addr.is_null() { + return ffi_err!(IdeviceError::FfiInvalidArg); + } + + // Reinterpret as the real platform sockaddr for parsing + let addr = addr as *const SockAddr; + + let addr = match c_socket_to_rust(addr, addr_len as _) { Ok(a) => a, Err(e) => return ffi_err!(e), }; let u = UsbmuxdAddr::TcpSocket(addr); - let boxed = Box::new(UsbmuxdAddrHandle(u)); - unsafe { *usbmuxd_addr = Box::into_raw(boxed) }; - null_mut() + unsafe { + *usbmuxd_addr = Box::into_raw(boxed); + } + std::ptr::null_mut() } /// Creates a new UsbmuxdAddr struct with a unix socket diff --git a/ffi/src/util.rs b/ffi/src/util.rs index 5bada5c..6088109 100644 --- a/ffi/src/util.rs +++ b/ffi/src/util.rs @@ -1,75 +1,185 @@ // Jackson Coxson -use std::{ - ffi::c_int, - net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}, -}; +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; use idevice::IdeviceError; -use libc::{sockaddr_in, sockaddr_in6}; + +// portable FFI-facing types (only used in signatures) +#[allow(non_camel_case_types)] +#[repr(C)] +pub struct idevice_sockaddr { + _priv: [u8; 0], // opaque; acts as "struct sockaddr" placeholder +} +#[cfg(unix)] +#[allow(non_camel_case_types)] +pub type idevice_socklen_t = libc::socklen_t; +#[cfg(windows)] +#[allow(non_camel_case_types)] +pub type idevice_socklen_t = i32; + +// platform sockaddr aliases for implementation +#[cfg(unix)] +pub(crate) type SockAddr = libc::sockaddr; +#[cfg(windows)] +use windows_sys::Win32::Networking::WinSock as winsock; +#[cfg(windows)] +pub(crate) type SockAddr = winsock::SOCKADDR; + +#[cfg(unix)] +use libc::{self, sockaddr_in, sockaddr_in6}; + +#[cfg(windows)] +use windows_sys::Win32::Networking::WinSock::{ + AF_INET, AF_INET6, SOCKADDR_IN as sockaddr_in, SOCKADDR_IN6 as sockaddr_in6, +}; + +#[cfg(unix)] +type SockLen = libc::socklen_t; +#[cfg(windows)] +type SockLen = i32; // socklen_t is an int on Windows + +#[inline] +fn invalid_arg() -> Result { + Err(IdeviceError::FfiInvalidArg) +} pub(crate) fn c_socket_to_rust( - addr: *const libc::sockaddr, - addr_len: libc::socklen_t, + addr: *const SockAddr, + addr_len: SockLen, ) -> Result { - Ok(unsafe { - match (*addr).sa_family as c_int { + if addr.is_null() { + log::error!("null sockaddr"); + return invalid_arg(); + } + + unsafe { + let family = (*addr).sa_family; + + #[cfg(unix)] + match family as i32 { libc::AF_INET => { if (addr_len as usize) < std::mem::size_of::() { log::error!("Invalid sockaddr_in size"); - return Err(IdeviceError::FfiInvalidArg); + return invalid_arg(); } - let addr_in = *(addr as *const sockaddr_in); - let ip = std::net::Ipv4Addr::from(u32::from_be(addr_in.sin_addr.s_addr)); - let port = u16::from_be(addr_in.sin_port); - std::net::SocketAddr::V4(std::net::SocketAddrV4::new(ip, port)) + let a = &*(addr as *const sockaddr_in); + let ip = Ipv4Addr::from(u32::from_be(a.sin_addr.s_addr)); + let port = u16::from_be(a.sin_port); + Ok(SocketAddr::V4(std::net::SocketAddrV4::new(ip, port))) } libc::AF_INET6 => { - if addr_len as usize >= std::mem::size_of::() { - let addr_in6 = *(addr as *const sockaddr_in6); - let ip = std::net::Ipv6Addr::from(addr_in6.sin6_addr.s6_addr); - let port = u16::from_be(addr_in6.sin6_port); - std::net::SocketAddr::V6(std::net::SocketAddrV6::new( - ip, - port, - addr_in6.sin6_flowinfo, - addr_in6.sin6_scope_id, - )) - } else { + if (addr_len as usize) < std::mem::size_of::() { log::error!("Invalid sockaddr_in6 size"); - return Err(IdeviceError::FfiInvalidArg); + return invalid_arg(); } + let a = &*(addr as *const sockaddr_in6); + let ip = Ipv6Addr::from(a.sin6_addr.s6_addr); + let port = u16::from_be(a.sin6_port); + Ok(SocketAddr::V6(std::net::SocketAddrV6::new( + ip, + port, + a.sin6_flowinfo, + a.sin6_scope_id, + ))) + } + _ => { + log::error!( + "Unsupported socket address family: {}", + (*addr).sa_family as i32 + ); + invalid_arg() + } + } + + #[cfg(windows)] + match family { + AF_INET => { + if (addr_len as usize) < std::mem::size_of::() { + log::error!("Invalid SOCKADDR_IN size"); + return invalid_arg(); + } + let a = &*(addr as *const sockaddr_in); + // IN_ADDR is a union; use S_un.S_addr (network byte order) + let ip_be = a.sin_addr.S_un.S_addr; + let ip = Ipv4Addr::from(u32::from_be(ip_be)); + let port = u16::from_be(a.sin_port); + Ok(SocketAddr::V4(std::net::SocketAddrV4::new(ip, port))) + } + AF_INET6 => { + if (addr_len as usize) < std::mem::size_of::() { + log::error!("Invalid SOCKADDR_IN6 size"); + return invalid_arg(); + } + let a = &*(addr as *const sockaddr_in6); + // IN6_ADDR is a union; read the 16 Byte array + let bytes: [u8; 16] = a.sin6_addr.u.Byte; + let ip = Ipv6Addr::from(bytes); + let port = u16::from_be(a.sin6_port); + let scope_id = a.Anonymous.sin6_scope_id; + Ok(SocketAddr::V6(std::net::SocketAddrV6::new( + ip, + port, + a.sin6_flowinfo, + scope_id, + ))) } _ => { log::error!("Unsupported socket address family: {}", (*addr).sa_family); - return Err(IdeviceError::FfiInvalidArg); + invalid_arg() } } - }) + } } -pub(crate) fn c_addr_to_rust(addr: *const libc::sockaddr) -> Result { +pub(crate) fn c_addr_to_rust(addr: *const SockAddr) -> Result { + if addr.is_null() { + log::error!("null sockaddr"); + return invalid_arg(); + } + unsafe { - // Check the address family - match (*addr).sa_family as c_int { + #[cfg(unix)] + let family = (*addr).sa_family as i32; + #[cfg(windows)] + let family = (*addr).sa_family; + + #[cfg(unix)] + match family { libc::AF_INET => { - // Convert sockaddr_in (IPv4) to IpAddr - let sockaddr_in = addr as *const sockaddr_in; - let ip = (*sockaddr_in).sin_addr.s_addr; - let octets = u32::from_be(ip).to_be_bytes(); + let a = &*(addr as *const sockaddr_in); + let octets = u32::from_be(a.sin_addr.s_addr).to_be_bytes(); Ok(IpAddr::V4(Ipv4Addr::new( octets[0], octets[1], octets[2], octets[3], ))) } libc::AF_INET6 => { - // Convert sockaddr_in6 (IPv6) to IpAddr - let sockaddr_in6 = addr as *const sockaddr_in6; - let ip = (*sockaddr_in6).sin6_addr.s6_addr; - Ok(IpAddr::V6(Ipv6Addr::from(ip))) + let a = &*(addr as *const sockaddr_in6); + Ok(IpAddr::V6(Ipv6Addr::from(a.sin6_addr.s6_addr))) + } + _ => { + log::error!( + "Unsupported socket address family: {}", + (*addr).sa_family as i32 + ); + invalid_arg() + } + } + + #[cfg(windows)] + match family { + AF_INET => { + let a = &*(addr as *const sockaddr_in); + let ip_be = a.sin_addr.S_un.S_addr; + Ok(IpAddr::V4(Ipv4Addr::from(u32::from_be(ip_be)))) + } + AF_INET6 => { + let a = &*(addr as *const sockaddr_in6); + let bytes: [u8; 16] = a.sin6_addr.u.Byte; + Ok(IpAddr::V6(Ipv6Addr::from(bytes))) } _ => { log::error!("Unsupported socket address family: {}", (*addr).sa_family); - Err(IdeviceError::FfiInvalidArg) + invalid_arg() } } }