RemoteXPC bindings

This commit is contained in:
Jackson Coxson
2025-03-26 10:54:27 -06:00
parent 40936d1183
commit 80e502c94d
3 changed files with 493 additions and 0 deletions

204
ffi/examples/remotexpc.c Normal file
View File

@@ -0,0 +1,204 @@
// Jackson Coxson
#include "idevice.h"
#include <arpa/inet.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
void print_service_details(XPCServiceHandle *service) {
printf(" Service Details:\n");
printf(" Entitlement: %s\n", service->entitlement);
printf(" Port: %d\n", service->port);
printf(" Uses Remote XPC: %s\n",
service->uses_remote_xpc ? "true" : "false");
printf(" Service Version: %lld\n", service->service_version);
if (service->features_count > 0) {
printf(" Features:\n");
for (size_t i = 0; i < service->features_count; i++) {
printf(" - %s\n", service->features[i]);
}
}
}
int main() {
// Initialize logger
idevice_init_logger(Debug, Disabled, NULL);
// Create the socket address (replace with your device's IP)
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(LOCKDOWN_PORT);
inet_pton(AF_INET, "10.7.0.2", &addr.sin_addr);
// Read pairing file (replace with your pairing file path)
IdevicePairingFile *pairing_file = NULL;
IdeviceErrorCode err =
idevice_pairing_file_read("pairing_file.plist", &pairing_file);
if (err != IdeviceSuccess) {
fprintf(stderr, "Failed to read pairing file: %d\n", err);
return 1;
}
/*****************************************************************
* TCP Provider and CoreDeviceProxy Test
*****************************************************************/
printf("=== Testing TCP Provider and CoreDeviceProxy ===\n");
// Create TCP provider
TcpProviderHandle *tcp_provider = NULL;
err = idevice_tcp_provider_new((struct sockaddr *)&addr, pairing_file,
"CoreDeviceProxyTest", &tcp_provider);
if (err != IdeviceSuccess) {
fprintf(stderr, "Failed to create TCP provider: %d\n", err);
idevice_pairing_file_free(pairing_file);
return 1;
}
// Connect to CoreDeviceProxy
CoreDeviceProxyHandle *core_device = NULL;
err = core_device_proxy_connect_tcp(tcp_provider, &core_device);
if (err != IdeviceSuccess) {
fprintf(stderr, "Failed to connect to CoreDeviceProxy: %d\n", err);
tcp_provider_free(tcp_provider);
return 1;
}
tcp_provider_free(tcp_provider);
// Get client parameters
uint16_t mtu;
char *address = NULL;
char *netmask = NULL;
err = core_device_proxy_get_client_parameters(core_device, &mtu, &address,
&netmask);
if (err != IdeviceSuccess) {
fprintf(stderr, "Failed to get client parameters: %d\n", err);
core_device_proxy_free(core_device);
return 1;
}
printf("Client Parameters:\n");
printf(" MTU: %d\n", mtu);
printf(" Address: %s\n", address);
printf(" Netmask: %s\n", netmask);
idevice_string_free(address);
idevice_string_free(netmask);
// Get server address
char *server_address = NULL;
err = core_device_proxy_get_server_address(core_device, &server_address);
if (err != IdeviceSuccess) {
fprintf(stderr, "Failed to get server address: %d\n", err);
core_device_proxy_free(core_device);
return 1;
}
printf("Server Address: %s\n", server_address);
idevice_string_free(server_address);
// Get server RSD port
uint16_t rsd_port;
err = core_device_proxy_get_server_rsd_port(core_device, &rsd_port);
if (err != IdeviceSuccess) {
fprintf(stderr, "Failed to get server RSD port: %d\n", err);
core_device_proxy_free(core_device);
return 1;
}
printf("Server RSD Port: %d\n", rsd_port);
// Create TCP tunnel adapter
AdapterHandle *adapter = NULL;
err = core_device_proxy_create_tcp_adapter(core_device, &adapter);
if (err != IdeviceSuccess) {
fprintf(stderr, "Failed to create TCP adapter: %d\n", err);
} else {
printf("Successfully created TCP tunnel adapter\n");
}
err = adapter_connect(adapter, rsd_port);
if (err != IdeviceSuccess) {
fprintf(stderr, "Failed to connect to RSD port: %d\n", err);
} else {
printf("Successfully connected to RSD port\n");
}
/*****************************************************************
* XPC Device Test
*****************************************************************/
printf("\n=== Testing XPC Device ===\n");
// Create XPC device
XPCDeviceAdapterHandle *xpc_device = NULL;
err = xpc_device_new(adapter, &xpc_device);
if (err != IdeviceSuccess) {
fprintf(stderr, "Failed to create XPC device: %d\n", err);
core_device_proxy_free(core_device);
return 1;
}
// List all services
char **service_names = NULL;
size_t service_count = 0;
err =
xpc_device_get_service_names(xpc_device, &service_names, &service_count);
if (err != IdeviceSuccess) {
fprintf(stderr, "Failed to get service names: %d\n", err);
xpc_device_free(xpc_device);
return 1;
}
printf("Available Services (%zu):\n", service_count);
for (size_t i = 0; i < service_count; i++) {
printf("- %s\n", service_names[i]);
// Get service details for each service
XPCServiceHandle *service = NULL;
err = xpc_device_get_service(xpc_device, service_names[i], &service);
if (err == IdeviceSuccess) {
print_service_details(service);
xpc_service_free(service);
} else {
printf(" Failed to get service details: %d\n", err);
}
}
xpc_device_free_service_names(service_names, service_count);
// Test getting a specific service
const char *test_service_name = "com.apple.internal.dt.remote.debugproxy";
XPCServiceHandle *test_service = NULL;
err = xpc_device_get_service(xpc_device, test_service_name, &test_service);
if (err == IdeviceSuccess) {
printf("\nSuccessfully retrieved service '%s':\n", test_service_name);
print_service_details(test_service);
xpc_service_free(test_service);
} else {
printf("\nFailed to get service '%s': %d\n", test_service_name, err);
}
/*****************************************************************
* Adapter return
*****************************************************************/
AdapterHandle *adapter_return = NULL;
err = xpc_device_adapter_into_inner(xpc_device, &adapter_return);
if (err != IdeviceSuccess) {
fprintf(stderr, "Failed to extract adapter: %d\n", err);
} else {
printf("Successfully extracted adapter\n");
}
err = adapter_close(adapter_return);
if (err != IdeviceSuccess) {
fprintf(stderr, "Failed to close adapter port: %d\n", err);
} else {
printf("Successfully closed adapter port\n");
}
/*****************************************************************
* Cleanup
*****************************************************************/
adapter_free(adapter_return);
printf("\nAll tests completed successfully!\n");
return 0;
}

