Files
idevice/ffi/src/rsd.rs
2025-10-23 12:29:59 -06:00

526 lines
16 KiB
Rust

//! 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::{self, null_mut};
use idevice::rsd::RsdHandshake;
use crate::{IdeviceFfiError, ReadWriteOpaque, ffi_err, run_sync};
/// Opaque handle to an RsdHandshake
#[derive(Clone)]
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 IdeviceFfiError on error, null on success
///
/// # Safety
/// `socket` must be a valid pointer to a ReadWrite handle allocated by this library. It is
/// consumed and may not be used again.
/// `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 ReadWriteOpaque,
handle: *mut *mut RsdHandshakeHandle,
) -> *mut IdeviceFfiError {
if socket.is_null() || handle.is_null() {
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let wrapper = unsafe { &mut *socket };
let res = match wrapper.inner.take() {
Some(mut w) => run_sync(async move { RsdHandshake::new(w.as_mut()).await }),
None => {
return ffi_err!(IdeviceError::FfiInvalidArg);
}
};
match res {
Ok(handshake) => {
let boxed = Box::new(RsdHandshakeHandle(handshake));
unsafe { *handle = Box::into_raw(boxed) };
null_mut()
}
Err(e) => ffi_err!(e),
}
}
/// Gets the protocol version from the RSD handshake
///
/// # Arguments
/// * [`handle`] - A valid RsdHandshake handle
/// * [`version`] - Pointer to store the protocol version
///
/// # Returns
/// An IdeviceFfiError on error, null on success
///
/// # 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,
) -> *mut IdeviceFfiError {
if handle.is_null() || version.is_null() {
return ffi_err!(IdeviceError::FfiInvalidArg);
}
unsafe {
*version = (*handle).0.protocol_version;
}
null_mut()
}
/// 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 IdeviceFfiError on error, null on success
///
/// # 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,
) -> *mut IdeviceFfiError {
if handle.is_null() || uuid.is_null() {
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let uuid_str = &unsafe { &*handle }.0.uuid;
match CString::new(uuid_str.as_str()) {
Ok(c_str) => {
unsafe { *uuid = c_str.into_raw() };
null_mut()
}
Err(_) => ffi_err!(IdeviceError::FfiInvalidString),
}
}
/// Gets all available services from the RSD handshake
///
/// # Arguments
/// * [`handle`] - A valid RsdHandshake handle
/// * [`services`] - Pointer to store the services array
///
/// # Returns
/// An IdeviceFfiError on error, null on success
///
/// # 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,
) -> *mut IdeviceFfiError {
if handle.is_null() || services.is_null() {
return ffi_err!(IdeviceError::FfiInvalidArg);
}
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 ffi_err!(IdeviceError::FfiInvalidString);
}
}
}
// 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) };
null_mut()
}
/// 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 IdeviceFfiError on error, null on success
///
/// # 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,
) -> *mut IdeviceFfiError {
if handle.is_null() || service_name.is_null() || available.is_null() {
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let name = match unsafe { CStr::from_ptr(service_name) }.to_str() {
Ok(s) => s,
Err(_) => return ffi_err!(IdeviceError::FfiInvalidArg),
};
let handshake = unsafe { &*handle };
unsafe { *available = handshake.0.services.contains_key(name) };
null_mut()
}
/// 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 IdeviceFfiError on error, null on success
///
/// # 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,
) -> *mut IdeviceFfiError {
if handle.is_null() || service_name.is_null() || service_info.is_null() {
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let name = match unsafe { CStr::from_ptr(service_name) }.to_str() {
Ok(s) => s,
Err(_) => return ffi_err!(IdeviceError::FfiInvalidArg),
};
let handshake = unsafe { &*handle };
let service = match handshake.0.services.get(name) {
Some(s) => s,
None => return ffi_err!(IdeviceError::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 ffi_err!(IdeviceError::FfiInvalidString),
};
let c_entitlement = match CString::new(service.entitlement.as_str()) {
Ok(s) => s.into_raw(),
Err(_) => {
unsafe {
let _ = CString::from_raw(c_name);
}
return ffi_err!(IdeviceError::FfiInvalidString);
}
};
// 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 ffi_err!(IdeviceError::FfiInvalidString);
}
}
}
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) };
null_mut()
}
/// Clones an RSD handshake
///
/// # Safety
/// Pass a valid pointer allocated by this library
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rsd_handshake_clone(
handshake: *mut RsdHandshakeHandle,
) -> *mut RsdHandshakeHandle {
if handshake.is_null() {
return null_mut();
}
let handshake = unsafe { &mut *handshake };
let new_handshake = handshake.clone();
Box::into_raw(Box::new(new_handshake))
}
/// 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) };
}
}