Refactor FFI bindings

This commit is contained in:
Jackson Coxson
2025-05-26 12:52:23 -06:00
parent 15261861e0
commit fa88c2c87d
22 changed files with 1005 additions and 1476 deletions

502
ffi/src/rsd.rs Normal file
View File

@@ -0,0 +1,502 @@
//! Remote Service Discovery (RSD) Client Bindings
//!
//! Provides C-compatible bindings for RSD handshake and service discovery on iOS devices.
use std::ffi::{CStr, CString};
use std::ptr;
use idevice::{ReadWrite, rsd::RsdHandshake};
use crate::{IdeviceErrorCode, RUNTIME};
/// Opaque handle to an RsdHandshake
pub struct RsdHandshakeHandle(pub RsdHandshake);
/// C-compatible representation of an RSD service
#[repr(C)]
pub struct CRsdService {
/// Service name (null-terminated string)
pub name: *mut libc::c_char,
/// Required entitlement (null-terminated string)
pub entitlement: *mut libc::c_char,
/// Port number
pub port: u16,
/// Whether service uses remote XPC
pub uses_remote_xpc: bool,
/// Number of features
pub features_count: libc::size_t,
/// Array of feature strings
pub features: *mut *mut libc::c_char,
/// Service version (-1 if not present)
pub service_version: i64,
}
/// Array of RSD services returned by rsd_get_services
#[repr(C)]
pub struct CRsdServiceArray {
/// Array of services
pub services: *mut CRsdService,
/// Number of services in array
pub count: libc::size_t,
}
/// Creates a new RSD handshake from a ReadWrite connection
///
/// # Arguments
/// * [`socket`] - The connection to use for communication
/// * [`handle`] - Pointer to store the newly created RsdHandshake handle
///
/// # Returns
/// An error code indicating success or failure
///
/// # Safety
/// `socket` must be a valid pointer to a ReadWrite handle allocated by this library
/// `handle` must be a valid pointer to a location where the handle will be stored
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rsd_handshake_new(
socket: *mut Box<dyn ReadWrite>,
handle: *mut *mut RsdHandshakeHandle,
) -> IdeviceErrorCode {
if socket.is_null() || handle.is_null() {
return IdeviceErrorCode::InvalidArg;
}
let connection = unsafe { Box::from_raw(socket) };
let res = RUNTIME.block_on(async move { RsdHandshake::new(*connection).await });
match res {
Ok(handshake) => {
let boxed = Box::new(RsdHandshakeHandle(handshake));
unsafe { *handle = Box::into_raw(boxed) };
IdeviceErrorCode::IdeviceSuccess
}
Err(e) => e.into(),
}
}
/// Gets the protocol version from the RSD handshake
///
/// # Arguments
/// * [`handle`] - A valid RsdHandshake handle
/// * [`version`] - Pointer to store the protocol version
///
/// # Returns
/// An error code indicating success or failure
///
/// # Safety
/// `handle` must be a valid pointer to a handle allocated by this library
/// `version` must be a valid pointer to store the version
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rsd_get_protocol_version(
handle: *mut RsdHandshakeHandle,
version: *mut libc::size_t,
) -> IdeviceErrorCode {
if handle.is_null() || version.is_null() {
return IdeviceErrorCode::InvalidArg;
}
unsafe {
*version = (*handle).0.protocol_version;
}
IdeviceErrorCode::IdeviceSuccess
}
/// Gets the UUID from the RSD handshake
///
/// # Arguments
/// * [`handle`] - A valid RsdHandshake handle
/// * [`uuid`] - Pointer to store the UUID string (caller must free with rsd_free_string)
///
/// # Returns
/// An error code indicating success or failure
///
/// # Safety
/// `handle` must be a valid pointer to a handle allocated by this library
/// `uuid` must be a valid pointer to store the string pointer
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rsd_get_uuid(
handle: *mut RsdHandshakeHandle,
uuid: *mut *mut libc::c_char,
) -> IdeviceErrorCode {
if handle.is_null() || uuid.is_null() {
return IdeviceErrorCode::InvalidArg;
}
let uuid_str = &unsafe { &*handle }.0.uuid;
match CString::new(uuid_str.as_str()) {
Ok(c_str) => {
unsafe { *uuid = c_str.into_raw() };
IdeviceErrorCode::IdeviceSuccess
}
Err(_) => IdeviceErrorCode::InvalidString,
}
}
/// Gets all available services from the RSD handshake
///
/// # Arguments
/// * [`handle`] - A valid RsdHandshake handle
/// * [`services`] - Pointer to store the services array
///
/// # Returns
/// An error code indicating success or failure
///
/// # Safety
/// `handle` must be a valid pointer to a handle allocated by this library
/// `services` must be a valid pointer to store the services array
/// Caller must free the returned array with rsd_free_services
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rsd_get_services(
handle: *mut RsdHandshakeHandle,
services: *mut *mut CRsdServiceArray,
) -> IdeviceErrorCode {
if handle.is_null() || services.is_null() {
return IdeviceErrorCode::InvalidArg;
}
let handshake = unsafe { &*handle };
let service_map = &handshake.0.services;
let count = service_map.len();
let mut c_services = Vec::with_capacity(count);
for (name, service) in service_map.iter() {
// Convert name
let c_name = match CString::new(name.as_str()) {
Ok(s) => s.into_raw(),
Err(_) => continue,
};
// Convert entitlement
let c_entitlement = match CString::new(service.entitlement.as_str()) {
Ok(s) => s.into_raw(),
Err(_) => {
unsafe {
let _ = CString::from_raw(c_name);
}
continue;
}
};
// Convert features
let (features_ptr, features_count) = match &service.features {
Some(features) => {
let mut c_features = Vec::with_capacity(features.len());
for feature in features {
match CString::new(feature.as_str()) {
Ok(s) => c_features.push(s.into_raw()),
Err(_) => {
// Clean up already allocated features
for f in c_features {
unsafe {
let _ = CString::from_raw(f);
}
}
// Return early to avoid the move below
return IdeviceErrorCode::InvalidString;
}
}
}
// All features converted successfully
let boxed = c_features.into_boxed_slice();
let ptr = Box::into_raw(boxed) as *mut *mut libc::c_char;
(ptr, features.len())
}
None => (ptr::null_mut(), 0),
};
let c_service = CRsdService {
name: c_name,
entitlement: c_entitlement,
port: service.port,
uses_remote_xpc: service.uses_remote_xpc,
features_count,
features: features_ptr,
service_version: service.service_version.unwrap_or(-1),
};
c_services.push(c_service);
}
let boxed_services = c_services.into_boxed_slice();
let array = Box::new(CRsdServiceArray {
services: Box::into_raw(boxed_services) as *mut CRsdService,
count,
});
unsafe { *services = Box::into_raw(array) };
IdeviceErrorCode::IdeviceSuccess
}
/// Checks if a specific service is available
///
/// # Arguments
/// * [`handle`] - A valid RsdHandshake handle
/// * [`service_name`] - Name of the service to check for
/// * [`available`] - Pointer to store the availability result
///
/// # Returns
/// An error code indicating success or failure
///
/// # Safety
/// `handle` must be a valid pointer to a handle allocated by this library
/// `service_name` must be a valid C string
/// `available` must be a valid pointer to store the boolean result
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rsd_service_available(
handle: *mut RsdHandshakeHandle,
service_name: *const libc::c_char,
available: *mut bool,
) -> IdeviceErrorCode {
if handle.is_null() || service_name.is_null() || available.is_null() {
return IdeviceErrorCode::InvalidArg;
}
let name = match unsafe { CStr::from_ptr(service_name) }.to_str() {
Ok(s) => s,
Err(_) => return IdeviceErrorCode::InvalidArg,
};
let handshake = unsafe { &*handle };
unsafe { *available = handshake.0.services.contains_key(name) };
IdeviceErrorCode::IdeviceSuccess
}
/// Gets information about a specific service
///
/// # Arguments
/// * [`handle`] - A valid RsdHandshake handle
/// * [`service_name`] - Name of the service to get info for
/// * [`service_info`] - Pointer to store the service information
///
/// # Returns
/// An error code indicating success or failure
///
/// # Safety
/// `handle` must be a valid pointer to a handle allocated by this library
/// `service_name` must be a valid C string
/// `service_info` must be a valid pointer to store the service info
/// Caller must free the returned service with rsd_free_service
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rsd_get_service_info(
handle: *mut RsdHandshakeHandle,
service_name: *const libc::c_char,
service_info: *mut *mut CRsdService,
) -> IdeviceErrorCode {
if handle.is_null() || service_name.is_null() || service_info.is_null() {
return IdeviceErrorCode::InvalidArg;
}
let name = match unsafe { CStr::from_ptr(service_name) }.to_str() {
Ok(s) => s,
Err(_) => return IdeviceErrorCode::InvalidArg,
};
let handshake = unsafe { &*handle };
let service = match handshake.0.services.get(name) {
Some(s) => s,
None => return IdeviceErrorCode::ServiceNotFound,
};
// Convert service to C representation (similar to rsd_get_services logic)
let c_name = match CString::new(name) {
Ok(s) => s.into_raw(),
Err(_) => return IdeviceErrorCode::InvalidString,
};
let c_entitlement = match CString::new(service.entitlement.as_str()) {
Ok(s) => s.into_raw(),
Err(_) => {
unsafe {
let _ = CString::from_raw(c_name);
}
return IdeviceErrorCode::InvalidString;
}
};
// Convert features
let (features_ptr, features_count) = match &service.features {
Some(features) => {
let mut c_features = Vec::with_capacity(features.len());
for feature in features {
match CString::new(feature.as_str()) {
Ok(s) => c_features.push(s.into_raw()),
Err(_) => {
// Clean up already allocated features
for f in c_features {
unsafe {
let _ = CString::from_raw(f);
}
}
// Clean up name and entitlement
unsafe {
let _ = CString::from_raw(c_name);
let _ = CString::from_raw(c_entitlement);
}
return IdeviceErrorCode::InvalidString;
}
}
}
let boxed = c_features.into_boxed_slice();
(
Box::into_raw(boxed) as *mut *mut libc::c_char,
features.len(),
)
}
None => (ptr::null_mut(), 0),
};
let c_service = Box::new(CRsdService {
name: c_name,
entitlement: c_entitlement,
port: service.port,
uses_remote_xpc: service.uses_remote_xpc,
features_count,
features: features_ptr,
service_version: service.service_version.unwrap_or(-1),
});
unsafe { *service_info = Box::into_raw(c_service) };
IdeviceErrorCode::IdeviceSuccess
}
/// Frees a string returned by RSD functions
///
/// # Arguments
/// * [`string`] - The string to free
///
/// # Safety
/// Must only be called with strings returned from RSD functions
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rsd_free_string(string: *mut libc::c_char) {
if !string.is_null() {
unsafe {
let _ = CString::from_raw(string);
}
}
}
/// Frees a single service returned by rsd_get_service_info
///
/// # Arguments
/// * [`service`] - The service to free
///
/// # Safety
/// Must only be called with services returned from rsd_get_service_info
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rsd_free_service(service: *mut CRsdService) {
if service.is_null() {
return;
}
let service_box = unsafe { Box::from_raw(service) };
// Free name
if !service_box.name.is_null() {
unsafe {
let _ = CString::from_raw(service_box.name);
}
}
// Free entitlement
if !service_box.entitlement.is_null() {
unsafe {
let _ = CString::from_raw(service_box.entitlement);
}
}
// Free features array
if !service_box.features.is_null() && service_box.features_count > 0 {
let features_slice = unsafe {
std::slice::from_raw_parts_mut(service_box.features, service_box.features_count)
};
for feature_ptr in features_slice.iter() {
if !feature_ptr.is_null() {
unsafe {
let _ = CString::from_raw(*feature_ptr);
}
}
}
unsafe {
let _ = Box::from_raw(features_slice);
}
}
}
/// Frees services array returned by rsd_get_services
///
/// # Arguments
/// * [`services`] - The services array to free
///
/// # Safety
/// Must only be called with arrays returned from rsd_get_services
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rsd_free_services(services: *mut CRsdServiceArray) {
if services.is_null() {
return;
}
let services_box = unsafe { Box::from_raw(services) };
if !services_box.services.is_null() && services_box.count > 0 {
let services_slice =
unsafe { std::slice::from_raw_parts_mut(services_box.services, services_box.count) };
// Free each service
for service in services_slice.iter() {
// Free name
if !service.name.is_null() {
unsafe {
let _ = CString::from_raw(service.name);
}
}
// Free entitlement
if !service.entitlement.is_null() {
unsafe {
let _ = CString::from_raw(service.entitlement);
}
}
// Free features array
if !service.features.is_null() && service.features_count > 0 {
let features_slice = unsafe {
std::slice::from_raw_parts_mut(service.features, service.features_count)
};
for feature_ptr in features_slice.iter() {
if !feature_ptr.is_null() {
unsafe {
let _ = CString::from_raw(*feature_ptr);
}
}
}
unsafe {
let _ = Box::from_raw(features_slice);
}
}
}
unsafe {
let _ = Box::from_raw(services_slice);
}
}
}
/// Frees an RSD handshake handle
///
/// # Arguments
/// * [`handle`] - The handle to free
///
/// # Safety
/// `handle` must be a valid pointer to a handle allocated by this library,
/// or NULL (in which case this function does nothing)
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rsd_handshake_free(handle: *mut RsdHandshakeHandle) {
if !handle.is_null() {
let _ = unsafe { Box::from_raw(handle) };
}
}