From 55e7d6366fffdaa38e9ecb796bfd6fd79ef5aa7b Mon Sep 17 00:00:00 2001 From: Jackson Coxson Date: Mon, 24 Mar 2025 15:30:10 -0600 Subject: [PATCH] Bindings for providers and usbmuxd --- ffi/src/provider.rs | 150 +++++++++++++++++++++++++++++++++++ ffi/src/usbmuxd.rs | 188 ++++++++++++++++++++++++++++++++++++++++++++ ffi/src/util.rs | 77 ++++++++++++++++++ 3 files changed, 415 insertions(+) create mode 100644 ffi/src/provider.rs create mode 100644 ffi/src/usbmuxd.rs create mode 100644 ffi/src/util.rs diff --git a/ffi/src/provider.rs b/ffi/src/provider.rs new file mode 100644 index 0000000..063af7c --- /dev/null +++ b/ffi/src/provider.rs @@ -0,0 +1,150 @@ +// Jackson Coxson + +use idevice::provider::{TcpProvider, UsbmuxdProvider}; +use std::ffi::CStr; +use std::os::raw::c_char; + +use crate::{IdeviceErrorCode, usbmuxd::UsbmuxdAddrHandle, util}; + +pub struct TcpProviderHandle(pub TcpProvider); +pub struct UsbmuxdProviderHandle(pub UsbmuxdProvider); + +/// Creates a TCP provider for idevice +/// +/// # Arguments +/// * [`ip`] - The sockaddr IP to connect to +/// * [`pairing_file`] - The pairing file handle to use +/// * [`label`] - The label to use with the connection +/// * [`provider`] - A pointer to a newly allocated provider +/// +/// # Returns +/// An error code indicating success or failure +/// +/// # Safety +/// `ip` must be a valid sockaddr +/// `pairing_file` 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 +#[unsafe(no_mangle)] +pub unsafe extern "C" fn idevice_tcp_provider_new( + ip: *const libc::sockaddr, + pairing_file: *mut crate::pairing_file::IdevicePairingFile, + label: *const c_char, + provider: *mut *mut TcpProviderHandle, +) -> IdeviceErrorCode { + let addr = match util::c_addr_to_rust(ip) { + Ok(i) => i, + Err(e) => { + return 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 IdeviceErrorCode::InvalidString; + } + }; + + let pairing_file = unsafe { Box::from_raw(pairing_file) }; + let t = TcpProvider { + addr, + pairing_file: pairing_file.0, + label, + }; + + let boxed = Box::new(TcpProviderHandle(t)); + unsafe { *provider = Box::into_raw(boxed) }; + IdeviceErrorCode::IdeviceSuccess +} + +/// Frees a TcpProvider handle +/// +/// # Arguments +/// * [`provider`] - The provider handle to free +/// +/// # Safety +/// `provider` must be a valid pointer to a TcpProvider handle that was allocated by this library, +/// or NULL (in which case this function does nothing) +#[unsafe(no_mangle)] +pub unsafe extern "C" fn tcp_provider_free(provider: *mut TcpProvider) { + if !provider.is_null() { + unsafe { drop(Box::from_raw(provider)) }; + } +} + +/// Creates a usbmuxd provider for idevice +/// +/// # Arguments +/// * [`addr`] - The UsbmuxdAddr handle to connect to +/// * [`tag`] - The tag returned in usbmuxd responses +/// * [`udid`] - The UDID of the device to connect to +/// * [`device_id`] - The muxer ID of the device to connect to +/// * [`pairing_file`] - The pairing file handle to use +/// * [`label`] - The label to use with the connection +/// * [`provider`] - A pointer to a newly allocated provider +/// +/// # Returns +/// An error code indicating success or failure +/// +/// # Safety +/// `addr` must be a valid pointer to UsbmuxdAddrHandle created by this library, and never used again +/// `udid` must be a valid CStr +/// `pairing_file` 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 +#[unsafe(no_mangle)] +pub unsafe extern "C" fn usbmuxd_provider_new( + addr: *mut UsbmuxdAddrHandle, + tag: u32, + udid: *const c_char, + device_id: u32, + label: *const c_char, + provider: *mut *mut UsbmuxdProviderHandle, +) -> IdeviceErrorCode { + let udid = match unsafe { CStr::from_ptr(udid) }.to_str() { + Ok(u) => u.to_string(), + Err(e) => { + log::error!("Invalid UDID string: {e:?}"); + return IdeviceErrorCode::InvalidArgument; + } + }; + + let label = match unsafe { CStr::from_ptr(label) }.to_str() { + Ok(l) => l.to_string(), + Err(e) => { + log::error!("Invalid UDID string: {e:?}"); + return IdeviceErrorCode::InvalidArgument; + } + }; + + let addr = unsafe { Box::from_raw(addr) }.0; + + let p = UsbmuxdProvider { + addr, + tag, + udid, + device_id, + label, + }; + + let boxed = Box::new(UsbmuxdProviderHandle(p)); + unsafe { *provider = Box::into_raw(boxed) }; + + IdeviceErrorCode::IdeviceSuccess +} + +/// Frees a UsbmuxdProvider handle +/// +/// # Arguments +/// * [`provider`] - The provider handle to free +/// +/// # Safety +/// `provider` must be a valid pointer to a UsbmuxdProvider handle that was allocated by this library, +/// or NULL (in which case this function does nothing) +#[unsafe(no_mangle)] +pub unsafe extern "C" fn usbmuxd_provider_free(provider: *mut UsbmuxdProvider) { + if !provider.is_null() { + unsafe { drop(Box::from_raw(provider)) }; + } +} diff --git a/ffi/src/usbmuxd.rs b/ffi/src/usbmuxd.rs new file mode 100644 index 0000000..0012006 --- /dev/null +++ b/ffi/src/usbmuxd.rs @@ -0,0 +1,188 @@ +// Jackson Coxson + +use std::ffi::{CStr, c_char}; + +use crate::{IdeviceErrorCode, RUNTIME, util::c_socket_to_rust}; +use idevice::{ + IdeviceError, + usbmuxd::{UsbmuxdAddr, UsbmuxdConnection}, +}; + +pub struct UsbmuxdConnectionHandle(pub UsbmuxdConnection); +pub struct UsbmuxdAddrHandle(pub UsbmuxdAddr); + +/// Connects to a usbmuxd instance over TCP +/// +/// # Arguments +/// * [`addr`] - The socket address to connect to +/// * [`addr_len`] - Length of the socket +/// * [`tag`] - A tag that will be returned by usbmuxd responses +/// * [`usbmuxd_connection`] - On success, will be set to point to a newly allocated UsbmuxdConnection handle +/// +/// # Returns +/// An error code indicating success or failure +/// +/// # Safety +/// `addr` must be a valid sockaddr +/// `label` must be a valid null-terminated C string +/// `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, + tag: u32, + usbmuxd_connection: *mut *mut UsbmuxdConnectionHandle, +) -> IdeviceErrorCode { + let addr = match c_socket_to_rust(addr, addr_len) { + Ok(a) => a, + Err(e) => return e, + }; + + let res: Result = RUNTIME.block_on(async move { + let stream = tokio::net::TcpStream::connect(addr).await?; + Ok(UsbmuxdConnection::new(Box::new(stream), tag)) + }); + + match res { + Ok(r) => { + let boxed = Box::new(UsbmuxdConnectionHandle(r)); + unsafe { *usbmuxd_connection = Box::into_raw(boxed) }; + IdeviceErrorCode::IdeviceSuccess + } + Err(e) => e.into(), + } +} + +/// Connects to a usbmuxd instance over unix socket +/// +/// # Arguments +/// * [`addr`] - The socket path to connect to +/// * [`tag`] - A tag that will be returned by usbmuxd responses +/// * [`usbmuxd_connection`] - On success, will be set to point to a newly allocated UsbmuxdConnection handle +/// +/// # Returns +/// An error code indicating success or failure +/// +/// # Safety +/// `addr` must be a valid CStr +/// `label` must be a valid null-terminated C string +/// `usbmuxd_connection` must be a valid, non-null pointer to a location where the handle will be stored +#[unsafe(no_mangle)] +#[cfg(unix)] +pub unsafe extern "C" fn idevice_usbmuxd_new_unix_socket_connection( + addr: *const c_char, + tag: u32, + usbmuxd_connection: *mut *mut UsbmuxdConnectionHandle, +) -> IdeviceErrorCode { + let addr = match unsafe { CStr::from_ptr(addr).to_str() } { + Ok(s) => s, + Err(_) => return IdeviceErrorCode::InvalidArg, + }; + + let res: Result = RUNTIME.block_on(async move { + let stream = tokio::net::UnixStream::connect(addr).await?; + Ok(UsbmuxdConnection::new(Box::new(stream), tag)) + }); + + match res { + Ok(r) => { + let boxed = Box::new(UsbmuxdConnectionHandle(r)); + unsafe { *usbmuxd_connection = Box::into_raw(boxed) }; + IdeviceErrorCode::IdeviceSuccess + } + Err(e) => e.into(), + } +} + +/// Frees a UsbmuxdConnection handle +/// +/// # Arguments +/// * [`usbmuxd_connection`] - The UsbmuxdConnection handle to free +/// +/// # Safety +/// `usbmuxd_connection` must be a valid pointer to a UsbmuxdConnection handle that was allocated by this library, +/// or NULL (in which case this function does nothing) +#[unsafe(no_mangle)] +pub unsafe extern "C" fn idevice_usbmuxd_connection_free( + usbmuxd_connection: *mut UsbmuxdConnectionHandle, +) { + if !usbmuxd_connection.is_null() { + let _ = unsafe { Box::from_raw(usbmuxd_connection) }; + } +} + +/// Creates a usbmuxd TCP address struct +/// +/// # Arguments +/// * [`addr`] - The socket address to connect to +/// * [`addr_len`] - Length of the socket +/// * [`usbmuxd_addr`] - On success, will be set to point to a newly allocated UsbmuxdAddr handle +/// +/// # Returns +/// An error code indicating success or failure +/// +/// # Safety +/// `addr` must be a valid sockaddr +/// `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, + usbmuxd_addr: *mut *mut UsbmuxdAddrHandle, +) -> IdeviceErrorCode { + let addr = match c_socket_to_rust(addr, addr_len) { + Ok(a) => a, + Err(e) => return e, + }; + + let u = UsbmuxdAddr::TcpSocket(addr); + + let boxed = Box::new(UsbmuxdAddrHandle(u)); + unsafe { *usbmuxd_addr = Box::into_raw(boxed) }; + IdeviceErrorCode::IdeviceSuccess +} + +/// Creates a new UsbmuxdAddr struct with a unix socket +/// +/// # Arguments +/// * [`addr`] - The socket path to connect to +/// * [`usbmuxd_addr`] - On success, will be set to point to a newly allocated UsbmuxdAddr handle +/// +/// # Returns +/// An error code indicating success or failure +/// +/// # Safety +/// `addr` must be a valid CStr +/// `usbmuxd_addr` must be a valid, non-null pointer to a location where the handle will be stored +#[unsafe(no_mangle)] +#[cfg(unix)] +pub unsafe extern "C" fn idevice_usbmuxd_unix_addr_new( + addr: *const c_char, + usbmuxd_addr: *mut *mut UsbmuxdAddrHandle, +) -> IdeviceErrorCode { + let addr = match unsafe { CStr::from_ptr(addr).to_str() } { + Ok(s) => s, + Err(_) => return IdeviceErrorCode::InvalidArg, + }; + + let u = UsbmuxdAddr::UnixSocket(addr.to_string()); + + let boxed = Box::new(UsbmuxdAddrHandle(u)); + unsafe { *usbmuxd_addr = Box::into_raw(boxed) }; + IdeviceErrorCode::IdeviceSuccess +} + +/// Frees a UsbmuxdAddr handle +/// +/// # Arguments +/// * [`usbmuxd_addr`] - The UsbmuxdAddr handle to free +/// +/// # Safety +/// `usbmuxd_addr` must be a valid pointer to a UsbmuxdAddr handle that was allocated by this library, +/// or NULL (in which case this function does nothing) +#[unsafe(no_mangle)] +pub unsafe extern "C" fn idevice_usbmuxd_addr_free(usbmuxd_addr: *mut UsbmuxdAddrHandle) { + if !usbmuxd_addr.is_null() { + let _ = unsafe { Box::from_raw(usbmuxd_addr) }; + } +} diff --git a/ffi/src/util.rs b/ffi/src/util.rs new file mode 100644 index 0000000..cacabc7 --- /dev/null +++ b/ffi/src/util.rs @@ -0,0 +1,77 @@ +// Jackson Coxson + +use std::{ + ffi::c_int, + net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}, +}; + +use libc::{sockaddr_in, sockaddr_in6}; + +use crate::IdeviceErrorCode; + +pub(crate) fn c_socket_to_rust( + addr: *const libc::sockaddr, + addr_len: libc::socklen_t, +) -> Result { + Ok(unsafe { + match (*addr).sa_family as c_int { + libc::AF_INET => { + if (addr_len as usize) < std::mem::size_of::() { + log::error!("Invalid sockaddr_in size"); + return Err(IdeviceErrorCode::InvalidArg); + } + 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)) + } + 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 { + log::error!("Invalid sockaddr_in6 size"); + return Err(IdeviceErrorCode::InvalidArg); + } + } + _ => { + log::error!("Unsupported socket address family: {}", (*addr).sa_family); + return Err(IdeviceErrorCode::InvalidArg); + } + } + }) +} + +pub(crate) fn c_addr_to_rust(addr: *const libc::sockaddr) -> Result { + unsafe { + // Check the address family + match (*addr).sa_family as c_int { + 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(); + 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))) + } + _ => { + log::error!("Unsupported socket address family: {}", (*addr).sa_family); + Err(IdeviceErrorCode::InvalidArg) + } + } + } +}