View File

@@ -9,6 +9,7 @@ pub mod logging;
pub mod mounter;
mod pairing_file;
pub mod provider;
pub mod remotexpc;
pub mod usbmuxd;
pub mod util;

288
ffi/src/remotexpc.rs Normal file
View File

@@ -0,0 +1,288 @@
// Jackson Coxson
use std::ffi::{CStr, CString, c_char};
use idevice::{tcp::adapter::Adapter, xpc::XPCDevice};
use crate::{IdeviceErrorCode, RUNTIME, core_device_proxy::AdapterHandle};
/// Opaque handle to an XPCDevice
pub struct XPCDeviceAdapterHandle(pub XPCDevice<Adapter>);
/// Opaque handle to an XPCService
#[repr(C)]
pub struct XPCServiceHandle {
pub entitlement: *mut c_char,
pub port: u16,
pub uses_remote_xpc: bool,
pub features: *mut *mut c_char,
pub features_count: usize,
pub service_version: i64,
}
impl XPCServiceHandle {
pub fn new(
entitlement: *mut c_char,
port: u16,
uses_remote_xpc: bool,
features: *mut *mut c_char,
features_count: usize,
service_version: i64,
) -> Self {
Self {
entitlement,
port,
uses_remote_xpc,
features,
features_count,
service_version,
}
}
}
/// Creates a new XPCDevice from an adapter
///
/// # Arguments
/// * [`adapter`] - The adapter to use for communication
/// * [`device`] - Pointer to store the newly created XPCDevice handle
///
/// # Returns
/// An error code indicating success or failure
///
/// # Safety
/// `adapter` must be a valid pointer to a handle allocated by this library
/// `device` must be a valid pointer to a location where the handle will be stored
#[unsafe(no_mangle)]
pub unsafe extern "C" fn xpc_device_new(
adapter: *mut AdapterHandle,
device: *mut *mut XPCDeviceAdapterHandle,
) -> IdeviceErrorCode {
if adapter.is_null() || device.is_null() {
return IdeviceErrorCode::InvalidArg;
}
let adapter = unsafe { Box::from_raw(adapter) };
let res = RUNTIME.block_on(async move { XPCDevice::new(adapter.0).await });
match res {
// we have to unwrap res to avoid just getting a reference
Ok(_) => {
let boxed = Box::new(XPCDeviceAdapterHandle(res.unwrap()));
unsafe { *device = Box::into_raw(boxed) };
IdeviceErrorCode::IdeviceSuccess
}
Err(e) => e.into(),
}
}
/// Frees an XPCDevice handle
///
/// # Arguments
/// * [`handle`] - The handle to free
///
/// # Safety
/// `handle` must be a valid pointer to a handle allocated by this library or NULL
#[unsafe(no_mangle)]
pub unsafe extern "C" fn xpc_device_free(handle: *mut XPCDeviceAdapterHandle) {
if !handle.is_null() {
let _ = unsafe { Box::from_raw(handle) };
}
}
/// Gets a service by name from the XPCDevice
///
/// # Arguments
/// * [`handle`] - The XPCDevice handle
/// * [`service_name`] - The name of the service to get
/// * [`service`] - Pointer to store the newly created XPCService handle
///
/// # 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 null-terminated C string
/// `service` must be a valid pointer to a location where the handle will be stored
#[unsafe(no_mangle)]
pub unsafe extern "C" fn xpc_device_get_service(
handle: *mut XPCDeviceAdapterHandle,
service_name: *const c_char,
service: *mut *mut XPCServiceHandle,
) -> IdeviceErrorCode {
if handle.is_null() || service_name.is_null() || service.is_null() {
return IdeviceErrorCode::InvalidArg;
}
let device = unsafe { &(*handle).0 };
let service_name_cstr = unsafe { CStr::from_ptr(service_name) };
let service_name = match service_name_cstr.to_str() {
Ok(s) => s,
Err(_) => return IdeviceErrorCode::InvalidArg,
};
let xpc_service = match device.services.get(service_name) {
Some(s) => s,
None => return IdeviceErrorCode::ServiceNotFound,
};
// Convert features to C array if they exist
let (features_ptr, features_count) = if let Some(features) = &xpc_service.features {
let mut features_vec: Vec<*mut c_char> = features
.iter()
.map(|f| CString::new(f.as_str()).unwrap().into_raw())
.collect();
features_vec.shrink_to_fit();
let mut features_vec = Box::new(features_vec);
let features_ptr = features_vec.as_mut_ptr();
let features_len = features_vec.len();
Box::leak(features_vec);
(features_ptr, features_len)
} else {
(std::ptr::null_mut(), 0)
};
let boxed = Box::new(XPCServiceHandle {
entitlement: CString::new(xpc_service.entitlement.as_str())
.unwrap()
.into_raw(),
port: xpc_service.port,
uses_remote_xpc: xpc_service.uses_remote_xpc,
features: features_ptr,
features_count,
service_version: xpc_service.service_version.unwrap_or(-1),
});
unsafe { *service = Box::into_raw(boxed) };
IdeviceErrorCode::IdeviceSuccess
}
/// Returns the adapter in the RemoteXPC Device
///
/// # Arguments
/// * [`handle`] - The handle to get the adapter from
/// * [`adapter`] - The newly allocated AdapterHandle
///
/// # Safety
/// `handle` must be a valid pointer to a handle allocated by this library or NULL, and never used again
#[unsafe(no_mangle)]
pub unsafe extern "C" fn xpc_device_adapter_into_inner(
handle: *mut XPCDeviceAdapterHandle,
adapter: *mut *mut AdapterHandle,
) -> IdeviceErrorCode {
if handle.is_null() {
return IdeviceErrorCode::InvalidArg;
}
let device = unsafe { Box::from_raw(handle).0 };
let adapter_obj = device.into_inner();
let boxed = Box::new(AdapterHandle(adapter_obj));
unsafe { *adapter = Box::into_raw(boxed) };
IdeviceErrorCode::IdeviceSuccess
}
/// Frees an XPCService handle
///
/// # Arguments
/// * [`handle`] - The handle to free
///
/// # Safety
/// `handle` must be a valid pointer to a handle allocated by this library or NULL
#[unsafe(no_mangle)]
pub unsafe extern "C" fn xpc_service_free(handle: *mut XPCServiceHandle) {
if !handle.is_null() {
let handle = unsafe { Box::from_raw(handle) };
// Free the entitlement string
if !handle.entitlement.is_null() {
let _ = unsafe { CString::from_raw(handle.entitlement) };
}
// Free the features array
if !handle.features.is_null() {
for i in 0..handle.features_count {
let feature_ptr = unsafe { *handle.features.add(i) };
if !feature_ptr.is_null() {
let _ = unsafe { CString::from_raw(feature_ptr) };
}
}
let _ = unsafe {
Vec::from_raw_parts(
handle.features,
handle.features_count,
handle.features_count,
)
};
}
}
}
/// Gets the list of available service names
///
/// # Arguments
/// * [`handle`] - The XPCDevice handle
/// * [`names`] - Pointer to store the array of service names
/// * [`count`] - Pointer to store the number of services
///
/// # Returns
/// An error code indicating success or failure
///
/// # Safety
/// `handle` must be a valid pointer to a handle allocated by this library
/// `names` must be a valid pointer to a location where the array will be stored
/// `count` must be a valid pointer to a location where the count will be stored
#[unsafe(no_mangle)]
pub unsafe extern "C" fn xpc_device_get_service_names(
handle: *mut XPCDeviceAdapterHandle,
names: *mut *mut *mut c_char,
count: *mut usize,
) -> IdeviceErrorCode {
if handle.is_null() || names.is_null() || count.is_null() {
return IdeviceErrorCode::InvalidArg;
}
let device = unsafe { &(*handle).0 };
let service_names: Vec<CString> = device
.services
.keys()
.map(|k| CString::new(k.as_str()).unwrap())
.collect();
let mut name_ptrs: Vec<*mut c_char> = service_names.into_iter().map(|s| s.into_raw()).collect();
if name_ptrs.is_empty() {
unsafe {
*names = std::ptr::null_mut();
*count = 0;
}
} else {
name_ptrs.shrink_to_fit();
unsafe {
*names = name_ptrs.as_mut_ptr();
*count = name_ptrs.len();
}
std::mem::forget(name_ptrs);
}
IdeviceErrorCode::IdeviceSuccess
}
/// Frees a list of service names
///
/// # Arguments
/// * [`names`] - The array of service names to free
/// * [`count`] - The number of services in the array
///
/// # Safety
/// `names` must be a valid pointer to an array of `count` C strings
#[unsafe(no_mangle)]
pub unsafe extern "C" fn xpc_device_free_service_names(names: *mut *mut c_char, count: usize) {
if !names.is_null() && count > 0 {
let names_vec = unsafe { Vec::from_raw_parts(names, count, count) };
for name in names_vec {
if !name.is_null() {
let _ = unsafe { CString::from_raw(name) };
}
}
}
}