Merge pull request #9 from jkcoxson/xpc-rewrite

Xpc rewrite
This commit is contained in:
Jackson Coxson
2025-05-26 20:40:00 -06:00
committed by GitHub
61 changed files with 2842 additions and 3607 deletions

12
Cargo.lock generated
View File

@@ -112,17 +112,6 @@ dependencies = [
"pin-project-lite",
]
[[package]]
name = "async-recursion"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.101",
]
[[package]]
name = "async-task"
version = "4.7.1"
@@ -1186,7 +1175,6 @@ dependencies = [
name = "idevice"
version = "0.1.33"
dependencies = [
"async-recursion",
"base64",
"byteorder",
"bytes",

View File

@@ -5,7 +5,7 @@ edition = "2024"
[dependencies]
idevice = { path = "../idevice", features = ["full"] }
idevice = { path = "../idevice" }
log = "0.4.26"
simplelog = "0.12.2"
once_cell = "1.21.1"
@@ -14,6 +14,55 @@ libc = "0.2.171"
plist = "1.7.1"
plist_plus = { version = "0.2.6", features = ["dynamic"] }
[features]
afc = ["idevice/afc"]
amfi = ["idevice/amfi"]
core_device_proxy = ["idevice/core_device_proxy"]
crashreportcopymobile = ["idevice/crashreportcopymobile"]
debug_proxy = ["idevice/debug_proxy"]
dvt = ["idevice/dvt"]
heartbeat = ["idevice/heartbeat"]
house_arrest = ["idevice/house_arrest"]
installation_proxy = ["idevice/installation_proxy"]
springboardservices = ["idevice/springboardservices"]
misagent = ["idevice/misagent"]
mobile_image_mounter = ["idevice/mobile_image_mounter"]
location_simulation = ["idevice/location_simulation"]
pair = ["idevice/pair"]
rsd = ["idevice/rsd"]
syslog_relay = ["idevice/syslog_relay"]
tcp = ["idevice/tcp"]
tunnel_tcp_stack = ["idevice/tunnel_tcp_stack"]
tss = ["idevice/tss"]
tunneld = ["idevice/tunneld"]
usbmuxd = ["idevice/usbmuxd"]
xpc = ["idevice/xpc"]
full = [
"afc",
"amfi",
"core_device_proxy",
"crashreportcopymobile",
"debug_proxy",
"dvt",
"heartbeat",
"house_arrest",
"installation_proxy",
"misagent",
"mobile_image_mounter",
"pair",
"usbmuxd",
"xpc",
"location_simulation",
"rsd",
"tcp",
"tunnel_tcp_stack",
"tss",
"tunneld",
"springboardservices",
"syslog_relay",
]
default = ["full"]
[build-dependencies]
cbindgen = "0.28.0"

View File

@@ -11,7 +11,6 @@ set(EXAMPLES_DIR ${CMAKE_SOURCE_DIR}/../examples)
# Find all C example files
file(GLOB EXAMPLE_SOURCES ${EXAMPLES_DIR}/*.c)
find_package(OpenSSL REQUIRED)
# Create an executable for each example file
foreach(EXAMPLE_FILE ${EXAMPLE_SOURCES})
@@ -27,14 +26,10 @@ foreach(EXAMPLE_FILE ${EXAMPLE_SOURCES})
# Link the static Rust library
target_link_libraries(${EXAMPLE_NAME} PRIVATE ${STATIC_LIB})
# Link OpenSSL
target_link_libraries(${EXAMPLE_NAME} PRIVATE OpenSSL::SSL OpenSSL::Crypto)
# libplist
if( APPLE )
# use static linking
pkg_search_module(PLIST REQUIRED libplist-2.0)
find_library( LIBPLIST libplist-2.0.a REQUIRED )
message( STATUS "(Static linking) LIBPLIST " ${LIBPLIST} )
target_link_libraries ( ${EXAMPLE_NAME} PRIVATE ${LIBPLIST} )

View File

@@ -169,7 +169,7 @@ int main(int argc, char **argv) {
}
// Create TCP provider
TcpProviderHandle *provider = NULL;
IdeviceProviderHandle *provider = NULL;
err = idevice_tcp_provider_new((struct sockaddr *)&addr, pairing_file,
"ImageMounterTest", &provider);
if (err != IdeviceSuccess) {
@@ -180,13 +180,13 @@ int main(int argc, char **argv) {
// Connect to AFC service
AfcClientHandle *client = NULL;
err = afc_client_connect_tcp(provider, &client);
err = afc_client_connect(provider, &client);
if (err != IdeviceSuccess) {
fprintf(stderr, "Failed to connect to AFC service: %d\n", err);
tcp_provider_free(provider);
idevice_provider_free(provider);
return 1;
}
tcp_provider_free(provider);
idevice_provider_free(provider);
// Process command
int success = 1;

View File

@@ -17,7 +17,7 @@ void print_usage(const char *program_name) {
int main(int argc, char **argv) {
// Initialize logger
idevice_init_logger(Debug, Disabled, NULL);
idevice_init_logger(Info, Disabled, NULL);
if (argc < 2) {
print_usage(argv[0]);
@@ -25,7 +25,7 @@ int main(int argc, char **argv) {
}
const char *device_ip = argv[1];
const char *pairing_file = argc > 2 ? argv[2] : "pairing_file.plist";
const char *pairing_file = argc > 2 ? argv[2] : "pairing.plist";
/*****************************************************************
* CoreDeviceProxy Setup
@@ -51,7 +51,7 @@ int main(int argc, char **argv) {
}
// Create TCP provider
TcpProviderHandle *tcp_provider = NULL;
IdeviceProviderHandle *tcp_provider = NULL;
err = idevice_tcp_provider_new((struct sockaddr *)&addr, pairing,
"DebugProxyShell", &tcp_provider);
if (err != IdeviceSuccess) {
@@ -62,14 +62,13 @@ int main(int argc, char **argv) {
// Connect to CoreDeviceProxy
CoreDeviceProxyHandle *core_device = NULL;
err = core_device_proxy_connect_tcp(tcp_provider, &core_device);
err = core_device_proxy_connect(tcp_provider, &core_device);
if (err != IdeviceSuccess) {
fprintf(stderr, "Failed to connect to CoreDeviceProxy: %d\n", err);
tcp_provider_free(tcp_provider);
idevice_pairing_file_free(pairing);
idevice_provider_free(tcp_provider);
return 1;
}
tcp_provider_free(tcp_provider);
idevice_provider_free(tcp_provider);
// Get server RSD port
uint16_t rsd_port;
@@ -77,7 +76,6 @@ int main(int argc, char **argv) {
if (err != IdeviceSuccess) {
fprintf(stderr, "Failed to get server RSD port: %d\n", err);
core_device_proxy_free(core_device);
idevice_pairing_file_free(pairing);
return 1;
}
printf("Server RSD Port: %d\n", rsd_port);
@@ -92,92 +90,45 @@ int main(int argc, char **argv) {
if (err != IdeviceSuccess) {
fprintf(stderr, "Failed to create TCP adapter: %d\n", err);
core_device_proxy_free(core_device);
idevice_pairing_file_free(pairing);
return 1;
}
// Connect to RSD port
err = adapter_connect(adapter, rsd_port);
AdapterStreamHandle *stream = NULL;
err = adapter_connect(adapter, rsd_port, &stream);
if (err != IdeviceSuccess) {
fprintf(stderr, "Failed to connect to RSD port: %d\n", err);
adapter_free(adapter);
core_device_proxy_free(core_device);
idevice_pairing_file_free(pairing);
return 1;
}
printf("Successfully connected to RSD port\n");
/*****************************************************************
* XPC Device Setup
* RSD Handshake
*****************************************************************/
printf("\n=== Setting up XPC Device ===\n");
printf("\n=== Performing RSD Handshake ===\n");
XPCDeviceAdapterHandle *xpc_device = NULL;
err = xpc_device_new(adapter, &xpc_device);
RsdHandshakeHandle *handshake = NULL;
err = rsd_handshake_new(stream, &handshake);
if (err != IdeviceSuccess) {
fprintf(stderr, "Failed to create XPC device: %d\n", err);
fprintf(stderr, "Failed to perform RSD handshake: %d\n", err);
adapter_close(stream);
adapter_free(adapter);
core_device_proxy_free(core_device);
idevice_pairing_file_free(pairing);
return 1;
}
/*****************************************************************
* Get Debug Proxy Service
*****************************************************************/
printf("\n=== Getting Debug Proxy Service ===\n");
XPCServiceHandle *debug_service = NULL;
err = xpc_device_get_service(
xpc_device, "com.apple.internal.dt.remote.debugproxy", &debug_service);
if (err != IdeviceSuccess) {
fprintf(stderr, "Failed to get debug proxy service: %d\n", err);
xpc_device_free(xpc_device);
adapter_free(adapter);
core_device_proxy_free(core_device);
idevice_pairing_file_free(pairing);
return 1;
}
printf("Debug Proxy Service Port: %d\n", debug_service->port);
/*****************************************************************
* Debug Proxy Setup
*****************************************************************/
printf("\n=== Setting up Debug Proxy ===\n");
// Get the adapter back from XPC device
AdapterHandle *debug_adapter = NULL;
err = xpc_device_adapter_into_inner(xpc_device, &debug_adapter);
if (err != IdeviceSuccess) {
fprintf(stderr, "Failed to extract adapter: %d\n", err);
xpc_service_free(debug_service);
xpc_device_free(xpc_device);
core_device_proxy_free(core_device);
idevice_pairing_file_free(pairing);
return 1;
}
// Connect to debug proxy port
err = adapter_connect(debug_adapter, debug_service->port);
if (err != IdeviceSuccess) {
fprintf(stderr, "Failed to connect to debug proxy port: %d\n", err);
adapter_free(debug_adapter);
xpc_service_free(debug_service);
core_device_proxy_free(core_device);
idevice_pairing_file_free(pairing);
return 1;
}
printf("Successfully connected to debug proxy port\n");
// Create DebugProxyClient
DebugProxyAdapterHandle *debug_proxy = NULL;
err = debug_proxy_adapter_new(debug_adapter, &debug_proxy);
DebugProxyHandle *debug_proxy = NULL;
err = debug_proxy_connect_rsd(adapter, handshake, &debug_proxy);
if (err != IdeviceSuccess) {
fprintf(stderr, "Failed to create debug proxy client: %d\n", err);
adapter_free(debug_adapter);
xpc_service_free(debug_service);
core_device_proxy_free(core_device);
idevice_pairing_file_free(pairing);
rsd_handshake_free(handshake);
adapter_free(adapter);
return 1;
}
@@ -264,7 +215,8 @@ int main(int argc, char **argv) {
* Cleanup
*****************************************************************/
debug_proxy_free(debug_proxy);
xpc_service_free(debug_service);
rsd_handshake_free(handshake);
adapter_free(adapter);
printf("\nDebug session ended\n");
return 0;

View File

@@ -15,7 +15,7 @@ void print_usage(const char *program_name) {
int main(int argc, char **argv) {
// Initialize logger
idevice_init_logger(Debug, Disabled, NULL);
idevice_init_logger(Info, Disabled, NULL);
if (argc < 3) {
print_usage(argv[0]);
@@ -42,17 +42,17 @@ int main(int argc, char **argv) {
}
// Read pairing file
IdevicePairingFile *pairing = NULL;
IdeviceErrorCode err = idevice_pairing_file_read(pairing_file, &pairing);
struct IdevicePairingFile *pairing = NULL;
enum IdeviceErrorCode err = idevice_pairing_file_read(pairing_file, &pairing);
if (err != IdeviceSuccess) {
fprintf(stderr, "Failed to read pairing file: %d\n", err);
return 1;
}
// Create TCP provider
TcpProviderHandle *tcp_provider = NULL;
struct IdeviceProviderHandle *provider = NULL;
err = idevice_tcp_provider_new((struct sockaddr *)&addr, pairing,
"ProcessDebugTest", &tcp_provider);
"ProcessDebugTest", &provider);
if (err != IdeviceSuccess) {
fprintf(stderr, "Failed to create TCP provider: %d\n", err);
idevice_pairing_file_free(pairing);
@@ -60,14 +60,14 @@ int main(int argc, char **argv) {
}
// Connect to CoreDeviceProxy
CoreDeviceProxyHandle *core_device = NULL;
err = core_device_proxy_connect_tcp(tcp_provider, &core_device);
struct CoreDeviceProxyHandle *core_device = NULL;
err = core_device_proxy_connect(provider, &core_device);
if (err != IdeviceSuccess) {
fprintf(stderr, "Failed to connect to CoreDeviceProxy: %d\n", err);
tcp_provider_free(tcp_provider);
idevice_provider_free(provider);
return 1;
}
tcp_provider_free(tcp_provider);
idevice_provider_free(provider);
// Get server RSD port
uint16_t rsd_port;
@@ -84,7 +84,7 @@ int main(int argc, char **argv) {
*****************************************************************/
printf("\n=== Creating TCP Tunnel Adapter ===\n");
AdapterHandle *adapter = NULL;
struct 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);
@@ -93,46 +93,59 @@ int main(int argc, char **argv) {
}
// Connect to RSD port
err = adapter_connect(adapter, rsd_port);
struct ReadWriteOpaque *rsd_stream = NULL;
err = adapter_connect(adapter, rsd_port, &rsd_stream);
if (err != IdeviceSuccess) {
fprintf(stderr, "Failed to connect to RSD port: %d\n", err);
adapter_free(adapter);
core_device_proxy_free(core_device);
return 1;
}
printf("Successfully connected to RSD port\n");
adapter_pcap(adapter, "jit.pcap");
/*****************************************************************
* XPC Device Setup
* RSD Handshake
*****************************************************************/
printf("\n=== Setting up XPC Device ===\n");
printf("\n=== Performing RSD Handshake ===\n");
XPCDeviceAdapterHandle *xpc_device = NULL;
err = xpc_device_new(adapter, &xpc_device);
struct RsdHandshakeHandle *rsd_handshake = NULL;
err = rsd_handshake_new(rsd_stream, &rsd_handshake);
if (err != IdeviceSuccess) {
fprintf(stderr, "Failed to create XPC device: %d\n", err);
fprintf(stderr, "Failed to create RSD handshake: %d\n", err);
adapter_free(adapter);
core_device_proxy_free(core_device);
return 1;
}
// Get DebugProxy service
XPCServiceHandle *debug_service = NULL;
err = xpc_device_get_service(
xpc_device, "com.apple.internal.dt.remote.debugproxy", &debug_service);
// Get services
struct CRsdServiceArray *services = NULL;
err = rsd_get_services(rsd_handshake, &services);
if (err != IdeviceSuccess) {
fprintf(stderr, "Failed to get debug proxy service: %d\n", err);
fprintf(stderr, "Failed to get RSD services: %d\n", err);
rsd_handshake_free(rsd_handshake);
adapter_free(adapter);
return 1;
}
// Get ProcessControl service
XPCServiceHandle *pc_service = NULL;
err = xpc_device_get_service(xpc_device, "com.apple.instruments.dtservicehub",
&pc_service);
if (err != IdeviceSuccess) {
fprintf(stderr, "Failed to get process control service: %d\n", err);
xpc_device_free(xpc_device);
core_device_proxy_free(core_device);
// Find debug proxy and process control services
uint16_t debug_port = 0;
uint16_t pc_port = 0;
for (size_t i = 0; i < services->count; i++) {
struct CRsdService *service = &services->services[i];
if (strcmp(service->name, "com.apple.internal.dt.remote.debugproxy") == 0) {
debug_port = service->port;
} else if (strcmp(service->name, "com.apple.instruments.dtservicehub") ==
0) {
pc_port = service->port;
}
}
rsd_free_services(services);
if (debug_port == 0 || pc_port == 0) {
fprintf(stderr, "Required services not found\n");
adapter_free(adapter);
return 1;
}
@@ -141,46 +154,31 @@ int main(int argc, char **argv) {
*****************************************************************/
printf("\n=== Launching App ===\n");
// Get the adapter back from XPC device
AdapterHandle *pc_adapter = NULL;
err = xpc_device_adapter_into_inner(xpc_device, &pc_adapter);
if (err != IdeviceSuccess) {
fprintf(stderr, "Failed to extract adapter: %d\n", err);
xpc_device_free(xpc_device);
core_device_proxy_free(core_device);
return 1;
}
// Connect to process control port
err = adapter_connect(pc_adapter, pc_service->port);
struct ReadWriteOpaque *pc_stream = NULL;
err = adapter_connect(adapter, pc_port, &pc_stream);
if (err != IdeviceSuccess) {
fprintf(stderr, "Failed to connect to process control port: %d\n", err);
adapter_free(pc_adapter);
xpc_service_free(pc_service);
core_device_proxy_free(core_device);
adapter_free(adapter);
return 1;
}
printf("Successfully connected to process control port\n");
// Create RemoteServerClient
RemoteServerAdapterHandle *remote_server = NULL;
err = remote_server_adapter_new(pc_adapter, &remote_server);
struct RemoteServerHandle *remote_server = NULL;
err = remote_server_new(pc_stream, &remote_server);
if (err != IdeviceSuccess) {
fprintf(stderr, "Failed to create remote server: %d\n", err);
adapter_free(pc_adapter);
xpc_service_free(pc_service);
core_device_proxy_free(core_device);
adapter_free(adapter);
return 1;
}
// Create ProcessControlClient
ProcessControlAdapterHandle *process_control = NULL;
struct ProcessControlHandle *process_control = NULL;
err = process_control_new(remote_server, &process_control);
if (err != IdeviceSuccess) {
fprintf(stderr, "Failed to create process control client: %d\n", err);
remote_server_free(remote_server);
xpc_service_free(pc_service);
core_device_proxy_free(core_device);
return 1;
}
@@ -192,8 +190,6 @@ int main(int argc, char **argv) {
fprintf(stderr, "Failed to launch app: %d\n", err);
process_control_free(process_control);
remote_server_free(remote_server);
xpc_service_free(pc_service);
core_device_proxy_free(core_device);
return 1;
}
printf("Successfully launched app with PID: %" PRIu64 "\n", pid);
@@ -203,44 +199,24 @@ int main(int argc, char **argv) {
*****************************************************************/
printf("\n=== Attaching Debugger ===\n");
// Get the adapter back from the remote server
AdapterHandle *debug_adapter = NULL;
err = remote_server_adapter_into_inner(remote_server, &debug_adapter);
if (err != IdeviceSuccess) {
fprintf(stderr, "Failed to extract adapter: %d\n", err);
xpc_service_free(debug_service);
process_control_free(process_control);
remote_server_free(remote_server);
xpc_service_free(pc_service);
core_device_proxy_free(core_device);
return 1;
}
// Connect to debug proxy port
err = adapter_connect(debug_adapter, debug_service->port);
struct ReadWriteOpaque *debug_stream = NULL;
err = adapter_connect(adapter, debug_port, &debug_stream);
if (err != IdeviceSuccess) {
fprintf(stderr, "Failed to connect to debug proxy port: %d\n", err);
adapter_free(debug_adapter);
xpc_service_free(debug_service);
process_control_free(process_control);
remote_server_free(remote_server);
xpc_service_free(pc_service);
core_device_proxy_free(core_device);
return 1;
}
printf("Successfully connected to debug proxy port\n");
// Create DebugProxyClient
DebugProxyAdapterHandle *debug_proxy = NULL;
err = debug_proxy_adapter_new(debug_adapter, &debug_proxy);
struct DebugProxyHandle *debug_proxy = NULL;
err = debug_proxy_connect_rsd(adapter, rsd_handshake, &debug_proxy);
if (err != IdeviceSuccess) {
fprintf(stderr, "Failed to create debug proxy client: %d\n", err);
adapter_free(debug_adapter);
xpc_service_free(debug_service);
process_control_free(process_control);
remote_server_free(remote_server);
xpc_service_free(pc_service);
core_device_proxy_free(core_device);
return 1;
}
@@ -248,17 +224,13 @@ int main(int argc, char **argv) {
char attach_command[64];
snprintf(attach_command, sizeof(attach_command), "vAttach;%" PRIx64, pid);
DebugserverCommandHandle *attach_cmd =
struct DebugserverCommandHandle *attach_cmd =
debugserver_command_new(attach_command, NULL, 0);
if (attach_cmd == NULL) {
fprintf(stderr, "Failed to create attach command\n");
debug_proxy_free(debug_proxy);
adapter_free(debug_adapter);
xpc_service_free(debug_service);
process_control_free(process_control);
remote_server_free(remote_server);
xpc_service_free(pc_service);
core_device_proxy_free(core_device);
return 1;
}
@@ -274,7 +246,8 @@ int main(int argc, char **argv) {
}
// Send detach command
DebugserverCommandHandle *detach_cmd = debugserver_command_new("D", NULL, 0);
struct DebugserverCommandHandle *detach_cmd =
debugserver_command_new("D", NULL, 0);
if (detach_cmd == NULL) {
fprintf(stderr, "Failed to create detach command\n");
} else {
@@ -296,7 +269,10 @@ int main(int argc, char **argv) {
* Cleanup
*****************************************************************/
debug_proxy_free(debug_proxy);
xpc_service_free(debug_service);
process_control_free(process_control);
remote_server_free(remote_server);
adapter_free(adapter);
rsd_handshake_free(rsd_handshake);
printf("\nDebug session completed\n");
return 0;

View File

@@ -28,7 +28,7 @@ int main() {
}
// Create TCP provider
TcpProviderHandle *provider = NULL;
IdeviceProviderHandle *provider = NULL;
err = idevice_tcp_provider_new((struct sockaddr *)&addr, pairing_file,
"ExampleProvider", &provider);
if (err != IdeviceSuccess) {
@@ -39,13 +39,13 @@ int main() {
// Connect to installation proxy
HeartbeatClientHandle *client = NULL;
err = heartbeat_connect_tcp(provider, &client);
err = heartbeat_connect(provider, &client);
if (err != IdeviceSuccess) {
fprintf(stderr, "Failed to connect to installation proxy: %d\n", err);
tcp_provider_free(provider);
idevice_provider_free(provider);
return 1;
}
tcp_provider_free(provider);
idevice_provider_free(provider);
u_int64_t current_interval = 15;
while (1) {

View File

@@ -7,187 +7,189 @@
#include <unistd.h>
void print_usage() {
printf("Usage: ipa_installer [options] <ipa_path>\n");
printf("Options:\n");
printf(" --ip IP_ADDRESS Device IP address (default: 10.7.0.2)\n");
printf(" --pairing FILE Pairing file path (default: pairing_file.plist)\n");
printf(" --udid UDID Device UDID (optional)\n");
printf("Usage: ipa_installer [options] <ipa_path>\n");
printf("Options:\n");
printf(" --ip IP_ADDRESS Device IP address (default: 10.7.0.2)\n");
printf(" --pairing FILE Pairing file path (default: "
"pairing_file.plist)\n");
printf(" --udid UDID Device UDID (optional)\n");
}
int read_file(const char *filename, uint8_t **data, size_t *length) {
FILE *file = fopen(filename, "rb");
if (!file) {
perror("Failed to open file");
return 0;
}
FILE *file = fopen(filename, "rb");
if (!file) {
perror("Failed to open file");
return 0;
}
fseek(file, 0, SEEK_END);
*length = ftell(file);
fseek(file, 0, SEEK_SET);
*data = malloc(*length);
if (!*data) {
perror("Failed to allocate memory");
fclose(file);
return 0;
}
if (fread(*data, 1, *length, file) != *length) {
perror("Failed to read file");
free(*data);
fclose(file);
return 0;
}
fseek(file, 0, SEEK_END);
*length = ftell(file);
fseek(file, 0, SEEK_SET);
*data = malloc(*length);
if (!*data) {
perror("Failed to allocate memory");
fclose(file);
return 1;
return 0;
}
if (fread(*data, 1, *length, file) != *length) {
perror("Failed to read file");
free(*data);
fclose(file);
return 0;
}
fclose(file);
return 1;
}
int main(int argc, char **argv) {
// Initialize logger
idevice_init_logger(Debug, Disabled, NULL);
// Initialize logger
idevice_init_logger(Debug, Disabled, NULL);
// Default values
char *ip = "10.7.0.2";
char *pairing_file_path = "pairing_file.plist";
char *udid = NULL;
char *ipa_path = NULL;
// Default values
char *ip = "10.7.0.2";
char *pairing_file_path = "pairing_file.plist";
char *udid = NULL;
char *ipa_path = NULL;
// Parse arguments
for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "--ip") == 0) {
if (i + 1 >= argc) {
printf("Error: Missing IP address argument\n");
return 1;
}
ip = argv[++i];
} else if (strcmp(argv[i], "--pairing") == 0) {
if (i + 1 >= argc) {
printf("Error: Missing pairing file argument\n");
return 1;
}
pairing_file_path = argv[++i];
} else if (strcmp(argv[i], "--udid") == 0) {
if (i + 1 >= argc) {
printf("Error: Missing UDID argument\n");
return 1;
}
udid = argv[++i];
} else if (strcmp(argv[i], "help") == 0) {
print_usage();
return 0;
} else {
ipa_path = argv[i];
break;
}
}
if (!ipa_path) {
print_usage();
// Parse arguments
for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "--ip") == 0) {
if (i + 1 >= argc) {
printf("Error: Missing IP address argument\n");
return 1;
}
// Create the socket address
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(LOCKDOWN_PORT);
if (inet_pton(AF_INET, ip, &addr.sin_addr) != 1) {
fprintf(stderr, "Invalid IP address\n");
}
ip = argv[++i];
} else if (strcmp(argv[i], "--pairing") == 0) {
if (i + 1 >= argc) {
printf("Error: Missing pairing file argument\n");
return 1;
}
// Read pairing file
IdevicePairingFile *pairing_file = NULL;
IdeviceErrorCode err = idevice_pairing_file_read(pairing_file_path, &pairing_file);
if (err != IdeviceSuccess) {
fprintf(stderr, "Failed to read pairing file: %d\n", err);
}
pairing_file_path = argv[++i];
} else if (strcmp(argv[i], "--udid") == 0) {
if (i + 1 >= argc) {
printf("Error: Missing UDID argument\n");
return 1;
}
// Create TCP provider
TcpProviderHandle *provider = NULL;
err = idevice_tcp_provider_new((struct sockaddr *)&addr, pairing_file,
"IPAInstaller", &provider);
if (err != IdeviceSuccess) {
fprintf(stderr, "Failed to create TCP provider: %d\n", err);
idevice_pairing_file_free(pairing_file);
return 1;
}
// Connect to AFC service
AfcClientHandle *afc_client = NULL;
err = afc_client_connect_tcp(provider, &afc_client);
if (err != IdeviceSuccess) {
fprintf(stderr, "Failed to connect to AFC service: %d\n", err);
tcp_provider_free(provider);
return 1;
}
// Extract filename from path
char *filename = strrchr(ipa_path, '/');
if (filename == NULL) {
filename = ipa_path;
}
udid = argv[++i];
} else if (strcmp(argv[i], "help") == 0) {
print_usage();
return 0;
} else {
filename++; // Skip the '/'
ipa_path = argv[i];
break;
}
}
// Create destination path
char dest_path[256];
snprintf(dest_path, sizeof(dest_path), "/PublicStaging/%s", filename);
if (!ipa_path) {
print_usage();
return 1;
}
// Upload IPA file
printf("Uploading %s to %s...\n", ipa_path, dest_path);
uint8_t *data = NULL;
size_t length = 0;
if (!read_file(ipa_path, &data, &length)) {
fprintf(stderr, "Failed to read IPA file\n");
afc_client_free(afc_client);
return 1;
}
// Create the socket address
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(LOCKDOWN_PORT);
if (inet_pton(AF_INET, ip, &addr.sin_addr) != 1) {
fprintf(stderr, "Invalid IP address\n");
return 1;
}
AfcFileHandle *file = NULL;
err = afc_file_open(afc_client, dest_path, AfcWrOnly | AfcCreat, &file);
if (err != IdeviceSuccess) {
fprintf(stderr, "Failed to open destination file: %d\n", err);
free(data);
afc_client_free(afc_client);
return 1;
}
// Read pairing file
IdevicePairingFile *pairing_file = NULL;
IdeviceErrorCode err =
idevice_pairing_file_read(pairing_file_path, &pairing_file);
if (err != IdeviceSuccess) {
fprintf(stderr, "Failed to read pairing file: %d\n", err);
return 1;
}
err = afc_file_write(file, data, length);
free(data);
afc_file_close(file);
if (err != IdeviceSuccess) {
fprintf(stderr, "Failed to write file: %d\n", err);
afc_client_free(afc_client);
return 1;
}
printf("Upload completed successfully\n");
// Create TCP provider
IdeviceProviderHandle *provider = NULL;
err = idevice_tcp_provider_new((struct sockaddr *)&addr, pairing_file,
"IPAInstaller", &provider);
if (err != IdeviceSuccess) {
fprintf(stderr, "Failed to create TCP provider: %d\n", err);
idevice_pairing_file_free(pairing_file);
return 1;
}
// Connect to installation proxy
InstallationProxyClientHandle *instproxy_client = NULL;
err = installation_proxy_connect_tcp(provider, &instproxy_client);
if (err != IdeviceSuccess) {
fprintf(stderr, "Failed to connect to installation proxy: %d\n", err);
afc_client_free(afc_client);
return 1;
}
// Connect to AFC service
AfcClientHandle *afc_client = NULL;
err = afc_client_connect(provider, &afc_client);
if (err != IdeviceSuccess) {
fprintf(stderr, "Failed to connect to AFC service: %d\n", err);
idevice_provider_free(provider);
return 1;
}
// Install the uploaded IPA
printf("Installing %s...\n", dest_path);
err = installation_proxy_install(instproxy_client, dest_path, NULL);
if (err != IdeviceSuccess) {
fprintf(stderr, "Failed to install IPA: %d\n", err);
} else {
printf("Installation completed successfully\n");
}
// Extract filename from path
char *filename = strrchr(ipa_path, '/');
if (filename == NULL) {
filename = ipa_path;
} else {
filename++; // Skip the '/'
}
// Cleanup
installation_proxy_client_free(instproxy_client);
// Create destination path
char dest_path[256];
snprintf(dest_path, sizeof(dest_path), "/PublicStaging/%s", filename);
// Upload IPA file
printf("Uploading %s to %s...\n", ipa_path, dest_path);
uint8_t *data = NULL;
size_t length = 0;
if (!read_file(ipa_path, &data, &length)) {
fprintf(stderr, "Failed to read IPA file\n");
afc_client_free(afc_client);
tcp_provider_free(provider);
return 1;
}
return err == IdeviceSuccess ? 0 : 1;
AfcFileHandle *file = NULL;
err = afc_file_open(afc_client, dest_path, AfcWrOnly, &file);
if (err != IdeviceSuccess) {
fprintf(stderr, "Failed to open destination file: %d\n", err);
free(data);
afc_client_free(afc_client);
return 1;
}
err = afc_file_write(file, data, length);
free(data);
afc_file_close(file);
if (err != IdeviceSuccess) {
fprintf(stderr, "Failed to write file: %d\n", err);
afc_client_free(afc_client);
return 1;
}
printf("Upload completed successfully\n");
// Connect to installation proxy
InstallationProxyClientHandle *instproxy_client = NULL;
err = installation_proxy_connect_tcp(provider, &instproxy_client);
if (err != IdeviceSuccess) {
fprintf(stderr, "Failed to connect to installation proxy: %d\n", err);
afc_client_free(afc_client);
return 1;
}
// Install the uploaded IPA
printf("Installing %s...\n", dest_path);
err = installation_proxy_install(instproxy_client, dest_path, NULL);
if (err != IdeviceSuccess) {
fprintf(stderr, "Failed to install IPA: %d\n", err);
} else {
printf("Installation completed successfully\n");
}
// Cleanup
installation_proxy_client_free(instproxy_client);
afc_client_free(afc_client);
idevice_provider_free(provider);
return err == IdeviceSuccess ? 0 : 1;
}

View File

@@ -27,7 +27,7 @@ int main() {
}
// Create TCP provider
TcpProviderHandle *provider = NULL;
IdeviceProviderHandle *provider = NULL;
err = idevice_tcp_provider_new((struct sockaddr *)&addr, pairing_file,
"ExampleProvider", &provider);
if (err != IdeviceSuccess) {
@@ -41,7 +41,7 @@ int main() {
err = installation_proxy_connect_tcp(provider, &client);
if (err != IdeviceSuccess) {
fprintf(stderr, "Failed to connect to installation proxy: %d\n", err);
tcp_provider_free(provider);
idevice_provider_free(provider);
return 1;
}
@@ -56,7 +56,7 @@ int main() {
if (err != IdeviceSuccess) {
fprintf(stderr, "Failed to get apps: %d\n", err);
installation_proxy_client_free(client);
tcp_provider_free(provider);
idevice_provider_free(provider);
return 1;
}
@@ -79,7 +79,7 @@ int main() {
// Cleanup
installation_proxy_client_free(client);
tcp_provider_free(provider);
idevice_provider_free(provider);
return 0;
}

View File

@@ -40,7 +40,7 @@ int main(int argc, char **argv) {
}
// Create TCP provider
TcpProviderHandle *tcp_provider = NULL;
IdeviceProviderHandle *tcp_provider = NULL;
err = idevice_tcp_provider_new((struct sockaddr *)&addr, pairing,
"LocationSimCLI", &tcp_provider);
if (err != IdeviceSuccess) {
@@ -51,8 +51,8 @@ int main(int argc, char **argv) {
// Connect to CoreDeviceProxy
CoreDeviceProxyHandle *core_device = NULL;
err = core_device_proxy_connect_tcp(tcp_provider, &core_device);
tcp_provider_free(tcp_provider);
err = core_device_proxy_connect(tcp_provider, &core_device);
idevice_provider_free(tcp_provider);
idevice_pairing_file_free(pairing);
if (err != IdeviceSuccess) {
fprintf(stderr, "Failed to connect to CoreDeviceProxy: %d\n", err);
@@ -77,62 +77,36 @@ int main(int argc, char **argv) {
return 1;
}
err = adapter_connect(adapter, rsd_port);
// Connect to RSD port
AdapterStreamHandle *stream = NULL;
err = adapter_connect(adapter, rsd_port, &stream);
if (err != IdeviceSuccess) {
fprintf(stderr, "Failed to connect to RSD port: %d\n", err);
adapter_free(adapter);
return 1;
}
// Create XPC device
XPCDeviceAdapterHandle *xpc_device = NULL;
err = xpc_device_new(adapter, &xpc_device);
RsdHandshakeHandle *handshake = NULL;
err = rsd_handshake_new(stream, &handshake);
if (err != IdeviceSuccess) {
fprintf(stderr, "Failed to create XPC device: %d\n", err);
fprintf(stderr, "Failed to perform RSD handshake: %d\n", err);
adapter_close(stream);
adapter_free(adapter);
return 1;
}
// Get debug proxy service
XPCServiceHandle *dvt_service = NULL;
err = xpc_device_get_service(
xpc_device, "com.apple.instruments.server.services.LocationSimulation",
&dvt_service);
if (err != IdeviceSuccess) {
fprintf(stderr, "Failed to get DVT service: %d\n", err);
xpc_device_free(xpc_device);
return 1;
}
// Reuse the adapter and connect to debug proxy port
AdapterHandle *debug_adapter = NULL;
err = xpc_device_adapter_into_inner(xpc_device, &debug_adapter);
xpc_device_free(xpc_device);
xpc_service_free(dvt_service);
if (err != IdeviceSuccess) {
fprintf(stderr, "Failed to extract adapter: %d\n", err);
return 1;
}
adapter_close(debug_adapter);
err = adapter_connect(debug_adapter, dvt_service->port);
if (err != IdeviceSuccess) {
fprintf(stderr, "Failed to connect to debug proxy port: %d\n", err);
adapter_free(debug_adapter);
return 1;
}
// Create RemoteServerClient
RemoteServerAdapterHandle *remote_server = NULL;
err = remote_server_adapter_new(debug_adapter, &remote_server);
RemoteServerHandle *remote_server = NULL;
err = remote_server_connect_rsd(adapter, handshake, &remote_server);
if (err != IdeviceSuccess) {
fprintf(stderr, "Failed to create remote server: %d\n", err);
adapter_free(debug_adapter);
adapter_free(adapter);
rsd_handshake_free(handshake);
return 1;
}
// Create LocationSimulationClient
LocationSimulationAdapterHandle *location_sim = NULL;
LocationSimulationHandle *location_sim = NULL;
err = location_simulation_new(remote_server, &location_sim);
if (err != IdeviceSuccess) {
fprintf(stderr, "Failed to create location simulation client: %d\n", err);

View File

@@ -29,7 +29,7 @@ int main() {
}
// Create TCP provider
TcpProviderHandle *provider = NULL;
IdeviceProviderHandle *provider = NULL;
err = idevice_tcp_provider_new((struct sockaddr *)&addr, pairing_file,
"LockdowndTest", &provider);
if (err != IdeviceSuccess) {
@@ -40,10 +40,10 @@ int main() {
// Connect to lockdownd
LockdowndClientHandle *client = NULL;
err = lockdownd_connect_tcp(provider, &client);
err = lockdownd_connect(provider, &client);
if (err != IdeviceSuccess) {
fprintf(stderr, "Failed to connect to lockdownd: %d\n", err);
tcp_provider_free(provider);
idevice_provider_free(provider);
return 1;
}
@@ -60,7 +60,7 @@ int main() {
if (err != IdeviceSuccess) {
fprintf(stderr, "Failed to start session: %d\n", err);
lockdownd_client_free(client);
tcp_provider_free(provider);
idevice_provider_free(provider);
return 1;
}
@@ -159,7 +159,7 @@ int main() {
// Cleanup
lockdownd_client_free(client);
tcp_provider_free(provider);
idevice_provider_free(provider);
return 0;
}

View File

@@ -99,7 +99,7 @@ int main(int argc, char **argv) {
}
// Create TCP provider
TcpProviderHandle *provider = NULL;
IdeviceProviderHandle *provider = NULL;
err = idevice_tcp_provider_new((struct sockaddr *)&addr, pairing_file,
"ImageMounterTest", &provider);
if (err != IdeviceSuccess) {
@@ -124,10 +124,10 @@ int main(int argc, char **argv) {
// Connect to lockdownd
LockdowndClientHandle *lockdown_client = NULL;
err = lockdownd_connect_tcp(provider, &lockdown_client);
err = lockdownd_connect(provider, &lockdown_client);
if (err != IdeviceSuccess) {
fprintf(stderr, "Failed to connect to lockdownd: %d\n", err);
tcp_provider_free(provider);
idevice_provider_free(provider);
free(image);
free(trustcache);
free(build_manifest);
@@ -139,7 +139,7 @@ int main(int argc, char **argv) {
if (err != IdeviceSuccess) {
fprintf(stderr, "Failed to start session: %d\n", err);
lockdownd_client_free(lockdown_client);
tcp_provider_free(provider);
idevice_provider_free(provider);
idevice_pairing_file_free(pairing_file_2);
free(image);
free(trustcache);
@@ -155,7 +155,7 @@ int main(int argc, char **argv) {
if (err != IdeviceSuccess) {
fprintf(stderr, "Failed to get UniqueChipID: %d\n", err);
lockdownd_client_free(lockdown_client);
tcp_provider_free(provider);
idevice_provider_free(provider);
free(image);
free(trustcache);
free(build_manifest);
@@ -168,11 +168,11 @@ int main(int argc, char **argv) {
// Connect to image mounter
ImageMounterHandle *mounter_client = NULL;
err = image_mounter_connect_tcp(provider, &mounter_client);
err = image_mounter_connect(provider, &mounter_client);
if (err != IdeviceSuccess) {
fprintf(stderr, "Failed to connect to image mounter: %d\n", err);
lockdownd_client_free(lockdown_client);
tcp_provider_free(provider);
idevice_provider_free(provider);
free(image);
free(trustcache);
free(build_manifest);
@@ -180,7 +180,7 @@ int main(int argc, char **argv) {
}
// Mount personalized image with progress callback
err = image_mounter_mount_personalized_tcp_with_callback(
err = image_mounter_mount_personalized_with_callback(
mounter_client, provider, image, image_len, trustcache, trustcache_len,
build_manifest, manifest_len,
NULL, // info_plist
@@ -195,7 +195,7 @@ int main(int argc, char **argv) {
// Cleanup
image_mounter_free(mounter_client);
lockdownd_client_free(lockdown_client);
tcp_provider_free(provider);
idevice_provider_free(provider);
free(image);
free(trustcache);
free(build_manifest);

View File

@@ -112,7 +112,7 @@ int main(int argc, char **argv) {
}
// Create TCP provider
TcpProviderHandle *provider = NULL;
IdeviceProviderHandle *provider = NULL;
err = idevice_tcp_provider_new((struct sockaddr *)&addr, pairing_file,
"ImageMounterTest", &provider);
if (err != IdeviceSuccess) {
@@ -123,13 +123,13 @@ int main(int argc, char **argv) {
// Connect to image mounter
ImageMounterHandle *client = NULL;
err = image_mounter_connect_tcp(provider, &client);
err = image_mounter_connect(provider, &client);
if (err != IdeviceSuccess) {
fprintf(stderr, "Failed to connect to image mounter: %d\n", err);
tcp_provider_free(provider);
idevice_provider_free(provider);
return 1;
}
tcp_provider_free(provider);
idevice_provider_free(provider);
// Process command
int success = 1;

View File

@@ -43,7 +43,7 @@ int main(int argc, char **argv) {
printf("=== Setting up CoreDeviceProxy ===\n");
// Create TCP provider
TcpProviderHandle *tcp_provider = NULL;
IdeviceProviderHandle *tcp_provider = NULL;
err = idevice_tcp_provider_new((struct sockaddr *)&addr, pairing,
"ProcessControlTest", &tcp_provider);
if (err != IdeviceSuccess) {
@@ -54,14 +54,14 @@ int main(int argc, char **argv) {
// Connect to CoreDeviceProxy
CoreDeviceProxyHandle *core_device = NULL;
err = core_device_proxy_connect_tcp(tcp_provider, &core_device);
err = core_device_proxy_connect(tcp_provider, &core_device);
if (err != IdeviceSuccess) {
fprintf(stderr, "Failed to connect to CoreDeviceProxy: %d\n", err);
tcp_provider_free(tcp_provider);
idevice_provider_free(tcp_provider);
idevice_pairing_file_free(pairing);
return 1;
}
tcp_provider_free(tcp_provider);
idevice_provider_free(tcp_provider);
// Get server RSD port
uint16_t rsd_port;
@@ -89,89 +89,37 @@ int main(int argc, char **argv) {
}
// Connect to RSD port
err = adapter_connect(adapter, rsd_port);
AdapterStreamHandle *stream = NULL;
err = adapter_connect(adapter, rsd_port, &stream);
if (err != IdeviceSuccess) {
fprintf(stderr, "Failed to connect to RSD port: %d\n", err);
adapter_free(adapter);
core_device_proxy_free(core_device);
idevice_pairing_file_free(pairing);
return 1;
}
printf("Successfully connected to RSD port\n");
/*****************************************************************
* XPC Device Setup
*****************************************************************/
printf("\n=== Setting up XPC Device ===\n");
XPCDeviceAdapterHandle *xpc_device = NULL;
err = xpc_device_new(adapter, &xpc_device);
RsdHandshakeHandle *handshake = NULL;
err = rsd_handshake_new(stream, &handshake);
if (err != IdeviceSuccess) {
fprintf(stderr, "Failed to create XPC device: %d\n", err);
fprintf(stderr, "Failed to perform RSD handshake: %d\n", err);
adapter_close(stream);
adapter_free(adapter);
core_device_proxy_free(core_device);
idevice_pairing_file_free(pairing);
return 1;
}
/*****************************************************************
* Get DVT Service
*****************************************************************/
printf("\n=== Getting Debug Proxy Service ===\n");
XPCServiceHandle *dvt_service = NULL;
err = xpc_device_get_service(xpc_device, "com.apple.instruments.dtservicehub",
&dvt_service);
if (err != IdeviceSuccess) {
fprintf(stderr, "Failed to get DVT service: %d\n", err);
xpc_device_free(xpc_device);
return 1;
}
printf("Debug Proxy Service Port: %d\n", dvt_service->port);
/*****************************************************************
* Remote Server Setup
*****************************************************************/
printf("\n=== Setting up Remote Server ===\n");
// Get the adapter back from XPC device
AdapterHandle *debug_adapter = NULL;
err = xpc_device_adapter_into_inner(xpc_device, &debug_adapter);
if (err != IdeviceSuccess) {
fprintf(stderr, "Failed to extract adapter: %d\n", err);
xpc_service_free(dvt_service);
xpc_device_free(xpc_device);
return 1;
}
// Connect to debug proxy port
adapter_close(debug_adapter);
err = adapter_connect(debug_adapter, dvt_service->port);
if (err != IdeviceSuccess) {
fprintf(stderr, "Failed to connect to debug proxy port: %d\n", err);
adapter_free(debug_adapter);
xpc_service_free(dvt_service);
return 1;
}
printf("Successfully connected to debug proxy port\n");
// Create RemoteServerClient
RemoteServerAdapterHandle *remote_server = NULL;
err = remote_server_adapter_new(debug_adapter, &remote_server);
RemoteServerHandle *remote_server = NULL;
err = remote_server_connect_rsd(adapter, handshake, &remote_server);
if (err != IdeviceSuccess) {
fprintf(stderr, "Failed to create remote server: %d\n", err);
adapter_free(debug_adapter);
xpc_service_free(dvt_service);
adapter_free(adapter);
rsd_handshake_free(handshake);
return 1;
}
/*****************************************************************
* Process Control Test
*****************************************************************/
printf("\n=== Testing Process Control ===\n");
// Create ProcessControlClient
ProcessControlAdapterHandle *process_control = NULL;
ProcessControlHandle *process_control = NULL;
err = process_control_new(remote_server, &process_control);
if (err != IdeviceSuccess) {
fprintf(stderr, "Failed to create process control client: %d\n", err);

View File

@@ -1,204 +0,0 @@
// 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

@@ -1,35 +1,49 @@
// Jackson Coxson
use std::ffi::{CString, c_char};
use std::ffi::{CStr, c_char};
use idevice::tcp::stream::AdapterStream;
use crate::core_device_proxy::AdapterHandle;
use crate::{IdeviceErrorCode, RUNTIME};
use crate::{IdeviceErrorCode, RUNTIME, ReadWriteOpaque};
pub struct AdapterStreamHandle<'a>(pub AdapterStream<'a>);
/// Connects the adapter to a specific port
///
/// # Arguments
/// * [`handle`] - The adapter handle
/// * [`adapter_handle`] - The adapter handle
/// * [`port`] - The port to connect to
/// * [`stream_handle`] - A pointer to allocate the new stream to
///
/// # Returns
/// An error code indicating success or failure
///
/// # Safety
/// `handle` must be a valid pointer to a handle allocated by this library
/// `handle` must be a valid pointer to a handle allocated by this library.
/// Any stream allocated must be used in the same thread as the adapter. The handles are NOT thread
/// safe.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn adapter_connect(
handle: *mut AdapterHandle,
adapter_handle: *mut AdapterHandle,
port: u16,
stream_handle: *mut *mut ReadWriteOpaque,
) -> IdeviceErrorCode {
if handle.is_null() {
if adapter_handle.is_null() || stream_handle.is_null() {
return IdeviceErrorCode::InvalidArg;
}
let adapter = unsafe { &mut (*handle).0 };
let res = RUNTIME.block_on(async move { adapter.connect(port).await });
let adapter = unsafe { &mut (*adapter_handle).0 };
let res = RUNTIME.block_on(async move { AdapterStream::connect(adapter, port).await });
match res {
Ok(_) => IdeviceErrorCode::IdeviceSuccess,
Ok(r) => {
let boxed = Box::new(ReadWriteOpaque {
inner: Some(Box::new(r)),
});
unsafe { *stream_handle = Box::into_raw(boxed) };
IdeviceErrorCode::IdeviceSuccess
}
Err(e) => {
log::error!("Adapter connect failed: {}", e);
IdeviceErrorCode::AdapterIOFailed
@@ -59,7 +73,7 @@ pub unsafe extern "C" fn adapter_pcap(
}
let adapter = unsafe { &mut (*handle).0 };
let c_str = unsafe { CString::from_raw(path as *mut c_char) };
let c_str = unsafe { CStr::from_ptr(path) };
let path_str = match c_str.to_str() {
Ok(s) => s,
Err(_) => return IdeviceErrorCode::InvalidArg,
@@ -79,7 +93,7 @@ pub unsafe extern "C" fn adapter_pcap(
/// Closes the adapter connection
///
/// # Arguments
/// * [`handle`] - The adapter handle
/// * [`handle`] - The adapter stream handle
///
/// # Returns
/// An error code indicating success or failure
@@ -87,7 +101,7 @@ pub unsafe extern "C" fn adapter_pcap(
/// # Safety
/// `handle` must be a valid pointer to a handle allocated by this library
#[unsafe(no_mangle)]
pub unsafe extern "C" fn adapter_close(handle: *mut AdapterHandle) -> IdeviceErrorCode {
pub unsafe extern "C" fn adapter_close(handle: *mut AdapterStreamHandle) -> IdeviceErrorCode {
if handle.is_null() {
return IdeviceErrorCode::InvalidArg;
}
@@ -119,7 +133,7 @@ pub unsafe extern "C" fn adapter_close(handle: *mut AdapterHandle) -> IdeviceErr
/// `data` must be a valid pointer to at least `length` bytes
#[unsafe(no_mangle)]
pub unsafe extern "C" fn adapter_send(
handle: *mut AdapterHandle,
handle: *mut AdapterStreamHandle,
data: *const u8,
length: usize,
) -> IdeviceErrorCode {
@@ -158,7 +172,7 @@ pub unsafe extern "C" fn adapter_send(
/// `length` must be a valid pointer to a usize
#[unsafe(no_mangle)]
pub unsafe extern "C" fn adapter_recv(
handle: *mut AdapterHandle,
handle: *mut AdapterStreamHandle,
data: *mut u8,
length: *mut usize,
max_length: usize,

View File

@@ -1,23 +1,19 @@
// Jackson Coxson
use std::ffi::c_void;
use idevice::{
IdeviceError, IdeviceService,
afc::{AfcClient, DeviceInfo, FileInfo},
provider::IdeviceProvider,
};
use crate::{
IdeviceErrorCode, IdeviceHandle, RUNTIME,
provider::{TcpProviderHandle, UsbmuxdProviderHandle},
};
use crate::{IdeviceErrorCode, IdeviceHandle, RUNTIME, provider::IdeviceProviderHandle};
pub struct AfcClientHandle(pub AfcClient);
/// Connects to the AFC service using a TCP provider
///
/// # Arguments
/// * [`provider`] - A TcpProvider
/// * [`provider`] - An IdeviceProvider
/// * [`client`] - On success, will be set to point to a newly allocated AfcClient handle
///
/// # Returns
@@ -27,8 +23,8 @@ pub struct AfcClientHandle(pub AfcClient);
/// `provider` must be a valid pointer to a handle allocated by this library
/// `client` must be a valid, non-null pointer to a location where the handle will be stored
#[unsafe(no_mangle)]
pub unsafe extern "C" fn afc_client_connect_tcp(
provider: *mut TcpProviderHandle,
pub unsafe extern "C" fn afc_client_connect(
provider: *mut IdeviceProviderHandle,
client: *mut *mut AfcClientHandle,
) -> IdeviceErrorCode {
if provider.is_null() || client.is_null() {
@@ -36,55 +32,10 @@ pub unsafe extern "C" fn afc_client_connect_tcp(
return IdeviceErrorCode::InvalidArg;
}
let res: Result<AfcClient, IdeviceError> = RUNTIME.block_on(async move {
let provider_box = unsafe { Box::from_raw(provider) };
let provider_ref = &provider_box.0;
let result = AfcClient::connect(provider_ref).await;
std::mem::forget(provider_box);
result
});
let res = RUNTIME.block_on(async {
let provider_ref: &dyn IdeviceProvider = unsafe { &*(*provider).0 };
match res {
Ok(r) => {
let boxed = Box::new(AfcClientHandle(r));
unsafe { *client = Box::into_raw(boxed) };
IdeviceErrorCode::IdeviceSuccess
}
Err(e) => {
let _ = unsafe { Box::from_raw(provider) };
e.into()
}
}
}
/// Connects to the AFC service using a Usbmuxd provider
///
/// # Arguments
/// * [`provider`] - A UsbmuxdProvider
/// * [`client`] - On success, will be set to point to a newly allocated AfcClient handle
///
/// # Returns
/// An error code indicating success or failure
///
/// # Safety
/// `provider` must be a valid pointer to a handle allocated by this library
/// `client` must be a valid, non-null pointer to a location where the handle will be stored
#[unsafe(no_mangle)]
pub unsafe extern "C" fn afc_client_connect_usbmuxd(
provider: *mut UsbmuxdProviderHandle,
client: *mut *mut AfcClientHandle,
) -> IdeviceErrorCode {
if provider.is_null() {
log::error!("Provider is null");
return IdeviceErrorCode::InvalidArg;
}
let res: Result<AfcClient, IdeviceError> = RUNTIME.block_on(async move {
let provider_box = unsafe { Box::from_raw(provider) };
let provider_ref = &provider_box.0;
let result = AfcClient::connect(provider_ref).await;
std::mem::forget(provider_box);
result
AfcClient::connect(provider_ref).await
});
match res {
@@ -114,7 +65,7 @@ pub unsafe extern "C" fn afc_client_new(
socket: *mut IdeviceHandle,
client: *mut *mut AfcClientHandle,
) -> IdeviceErrorCode {
if socket.is_null() {
if socket.is_null() || client.is_null() {
return IdeviceErrorCode::InvalidArg;
}
let socket = unsafe { Box::from_raw(socket) }.0;
@@ -231,7 +182,7 @@ pub unsafe extern "C" fn afc_make_directory(
client: *mut AfcClientHandle,
path: *const libc::c_char,
) -> IdeviceErrorCode {
if path.is_null() {
if client.is_null() || path.is_null() {
return IdeviceErrorCode::InvalidArg;
}
@@ -242,11 +193,8 @@ pub unsafe extern "C" fn afc_make_directory(
};
let res: Result<(), IdeviceError> = RUNTIME.block_on(async move {
let mut client_box = unsafe { Box::from_raw(client) };
let client_ref = &mut client_box.0;
let result = client_ref.mk_dir(path).await;
std::mem::forget(client_box);
result
let client_ref = unsafe { &mut (*client).0 };
client_ref.mk_dir(path).await
});
match res {
@@ -286,7 +234,7 @@ pub unsafe extern "C" fn afc_get_file_info(
path: *const libc::c_char,
info: *mut AfcFileInfo,
) -> IdeviceErrorCode {
if path.is_null() || info.is_null() {
if client.is_null() || path.is_null() || info.is_null() {
return IdeviceErrorCode::InvalidArg;
}
@@ -297,11 +245,8 @@ pub unsafe extern "C" fn afc_get_file_info(
};
let res: Result<FileInfo, IdeviceError> = RUNTIME.block_on(async move {
let mut client_box = unsafe { Box::from_raw(client) };
let client_ref = &mut client_box.0;
let result = client_ref.get_file_info(path).await;
std::mem::forget(client_box);
result
let client_ref = unsafe { &mut (*client).0 };
client_ref.get_file_info(path).await
});
match res {
@@ -380,16 +325,13 @@ pub unsafe extern "C" fn afc_get_device_info(
client: *mut AfcClientHandle,
info: *mut AfcDeviceInfo,
) -> IdeviceErrorCode {
if info.is_null() {
if client.is_null() || info.is_null() {
return IdeviceErrorCode::InvalidArg;
}
let res: Result<DeviceInfo, IdeviceError> = RUNTIME.block_on(async move {
let mut client_box = unsafe { Box::from_raw(client) };
let client_ref = &mut client_box.0;
let result = client_ref.get_device_info().await;
std::mem::forget(client_box);
result
let client_ref = unsafe { &mut (*client).0 };
client_ref.get_device_info().await
});
match res {
@@ -441,7 +383,7 @@ pub unsafe extern "C" fn afc_remove_path(
client: *mut AfcClientHandle,
path: *const libc::c_char,
) -> IdeviceErrorCode {
if path.is_null() {
if client.is_null() || path.is_null() {
return IdeviceErrorCode::InvalidArg;
}
@@ -452,11 +394,8 @@ pub unsafe extern "C" fn afc_remove_path(
};
let res: Result<(), IdeviceError> = RUNTIME.block_on(async move {
let mut client_box = unsafe { Box::from_raw(client) };
let client_ref = &mut client_box.0;
let result = client_ref.remove(path).await;
std::mem::forget(client_box);
result
let client_ref = unsafe { &mut (*client).0 };
client_ref.remove(path).await
});
match res {
@@ -482,7 +421,7 @@ pub unsafe extern "C" fn afc_remove_path_and_contents(
client: *mut AfcClientHandle,
path: *const libc::c_char,
) -> IdeviceErrorCode {
if path.is_null() {
if client.is_null() || path.is_null() {
return IdeviceErrorCode::InvalidArg;
}
@@ -493,11 +432,8 @@ pub unsafe extern "C" fn afc_remove_path_and_contents(
};
let res: Result<(), IdeviceError> = RUNTIME.block_on(async move {
let mut client_box = unsafe { Box::from_raw(client) };
let client_ref = &mut client_box.0;
let result = client_ref.remove_all(path).await;
std::mem::forget(client_box);
result
let client_ref = unsafe { &mut (*client).0 };
client_ref.remove_all(path).await
});
match res {
@@ -531,7 +467,7 @@ impl From<AfcFopenMode> for idevice::afc::opcode::AfcFopenMode {
/// Handle for an open file on the device
#[allow(dead_code)]
pub struct AfcFileHandle(*mut c_void); // Opaque pointer
pub struct AfcFileHandle<'a>(Box<idevice::afc::file::FileDescriptor<'a>>); // Opaque pointer
/// Opens a file on the device
///
@@ -546,7 +482,9 @@ pub struct AfcFileHandle(*mut c_void); // Opaque pointer
///
/// # Safety
/// All pointers must be valid and non-null
/// `path` must be a valid null-terminated C string
/// `path` must be a valid null-terminated C string.
/// The file handle MAY NOT be used from another thread, and is
/// dependant upon the client it was created by.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn afc_file_open(
client: *mut AfcClientHandle,
@@ -554,7 +492,7 @@ pub unsafe extern "C" fn afc_file_open(
mode: AfcFopenMode,
handle: *mut *mut AfcFileHandle,
) -> IdeviceErrorCode {
if path.is_null() || handle.is_null() {
if client.is_null() || path.is_null() || handle.is_null() {
return IdeviceErrorCode::InvalidArg;
}
@@ -567,18 +505,15 @@ pub unsafe extern "C" fn afc_file_open(
let mode = mode.into();
let res: Result<*mut AfcFileHandle, IdeviceError> = RUNTIME.block_on(async move {
let mut client_box = unsafe { Box::from_raw(client) };
let client_ref = &mut client_box.0;
let client_ref = unsafe { &mut (*client).0 };
let result = client_ref.open(path, mode).await;
let res = match result {
match result {
Ok(f) => {
let boxed = Box::new(f);
Ok(Box::into_raw(boxed) as *mut AfcFileHandle)
}
Err(e) => Err(e),
};
std::mem::forget(client_box);
res
}
});
match res {
@@ -716,7 +651,7 @@ pub unsafe extern "C" fn afc_make_link(
source: *const libc::c_char,
link_type: AfcLinkType,
) -> IdeviceErrorCode {
if target.is_null() || source.is_null() {
if client.is_null() || target.is_null() || source.is_null() {
return IdeviceErrorCode::InvalidArg;
}
@@ -738,11 +673,8 @@ pub unsafe extern "C" fn afc_make_link(
};
let res: Result<(), IdeviceError> = RUNTIME.block_on(async move {
let mut client_box = unsafe { Box::from_raw(client) };
let client_ref = &mut client_box.0;
let result = client_ref.link(target, source, link_type).await;
std::mem::forget(client_box);
result
let client_ref = unsafe { &mut (*client).0 };
client_ref.link(target, source, link_type).await
});
match res {
@@ -770,7 +702,7 @@ pub unsafe extern "C" fn afc_rename_path(
source: *const libc::c_char,
target: *const libc::c_char,
) -> IdeviceErrorCode {
if source.is_null() || target.is_null() {
if client.is_null() || source.is_null() || target.is_null() {
return IdeviceErrorCode::InvalidArg;
}
@@ -787,11 +719,8 @@ pub unsafe extern "C" fn afc_rename_path(
};
let res: Result<(), IdeviceError> = RUNTIME.block_on(async move {
let mut client_box = unsafe { Box::from_raw(client) };
let client_ref = &mut client_box.0;
let result = client_ref.rename(source, target).await;
std::mem::forget(client_box);
result
let client_ref = unsafe { &mut (*client).0 };
client_ref.rename(source, target).await
});
match res {
@@ -799,3 +728,18 @@ pub unsafe extern "C" fn afc_rename_path(
Err(e) => e.into(),
}
}
/// Frees memory allocated by a file read function allocated by this library
///
/// # Arguments
/// * [`info`] - Pointer to AfcDeviceInfo struct to free
///
/// # Safety
/// `info` must be a valid pointer to an AfcDeviceInfo struct previously returned by afc_get_device_info
#[unsafe(no_mangle)]
pub unsafe extern "C" fn afc_file_read_data_free(data: *mut u8, length: libc::size_t) {
if !data.is_null() {
let boxed = unsafe { Box::from_raw(std::slice::from_raw_parts_mut(data, length)) };
drop(boxed);
}
}

View File

@@ -1,18 +1,15 @@
// Jackson Coxson
use idevice::{IdeviceError, IdeviceService, amfi::AmfiClient};
use idevice::{IdeviceError, IdeviceService, amfi::AmfiClient, provider::IdeviceProvider};
use crate::{
IdeviceErrorCode, IdeviceHandle, RUNTIME,
provider::{TcpProviderHandle, UsbmuxdProviderHandle},
};
use crate::{IdeviceErrorCode, IdeviceHandle, RUNTIME, provider::IdeviceProviderHandle};
pub struct AmfiClientHandle(pub AmfiClient);
/// Automatically creates and connects to AMFI service, returning a client handle
///
/// # Arguments
/// * [`provider`] - A TcpProvider
/// * [`provider`] - An IdeviceProvider
/// * [`client`] - On success, will be set to point to a newly allocated AmfiClient handle
///
/// # Returns
@@ -22,8 +19,8 @@ pub struct AmfiClientHandle(pub AmfiClient);
/// `provider` must be a valid pointer to a handle allocated by this library
/// `client` must be a valid, non-null pointer to a location where the handle will be stored
#[unsafe(no_mangle)]
pub unsafe extern "C" fn amfi_connect_tcp(
provider: *mut TcpProviderHandle,
pub unsafe extern "C" fn amfi_connect(
provider: *mut IdeviceProviderHandle,
client: *mut *mut AmfiClientHandle,
) -> IdeviceErrorCode {
if provider.is_null() || client.is_null() {
@@ -32,70 +29,10 @@ pub unsafe extern "C" fn amfi_connect_tcp(
}
let res: Result<AmfiClient, IdeviceError> = RUNTIME.block_on(async move {
// Take ownership of the provider (without immediately dropping it)
let provider_box = unsafe { Box::from_raw(provider) };
// Get a reference to the inner value
let provider_ref = &provider_box.0;
let provider_ref: &dyn IdeviceProvider = unsafe { &*(*provider).0 };
// Connect using the reference
let result = AmfiClient::connect(provider_ref).await;
// Explicitly keep the provider_box alive until after connect completes
std::mem::forget(provider_box);
result
});
match res {
Ok(r) => {
let boxed = Box::new(AmfiClientHandle(r));
unsafe { *client = Box::into_raw(boxed) };
IdeviceErrorCode::IdeviceSuccess
}
Err(e) => {
// If connection failed, the provider_box was already forgotten,
// so we need to reconstruct it to avoid leak
let _ = unsafe { Box::from_raw(provider) };
e.into()
}
}
}
/// Automatically creates and connects to AMFI service, returning a client handle
///
/// # Arguments
/// * [`provider`] - A UsbmuxdProvider
/// * [`client`] - On success, will be set to point to a newly allocated AmfiClient handle
///
/// # Returns
/// An error code indicating success or failure
///
/// # Safety
/// `provider` must be a valid pointer to a handle allocated by this library
/// `client` must be a valid, non-null pointer to a location where the handle will be stored
#[unsafe(no_mangle)]
pub unsafe extern "C" fn amfi_connect_usbmuxd(
provider: *mut UsbmuxdProviderHandle,
client: *mut *mut AmfiClientHandle,
) -> IdeviceErrorCode {
if provider.is_null() {
log::error!("Provider is null");
return IdeviceErrorCode::InvalidArg;
}
let res: Result<AmfiClient, IdeviceError> = RUNTIME.block_on(async move {
// Take ownership of the provider (without immediately dropping it)
let provider_box = unsafe { Box::from_raw(provider) };
// Get a reference to the inner value
let provider_ref = &provider_box.0;
// Connect using the reference
let result = AmfiClient::connect(provider_ref).await;
// Explicitly keep the provider_box alive until after connect completes
std::mem::forget(provider_box);
result
AmfiClient::connect(provider_ref).await
});
match res {
@@ -118,16 +55,18 @@ pub unsafe extern "C" fn amfi_connect_usbmuxd(
/// An error code indicating success or failure
///
/// # Safety
/// `socket` must be a valid pointer to a handle allocated by this library
/// `socket` must be a valid pointer to a handle allocated by this library. It is consumed, and
/// should not be used again.
/// `client` must be a valid, non-null pointer to a location where the handle will be stored
#[unsafe(no_mangle)]
pub unsafe extern "C" fn amfi_new(
socket: *mut IdeviceHandle,
client: *mut *mut AmfiClientHandle,
) -> IdeviceErrorCode {
if socket.is_null() {
if socket.is_null() || client.is_null() {
return IdeviceErrorCode::InvalidArg;
}
let socket = unsafe { Box::from_raw(socket) }.0;
let r = AmfiClient::new(socket);
let boxed = Box::new(AmfiClientHandle(r));
@@ -149,16 +88,13 @@ pub unsafe extern "C" fn amfi_new(
pub unsafe extern "C" fn amfi_reveal_developer_mode_option_in_ui(
client: *mut AmfiClientHandle,
) -> IdeviceErrorCode {
if client.is_null() {
return IdeviceErrorCode::InvalidArg;
}
let res: Result<(), IdeviceError> = RUNTIME.block_on(async move {
// Take ownership of the client
let mut client_box = unsafe { Box::from_raw(client) };
// Get a reference to the inner value
let client_ref = &mut client_box.0;
let res = client_ref.reveal_developer_mode_option_in_ui().await;
std::mem::forget(client_box);
res
let client_ref = unsafe { &mut (*client).0 };
client_ref.reveal_developer_mode_option_in_ui().await
});
match res {
Ok(_) => IdeviceErrorCode::IdeviceSuccess,
@@ -180,16 +116,13 @@ pub unsafe extern "C" fn amfi_reveal_developer_mode_option_in_ui(
pub unsafe extern "C" fn amfi_enable_developer_mode(
client: *mut AmfiClientHandle,
) -> IdeviceErrorCode {
if client.is_null() {
return IdeviceErrorCode::InvalidArg;
}
let res: Result<(), IdeviceError> = RUNTIME.block_on(async move {
// Take ownership of the client
let mut client_box = unsafe { Box::from_raw(client) };
// Get a reference to the inner value
let client_ref = &mut client_box.0;
let res = client_ref.enable_developer_mode().await;
std::mem::forget(client_box);
res
let client_ref = unsafe { &mut (*client).0 };
client_ref.enable_developer_mode().await
});
match res {
Ok(_) => IdeviceErrorCode::IdeviceSuccess,
@@ -211,16 +144,13 @@ pub unsafe extern "C" fn amfi_enable_developer_mode(
pub unsafe extern "C" fn amfi_accept_developer_mode(
client: *mut AmfiClientHandle,
) -> IdeviceErrorCode {
if client.is_null() {
return IdeviceErrorCode::InvalidArg;
}
let res: Result<(), IdeviceError> = RUNTIME.block_on(async move {
// Take ownership of the client
let mut client_box = unsafe { Box::from_raw(client) };
// Get a reference to the inner value
let client_ref = &mut client_box.0;
let res = client_ref.accept_developer_mode().await;
std::mem::forget(client_box);
res
let client_ref = unsafe { &mut (*client).0 };
client_ref.accept_developer_mode().await
});
match res {
Ok(_) => IdeviceErrorCode::IdeviceSuccess,

View File

@@ -3,13 +3,11 @@
use std::ffi::{CString, c_char};
use idevice::{
IdeviceError, IdeviceService, core_device_proxy::CoreDeviceProxy, tcp::adapter::Adapter,
IdeviceError, IdeviceService, core_device_proxy::CoreDeviceProxy, provider::IdeviceProvider,
tcp::adapter::Adapter,
};
use crate::{
IdeviceErrorCode, IdeviceHandle, RUNTIME,
provider::{TcpProviderHandle, UsbmuxdProviderHandle},
};
use crate::{IdeviceErrorCode, IdeviceHandle, RUNTIME, provider::IdeviceProviderHandle};
pub struct CoreDeviceProxyHandle(pub CoreDeviceProxy);
pub struct AdapterHandle(pub Adapter);
@@ -17,7 +15,7 @@ pub struct AdapterHandle(pub Adapter);
/// Automatically creates and connects to Core Device Proxy, returning a client handle
///
/// # Arguments
/// * [`provider`] - A TcpProvider
/// * [`provider`] - An IdeviceProvider
/// * [`client`] - On success, will be set to point to a newly allocated CoreDeviceProxy handle
///
/// # Returns
@@ -27,8 +25,8 @@ pub struct AdapterHandle(pub Adapter);
/// `provider` must be a valid pointer to a handle allocated by this library
/// `client` must be a valid, non-null pointer to a location where the handle will be stored
#[unsafe(no_mangle)]
pub unsafe extern "C" fn core_device_proxy_connect_tcp(
provider: *mut TcpProviderHandle,
pub unsafe extern "C" fn core_device_proxy_connect(
provider: *mut IdeviceProviderHandle,
client: *mut *mut CoreDeviceProxyHandle,
) -> IdeviceErrorCode {
if provider.is_null() || client.is_null() {
@@ -37,70 +35,10 @@ pub unsafe extern "C" fn core_device_proxy_connect_tcp(
}
let res: Result<CoreDeviceProxy, IdeviceError> = RUNTIME.block_on(async move {
// Take ownership of the provider (without immediately dropping it)
let provider_box = unsafe { Box::from_raw(provider) };
// Get a reference to the inner value
let provider_ref = &provider_box.0;
let provider_ref: &dyn IdeviceProvider = unsafe { &*(*provider).0 };
// Connect using the reference
let result = CoreDeviceProxy::connect(provider_ref).await;
// Explicitly keep the provider_box alive until after connect completes
std::mem::forget(provider_box);
result
});
match res {
Ok(r) => {
let boxed = Box::new(CoreDeviceProxyHandle(r));
unsafe { *client = Box::into_raw(boxed) };
IdeviceErrorCode::IdeviceSuccess
}
Err(e) => {
// If connection failed, the provider_box was already forgotten,
// so we need to reconstruct it to avoid leak
let _ = unsafe { Box::from_raw(provider) };
e.into()
}
}
}
/// Automatically creates and connects to Core Device Proxy, returning a client handle
///
/// # Arguments
/// * [`provider`] - A UsbmuxdProvider
/// * [`client`] - On success, will be set to point to a newly allocated CoreDeviceProxy handle
///
/// # Returns
/// An error code indicating success or failure
///
/// # Safety
/// `provider` must be a valid pointer to a handle allocated by this library
/// `client` must be a valid, non-null pointer to a location where the handle will be stored
#[unsafe(no_mangle)]
pub unsafe extern "C" fn core_device_proxy_connect_usbmuxd(
provider: *mut UsbmuxdProviderHandle,
client: *mut *mut CoreDeviceProxyHandle,
) -> IdeviceErrorCode {
if provider.is_null() {
log::error!("Provider is null");
return IdeviceErrorCode::InvalidArg;
}
let res: Result<CoreDeviceProxy, IdeviceError> = RUNTIME.block_on(async move {
// Take ownership of the provider (without immediately dropping it)
let provider_box = unsafe { Box::from_raw(provider) };
// Get a reference to the inner value
let provider_ref = &provider_box.0;
// Connect using the reference
let result = CoreDeviceProxy::connect(provider_ref).await;
// Explicitly keep the provider_box alive until after connect completes
std::mem::forget(provider_box);
result
CoreDeviceProxy::connect(provider_ref).await
});
match res {
@@ -123,14 +61,15 @@ pub unsafe extern "C" fn core_device_proxy_connect_usbmuxd(
/// An error code indicating success or failure
///
/// # Safety
/// `socket` must be a valid pointer to a handle allocated by this library
/// `socket` must be a valid pointer to a handle allocated by this library. It is consumed and
/// may not be used again.
/// `client` must be a valid, non-null pointer to a location where the handle will be stored
#[unsafe(no_mangle)]
pub unsafe extern "C" fn core_device_proxy_new(
socket: *mut IdeviceHandle,
client: *mut *mut CoreDeviceProxyHandle,
) -> IdeviceErrorCode {
if socket.is_null() {
if socket.is_null() || client.is_null() {
return IdeviceErrorCode::InvalidArg;
}
let socket = unsafe { Box::from_raw(socket) }.0;
@@ -233,8 +172,8 @@ pub unsafe extern "C" fn core_device_proxy_recv(
/// # Arguments
/// * [`handle`] - The CoreDeviceProxy handle
/// * [`mtu`] - Pointer to store the MTU value
/// * [`address`] - Pointer to store the IP address string (must be at least 16 bytes)
/// * [`netmask`] - Pointer to store the netmask string (must be at least 16 bytes)
/// * [`address`] - Pointer to store the IP address string
/// * [`netmask`] - Pointer to store the netmask string
///
/// # Returns
/// An error code indicating success or failure
@@ -262,9 +201,21 @@ pub unsafe extern "C" fn core_device_proxy_get_client_parameters(
*mtu = params.mtu;
}
// Allocate both strings, but handle partial failure
let address_cstring = match CString::new(params.address.clone()) {
Ok(s) => s,
Err(_) => return IdeviceErrorCode::InvalidString,
};
let netmask_cstring = match CString::new(params.netmask.clone()) {
Ok(s) => s,
Err(_) => return IdeviceErrorCode::InvalidString,
};
// Only assign to output pointers after both succeed
unsafe {
*address = CString::new(params.address.clone()).unwrap().into_raw();
*netmask = CString::new(params.netmask.clone()).unwrap().into_raw();
*address = address_cstring.into_raw();
*netmask = netmask_cstring.into_raw();
}
IdeviceErrorCode::IdeviceSuccess
@@ -274,7 +225,7 @@ pub unsafe extern "C" fn core_device_proxy_get_client_parameters(
///
/// # Arguments
/// * [`handle`] - The CoreDeviceProxy handle
/// * [`address`] - Pointer to store the server address string (must be at least 16 bytes)
/// * [`address`] - Pointer to store the server address string
///
/// # Returns
/// An error code indicating success or failure
@@ -294,9 +245,10 @@ pub unsafe extern "C" fn core_device_proxy_get_server_address(
let proxy = unsafe { &(*handle).0 };
unsafe {
*address = CString::new(proxy.handshake.server_address.clone())
.unwrap()
.into_raw();
*address = match CString::new(proxy.handshake.server_address.clone()) {
Ok(s) => s.into_raw(),
Err(_) => return IdeviceErrorCode::InvalidString,
};
}
IdeviceErrorCode::IdeviceSuccess

View File

@@ -5,13 +5,15 @@ use std::os::raw::c_int;
use std::ptr;
use idevice::debug_proxy::{DebugProxyClient, DebugserverCommand};
use idevice::tcp::adapter::Adapter;
use idevice::tcp::stream::AdapterStream;
use idevice::{IdeviceError, ReadWrite, RsdService};
use crate::core_device_proxy::AdapterHandle;
use crate::rsd::RsdHandshakeHandle;
use crate::{IdeviceErrorCode, RUNTIME};
/// Opaque handle to a DebugProxyClient
pub struct DebugProxyAdapterHandle(pub DebugProxyClient<Adapter>);
pub struct DebugProxyHandle(pub DebugProxyClient<Box<dyn ReadWrite>>);
/// Represents a debugserver command
#[repr(C)]
@@ -56,16 +58,26 @@ pub unsafe extern "C" fn debugserver_command_new(
let argv_len = argv_vec.len();
let boxed = Box::new(DebugserverCommandHandle {
name: CString::new(name).unwrap().into_raw(),
name: match CString::new(name) {
Ok(n) => n.into_raw(),
Err(_) => return ptr::null_mut(),
},
argv: if argv_vec.is_empty() {
ptr::null_mut()
} else {
let mut argv_ptrs: Vec<*mut c_char> = argv_vec
let argv_ptrs: Result<Vec<*mut c_char>, _> = argv_vec
.into_iter()
.map(|s| CString::new(s).unwrap().into_raw())
.map(|s| CString::new(s).map(|cs| cs.into_raw()))
.collect();
let mut argv_ptrs = match argv_ptrs {
Ok(ptrs) => ptrs,
Err(_) => return ptr::null_mut(),
};
argv_ptrs.shrink_to_fit();
argv_ptrs.as_mut_ptr()
let ptr = argv_ptrs.as_mut_ptr();
std::mem::forget(argv_ptrs);
ptr
},
argv_count: argv_len,
});
@@ -106,7 +118,48 @@ pub unsafe extern "C" fn debugserver_command_free(command: *mut DebugserverComma
/// Creates a new DebugProxyClient
///
/// # Arguments
/// * [`socket`] - The socket to use for communication
/// * [`provider`] - An adapter created by this library
/// * [`handshake`] - An RSD handshake from the same provider
///
/// # Returns
/// An error code indicating success or failure
///
/// # Safety
/// `provider` must be a valid pointer to a handle allocated by this library
/// `handshake` must be a valid pointer to a location where the handle will be stored
#[unsafe(no_mangle)]
pub unsafe extern "C" fn debug_proxy_connect_rsd(
provider: *mut AdapterHandle,
handshake: *mut RsdHandshakeHandle,
handle: *mut *mut DebugProxyHandle,
) -> IdeviceErrorCode {
if provider.is_null() || handshake.is_null() || handshake.is_null() {
return IdeviceErrorCode::InvalidArg;
}
let res: Result<DebugProxyClient<AdapterStream>, IdeviceError> = RUNTIME.block_on(async move {
let provider_ref = unsafe { &mut (*provider).0 };
let handshake_ref = unsafe { &mut (*handshake).0 };
// Connect using the reference
DebugProxyClient::connect_rsd(provider_ref, handshake_ref).await
});
match res {
Ok(d) => {
let boxed = Box::new(DebugProxyHandle(DebugProxyClient::new(Box::new(
d.into_inner(),
))));
unsafe { *handle = Box::into_raw(boxed) };
IdeviceErrorCode::IdeviceSuccess
}
Err(e) => e.into(),
}
}
/// Creates a new DebugProxyClient
///
/// # Arguments
/// * [`socket`] - The socket to use for communication. Any object that supports ReadWrite.
/// * [`handle`] - Pointer to store the newly created DebugProxyClient handle
///
/// # Returns
@@ -116,19 +169,19 @@ pub unsafe extern "C" fn debugserver_command_free(command: *mut DebugserverComma
/// `socket` must be a valid pointer to a 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 debug_proxy_adapter_new(
socket: *mut AdapterHandle,
handle: *mut *mut DebugProxyAdapterHandle,
pub unsafe extern "C" fn debug_proxy_new(
socket: *mut Box<dyn ReadWrite>,
handle: *mut *mut DebugProxyHandle,
) -> IdeviceErrorCode {
if socket.is_null() || handle.is_null() {
return IdeviceErrorCode::InvalidArg;
}
let socket = unsafe { Box::from_raw(socket) };
let client = DebugProxyClient::new(socket.0);
let client = DebugProxyClient::new(*socket);
let new_handle = DebugProxyHandle(client);
let boxed = Box::new(DebugProxyAdapterHandle(client));
unsafe { *handle = Box::into_raw(boxed) };
unsafe { *handle = Box::into_raw(Box::new(new_handle)) };
IdeviceErrorCode::IdeviceSuccess
}
@@ -140,7 +193,7 @@ pub unsafe extern "C" fn debug_proxy_adapter_new(
/// # Safety
/// `handle` must be a valid pointer to a handle allocated by this library or NULL
#[unsafe(no_mangle)]
pub unsafe extern "C" fn debug_proxy_free(handle: *mut DebugProxyAdapterHandle) {
pub unsafe extern "C" fn debug_proxy_free(handle: *mut DebugProxyHandle) {
if !handle.is_null() {
let _ = unsafe { Box::from_raw(handle) };
}
@@ -161,7 +214,7 @@ pub unsafe extern "C" fn debug_proxy_free(handle: *mut DebugProxyAdapterHandle)
/// `response` must be a valid pointer to a location where the string will be stored
#[unsafe(no_mangle)]
pub unsafe extern "C" fn debug_proxy_send_command(
handle: *mut DebugProxyAdapterHandle,
handle: *mut DebugProxyHandle,
command: *mut DebugserverCommandHandle,
response: *mut *mut c_char,
) -> IdeviceErrorCode {
@@ -192,7 +245,10 @@ pub unsafe extern "C" fn debug_proxy_send_command(
match res {
Ok(Some(r)) => {
let cstr = CString::new(r).unwrap();
let cstr = match CString::new(r) {
Ok(c) => c,
Err(_) => return IdeviceErrorCode::InvalidString,
};
unsafe { *response = cstr.into_raw() };
IdeviceErrorCode::IdeviceSuccess
}
@@ -218,7 +274,7 @@ pub unsafe extern "C" fn debug_proxy_send_command(
/// `response` must be a valid pointer to a location where the string will be stored
#[unsafe(no_mangle)]
pub unsafe extern "C" fn debug_proxy_read_response(
handle: *mut DebugProxyAdapterHandle,
handle: *mut DebugProxyHandle,
response: *mut *mut c_char,
) -> IdeviceErrorCode {
if handle.is_null() || response.is_null() {
@@ -230,7 +286,10 @@ pub unsafe extern "C" fn debug_proxy_read_response(
match res {
Ok(Some(r)) => {
let cstr = CString::new(r).unwrap();
let cstr = match CString::new(r) {
Ok(c) => c,
Err(_) => return IdeviceErrorCode::InvalidString,
};
unsafe { *response = cstr.into_raw() };
IdeviceErrorCode::IdeviceSuccess
}
@@ -257,7 +316,7 @@ pub unsafe extern "C" fn debug_proxy_read_response(
/// `data` must be a valid pointer to `len` bytes
#[unsafe(no_mangle)]
pub unsafe extern "C" fn debug_proxy_send_raw(
handle: *mut DebugProxyAdapterHandle,
handle: *mut DebugProxyHandle,
data: *const u8,
len: usize,
) -> IdeviceErrorCode {
@@ -290,7 +349,7 @@ pub unsafe extern "C" fn debug_proxy_send_raw(
/// `response` must be a valid pointer to a location where the string will be stored
#[unsafe(no_mangle)]
pub unsafe extern "C" fn debug_proxy_read(
handle: *mut DebugProxyAdapterHandle,
handle: *mut DebugProxyHandle,
len: usize,
response: *mut *mut c_char,
) -> IdeviceErrorCode {
@@ -303,7 +362,10 @@ pub unsafe extern "C" fn debug_proxy_read(
match res {
Ok(r) => {
let cstr = CString::new(r).unwrap();
let cstr = match CString::new(r) {
Ok(c) => c,
Err(_) => return IdeviceErrorCode::InvalidString,
};
unsafe { *response = cstr.into_raw() };
IdeviceErrorCode::IdeviceSuccess
}
@@ -328,7 +390,7 @@ pub unsafe extern "C" fn debug_proxy_read(
/// `response` must be a valid pointer to a location where the string will be stored
#[unsafe(no_mangle)]
pub unsafe extern "C" fn debug_proxy_set_argv(
handle: *mut DebugProxyAdapterHandle,
handle: *mut DebugProxyHandle,
argv: *const *const c_char,
argv_count: usize,
response: *mut *mut c_char,
@@ -358,7 +420,10 @@ pub unsafe extern "C" fn debug_proxy_set_argv(
match res {
Ok(r) => {
let cstr = CString::new(r).unwrap();
let cstr = match CString::new(r) {
Ok(c) => c,
Err(_) => return IdeviceErrorCode::InvalidString,
};
unsafe { *response = cstr.into_raw() };
IdeviceErrorCode::IdeviceSuccess
}
@@ -377,9 +442,7 @@ pub unsafe extern "C" fn debug_proxy_set_argv(
/// # Safety
/// `handle` must be a valid pointer
#[unsafe(no_mangle)]
pub unsafe extern "C" fn debug_proxy_send_ack(
handle: *mut DebugProxyAdapterHandle,
) -> IdeviceErrorCode {
pub unsafe extern "C" fn debug_proxy_send_ack(handle: *mut DebugProxyHandle) -> IdeviceErrorCode {
if handle.is_null() {
return IdeviceErrorCode::InvalidArg;
}
@@ -404,9 +467,7 @@ pub unsafe extern "C" fn debug_proxy_send_ack(
/// # Safety
/// `handle` must be a valid pointer
#[unsafe(no_mangle)]
pub unsafe extern "C" fn debug_proxy_send_nack(
handle: *mut DebugProxyAdapterHandle,
) -> IdeviceErrorCode {
pub unsafe extern "C" fn debug_proxy_send_nack(handle: *mut DebugProxyHandle) -> IdeviceErrorCode {
if handle.is_null() {
return IdeviceErrorCode::InvalidArg;
}
@@ -429,39 +490,9 @@ pub unsafe extern "C" fn debug_proxy_send_nack(
/// # Safety
/// `handle` must be a valid pointer
#[unsafe(no_mangle)]
pub unsafe extern "C" fn debug_proxy_set_ack_mode(
handle: *mut DebugProxyAdapterHandle,
enabled: c_int,
) {
pub unsafe extern "C" fn debug_proxy_set_ack_mode(handle: *mut DebugProxyHandle, enabled: c_int) {
if !handle.is_null() {
let client = unsafe { &mut (*handle).0 };
client.set_ack_mode(enabled != 0);
}
}
/// Returns the underlying socket from a DebugProxyClient
///
/// # Arguments
/// * [`handle`] - The handle to get the socket from
/// * [`adapter`] - The newly allocated ConnectionHandle
///
/// # Returns
/// An error code indicating success or failure
///
/// # 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 debug_proxy_adapter_into_inner(
handle: *mut DebugProxyAdapterHandle,
adapter: *mut *mut AdapterHandle,
) -> IdeviceErrorCode {
if handle.is_null() {
return IdeviceErrorCode::InvalidArg;
}
let client = unsafe { Box::from_raw(handle) };
let socket_obj = client.0.into_inner();
let boxed = Box::new(AdapterHandle(socket_obj));
unsafe { *adapter = Box::into_raw(boxed) };
IdeviceErrorCode::IdeviceSuccess
}

View File

@@ -33,7 +33,6 @@ pub enum IdeviceErrorCode {
ImageNotMounted = -25,
Reqwest = -26,
InternalError = -27,
Xpc = -28,
NsKeyedArchiveError = -29,
UnknownAuxValueType = -30,
UnknownChannel = -31,
@@ -50,9 +49,24 @@ pub enum IdeviceErrorCode {
UnknownAfcOpcode = -42,
InvalidAfcMagic = -43,
AfcMissingAttribute = -44,
ServiceNotFound = -45,
PairingDialogResponsePending = -46,
UserDeniedPairing = -47,
PasswordProtected = -48,
CrashReportMoverBadResponse = -49,
UnknownFrame = -50,
UnknownHttpSetting = -51,
UninitializedStreamId = -52,
UnknownXpcType = -53,
MalformedXpc = -54,
InvalidXpcMagic = -55,
UnexpectedXpcVersion = -56,
InvalidCString = -57,
HttpStreamReset = -58,
HttpGoAway = -59,
// FFI specific bindings
AdapterIOFailed = -996,
ServiceNotFound = -997,
BufferTooSmall = -998,
InvalidString = -999,
InvalidArg = -1000,
@@ -90,7 +104,6 @@ impl From<IdeviceError> for IdeviceErrorCode {
IdeviceError::ImageNotMounted => IdeviceErrorCode::ImageNotMounted,
IdeviceError::Reqwest(_) => IdeviceErrorCode::Reqwest,
IdeviceError::InternalError(_) => IdeviceErrorCode::InternalError,
IdeviceError::Xpc(_) => IdeviceErrorCode::Xpc,
IdeviceError::NsKeyedArchiveError(_) => IdeviceErrorCode::NsKeyedArchiveError,
IdeviceError::UnknownAuxValueType(_) => IdeviceErrorCode::UnknownAuxValueType,
IdeviceError::UnknownChannel(_) => IdeviceErrorCode::UnknownChannel,
@@ -109,6 +122,25 @@ impl From<IdeviceError> for IdeviceErrorCode {
IdeviceError::UnknownAfcOpcode => IdeviceErrorCode::UnknownAfcOpcode,
IdeviceError::InvalidAfcMagic => IdeviceErrorCode::InvalidAfcMagic,
IdeviceError::AfcMissingAttribute => IdeviceErrorCode::AfcMissingAttribute,
IdeviceError::ServiceNotFound => IdeviceErrorCode::ServiceNotFound,
IdeviceError::PairingDialogResponsePending => {
IdeviceErrorCode::PairingDialogResponsePending
}
IdeviceError::UserDeniedPairing => IdeviceErrorCode::UserDeniedPairing,
IdeviceError::PasswordProtected => IdeviceErrorCode::PasswordProtected,
IdeviceError::CrashReportMoverBadResponse(_) => {
IdeviceErrorCode::CrashReportMoverBadResponse
}
IdeviceError::UnknownFrame(_) => IdeviceErrorCode::UnknownFrame,
IdeviceError::UnknownHttpSetting(_) => IdeviceErrorCode::UnknownHttpSetting,
IdeviceError::UninitializedStreamId => IdeviceErrorCode::UninitializedStreamId,
IdeviceError::UnknownXpcType(_) => IdeviceErrorCode::UnknownXpcType,
IdeviceError::MalformedXpc => IdeviceErrorCode::MalformedXpc,
IdeviceError::InvalidXpcMagic => IdeviceErrorCode::InvalidXpcMagic,
IdeviceError::UnexpectedXpcVersion => IdeviceErrorCode::UnexpectedXpcVersion,
IdeviceError::InvalidCString => IdeviceErrorCode::InvalidCString,
IdeviceError::HttpStreamReset => IdeviceErrorCode::HttpStreamReset,
IdeviceError::HttpGoAway(_) => IdeviceErrorCode::HttpGoAway,
_ => IdeviceErrorCode::InternalError,
}
}

View File

@@ -1,20 +1,17 @@
// Jackson Coxson
use idevice::{IdeviceError, IdeviceService, heartbeat::HeartbeatClient};
use crate::{
IdeviceErrorCode, IdeviceHandle, RUNTIME,
provider::{TcpProviderHandle, UsbmuxdProviderHandle},
use idevice::{
IdeviceError, IdeviceService, heartbeat::HeartbeatClient, provider::IdeviceProvider,
};
use crate::{IdeviceErrorCode, IdeviceHandle, RUNTIME, provider::IdeviceProviderHandle};
pub struct HeartbeatClientHandle(pub HeartbeatClient);
#[allow(non_camel_case_types)]
pub struct plist_t;
/// Automatically creates and connects to Installation Proxy, returning a client handle
///
/// # Arguments
/// * [`provider`] - A TcpProvider
/// * [`provider`] - An IdeviceProvider
/// * [`client`] - On success, will be set to point to a newly allocated InstallationProxyClient handle
///
/// # Returns
@@ -24,8 +21,8 @@ pub struct plist_t;
/// `provider` must be a valid pointer to a handle allocated by this library
/// `client` must be a valid, non-null pointer to a location where the handle will be stored
#[unsafe(no_mangle)]
pub unsafe extern "C" fn heartbeat_connect_tcp(
provider: *mut TcpProviderHandle,
pub unsafe extern "C" fn heartbeat_connect(
provider: *mut IdeviceProviderHandle,
client: *mut *mut HeartbeatClientHandle,
) -> IdeviceErrorCode {
if provider.is_null() || client.is_null() {
@@ -34,18 +31,9 @@ pub unsafe extern "C" fn heartbeat_connect_tcp(
}
let res: Result<HeartbeatClient, IdeviceError> = RUNTIME.block_on(async move {
// Take ownership of the provider (without immediately dropping it)
let provider_box = unsafe { Box::from_raw(provider) };
// Get a reference to the inner value
let provider_ref = &provider_box.0;
let provider_ref: &dyn IdeviceProvider = unsafe { &*(*provider).0 };
// Connect using the reference
let result = HeartbeatClient::connect(provider_ref).await;
// Explicitly keep the provider_box alive until after connect completes
std::mem::forget(provider_box);
result
HeartbeatClient::connect(provider_ref).await
});
match res {
@@ -63,53 +51,6 @@ pub unsafe extern "C" fn heartbeat_connect_tcp(
}
}
/// Automatically creates and connects to Installation Proxy, returning a client handle
///
/// # Arguments
/// * [`provider`] - A UsbmuxdProvider
/// * [`client`] - On success, will be set to point to a newly allocated InstallationProxyClient handle
///
/// # Returns
/// An error code indicating success or failure
///
/// # Safety
/// `provider` must be a valid pointer to a handle allocated by this library
/// `client` must be a valid, non-null pointer to a location where the handle will be stored
#[unsafe(no_mangle)]
pub unsafe extern "C" fn heartbeat_connect_usbmuxd(
provider: *mut UsbmuxdProviderHandle,
client: *mut *mut HeartbeatClientHandle,
) -> IdeviceErrorCode {
if provider.is_null() {
log::error!("Provider is null");
return IdeviceErrorCode::InvalidArg;
}
let res: Result<HeartbeatClient, IdeviceError> = RUNTIME.block_on(async move {
// Take ownership of the provider (without immediately dropping it)
let provider_box = unsafe { Box::from_raw(provider) };
// Get a reference to the inner value
let provider_ref = &provider_box.0;
// Connect using the reference
let result = HeartbeatClient::connect(provider_ref).await;
// Explicitly keep the provider_box alive until after connect completes
std::mem::forget(provider_box);
result
});
match res {
Ok(r) => {
let boxed = Box::new(HeartbeatClientHandle(r));
unsafe { *client = Box::into_raw(boxed) };
IdeviceErrorCode::IdeviceSuccess
}
Err(e) => e.into(),
}
}
/// Automatically creates and connects to Installation Proxy, returning a client handle
///
/// # Arguments
@@ -120,14 +61,15 @@ pub unsafe extern "C" fn heartbeat_connect_usbmuxd(
/// An error code indicating success or failure
///
/// # Safety
/// `socket` must be a valid pointer to a handle allocated by this library
/// `socket` must be a valid pointer to a handle allocated by this library. The socket is consumed,
/// and may not be used again.
/// `client` must be a valid, non-null pointer to a location where the handle will be stored
#[unsafe(no_mangle)]
pub unsafe extern "C" fn heartbeat_new(
socket: *mut IdeviceHandle,
client: *mut *mut HeartbeatClientHandle,
) -> IdeviceErrorCode {
if socket.is_null() {
if socket.is_null() || client.is_null() {
return IdeviceErrorCode::InvalidArg;
}
let socket = unsafe { Box::from_raw(socket) }.0;
@@ -151,16 +93,12 @@ pub unsafe extern "C" fn heartbeat_new(
pub unsafe extern "C" fn heartbeat_send_polo(
client: *mut HeartbeatClientHandle,
) -> IdeviceErrorCode {
if client.is_null() {
return IdeviceErrorCode::InvalidArg;
}
let res: Result<(), IdeviceError> = RUNTIME.block_on(async move {
// Take ownership of the client
let mut client_box = unsafe { Box::from_raw(client) };
// Get a reference to the inner value
let client_ref = &mut client_box.0;
let res = client_ref.send_polo().await;
std::mem::forget(client_box);
res
let client_ref = unsafe { &mut (*client).0 };
client_ref.send_polo().await
});
match res {
Ok(_) => IdeviceErrorCode::IdeviceSuccess,
@@ -186,16 +124,12 @@ pub unsafe extern "C" fn heartbeat_get_marco(
interval: u64,
new_interval: *mut u64,
) -> IdeviceErrorCode {
if client.is_null() || new_interval.is_null() {
return IdeviceErrorCode::InvalidArg;
}
let res: Result<u64, IdeviceError> = RUNTIME.block_on(async move {
// Take ownership of the client
let mut client_box = unsafe { Box::from_raw(client) };
// Get a reference to the inner value
let client_ref = &mut client_box.0;
let new = client_ref.get_marco(interval).await;
std::mem::forget(client_box);
new
let client_ref = unsafe { &mut (*client).0 };
client_ref.get_marco(interval).await
});
match res {
Ok(n) => {

View File

@@ -2,20 +2,19 @@
use std::ffi::c_void;
use idevice::{IdeviceError, IdeviceService, installation_proxy::InstallationProxyClient};
use crate::{
IdeviceErrorCode, IdeviceHandle, RUNTIME,
provider::{TcpProviderHandle, UsbmuxdProviderHandle},
util,
use idevice::{
IdeviceError, IdeviceService, installation_proxy::InstallationProxyClient,
provider::IdeviceProvider,
};
use crate::{IdeviceErrorCode, IdeviceHandle, RUNTIME, provider::IdeviceProviderHandle, util};
pub struct InstallationProxyClientHandle(pub InstallationProxyClient);
/// Automatically creates and connects to Installation Proxy, returning a client handle
///
/// # Arguments
/// * [`provider`] - A TcpProvider
/// * [`provider`] - An IdeviceProvider
/// * [`client`] - On success, will be set to point to a newly allocated InstallationProxyClient handle
///
/// # Returns
@@ -26,7 +25,7 @@ pub struct InstallationProxyClientHandle(pub InstallationProxyClient);
/// `client` must be a valid, non-null pointer to a location where the handle will be stored
#[unsafe(no_mangle)]
pub unsafe extern "C" fn installation_proxy_connect_tcp(
provider: *mut TcpProviderHandle,
provider: *mut IdeviceProviderHandle,
client: *mut *mut InstallationProxyClientHandle,
) -> IdeviceErrorCode {
if provider.is_null() || client.is_null() {
@@ -35,70 +34,8 @@ pub unsafe extern "C" fn installation_proxy_connect_tcp(
}
let res: Result<InstallationProxyClient, IdeviceError> = RUNTIME.block_on(async move {
// Take ownership of the provider (without immediately dropping it)
let provider_box = unsafe { Box::from_raw(provider) };
// Get a reference to the inner value
let provider_ref = &provider_box.0;
// Connect using the reference
let result = InstallationProxyClient::connect(provider_ref).await;
// Explicitly keep the provider_box alive until after connect completes
std::mem::forget(provider_box);
result
});
match res {
Ok(r) => {
let boxed = Box::new(InstallationProxyClientHandle(r));
unsafe { *client = Box::into_raw(boxed) };
IdeviceErrorCode::IdeviceSuccess
}
Err(e) => {
// If connection failed, the provider_box was already forgotten,
// so we need to reconstruct it to avoid leak
let _ = unsafe { Box::from_raw(provider) };
e.into()
}
}
}
/// Automatically creates and connects to Installation Proxy, returning a client handle
///
/// # Arguments
/// * [`provider`] - A UsbmuxdProvider
/// * [`client`] - On success, will be set to point to a newly allocated InstallationProxyClient handle
///
/// # Returns
/// An error code indicating success or failure
///
/// # Safety
/// `provider` must be a valid pointer to a handle allocated by this library
/// `client` must be a valid, non-null pointer to a location where the handle will be stored
#[unsafe(no_mangle)]
pub unsafe extern "C" fn installation_proxy_connect_usbmuxd(
provider: *mut UsbmuxdProviderHandle,
client: *mut *mut InstallationProxyClientHandle,
) -> IdeviceErrorCode {
if provider.is_null() {
log::error!("Provider is null");
return IdeviceErrorCode::InvalidArg;
}
let res: Result<InstallationProxyClient, IdeviceError> = RUNTIME.block_on(async move {
// Take ownership of the provider (without immediately dropping it)
let provider_box = unsafe { Box::from_raw(provider) };
// Get a reference to the inner value
let provider_ref = &provider_box.0;
// Connect using the reference
let result = InstallationProxyClient::connect(provider_ref).await;
// Explicitly keep the provider_box alive until after connect completes
std::mem::forget(provider_box);
result
let provider_ref: &dyn IdeviceProvider = unsafe { &*(*provider).0 };
InstallationProxyClient::connect(provider_ref).await
});
match res {
@@ -121,14 +58,15 @@ pub unsafe extern "C" fn installation_proxy_connect_usbmuxd(
/// An error code indicating success or failure
///
/// # Safety
/// `socket` must be a valid pointer to a handle allocated by this library
/// `socket` must be a valid pointer to a handle allocated by this library. The socket is consumed,
/// and may not be used again.
/// `client` must be a valid, non-null pointer to a location where the handle will be stored
#[unsafe(no_mangle)]
pub unsafe extern "C" fn installation_proxy_new(
socket: *mut IdeviceHandle,
client: *mut *mut InstallationProxyClientHandle,
) -> IdeviceErrorCode {
if socket.is_null() {
if socket.is_null() || client.is_null() {
return IdeviceErrorCode::InvalidArg;
}
let socket = unsafe { Box::from_raw(socket) }.0;
@@ -201,11 +139,10 @@ pub unsafe extern "C" fn installation_proxy_get_apps(
});
match res {
Ok(r) => {
Ok(mut r) => {
let ptr = r.as_mut_ptr();
let len = r.len();
let boxed_slice = r.into_boxed_slice();
let ptr = boxed_slice.as_ptr();
std::mem::forget(boxed_slice);
std::mem::forget(r);
unsafe {
*out_result = ptr as *mut c_void;
@@ -637,11 +574,10 @@ pub unsafe extern "C" fn installation_proxy_browse(
});
match res {
Ok(r) => {
Ok(mut r) => {
let ptr = r.as_mut_ptr();
let len = r.len();
let boxed_slice = r.into_boxed_slice();
let ptr = boxed_slice.as_ptr();
std::mem::forget(boxed_slice);
std::mem::forget(r);
unsafe {
*out_result = ptr as *mut c_void;

View File

@@ -1,33 +1,50 @@
// Jackson Coxson
#[cfg(feature = "tunnel_tcp_stack")]
pub mod adapter;
#[cfg(feature = "afc")]
pub mod afc;
#[cfg(feature = "amfi")]
pub mod amfi;
#[cfg(feature = "core_device_proxy")]
pub mod core_device_proxy;
#[cfg(feature = "debug_proxy")]
pub mod debug_proxy;
mod errors;
#[cfg(feature = "heartbeat")]
pub mod heartbeat;
#[cfg(feature = "installation_proxy")]
pub mod installation_proxy;
#[cfg(feature = "location_simulation")]
pub mod location_simulation;
pub mod lockdown;
pub mod logging;
#[cfg(feature = "misagent")]
pub mod misagent;
pub mod mounter;
#[cfg(feature = "mobile_image_mounter")]
pub mod mobile_image_mounter;
#[cfg(feature = "os_trace_relay")]
pub mod os_trace_relay;
mod pairing_file;
#[cfg(feature = "dvt")]
pub mod process_control;
pub mod provider;
#[cfg(feature = "dvt")]
pub mod remote_server;
pub mod remotexpc;
pub mod sbservices;
#[cfg(feature = "xpc")]
pub mod rsd;
#[cfg(feature = "springboardservices")]
pub mod springboardservices;
#[cfg(feature = "syslog_relay")]
pub mod syslog_relay;
#[cfg(feature = "usbmuxd")]
pub mod usbmuxd;
pub mod util;
pub use errors::*;
pub use pairing_file::*;
use idevice::{Idevice, IdeviceSocket};
use idevice::{Idevice, IdeviceSocket, ReadWrite};
use once_cell::sync::Lazy;
use std::ffi::{CStr, CString, c_char};
use tokio::runtime::{self, Runtime};
@@ -42,6 +59,11 @@ static RUNTIME: Lazy<Runtime> = Lazy::new(|| {
pub const LOCKDOWN_PORT: u16 = 62078;
#[repr(C)]
pub struct ReadWriteOpaque {
pub inner: Option<Box<dyn ReadWrite>>,
}
/// Opaque C-compatible handle to an Idevice connection
pub struct IdeviceHandle(pub Idevice);
pub struct IdeviceSocketHandle(IdeviceSocket);

View File

@@ -1,11 +1,11 @@
// Jackson Coxson
use idevice::{dvt::location_simulation::LocationSimulationClient, tcp::adapter::Adapter};
use idevice::{ReadWrite, dvt::location_simulation::LocationSimulationClient};
use crate::{IdeviceErrorCode, RUNTIME, remote_server::RemoteServerAdapterHandle};
use crate::{IdeviceErrorCode, RUNTIME, remote_server::RemoteServerHandle};
/// Opaque handle to a ProcessControlClient
pub struct LocationSimulationAdapterHandle<'a>(pub LocationSimulationClient<'a, Adapter>);
pub struct LocationSimulationHandle<'a>(pub LocationSimulationClient<'a, Box<dyn ReadWrite>>);
/// Creates a new ProcessControlClient from a RemoteServerClient
///
@@ -21,8 +21,8 @@ pub struct LocationSimulationAdapterHandle<'a>(pub LocationSimulationClient<'a,
/// `handle` must be a valid pointer to a location where the handle will be stored
#[unsafe(no_mangle)]
pub unsafe extern "C" fn location_simulation_new(
server: *mut RemoteServerAdapterHandle,
handle: *mut *mut LocationSimulationAdapterHandle<'static>,
server: *mut RemoteServerHandle,
handle: *mut *mut LocationSimulationHandle<'static>,
) -> IdeviceErrorCode {
if server.is_null() || handle.is_null() {
return IdeviceErrorCode::InvalidArg;
@@ -33,7 +33,7 @@ pub unsafe extern "C" fn location_simulation_new(
match res {
Ok(client) => {
let boxed = Box::new(LocationSimulationAdapterHandle(client));
let boxed = Box::new(LocationSimulationHandle(client));
unsafe { *handle = Box::into_raw(boxed) };
IdeviceErrorCode::IdeviceSuccess
}
@@ -49,9 +49,7 @@ pub unsafe extern "C" fn location_simulation_new(
/// # Safety
/// `handle` must be a valid pointer to a handle allocated by this library or NULL
#[unsafe(no_mangle)]
pub unsafe extern "C" fn location_simulation_free(
handle: *mut LocationSimulationAdapterHandle<'static>,
) {
pub unsafe extern "C" fn location_simulation_free(handle: *mut LocationSimulationHandle<'static>) {
if !handle.is_null() {
let _ = unsafe { Box::from_raw(handle) };
}
@@ -69,7 +67,7 @@ pub unsafe extern "C" fn location_simulation_free(
/// All pointers must be valid or NULL where appropriate
#[unsafe(no_mangle)]
pub unsafe extern "C" fn location_simulation_clear(
handle: *mut LocationSimulationAdapterHandle<'static>,
handle: *mut LocationSimulationHandle<'static>,
) -> IdeviceErrorCode {
if handle.is_null() {
return IdeviceErrorCode::InvalidArg;
@@ -98,7 +96,7 @@ pub unsafe extern "C" fn location_simulation_clear(
/// All pointers must be valid or NULL where appropriate
#[unsafe(no_mangle)]
pub unsafe extern "C" fn location_simulation_set(
handle: *mut LocationSimulationAdapterHandle<'static>,
handle: *mut LocationSimulationHandle<'static>,
latitude: f64,
longitude: f64,
) -> IdeviceErrorCode {

View File

@@ -2,11 +2,10 @@
use std::ffi::c_void;
use idevice::{IdeviceError, IdeviceService, lockdown::LockdownClient};
use idevice::{IdeviceError, IdeviceService, lockdown::LockdownClient, provider::IdeviceProvider};
use crate::{
IdeviceErrorCode, IdeviceHandle, IdevicePairingFile, RUNTIME,
provider::{TcpProviderHandle, UsbmuxdProviderHandle},
IdeviceErrorCode, IdeviceHandle, IdevicePairingFile, RUNTIME, provider::IdeviceProviderHandle,
};
pub struct LockdowndClientHandle(pub LockdownClient);
@@ -14,7 +13,7 @@ pub struct LockdowndClientHandle(pub LockdownClient);
/// Connects to lockdownd service using TCP provider
///
/// # Arguments
/// * [`provider`] - A TcpProvider
/// * [`provider`] - An IdeviceProvider
/// * [`client`] - On success, will be set to point to a newly allocated LockdowndClient handle
///
/// # Returns
@@ -24,8 +23,8 @@ pub struct LockdowndClientHandle(pub LockdownClient);
/// `provider` must be a valid pointer to a handle allocated by this library
/// `client` must be a valid, non-null pointer to a location where the handle will be stored
#[unsafe(no_mangle)]
pub unsafe extern "C" fn lockdownd_connect_tcp(
provider: *mut TcpProviderHandle,
pub unsafe extern "C" fn lockdownd_connect(
provider: *mut IdeviceProviderHandle,
client: *mut *mut LockdowndClientHandle,
) -> IdeviceErrorCode {
if provider.is_null() || client.is_null() {
@@ -34,11 +33,8 @@ pub unsafe extern "C" fn lockdownd_connect_tcp(
}
let res: Result<LockdownClient, IdeviceError> = RUNTIME.block_on(async move {
let provider_box = unsafe { Box::from_raw(provider) };
let provider_ref = &provider_box.0;
let result = LockdownClient::connect(provider_ref).await;
std::mem::forget(provider_box);
result
let provider_ref: &dyn IdeviceProvider = unsafe { &*(*provider).0 };
LockdownClient::connect(provider_ref).await
});
match res {
@@ -54,57 +50,18 @@ pub unsafe extern "C" fn lockdownd_connect_tcp(
}
}
/// Connects to lockdownd service using Usbmuxd provider
///
/// # Arguments
/// * [`provider`] - A UsbmuxdProvider
/// * [`client`] - On success, will be set to point to a newly allocated LockdowndClient handle
///
/// # Returns
/// An error code indicating success or failure
///
/// # Safety
/// `provider` must be a valid pointer to a handle allocated by this library
/// `client` must be a valid, non-null pointer to a location where the handle will be stored
#[unsafe(no_mangle)]
pub unsafe extern "C" fn lockdownd_connect_usbmuxd(
provider: *mut UsbmuxdProviderHandle,
client: *mut *mut LockdowndClientHandle,
) -> IdeviceErrorCode {
if provider.is_null() || client.is_null() {
log::error!("Null pointer provided");
return IdeviceErrorCode::InvalidArg;
}
let res: Result<LockdownClient, IdeviceError> = RUNTIME.block_on(async move {
let provider_box = unsafe { Box::from_raw(provider) };
let provider_ref = &provider_box.0;
let result = LockdownClient::connect(provider_ref).await;
std::mem::forget(provider_box);
result
});
match res {
Ok(r) => {
let boxed = Box::new(LockdowndClientHandle(r));
unsafe { *client = Box::into_raw(boxed) };
IdeviceErrorCode::IdeviceSuccess
}
Err(e) => e.into(),
}
}
/// Creates a new LockdowndClient from an existing Idevice connection
///
/// # Arguments
/// * [`socket`] - An IdeviceSocket handle
/// * [`socket`] - An IdeviceSocket handle.
/// * [`client`] - On success, will be set to point to a newly allocated LockdowndClient handle
///
/// # Returns
/// An error code indicating success or failure
///
/// # Safety
/// `socket` must be a valid pointer to a handle allocated by this library
/// `socket` must be a valid pointer to a handle allocated by this library. The socket is consumed,
/// and maybe not be used again.
/// `client` must be a valid, non-null pointer to a location where the handle will be stored
#[unsafe(no_mangle)]
pub unsafe extern "C" fn lockdownd_new(
@@ -139,15 +96,10 @@ pub unsafe extern "C" fn lockdownd_start_session(
pairing_file: *mut IdevicePairingFile,
) -> IdeviceErrorCode {
let res: Result<(), IdeviceError> = RUNTIME.block_on(async move {
let mut client_box = unsafe { Box::from_raw(client) };
let pairing_file = unsafe { Box::from_raw(pairing_file) };
let client_ref = unsafe { &mut (*client).0 };
let pairing_file_ref = unsafe { &(*pairing_file).0 };
let client_ref = &mut client_box.0;
let res = client_ref.start_session(&pairing_file.0).await;
std::mem::forget(client_box);
std::mem::forget(pairing_file);
res
client_ref.start_session(pairing_file_ref).await
});
match res {
@@ -187,11 +139,8 @@ pub unsafe extern "C" fn lockdownd_start_service(
.into_owned();
let res: Result<(u16, bool), IdeviceError> = RUNTIME.block_on(async move {
let mut client_box = unsafe { Box::from_raw(client) };
let client_ref = &mut client_box.0;
let res = client_ref.start_service(identifier).await;
std::mem::forget(client_box);
res
let client_ref = unsafe { &mut (*client).0 };
client_ref.start_service(identifier).await
});
match res {
@@ -247,11 +196,8 @@ pub unsafe extern "C" fn lockdownd_get_value(
};
let res: Result<plist::Value, IdeviceError> = RUNTIME.block_on(async move {
let mut client_box = unsafe { Box::from_raw(client) };
let client_ref = &mut client_box.0;
let res = client_ref.get_value(value, domain).await;
std::mem::forget(client_box);
res
let client_ref = unsafe { &mut (*client).0 };
client_ref.get_value(value, domain).await
});
match res {
@@ -287,11 +233,8 @@ pub unsafe extern "C" fn lockdownd_get_all_values(
}
let res: Result<plist::Dictionary, IdeviceError> = RUNTIME.block_on(async move {
let mut client_box = unsafe { Box::from_raw(client) };
let client_ref = &mut client_box.0;
let res = client_ref.get_all_values().await;
std::mem::forget(client_box);
res
let client_ref = unsafe { &mut (*client).0 };
client_ref.get_all_values().await
});
match res {

View File

@@ -2,19 +2,16 @@
//!
//! Provides C-compatible bindings for interacting with the misagent service on iOS devices.
use idevice::{IdeviceError, IdeviceService, misagent::MisagentClient};
use idevice::{IdeviceError, IdeviceService, misagent::MisagentClient, provider::IdeviceProvider};
use crate::{
IdeviceErrorCode, RUNTIME,
provider::{TcpProviderHandle, UsbmuxdProviderHandle},
};
use crate::{IdeviceErrorCode, RUNTIME, provider::IdeviceProviderHandle};
pub struct MisagentClientHandle(pub MisagentClient);
/// Automatically creates and connects to Misagent, returning a client handle
///
/// # Arguments
/// * [`provider`] - A TcpProvider
/// * [`provider`] - An IdeviceProvider
/// * [`client`] - On success, will be set to point to a newly allocated MisagentClient handle
///
/// # Returns
@@ -24,8 +21,8 @@ pub struct MisagentClientHandle(pub MisagentClient);
/// `provider` must be a valid pointer to a handle allocated by this library
/// `client` must be a valid, non-null pointer to a location where the handle will be stored
#[unsafe(no_mangle)]
pub unsafe extern "C" fn misagent_connect_tcp(
provider: *mut TcpProviderHandle,
pub unsafe extern "C" fn misagent_connect(
provider: *mut IdeviceProviderHandle,
client: *mut *mut MisagentClientHandle,
) -> IdeviceErrorCode {
if provider.is_null() || client.is_null() {
@@ -34,70 +31,8 @@ pub unsafe extern "C" fn misagent_connect_tcp(
}
let res: Result<MisagentClient, IdeviceError> = RUNTIME.block_on(async move {
// Take ownership of the provider (without immediately dropping it)
let provider_box = unsafe { Box::from_raw(provider) };
// Get a reference to the inner value
let provider_ref = &provider_box.0;
// Connect using the reference
let result = MisagentClient::connect(provider_ref).await;
// Explicitly keep the provider_box alive until after connect completes
std::mem::forget(provider_box);
result
});
match res {
Ok(r) => {
let boxed = Box::new(MisagentClientHandle(r));
unsafe { *client = Box::into_raw(boxed) };
IdeviceErrorCode::IdeviceSuccess
}
Err(e) => {
// If connection failed, the provider_box was already forgotten,
// so we need to reconstruct it to avoid leak
let _ = unsafe { Box::from_raw(provider) };
e.into()
}
}
}
/// Automatically creates and connects to Misagent, returning a client handle
///
/// # Arguments
/// * [`provider`] - A UsbmuxdProvider
/// * [`client`] - On success, will be set to point to a newly allocated MisagentClient handle
///
/// # Returns
/// An error code indicating success or failure
///
/// # Safety
/// `provider` must be a valid pointer to a handle allocated by this library
/// `client` must be a valid, non-null pointer to a location where the handle will be stored
#[unsafe(no_mangle)]
pub unsafe extern "C" fn misagent_connect_usbmuxd(
provider: *mut UsbmuxdProviderHandle,
client: *mut *mut MisagentClientHandle,
) -> IdeviceErrorCode {
if provider.is_null() {
log::error!("Provider is null");
return IdeviceErrorCode::InvalidArg;
}
let res: Result<MisagentClient, IdeviceError> = RUNTIME.block_on(async move {
// Take ownership of the provider (without immediately dropping it)
let provider_box = unsafe { Box::from_raw(provider) };
// Get a reference to the inner value
let provider_ref = &provider_box.0;
// Connect using the reference
let result = MisagentClient::connect(provider_ref).await;
// Explicitly keep the provider_box alive until after connect completes
std::mem::forget(provider_box);
result
let provider_ref: &dyn IdeviceProvider = unsafe { &*(*provider).0 };
MisagentClient::connect(provider_ref).await
});
match res {

View File

@@ -2,21 +2,19 @@
use std::ffi::c_void;
use idevice::{IdeviceError, IdeviceService, mobile_image_mounter::ImageMounter};
use idevice::{
IdeviceError, IdeviceService, mobile_image_mounter::ImageMounter, provider::IdeviceProvider,
};
use plist::Value;
use crate::{
IdeviceErrorCode, IdeviceHandle, RUNTIME,
provider::{TcpProviderHandle, UsbmuxdProviderHandle},
util,
};
use crate::{IdeviceErrorCode, IdeviceHandle, RUNTIME, provider::IdeviceProviderHandle, util};
pub struct ImageMounterHandle(pub ImageMounter);
/// Connects to the Image Mounter service using a TCP provider
/// Connects to the Image Mounter service using a provider
///
/// # Arguments
/// * [`provider`] - A TcpProvider
/// * [`provider`] - An IdeviceProvider
/// * [`client`] - On success, will be set to point to a newly allocated ImageMounter handle
///
/// # Returns
@@ -26,8 +24,8 @@ pub struct ImageMounterHandle(pub ImageMounter);
/// `provider` must be a valid pointer to a handle allocated by this library
/// `client` must be a valid, non-null pointer to a location where the handle will be stored
#[unsafe(no_mangle)]
pub unsafe extern "C" fn image_mounter_connect_tcp(
provider: *mut TcpProviderHandle,
pub unsafe extern "C" fn image_mounter_connect(
provider: *mut IdeviceProviderHandle,
client: *mut *mut ImageMounterHandle,
) -> IdeviceErrorCode {
if provider.is_null() || client.is_null() {
@@ -36,11 +34,8 @@ pub unsafe extern "C" fn image_mounter_connect_tcp(
}
let res: Result<ImageMounter, IdeviceError> = RUNTIME.block_on(async move {
let provider_box = unsafe { Box::from_raw(provider) };
let provider_ref = &provider_box.0;
let result = ImageMounter::connect(provider_ref).await;
std::mem::forget(provider_box);
result
let provider_ref: &dyn IdeviceProvider = unsafe { &*(*provider).0 };
ImageMounter::connect(provider_ref).await
});
match res {
@@ -56,46 +51,6 @@ pub unsafe extern "C" fn image_mounter_connect_tcp(
}
}
/// Connects to the Image Mounter service using a Usbmuxd provider
///
/// # Arguments
/// * [`provider`] - A UsbmuxdProvider
/// * [`client`] - On success, will be set to point to a newly allocated ImageMounter handle
///
/// # Returns
/// An error code indicating success or failure
///
/// # Safety
/// `provider` must be a valid pointer to a handle allocated by this library
/// `client` must be a valid, non-null pointer to a location where the handle will be stored
#[unsafe(no_mangle)]
pub unsafe extern "C" fn image_mounter_connect_usbmuxd(
provider: *mut UsbmuxdProviderHandle,
client: *mut *mut ImageMounterHandle,
) -> IdeviceErrorCode {
if provider.is_null() {
log::error!("Provider is null");
return IdeviceErrorCode::InvalidArg;
}
let res: Result<ImageMounter, IdeviceError> = RUNTIME.block_on(async move {
let provider_box = unsafe { Box::from_raw(provider) };
let provider_ref = &provider_box.0;
let result = ImageMounter::connect(provider_ref).await;
std::mem::forget(provider_box);
result
});
match res {
Ok(r) => {
let boxed = Box::new(ImageMounterHandle(r));
unsafe { *client = Box::into_raw(boxed) };
IdeviceErrorCode::IdeviceSuccess
}
Err(e) => e.into(),
}
}
/// Creates a new ImageMounter client from an existing Idevice connection
///
/// # Arguments
@@ -159,11 +114,8 @@ pub unsafe extern "C" fn image_mounter_copy_devices(
devices_len: *mut libc::size_t,
) -> IdeviceErrorCode {
let res: Result<Vec<Value>, IdeviceError> = RUNTIME.block_on(async move {
let mut client_box = unsafe { Box::from_raw(client) };
let client_ref = &mut client_box.0;
let result = client_ref.copy_devices().await;
std::mem::forget(client_box);
result
let client_ref = unsafe { &mut (*client).0 };
client_ref.copy_devices().await
});
match res {
@@ -219,11 +171,8 @@ pub unsafe extern "C" fn image_mounter_lookup_image(
};
let res: Result<Vec<u8>, IdeviceError> = RUNTIME.block_on(async move {
let mut client_box = unsafe { Box::from_raw(client) };
let client_ref = &mut client_box.0;
let result = client_ref.lookup_image(image_type).await;
std::mem::forget(client_box);
result
let client_ref = unsafe { &mut (*client).0 };
client_ref.lookup_image(image_type).await
});
match res {
@@ -279,13 +228,10 @@ pub unsafe extern "C" fn image_mounter_upload_image(
let signature_slice = unsafe { std::slice::from_raw_parts(signature, signature_len) };
let res: Result<(), IdeviceError> = RUNTIME.block_on(async move {
let mut client_box = unsafe { Box::from_raw(client) };
let client_ref = &mut client_box.0;
let result = client_ref
let client_ref = unsafe { &mut (*client).0 };
client_ref
.upload_image(image_type, image_slice, signature_slice.to_vec())
.await;
std::mem::forget(client_box);
result
.await
});
match res {
@@ -349,18 +295,15 @@ pub unsafe extern "C" fn image_mounter_mount_image(
};
let res: Result<(), IdeviceError> = RUNTIME.block_on(async move {
let mut client_box = unsafe { Box::from_raw(client) };
let client_ref = &mut client_box.0;
let result = client_ref
let client_ref = unsafe { &mut (*client).0 };
client_ref
.mount_image(
image_type,
signature_slice.to_vec(),
trust_cache,
info_plist,
)
.await;
std::mem::forget(client_box);
result
.await
});
match res {
@@ -397,11 +340,8 @@ pub unsafe extern "C" fn image_mounter_unmount_image(
};
let res: Result<(), IdeviceError> = RUNTIME.block_on(async move {
let mut client_box = unsafe { Box::from_raw(client) };
let client_ref = &mut client_box.0;
let result = client_ref.unmount_image(mount_path).await;
std::mem::forget(client_box);
result
let client_ref = unsafe { &mut (*client).0 };
client_ref.unmount_image(mount_path).await
});
match res {
@@ -432,11 +372,8 @@ pub unsafe extern "C" fn image_mounter_query_developer_mode_status(
}
let res: Result<bool, IdeviceError> = RUNTIME.block_on(async move {
let mut client_box = unsafe { Box::from_raw(client) };
let client_ref = &mut client_box.0;
let result = client_ref.query_developer_mode_status().await;
std::mem::forget(client_box);
result
let client_ref = unsafe { &mut (*client).0 };
client_ref.query_developer_mode_status().await
});
match res {
@@ -478,13 +415,10 @@ pub unsafe extern "C" fn image_mounter_mount_developer(
let signature_slice = unsafe { std::slice::from_raw_parts(signature, signature_len) };
let res: Result<(), IdeviceError> = RUNTIME.block_on(async move {
let mut client_box = unsafe { Box::from_raw(client) };
let client_ref = &mut client_box.0;
let result = client_ref
let client_ref = unsafe { &mut (*client).0 };
client_ref
.mount_developer(image_slice, signature_slice.to_vec())
.await;
std::mem::forget(client_box);
result
.await
});
match res {
@@ -531,13 +465,10 @@ pub unsafe extern "C" fn image_mounter_query_personalization_manifest(
let signature_slice = unsafe { std::slice::from_raw_parts(signature, signature_len) };
let res: Result<Vec<u8>, IdeviceError> = RUNTIME.block_on(async move {
let mut client_box = unsafe { Box::from_raw(client) };
let client_ref = &mut client_box.0;
let result = client_ref
let client_ref = unsafe { &mut (*client).0 };
client_ref
.query_personalization_manifest(image_type, signature_slice.to_vec())
.await;
std::mem::forget(client_box);
result
.await
});
match res {
@@ -590,11 +521,8 @@ pub unsafe extern "C" fn image_mounter_query_nonce(
};
let res: Result<Vec<u8>, IdeviceError> = RUNTIME.block_on(async move {
let mut client_box = unsafe { Box::from_raw(client) };
let client_ref = &mut client_box.0;
let result = client_ref.query_nonce(image_type).await;
std::mem::forget(client_box);
result
let client_ref = unsafe { &mut (*client).0 };
client_ref.query_nonce(image_type).await
});
match res {
@@ -645,13 +573,10 @@ pub unsafe extern "C" fn image_mounter_query_personalization_identifiers(
};
let res: Result<plist::Dictionary, IdeviceError> = RUNTIME.block_on(async move {
let mut client_box = unsafe { Box::from_raw(client) };
let client_ref = &mut client_box.0;
let result = client_ref
let client_ref = unsafe { &mut (*client).0 };
client_ref
.query_personalization_identifiers(image_type)
.await;
std::mem::forget(client_box);
result
.await
});
match res {
@@ -679,11 +604,8 @@ pub unsafe extern "C" fn image_mounter_roll_personalization_nonce(
client: *mut ImageMounterHandle,
) -> IdeviceErrorCode {
let res: Result<(), IdeviceError> = RUNTIME.block_on(async move {
let mut client_box = unsafe { Box::from_raw(client) };
let client_ref = &mut client_box.0;
let result = client_ref.roll_personalization_nonce().await;
std::mem::forget(client_box);
result
let client_ref = unsafe { &mut (*client).0 };
client_ref.roll_personalization_nonce().await
});
match res {
@@ -707,11 +629,8 @@ pub unsafe extern "C" fn image_mounter_roll_cryptex_nonce(
client: *mut ImageMounterHandle,
) -> IdeviceErrorCode {
let res: Result<(), IdeviceError> = RUNTIME.block_on(async move {
let mut client_box = unsafe { Box::from_raw(client) };
let client_ref = &mut client_box.0;
let result = client_ref.roll_cryptex_nonce().await;
std::mem::forget(client_box);
result
let client_ref = unsafe { &mut (*client).0 };
client_ref.roll_cryptex_nonce().await
});
match res {
@@ -739,10 +658,11 @@ pub unsafe extern "C" fn image_mounter_roll_cryptex_nonce(
///
/// # Safety
/// All pointers must be valid (except optional ones which can be null)
#[cfg(feature = "tss")]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn image_mounter_mount_personalized_usbmuxd(
pub unsafe extern "C" fn image_mounter_mount_personalized(
client: *mut ImageMounterHandle,
provider: *mut UsbmuxdProviderHandle,
provider: *mut IdeviceProviderHandle,
image: *const u8,
image_len: libc::size_t,
trust_cache: *const u8,
@@ -772,11 +692,9 @@ pub unsafe extern "C" fn image_mounter_mount_personalized_usbmuxd(
};
let res: Result<(), IdeviceError> = RUNTIME.block_on(async move {
let mut client_box = unsafe { Box::from_raw(client) };
let provider_box = unsafe { Box::from_raw(provider) };
let client_ref = &mut client_box.0;
let provider_ref = &provider_box.0;
let result = client_ref
let client_ref = unsafe { &mut (*client).0 };
let provider_ref: &dyn IdeviceProvider = unsafe { &*(*provider).0 };
client_ref
.mount_personalized(
provider_ref,
image_slice.to_vec(),
@@ -785,87 +703,7 @@ pub unsafe extern "C" fn image_mounter_mount_personalized_usbmuxd(
info_plist,
unique_chip_id,
)
.await;
std::mem::forget(client_box);
std::mem::forget(provider_box);
result
});
match res {
Ok(_) => IdeviceErrorCode::IdeviceSuccess,
Err(e) => e.into(),
}
}
/// Mounts a personalized developer image
///
/// # Arguments
/// * [`client`] - A valid ImageMounter handle
/// * [`provider`] - A valid provider handle
/// * [`image`] - Pointer to the image data
/// * [`image_len`] - Length of the image data
/// * [`trust_cache`] - Pointer to the trust cache data
/// * [`trust_cache_len`] - Length of the trust cache data
/// * [`build_manifest`] - Pointer to the build manifest data
/// * [`build_manifest_len`] - Length of the build manifest data
/// * [`info_plist`] - Pointer to info plist (optional)
/// * [`unique_chip_id`] - The device's unique chip ID
///
/// # Returns
/// An error code indicating success or failure
///
/// # Safety
/// All pointers must be valid (except optional ones which can be null)
#[unsafe(no_mangle)]
pub unsafe extern "C" fn image_mounter_mount_personalized_tcp(
client: *mut ImageMounterHandle,
provider: *mut TcpProviderHandle,
image: *const u8,
image_len: libc::size_t,
trust_cache: *const u8,
trust_cache_len: libc::size_t,
build_manifest: *const u8,
build_manifest_len: libc::size_t,
info_plist: *const c_void,
unique_chip_id: u64,
) -> IdeviceErrorCode {
if provider.is_null() || image.is_null() || trust_cache.is_null() || build_manifest.is_null() {
return IdeviceErrorCode::InvalidArg;
}
let image_slice = unsafe { std::slice::from_raw_parts(image, image_len) };
let trust_cache_slice = unsafe { std::slice::from_raw_parts(trust_cache, trust_cache_len) };
let build_manifest_slice =
unsafe { std::slice::from_raw_parts(build_manifest, build_manifest_len) };
let info_plist = if !info_plist.is_null() {
Some(
unsafe { Box::from_raw(info_plist as *mut Value) }
.as_ref()
.clone(),
)
} else {
None
};
let res: Result<(), IdeviceError> = RUNTIME.block_on(async move {
let mut client_box = unsafe { Box::from_raw(client) };
let provider_box = unsafe { Box::from_raw(provider) };
let client_ref = &mut client_box.0;
let provider_ref = &provider_box.0;
let result = client_ref
.mount_personalized(
provider_ref,
image_slice.to_vec(),
trust_cache_slice.to_vec(),
build_manifest_slice,
info_plist,
unique_chip_id,
)
.await;
std::mem::forget(client_box);
std::mem::forget(provider_box);
result
.await
});
match res {
@@ -895,10 +733,11 @@ pub unsafe extern "C" fn image_mounter_mount_personalized_tcp(
///
/// # Safety
/// All pointers must be valid (except optional ones which can be null)
#[cfg(feature = "tss")]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn image_mounter_mount_personalized_usbmuxd_with_callback(
pub unsafe extern "C" fn image_mounter_mount_personalized_with_callback(
client: *mut ImageMounterHandle,
provider: *mut UsbmuxdProviderHandle,
provider: *mut IdeviceProviderHandle,
image: *const u8,
image_len: libc::size_t,
trust_cache: *const u8,
@@ -930,16 +769,14 @@ pub unsafe extern "C" fn image_mounter_mount_personalized_usbmuxd_with_callback(
};
let res: Result<(), IdeviceError> = RUNTIME.block_on(async move {
let mut client_box = unsafe { Box::from_raw(client) };
let provider_box = unsafe { Box::from_raw(provider) };
let client_ref = &mut client_box.0;
let provider_ref = &provider_box.0;
let client_ref = unsafe { &mut (*client).0 };
let provider_ref: &dyn IdeviceProvider = unsafe { &*(*provider).0 };
let callback_wrapper = |((progress, total), context)| async move {
callback(progress, total, context);
};
let result = client_ref
client_ref
.mount_personalized_with_callback(
provider_ref,
image_slice.to_vec(),
@@ -950,98 +787,7 @@ pub unsafe extern "C" fn image_mounter_mount_personalized_usbmuxd_with_callback(
callback_wrapper,
context,
)
.await;
std::mem::forget(client_box);
std::mem::forget(provider_box);
result
});
match res {
Ok(_) => IdeviceErrorCode::IdeviceSuccess,
Err(e) => e.into(),
}
}
/// Mounts a personalized developer image with progress callback
///
/// # Arguments
/// * [`client`] - A valid ImageMounter handle
/// * [`provider`] - A valid provider handle
/// * [`image`] - Pointer to the image data
/// * [`image_len`] - Length of the image data
/// * [`trust_cache`] - Pointer to the trust cache data
/// * [`trust_cache_len`] - Length of the trust cache data
/// * [`build_manifest`] - Pointer to the build manifest data
/// * [`build_manifest_len`] - Length of the build manifest data
/// * [`info_plist`] - Pointer to info plist (optional)
/// * [`unique_chip_id`] - The device's unique chip ID
/// * [`callback`] - Progress callback function
/// * [`context`] - User context to pass to callback
///
/// # Returns
/// An error code indicating success or failure
///
/// # Safety
/// All pointers must be valid (except optional ones which can be null)
#[unsafe(no_mangle)]
pub unsafe extern "C" fn image_mounter_mount_personalized_tcp_with_callback(
client: *mut ImageMounterHandle,
provider: *mut TcpProviderHandle,
image: *const u8,
image_len: libc::size_t,
trust_cache: *const u8,
trust_cache_len: libc::size_t,
build_manifest: *const u8,
build_manifest_len: libc::size_t,
info_plist: *const c_void,
unique_chip_id: u64,
callback: extern "C" fn(progress: libc::size_t, total: libc::size_t, context: *mut c_void),
context: *mut c_void,
) -> IdeviceErrorCode {
if provider.is_null() || image.is_null() || trust_cache.is_null() || build_manifest.is_null() {
return IdeviceErrorCode::InvalidArg;
}
let image_slice = unsafe { std::slice::from_raw_parts(image, image_len) };
let trust_cache_slice = unsafe { std::slice::from_raw_parts(trust_cache, trust_cache_len) };
let build_manifest_slice =
unsafe { std::slice::from_raw_parts(build_manifest, build_manifest_len) };
let info_plist = if !info_plist.is_null() {
Some(
unsafe { Box::from_raw(info_plist as *mut Value) }
.as_ref()
.clone(),
)
} else {
None
};
let res: Result<(), IdeviceError> = RUNTIME.block_on(async move {
let mut client_box = unsafe { Box::from_raw(client) };
let provider_box = unsafe { Box::from_raw(provider) };
let client_ref = &mut client_box.0;
let provider_ref = &provider_box.0;
let callback_wrapper = |((progress, total), context)| async move {
callback(progress, total, context);
};
let result = client_ref
.mount_personalized_with_callback(
provider_ref,
image_slice.to_vec(),
trust_cache_slice.to_vec(),
build_manifest_slice,
info_plist,
unique_chip_id,
callback_wrapper,
context,
)
.await;
std::mem::forget(client_box);
std::mem::forget(provider_box);
result
.await
});
match res {

View File

@@ -2,13 +2,13 @@
use std::ffi::{CStr, c_char};
use idevice::{dvt::process_control::ProcessControlClient, tcp::adapter::Adapter};
use idevice::{ReadWrite, dvt::process_control::ProcessControlClient};
use plist::{Dictionary, Value};
use crate::{IdeviceErrorCode, RUNTIME, remote_server::RemoteServerAdapterHandle};
use crate::{IdeviceErrorCode, RUNTIME, remote_server::RemoteServerHandle};
/// Opaque handle to a ProcessControlClient
pub struct ProcessControlAdapterHandle<'a>(pub ProcessControlClient<'a, Adapter>);
pub struct ProcessControlHandle<'a>(pub ProcessControlClient<'a, Box<dyn ReadWrite>>);
/// Creates a new ProcessControlClient from a RemoteServerClient
///
@@ -24,8 +24,8 @@ pub struct ProcessControlAdapterHandle<'a>(pub ProcessControlClient<'a, Adapter>
/// `handle` must be a valid pointer to a location where the handle will be stored
#[unsafe(no_mangle)]
pub unsafe extern "C" fn process_control_new(
server: *mut RemoteServerAdapterHandle,
handle: *mut *mut ProcessControlAdapterHandle<'static>,
server: *mut RemoteServerHandle,
handle: *mut *mut ProcessControlHandle<'static>,
) -> IdeviceErrorCode {
if server.is_null() || handle.is_null() {
return IdeviceErrorCode::InvalidArg;
@@ -36,7 +36,7 @@ pub unsafe extern "C" fn process_control_new(
match res {
Ok(client) => {
let boxed = Box::new(ProcessControlAdapterHandle(client));
let boxed = Box::new(ProcessControlHandle(client));
unsafe { *handle = Box::into_raw(boxed) };
IdeviceErrorCode::IdeviceSuccess
}
@@ -52,7 +52,7 @@ pub unsafe extern "C" fn process_control_new(
/// # Safety
/// `handle` must be a valid pointer to a handle allocated by this library or NULL
#[unsafe(no_mangle)]
pub unsafe extern "C" fn process_control_free(handle: *mut ProcessControlAdapterHandle<'static>) {
pub unsafe extern "C" fn process_control_free(handle: *mut ProcessControlHandle<'static>) {
if !handle.is_null() {
let _ = unsafe { Box::from_raw(handle) };
}
@@ -76,7 +76,7 @@ pub unsafe extern "C" fn process_control_free(handle: *mut ProcessControlAdapter
/// All pointers must be valid or NULL where appropriate
#[unsafe(no_mangle)]
pub unsafe extern "C" fn process_control_launch_app(
handle: *mut ProcessControlAdapterHandle<'static>,
handle: *mut ProcessControlHandle<'static>,
bundle_id: *const c_char,
env_vars: *const *const c_char,
env_vars_count: usize,
@@ -159,7 +159,7 @@ pub unsafe extern "C" fn process_control_launch_app(
/// `handle` must be a valid pointer to a handle allocated by this library
#[unsafe(no_mangle)]
pub unsafe extern "C" fn process_control_kill_app(
handle: *mut ProcessControlAdapterHandle<'static>,
handle: *mut ProcessControlHandle<'static>,
pid: u64,
) -> IdeviceErrorCode {
if handle.is_null() {
@@ -188,7 +188,7 @@ pub unsafe extern "C" fn process_control_kill_app(
/// `handle` must be a valid pointer to a handle allocated by this library
#[unsafe(no_mangle)]
pub unsafe extern "C" fn process_control_disable_memory_limit(
handle: *mut ProcessControlAdapterHandle<'static>,
handle: *mut ProcessControlHandle<'static>,
pid: u64,
) -> IdeviceErrorCode {
if handle.is_null() {

View File

@@ -1,13 +1,12 @@
// Jackson Coxson
use idevice::provider::{TcpProvider, UsbmuxdProvider};
use idevice::provider::{IdeviceProvider, 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);
pub struct IdeviceProviderHandle(pub Box<dyn IdeviceProvider>);
/// Creates a TCP provider for idevice
///
@@ -22,16 +21,21 @@ pub struct UsbmuxdProviderHandle(pub UsbmuxdProvider);
///
/// # Safety
/// `ip` must be a valid sockaddr
/// `pairing_file` must never be used again
/// `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,
pairing_file: *mut crate::pairing_file::IdevicePairingFile,
label: *const c_char,
provider: *mut *mut TcpProviderHandle,
provider: *mut *mut IdeviceProviderHandle,
) -> IdeviceErrorCode {
if ip.is_null() || label.is_null() || provider.is_null() {
return IdeviceErrorCode::InvalidArg;
}
let addr = match util::c_addr_to_rust(ip) {
Ok(i) => i,
Err(e) => {
@@ -53,23 +57,23 @@ pub unsafe extern "C" fn idevice_tcp_provider_new(
label,
};
let boxed = Box::new(TcpProviderHandle(t));
let boxed = Box::new(IdeviceProviderHandle(Box::new(t)));
unsafe { *provider = Box::into_raw(boxed) };
IdeviceErrorCode::IdeviceSuccess
}
/// Frees a TcpProvider handle
/// Frees an IdeviceProvider 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)
/// `provider` must be a valid pointer to a IdeviceProvider handle that was allocated this library
/// or NULL (in which case this function does nothing)
#[unsafe(no_mangle)]
pub unsafe extern "C" fn tcp_provider_free(provider: *mut TcpProviderHandle) {
pub unsafe extern "C" fn idevice_provider_free(provider: *mut IdeviceProviderHandle) {
if !provider.is_null() {
log::debug!("Freeing TCP provider");
log::debug!("Freeing provider");
unsafe { drop(Box::from_raw(provider)) };
}
}
@@ -81,7 +85,6 @@ pub unsafe extern "C" fn tcp_provider_free(provider: *mut TcpProviderHandle) {
/// * [`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
///
@@ -91,7 +94,6 @@ pub unsafe extern "C" fn tcp_provider_free(provider: *mut TcpProviderHandle) {
/// # 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)]
@@ -101,21 +103,25 @@ pub unsafe extern "C" fn usbmuxd_provider_new(
udid: *const c_char,
device_id: u32,
label: *const c_char,
provider: *mut *mut UsbmuxdProviderHandle,
provider: *mut *mut IdeviceProviderHandle,
) -> IdeviceErrorCode {
if addr.is_null() || udid.is_null() || label.is_null() || provider.is_null() {
return IdeviceErrorCode::InvalidArg;
}
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;
return IdeviceErrorCode::InvalidString;
}
};
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;
log::error!("Invalid label string: {e:?}");
return IdeviceErrorCode::InvalidArg;
}
};
@@ -129,23 +135,8 @@ pub unsafe extern "C" fn usbmuxd_provider_new(
label,
};
let boxed = Box::new(UsbmuxdProviderHandle(p));
let boxed = Box::new(IdeviceProviderHandle(Box::new(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 UsbmuxdProviderHandle) {
if !provider.is_null() {
unsafe { drop(Box::from_raw(provider)) };
}
}

View File

@@ -1,46 +1,94 @@
// Jackson Coxson
use crate::core_device_proxy::AdapterHandle;
use crate::{IdeviceErrorCode, RUNTIME};
use idevice::IdeviceError;
use crate::rsd::RsdHandshakeHandle;
use crate::{IdeviceErrorCode, RUNTIME, ReadWriteOpaque};
use idevice::dvt::remote_server::RemoteServerClient;
use idevice::tcp::adapter::Adapter;
use idevice::tcp::stream::AdapterStream;
use idevice::{IdeviceError, ReadWrite, RsdService};
/// Opaque handle to a RemoteServerClient
pub struct RemoteServerAdapterHandle(pub RemoteServerClient<Adapter>);
pub struct RemoteServerHandle(pub RemoteServerClient<Box<dyn ReadWrite>>);
/// Creates a new RemoteServerClient from a ReadWrite connection
///
/// # Arguments
/// * [`connection`] - The connection to use for communication
/// * [`socket`] - The connection to use for communication, an object that implements ReadWrite
/// * [`handle`] - Pointer to store the newly created RemoteServerClient handle
///
/// # Returns
/// An error code indicating success or failure
///
/// # Safety
/// `connection` must be a valid pointer to a handle allocated by this library
/// `socket` must be a valid pointer to a 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 remote_server_adapter_new(
adapter: *mut crate::core_device_proxy::AdapterHandle,
handle: *mut *mut RemoteServerAdapterHandle,
pub unsafe extern "C" fn remote_server_new(
socket: *mut ReadWriteOpaque,
handle: *mut *mut RemoteServerHandle,
) -> IdeviceErrorCode {
if adapter.is_null() {
if socket.is_null() {
return IdeviceErrorCode::InvalidArg;
}
let connection = unsafe { Box::from_raw(adapter) };
let wrapper = unsafe { &mut *socket };
let res: Result<RemoteServerClient<Adapter>, IdeviceError> = RUNTIME.block_on(async move {
let mut client = RemoteServerClient::new(connection.0);
client.read_message(0).await?; // Until Message has bindings, we'll do the first read
Ok(client)
});
let res: Result<RemoteServerClient<Box<dyn ReadWrite>>, IdeviceError> =
match wrapper.inner.take() {
Some(stream) => RUNTIME.block_on(async move {
let mut client = RemoteServerClient::new(stream);
client.read_message(0).await?;
Ok(client)
}),
None => return IdeviceErrorCode::InvalidArg,
};
match res {
Ok(client) => {
let boxed = Box::new(RemoteServerAdapterHandle(client));
let boxed = Box::new(RemoteServerHandle(client));
unsafe { *handle = Box::into_raw(boxed) };
IdeviceErrorCode::IdeviceSuccess
}
Err(e) => e.into(),
}
}
/// Creates a new RemoteServerClient from a handshake and adapter
///
/// # Arguments
/// * [`provider`] - An adapter created by this library
/// * [`handshake`] - An RSD handshake from the same provider
///
/// # Returns
/// An error code indicating success or failure
///
/// # Safety
/// `provider` must be a valid pointer to a handle allocated by this library
/// `handshake` must be a valid pointer to a location where the handle will be stored
#[unsafe(no_mangle)]
pub unsafe extern "C" fn remote_server_connect_rsd(
provider: *mut AdapterHandle,
handshake: *mut RsdHandshakeHandle,
handle: *mut *mut RemoteServerHandle,
) -> IdeviceErrorCode {
if provider.is_null() || handshake.is_null() || handshake.is_null() {
return IdeviceErrorCode::InvalidArg;
}
let res: Result<RemoteServerClient<AdapterStream>, IdeviceError> =
RUNTIME.block_on(async move {
let provider_ref = unsafe { &mut (*provider).0 };
let handshake_ref = unsafe { &mut (*handshake).0 };
// Connect using the reference
RemoteServerClient::connect_rsd(provider_ref, handshake_ref).await
});
match res {
Ok(d) => {
let boxed = Box::new(RemoteServerHandle(RemoteServerClient::new(Box::new(
d.into_inner(),
))));
unsafe { *handle = Box::into_raw(boxed) };
IdeviceErrorCode::IdeviceSuccess
}
@@ -56,35 +104,8 @@ pub unsafe extern "C" fn remote_server_adapter_new(
/// # Safety
/// `handle` must be a valid pointer to a handle allocated by this library or NULL
#[unsafe(no_mangle)]
pub unsafe extern "C" fn remote_server_free(handle: *mut RemoteServerAdapterHandle) {
pub unsafe extern "C" fn remote_server_free(handle: *mut RemoteServerHandle) {
if !handle.is_null() {
let _ = unsafe { Box::from_raw(handle) };
}
}
/// Returns the underlying connection from a RemoteServerClient
///
/// # Arguments
/// * [`handle`] - The handle to get the connection from
/// * [`connection`] - The newly allocated ConnectionHandle
///
/// # Returns
/// An error code indicating success or failure
///
/// # 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 remote_server_adapter_into_inner(
handle: *mut RemoteServerAdapterHandle,
connection: *mut *mut AdapterHandle,
) -> IdeviceErrorCode {
if handle.is_null() || connection.is_null() {
return IdeviceErrorCode::InvalidArg;
}
let server = unsafe { Box::from_raw(handle) };
let connection_obj = server.0.into_inner();
let boxed = Box::new(AdapterHandle(connection_obj));
unsafe { *connection = Box::into_raw(boxed) };
IdeviceErrorCode::IdeviceSuccess
}

View File

@@ -1,288 +0,0 @@
// 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) };
}
}
}
}

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

@@ -0,0 +1,508 @@
//! 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::rsd::RsdHandshake;
use crate::{IdeviceErrorCode, RUNTIME, ReadWriteOpaque};
/// 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. 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,
) -> IdeviceErrorCode {
if socket.is_null() || handle.is_null() {
return IdeviceErrorCode::InvalidArg;
}
let wrapper = unsafe { &mut *socket };
let res = match wrapper.inner.take() {
Some(mut w) => RUNTIME.block_on(async move { RsdHandshake::new(w.as_mut()).await }),
None => {
return IdeviceErrorCode::InvalidArg;
}
};
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) };
}
}

View File

@@ -1,18 +1,18 @@
use std::ffi::{CStr, c_void};
use idevice::{IdeviceError, IdeviceService, springboardservices::SpringBoardServicesClient};
use crate::{
IdeviceErrorCode, IdeviceHandle, RUNTIME,
provider::{TcpProviderHandle, UsbmuxdProviderHandle},
use idevice::{
IdeviceError, IdeviceService, provider::IdeviceProvider,
springboardservices::SpringBoardServicesClient,
};
use crate::{IdeviceErrorCode, IdeviceHandle, RUNTIME, provider::IdeviceProviderHandle};
pub struct SpringBoardServicesClientHandle(pub SpringBoardServicesClient);
/// Connects to the Springboard service using a TCP provider
/// Connects to the Springboard service using a provider
///
/// # Arguments
/// * [`provider`] - A TcpProvider
/// * [`provider`] - An IdeviceProvider
/// * [`client`] - On success, will be set to point to a newly allocated SpringBoardServicesClient handle
///
/// # Returns
@@ -22,8 +22,8 @@ pub struct SpringBoardServicesClientHandle(pub SpringBoardServicesClient);
/// `provider` must be a valid pointer to a handle allocated by this library
/// `client` must be a valid, non-null pointer to a location where the handle will be stored
#[unsafe(no_mangle)]
pub unsafe extern "C" fn springboard_services_connect_tcp(
provider: *mut TcpProviderHandle,
pub unsafe extern "C" fn springboard_services_connect(
provider: *mut IdeviceProviderHandle,
client: *mut *mut SpringBoardServicesClientHandle,
) -> IdeviceErrorCode {
if provider.is_null() || client.is_null() {
@@ -32,18 +32,8 @@ pub unsafe extern "C" fn springboard_services_connect_tcp(
}
let res: Result<SpringBoardServicesClient, IdeviceError> = RUNTIME.block_on(async move {
// Take ownership of the provider (without immediately dropping it)
let provider_box = unsafe { Box::from_raw(provider) };
// Get a reference to the inner value
let provider_ref = &provider_box.0;
// Connect using the reference
let result = SpringBoardServicesClient::connect(provider_ref).await;
// Explicitly keep the provider_box alive until after connect completes
std::mem::forget(provider_box);
result
let provider_ref: &dyn IdeviceProvider = unsafe { &*(*provider).0 };
SpringBoardServicesClient::connect(provider_ref).await
});
match res {
@@ -61,53 +51,6 @@ pub unsafe extern "C" fn springboard_services_connect_tcp(
}
}
/// Connects to the Springboard service using a usbmuxd provider
///
/// # Arguments
/// * [`provider`] - A UsbmuxdProvider
/// * [`client`] - On success, will be set to point to a newly allocated SpringBoardServicesClient handle
///
/// # Returns
/// An error code indicating success or failure
///
/// # Safety
/// `provider` must be a valid pointer to a handle allocated by this library
/// `client` must be a valid, non-null pointer to a location where the handle will be stored
#[unsafe(no_mangle)]
pub unsafe extern "C" fn springboard_services_connect_usbmuxd(
provider: *mut UsbmuxdProviderHandle,
client: *mut *mut SpringBoardServicesClientHandle,
) -> IdeviceErrorCode {
if provider.is_null() {
log::error!("Provider is null");
return IdeviceErrorCode::InvalidArg;
}
let res: Result<SpringBoardServicesClient, IdeviceError> = RUNTIME.block_on(async move {
// Take ownership of the provider (without immediately dropping it)
let provider_box = unsafe { Box::from_raw(provider) };
// Get a reference to the inner value
let provider_ref = &provider_box.0;
// Connect using the reference
let result = SpringBoardServicesClient::connect(provider_ref).await;
// Explicitly keep the provider_box alive until after connect completes
std::mem::forget(provider_box);
result
});
match res {
Ok(r) => {
let boxed = Box::new(SpringBoardServicesClientHandle(r));
unsafe { *client = Box::into_raw(boxed) };
IdeviceErrorCode::IdeviceSuccess
}
Err(e) => e.into(),
}
}
/// Creates a new SpringBoardServices client from an existing Idevice connection
///
/// # Arguments
@@ -118,7 +61,8 @@ pub unsafe extern "C" fn springboard_services_connect_usbmuxd(
/// An error code indicating success or failure
///
/// # Safety
/// `socket` must be a valid pointer to a handle allocated by this library
/// `socket` must be a valid pointer to a handle allocated by this library. The socket is consumed,
/// and may not be used again.
/// `client` must be a valid, non-null pointer to a location where the handle will be stored
#[unsafe(no_mangle)]
pub unsafe extern "C" fn springboard_services_new(

View File

@@ -1,26 +1,26 @@
use std::os::raw::c_char;
use idevice::{syslog_relay::SyslogRelayClient, IdeviceError, IdeviceService};
use crate::{
provider::TcpProviderHandle, IdeviceErrorCode, RUNTIME
use idevice::{
IdeviceError, IdeviceService, provider::IdeviceProvider, syslog_relay::SyslogRelayClient,
};
use crate::{IdeviceErrorCode, RUNTIME, provider::IdeviceProviderHandle};
pub struct SyslogRelayClientHandle(pub SyslogRelayClient);
/// Automatically creates and connects to syslog relay, returning a client handle
///
/// # Arguments
/// * [`provider`] - A TcpProvider
/// * [`provider`] - An IdeviceProvider
/// * [`client`] - On success, will be set to point to a newly allocated SyslogRelayClient handle
///
/// # Safety
/// `provider` must be a valid pointer to a handle allocated by this library
/// `client` must be a valid, non-null pointer to a location where the handle will be stored
#[unsafe(no_mangle)]
pub extern "C" fn syslog_relay_connect_tcp(
provider: *mut TcpProviderHandle,
client: *mut *mut SyslogRelayClientHandle
pub unsafe extern "C" fn syslog_relay_connect_tcp(
provider: *mut IdeviceProviderHandle,
client: *mut *mut SyslogRelayClientHandle,
) -> IdeviceErrorCode {
if provider.is_null() {
log::error!("Null pointer provided");
@@ -28,14 +28,8 @@ pub extern "C" fn syslog_relay_connect_tcp(
}
let res: Result<SyslogRelayClient, IdeviceError> = RUNTIME.block_on(async move {
let provider_box = unsafe { Box::from_raw(provider) };
let provider_ref = &provider_box.0;
let result = SyslogRelayClient::connect(provider_ref).await;
std::mem::forget(provider_box);
result
let provider_ref: &dyn IdeviceProvider = unsafe { &*(*provider).0 };
SyslogRelayClient::connect(provider_ref).await
});
match res {
@@ -62,9 +56,7 @@ pub extern "C" fn syslog_relay_connect_tcp(
/// `handle` must be a valid pointer to the handle that was allocated by this library,
/// or NULL (in which case this function does nothing)
#[unsafe(no_mangle)]
pub extern "C" fn syslog_relay_client_free(
handle: *mut SyslogRelayClientHandle
) {
pub unsafe extern "C" fn syslog_relay_client_free(handle: *mut SyslogRelayClientHandle) {
if !handle.is_null() {
log::debug!("Freeing syslog relay client");
let _ = unsafe { Box::from_raw(handle) };
@@ -76,12 +68,12 @@ pub extern "C" fn syslog_relay_client_free(
/// # Arguments
/// * [`client`] - The SyslogRelayClient handle
/// * [`log_message`] - On success a newly allocated cstring will be set to point to the log message
///
///
/// # Safety
/// `client` must be a valid pointer to a handle allocated by this library
/// `log_message` must be a valid, non-null pointer to a location where the log message will be stored
#[unsafe(no_mangle)]
pub extern "C" fn syslog_relay_next(
pub unsafe extern "C" fn syslog_relay_next(
client: *mut SyslogRelayClientHandle,
log_message: *mut *mut c_char,
) -> IdeviceErrorCode {
@@ -89,20 +81,15 @@ pub extern "C" fn syslog_relay_next(
return IdeviceErrorCode::InvalidArg;
}
let res = RUNTIME.block_on(async {
unsafe { &mut *client }
.0
.next()
.await
});
let res = RUNTIME.block_on(async { unsafe { &mut *client }.0.next().await });
match res {
Ok(log) => {
use std::ffi::CString;
// null bytes are a curse in C, so just use spaces
let safe_log = log.replace('\0', " ");
match CString::new(safe_log) {
Ok(c_string) => {
unsafe { *log_message = c_string.into_raw() };
@@ -116,4 +103,4 @@ pub extern "C" fn syslog_relay_next(
}
Err(e) => e.into(),
}
}
}

View File

@@ -26,7 +26,6 @@ base64 = { version = "0.22" }
indexmap = { version = "2.7", features = ["serde"], optional = true }
uuid = { version = "1.12", features = ["serde", "v4"], optional = true }
async-recursion = { version = "1.1", optional = true }
chrono = { version = "0.4.40", optional = true, default-features = false }
serde_json = { version = "1", optional = true }
@@ -50,7 +49,7 @@ x509-cert = { version = "0.2", optional = true, features = [
], default-features = false }
[dev-dependencies]
tokio = { version = "1.43", features = ["fs"] }
tokio = { version = "1.43", features = ["full"] }
tun-rs = { version = "2.0.8", features = ["async_tokio"] }
bytes = "1.10.1"
@@ -69,19 +68,14 @@ misagent = []
mobile_image_mounter = ["dep:sha2"]
location_simulation = []
pair = ["chrono/default", "tokio/time", "dep:sha2", "dep:rsa", "dep:x509-cert"]
rsd = ["xpc"]
syslog_relay = ["dep:bytes"]
tcp = ["tokio/net"]
tunnel_tcp_stack = ["dep:rand", "dep:futures", "tokio/fs", "tokio/sync"]
tss = ["dep:uuid", "dep:reqwest"]
tunneld = ["dep:serde_json", "dep:json", "dep:reqwest"]
usbmuxd = ["tokio/net"]
xpc = [
"tokio/sync",
"dep:indexmap",
"dep:uuid",
"dep:async-recursion",
"dep:json",
]
xpc = ["dep:indexmap", "dep:uuid"]
full = [
"afc",
"amfi",
@@ -98,6 +92,7 @@ full = [
"usbmuxd",
"xpc",
"location_simulation",
"rsd",
"tcp",
"tunnel_tcp_stack",
"tss",

View File

@@ -15,12 +15,17 @@ pub mod tunneld;
#[cfg(feature = "usbmuxd")]
pub mod usbmuxd;
mod util;
#[cfg(feature = "xpc")]
pub mod xpc;
pub mod services;
pub use services::*;
#[cfg(feature = "xpc")]
pub use xpc::RemoteXpcClient;
use log::{debug, error, trace};
use provider::IdeviceProvider;
use provider::{IdeviceProvider, RsdProvider};
use rustls::{crypto::CryptoProvider, pki_types::ServerName};
use std::{
io::{self, BufWriter},
@@ -60,6 +65,25 @@ pub trait IdeviceService: Sized {
) -> impl std::future::Future<Output = Result<Self, IdeviceError>> + Send;
}
pub trait RsdService: Sized {
fn rsd_service_name() -> &'static str;
fn from_stream(
stream: Self::Stream,
) -> impl std::future::Future<Output = Result<Self, IdeviceError>> + Send;
fn connect_rsd<'a, S>(
provider: &'a mut impl RsdProvider<'a, Stream = S>,
handshake: &mut rsd::RsdHandshake,
) -> impl std::future::Future<Output = Result<Self, IdeviceError>>
where
Self: crate::RsdService<Stream = S>,
S: ReadWrite,
{
handshake.connect(provider)
}
type Stream: ReadWrite;
}
/// Type alias for boxed device connection sockets
///
/// Used to enable dynamic dispatch of different connection types while maintaining
@@ -419,6 +443,8 @@ pub enum IdeviceError {
HeartbeatTimeout,
#[error("not found")]
NotFound,
#[error("service not found")]
ServiceNotFound,
#[error("CDTunnel packet too short")]
CdtunnelPacketTooShort,
#[error("CDTunnel packet invalid magic")]
@@ -498,8 +524,44 @@ pub enum IdeviceError {
InternalError(String),
#[cfg(feature = "xpc")]
#[error("xpc message failed")]
Xpc(#[from] xpc::error::XPCError),
#[error("unknown http frame type")]
UnknownFrame(u8),
#[cfg(feature = "xpc")]
#[error("unknown http setting type")]
UnknownHttpSetting(u16),
#[cfg(feature = "xpc")]
#[error("Unintialized stream ID")]
UninitializedStreamId,
#[cfg(feature = "xpc")]
#[error("unknown XPC type")]
UnknownXpcType(u32),
#[cfg(feature = "xpc")]
#[error("malformed XPC message")]
MalformedXpc,
#[cfg(feature = "xpc")]
#[error("invalid XPC magic")]
InvalidXpcMagic,
#[cfg(feature = "xpc")]
#[error("unexpected XPC version")]
UnexpectedXpcVersion,
#[cfg(feature = "xpc")]
#[error("invalid C string")]
InvalidCString,
#[cfg(feature = "xpc")]
#[error("stream reset")]
HttpStreamReset,
#[cfg(feature = "xpc")]
#[error("go away packet received")]
HttpGoAway(String),
#[cfg(feature = "dvt")]
#[error("NSKeyedArchive error")]

View File

@@ -8,7 +8,7 @@ use std::{future::Future, pin::Pin};
#[cfg(feature = "tcp")]
use tokio::net::TcpStream;
use crate::{pairing_file::PairingFile, Idevice, IdeviceError};
use crate::{pairing_file::PairingFile, Idevice, IdeviceError, ReadWrite};
#[cfg(feature = "usbmuxd")]
use crate::usbmuxd::UsbmuxdAddr;
@@ -42,6 +42,14 @@ pub trait IdeviceProvider: Unpin + Send + Sync + std::fmt::Debug {
) -> Pin<Box<dyn Future<Output = Result<PairingFile, IdeviceError>> + Send>>;
}
pub trait RsdProvider<'a>: Unpin + Send + Sync + std::fmt::Debug {
fn connect_to_service_port(
&'a mut self,
port: u16,
) -> impl std::future::Future<Output = Result<Self::Stream, IdeviceError>> + Send;
type Stream: ReadWrite;
}
/// TCP-based device connection provider
#[cfg(feature = "tcp")]
#[derive(Debug)]
@@ -149,3 +157,15 @@ impl IdeviceProvider for UsbmuxdProvider {
})
}
}
#[cfg(feature = "tcp")]
impl<'a> RsdProvider<'a> for std::net::IpAddr {
async fn connect_to_service_port(
&'a mut self,
port: u16,
) -> Result<Self::Stream, IdeviceError> {
Ok(tokio::net::TcpStream::connect((*self, port)).await?)
}
type Stream = tokio::net::TcpStream;
}

View File

@@ -8,10 +8,22 @@ use log::debug;
use std::fmt::Write;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use crate::{IdeviceError, ReadWrite};
use crate::{IdeviceError, ReadWrite, RsdService};
/// The service name for the debug proxy as registered with lockdownd
pub const SERVICE_NAME: &str = "com.apple.internal.dt.remote.debugproxy";
impl<R: ReadWrite> RsdService for DebugProxyClient<R> {
fn rsd_service_name() -> &'static str {
"com.apple.internal.dt.remote.debugproxy"
}
async fn from_stream(stream: R) -> Result<Self, IdeviceError> {
Ok(Self {
socket: stream,
noack_mode: false,
})
}
type Stream = R;
}
/// Client for interacting with the iOS debug proxy service
///
@@ -102,6 +114,7 @@ impl<R: ReadWrite> DebugProxyClient<R> {
// Send the packet
self.socket.write_all(packet.as_bytes()).await?;
self.socket.flush().await?;
// Read the response
let response = self.read_response().await?;
@@ -161,6 +174,7 @@ impl<R: ReadWrite> DebugProxyClient<R> {
/// Returns `IdeviceError` if writing fails
pub async fn send_raw(&mut self, bytes: &[u8]) -> Result<(), IdeviceError> {
self.socket.write_all(bytes).await?;
self.socket.flush().await?;
Ok(())
}
@@ -242,6 +256,7 @@ impl<R: ReadWrite> DebugProxyClient<R> {
/// Returns `IdeviceError` if writing fails
pub async fn send_ack(&mut self) -> Result<(), IdeviceError> {
self.socket.write_all(b"+").await?;
self.socket.flush().await?;
Ok(())
}
@@ -251,6 +266,7 @@ impl<R: ReadWrite> DebugProxyClient<R> {
/// Returns `IdeviceError` if writing fails
pub async fn send_noack(&mut self) -> Result<(), IdeviceError> {
self.socket.write_all(b"-").await?;
self.socket.flush().await?;
Ok(())
}
@@ -300,4 +316,3 @@ impl From<&str> for DebugserverCommand {
s.to_string().into()
}
}

View File

@@ -1,9 +1,21 @@
// Jackson Coxson
use crate::{IdeviceError, ReadWrite, RsdService};
#[cfg(feature = "location_simulation")]
pub mod location_simulation;
pub mod message;
pub mod process_control;
pub mod remote_server;
pub const SERVICE_NAME: &str = "com.apple.instruments.dtservicehub";
impl<R: ReadWrite> RsdService for remote_server::RemoteServerClient<R> {
fn rsd_service_name() -> &'static str {
"com.apple.instruments.dtservicehub"
}
async fn from_stream(stream: R) -> Result<Self, IdeviceError> {
Ok(Self::new(stream))
}
type Stream = R;
}

View File

@@ -202,6 +202,7 @@ impl<R: ReadWrite> RemoteServerClient<R> {
let message = Message::new(mheader, pheader, aux, data);
debug!("Sending message: {message:#?}");
self.idevice.write_all(&message.serialize()).await?;
self.idevice.flush().await?;
Ok(())
}

View File

@@ -23,9 +23,9 @@ pub mod misagent;
pub mod mobile_image_mounter;
#[cfg(feature = "syslog_relay")]
pub mod os_trace_relay;
#[cfg(feature = "rsd")]
pub mod rsd;
#[cfg(feature = "springboardservices")]
pub mod springboardservices;
#[cfg(feature = "syslog_relay")]
pub mod syslog_relay;
#[cfg(feature = "xpc")]
pub mod xpc;

177
idevice/src/services/rsd.rs Normal file
View File

@@ -0,0 +1,177 @@
//! Remote Service Discovery
//! Communicates via XPC and returns advertised services
use std::collections::HashMap;
use log::warn;
use serde::Deserialize;
use crate::{provider::RsdProvider, IdeviceError, ReadWrite, RemoteXpcClient};
/// Describes an available XPC service
#[derive(Debug, Clone, Deserialize)]
pub struct RsdService {
/// Required entitlement to access this service
pub entitlement: String,
/// Port number where the service is available
pub port: u16,
/// Whether the service uses remote XPC
pub uses_remote_xpc: bool,
/// Optional list of supported features
pub features: Option<Vec<String>>,
/// Optional service version number
pub service_version: Option<i64>,
}
pub struct RsdHandshake {
pub services: HashMap<String, RsdService>,
pub protocol_version: usize,
pub properties: HashMap<String, plist::Value>,
pub uuid: String,
}
impl RsdHandshake {
pub async fn new(socket: impl ReadWrite) -> Result<Self, IdeviceError> {
let mut xpc_client = RemoteXpcClient::new(socket).await?;
let data = xpc_client.do_handshake().await?;
let services_dict = match data
.as_dictionary()
.and_then(|x| x.get("Services"))
.and_then(|x| x.as_dictionary())
{
Some(d) => d,
None => return Err(IdeviceError::UnexpectedResponse),
};
// Parse available services
let mut services: HashMap<String, RsdService> = HashMap::new();
for (name, service) in services_dict.into_iter() {
match service.as_dictionary() {
Some(service) => {
let entitlement = match service.get("Entitlement").and_then(|x| x.as_string()) {
Some(e) => e.to_string(),
None => {
warn!("Service did not contain entitlement string");
continue;
}
};
let port = match service
.get("Port")
.and_then(|x| x.as_string())
.and_then(|x| x.parse::<u16>().ok())
{
Some(e) => e,
None => {
warn!("Service did not contain port string");
continue;
}
};
let uses_remote_xpc = match service
.get("Properties")
.and_then(|x| x.as_dictionary())
.and_then(|x| x.get("UsesRemoteXPC"))
.and_then(|x| x.as_boolean())
{
Some(e) => e.to_owned(),
None => false, // default is false
};
let features = service
.get("Properties")
.and_then(|x| x.as_dictionary())
.and_then(|x| x.get("Features"))
.and_then(|x| x.as_array())
.map(|f| {
f.iter()
.filter_map(|x| x.as_string())
.map(|x| x.to_string())
.collect::<Vec<String>>()
});
let service_version = service
.get("Properties")
.and_then(|x| x.as_dictionary())
.and_then(|x| x.get("ServiceVersion"))
.and_then(|x| x.as_signed_integer())
.map(|e| e.to_owned());
services.insert(
name.to_string(),
RsdService {
entitlement,
port,
uses_remote_xpc,
features,
service_version,
},
);
}
None => {
warn!("Service is not a dictionary!");
continue;
}
}
}
let protocol_version = match data.as_dictionary().and_then(|x| {
x.get("MessagingProtocolVersion")
.and_then(|x| x.as_signed_integer())
}) {
Some(p) => p as usize,
None => {
return Err(IdeviceError::UnexpectedResponse);
}
};
let uuid = match data
.as_dictionary()
.and_then(|x| x.get("UUID").and_then(|x| x.as_string()))
{
Some(u) => u.to_string(),
None => {
return Err(IdeviceError::UnexpectedResponse);
}
};
let properties = match data
.as_dictionary()
.and_then(|x| x.get("Properties").and_then(|x| x.as_dictionary()))
{
Some(d) => d
.into_iter()
.map(|(name, prop)| (name.to_owned(), prop.to_owned()))
.collect::<HashMap<String, plist::Value>>(),
None => {
return Err(IdeviceError::UnexpectedResponse);
}
};
Ok(Self {
services,
protocol_version,
properties,
uuid,
})
}
pub async fn connect<'a, T, S>(
&mut self,
provider: &'a mut impl RsdProvider<'a, Stream = S>,
) -> Result<T, IdeviceError>
where
T: crate::RsdService<Stream = S>,
S: ReadWrite,
{
let service_name = T::rsd_service_name();
let service = match self.services.get(service_name) {
Some(s) => s,
None => {
return Err(IdeviceError::ServiceNotFound);
}
};
let stream = provider.connect_to_service_port(service.port).await?;
T::from_stream(stream).await
}
}

View File

@@ -1,103 +0,0 @@
// DebianArch
use super::http2::error::Http2Error;
use std::{
array::TryFromSliceError, error::Error, ffi::FromVecWithNulError, io, num::TryFromIntError,
str::Utf8Error,
};
#[derive(Debug)]
pub enum XPCError {
Io(io::Error),
Http2Error(Http2Error),
ParseError(ParseError),
Custom(String),
}
#[derive(Debug)]
pub enum ParseError {
TryFromSliceError(TryFromSliceError),
TryFromIntError(TryFromIntError),
FromVecWithNulError(FromVecWithNulError),
Utf8Error(Utf8Error),
}
impl From<TryFromSliceError> for XPCError {
fn from(value: TryFromSliceError) -> Self {
Self::ParseError(ParseError::TryFromSliceError(value))
}
}
impl From<TryFromIntError> for XPCError {
fn from(value: TryFromIntError) -> Self {
Self::ParseError(ParseError::TryFromIntError(value))
}
}
impl From<ParseError> for XPCError {
fn from(value: ParseError) -> Self {
Self::ParseError(value)
}
}
impl From<FromVecWithNulError> for XPCError {
fn from(value: FromVecWithNulError) -> Self {
Self::ParseError(ParseError::FromVecWithNulError(value))
}
}
impl From<Utf8Error> for XPCError {
fn from(value: Utf8Error) -> Self {
Self::ParseError(ParseError::Utf8Error(value))
}
}
impl From<io::Error> for XPCError {
fn from(value: io::Error) -> Self {
Self::Io(value)
}
}
impl From<&str> for XPCError {
fn from(value: &str) -> Self {
Self::Custom(value.to_string())
}
}
impl From<Http2Error> for XPCError {
fn from(value: Http2Error) -> Self {
Self::Http2Error(value)
}
}
impl std::fmt::Display for XPCError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"XPCError({})",
match self {
Self::Io(io) => io.to_string(),
Self::Http2Error(http2) => http2.to_string(),
Self::ParseError(e) => e.to_string(),
Self::Custom(s) => s.clone(),
}
)
}
}
impl std::fmt::Display for ParseError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"ParseError({})",
match self {
Self::TryFromSliceError(e) => e.to_string(),
Self::TryFromIntError(e) => e.to_string(),
Self::FromVecWithNulError(e) => e.to_string(),
Self::Utf8Error(e) => e.to_string(),
}
)
}
}
impl Error for XPCError {}

View File

@@ -1,62 +0,0 @@
// DebianArch
use std::{array::TryFromSliceError, error::Error, io, num::TryFromIntError};
use tokio::sync::mpsc::error::SendError;
#[derive(Debug)]
pub enum Http2Error {
Io(io::Error),
SendError,
TryFromIntError(TryFromIntError),
TryFromSliceError(TryFromSliceError),
Custom(String),
}
impl From<io::Error> for Http2Error {
fn from(value: io::Error) -> Self {
Self::Io(value)
}
}
impl<T> From<SendError<T>> for Http2Error {
fn from(_: SendError<T>) -> Self {
Self::SendError
}
}
impl From<&str> for Http2Error {
fn from(value: &str) -> Self {
Self::Custom(value.to_string())
}
}
impl From<TryFromIntError> for Http2Error {
fn from(value: TryFromIntError) -> Self {
Self::TryFromIntError(value)
}
}
impl From<TryFromSliceError> for Http2Error {
fn from(value: TryFromSliceError) -> Self {
Self::TryFromSliceError(value)
}
}
impl std::fmt::Display for Http2Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Http2Error({})",
match self {
Self::Io(io) => io.to_string(),
Self::SendError => "SendError".to_string(),
Self::TryFromIntError(e) => e.to_string(),
Self::TryFromSliceError(e) => e.to_string(),
Self::Custom(s) => s.clone(),
}
)
}
}
impl Error for Http2Error {}

View File

@@ -1,256 +0,0 @@
// DebianArch
use std::collections::HashMap;
use super::error::Http2Error;
pub const HTTP2_MAGIC: &[u8; 24] = b"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n";
#[derive(Debug)]
pub struct Frame {
pub stream_id: u32,
pub flags: u8,
pub frame_type: FrameType,
pub body: Vec<u8>,
}
impl Frame {
pub fn deserialize(buf: &[u8]) -> Result<Self, Http2Error> {
let mut len_buf = buf[0..3].to_vec();
len_buf.insert(0, 0);
let body_len = u32::from_be_bytes(len_buf.try_into().unwrap()) as usize;
let frame_type = buf[3];
let flags = buf[4];
let stream_id = u32::from_be_bytes(buf[5..9].try_into()?);
let body = buf[9..9 + body_len].to_vec();
Ok(Self {
stream_id,
flags,
frame_type: frame_type.into(),
body,
})
}
}
impl Framable for Frame {
fn serialize(&self) -> Vec<u8> {
let mut res = Vec::new();
let body_len = (self.body.len() as u32).to_be_bytes();
res.extend_from_slice(&[body_len[1], body_len[2], body_len[3]]); // [0..3]
res.push(self.frame_type.into()); // [3]
res.push(self.flags); // flag mask [4]
res.extend_from_slice(&self.stream_id.to_be_bytes()); // [4..8]
res.extend_from_slice(&self.body); // [9..9+len]
res
}
}
pub trait Framable: From<Frame> {
fn serialize(&self) -> Vec<u8>;
}
// Frame implementations:
pub struct SettingsFrame {
frame: Frame,
pub settings: HashMap<u16, u32>,
}
impl SettingsFrame {
pub const MAX_CONCURRENT_STREAMS: u16 = 0x03;
pub const INITIAL_WINDOW_SIZE: u16 = 0x04;
pub const ACK: u8 = 0x01;
pub fn new(/*stream_id: u32, */ settings: HashMap<u16, u32>, flags: u8) -> Self {
let mut body = Vec::new();
for setting in settings.clone() {
body.extend_from_slice(&setting.0.to_be_bytes());
body.extend_from_slice(&setting.1.to_be_bytes());
}
Self {
frame: Frame {
/*stream_id*/ stream_id: 0,
flags,
frame_type: FrameType::Settings,
body,
},
settings,
}
}
pub fn ack(/*stream_id: u32*/) -> Self {
Self {
frame: Frame {
/*stream_id*/ stream_id: 0,
flags: Self::ACK,
frame_type: FrameType::Settings,
body: Vec::new(),
},
settings: HashMap::new(),
}
}
}
impl Framable for SettingsFrame {
fn serialize(&self) -> Vec<u8> {
self.frame.serialize()
}
}
impl From<Frame> for SettingsFrame {
fn from(value: Frame) -> Self {
let mut idx = 0;
let mut settings = HashMap::new();
while idx < value.body.len() {
let key = u16::from_be_bytes(value.body[idx..idx + 2].try_into().unwrap());
let value = u32::from_be_bytes(value.body[idx + 2..idx + 2 + 4].try_into().unwrap());
settings.insert(key, value);
idx += 2 + 4;
}
Self {
frame: value,
settings,
}
}
}
pub struct WindowUpdateFrame {
frame: Frame,
}
impl WindowUpdateFrame {
// the frame's stream identifier indicates the affected stream; in the latter, the value "0" indicates that the entire connection is the subject of the frame.
pub fn new(stream_id: u32, window_increment: u32) -> Self {
if window_increment == 0 {
panic!("PROTOCOL_ERROR");
}
Self {
frame: Frame {
stream_id,
flags: Default::default(),
frame_type: FrameType::WindowUpdate,
body: window_increment.to_be_bytes().to_vec(),
},
}
}
}
impl Framable for WindowUpdateFrame {
fn serialize(&self) -> Vec<u8> {
self.frame.serialize()
}
}
impl From<Frame> for WindowUpdateFrame {
fn from(value: Frame) -> Self {
Self { frame: value }
}
}
pub struct HeadersFrame {
frame: Frame,
}
impl HeadersFrame {
pub const END_HEADERS: u8 = 0x04;
pub const PADDED: u8 = 0x08;
pub const PRIORITY: u8 = 0x20;
pub fn new(stream_id: u32, flags: u8) -> Self {
Self {
frame: Frame {
stream_id,
flags,
frame_type: FrameType::Headers,
body: Vec::new(),
},
}
}
}
impl Framable for HeadersFrame {
fn serialize(&self) -> Vec<u8> {
if self.frame.flags & Self::PADDED == Self::PADDED {
unimplemented!("haven't added padding support !")
};
if self.frame.flags & Self::PRIORITY == Self::PRIORITY {
unimplemented!("haven't added priority support !")
};
// let padding = 0; for 'PADDED' flag
// let priority_data = b""; // for PRIORITY flag
self.frame.serialize()
}
}
impl From<Frame> for HeadersFrame {
fn from(value: Frame) -> Self {
if value.flags & Self::PADDED == Self::PADDED {
unimplemented!("haven't added padding support !")
};
if value.flags & Self::PRIORITY == Self::PRIORITY {
unimplemented!("haven't added priority support !")
};
Self { frame: value }
}
}
pub struct DataFrame {
frame: Frame,
}
impl DataFrame {
// TODO: Handle padding flag.
pub fn new(stream_id: u32, data: Vec<u8>, flags: u8) -> Self {
Self {
frame: Frame {
stream_id,
flags,
frame_type: FrameType::Data,
body: data,
},
}
}
}
impl Framable for DataFrame {
fn serialize(&self) -> Vec<u8> {
self.frame.serialize()
}
}
impl From<Frame> for DataFrame {
fn from(value: Frame) -> Self {
Self { frame: value }
}
}
#[repr(u8)]
#[derive(Clone, Copy, Debug)]
pub enum FrameType {
Data = 0,
Headers = 1,
Priority = 2,
RstStream = 3,
Settings = 4,
PushPromise = 5,
Ping = 6,
GoAway = 7,
WindowUpdate = 8,
Continuation = 9,
}
impl From<FrameType> for u8 {
fn from(value: FrameType) -> Self {
value as u8
}
}
impl From<u8> for FrameType {
fn from(value: u8) -> Self {
unsafe { std::mem::transmute::<_, FrameType>(value) }
}
}

View File

@@ -1,144 +0,0 @@
// DebianArch
use async_recursion::async_recursion;
use error::Http2Error;
use std::collections::HashMap;
use tokio::{
io::{AsyncReadExt, AsyncWriteExt},
sync::mpsc::{self, Receiver, Sender},
};
pub mod error;
pub mod h2;
use h2::{
DataFrame, Framable, Frame, FrameType, HeadersFrame, SettingsFrame, WindowUpdateFrame,
HTTP2_MAGIC,
};
use crate::ReadWrite;
pub type Channels = HashMap<u32, (Sender<Vec<u8>>, Receiver<Vec<u8>>)>;
pub const INIT_STREAM: u32 = 0;
pub const ROOT_CHANNEL: u32 = 1;
pub const REPLY_CHANNEL: u32 = 3;
pub struct Connection<R: ReadWrite> {
pub stream: R,
channels: Channels,
window_size: u32,
}
impl<R: ReadWrite> Connection<R> {
pub async fn new(mut stream: R) -> Result<Self, Http2Error> {
stream.write_all(HTTP2_MAGIC).await?;
Ok(Self {
stream,
channels: HashMap::new(),
window_size: 1048576,
})
}
pub async fn send_frame<A: Framable>(&mut self, frame: A) -> Result<(), Http2Error> {
let body = &frame.serialize();
if body.len() > self.window_size as usize {
panic!("we need to chunk it :D")
}
self.stream.write_all(body).await?;
Ok(())
}
pub async fn read_data(&mut self) -> Result<Vec<u8>, Http2Error> {
loop {
let frame = self.read_frame().await?;
match frame.frame_type {
FrameType::Data => {
if frame.stream_id % 2 == 0 && !frame.body.is_empty() {
let frame_len: u32 = frame.body.len().try_into()?;
self.send_frame(WindowUpdateFrame::new(0, frame_len))
.await?;
self.send_frame(WindowUpdateFrame::new(frame.stream_id, frame_len))
.await?;
}
match self.channels.get_mut(&frame.stream_id) {
Some((sender, _receiver)) => {
sender.send(frame.body.clone()).await?;
}
None => {
let chan = mpsc::channel(100);
chan.0.send(frame.body.clone()).await?;
self.channels.insert(frame.stream_id, chan);
}
}
return Ok(frame.body);
}
FrameType::GoAway | FrameType::RstStream => {
let _last_streamid = u32::from_be_bytes(frame.body[0..4].try_into().unwrap());
return Err("connection closed, bye")?;
}
FrameType::Settings => {
let flags = frame.flags;
let settings_frame: SettingsFrame = frame.into();
if flags & SettingsFrame::ACK != SettingsFrame::ACK {
self.send_frame(SettingsFrame::ack()).await?;
}
if let Some(&window_size) = settings_frame
.settings
.get(&SettingsFrame::INITIAL_WINDOW_SIZE)
{
self.window_size = window_size;
}
}
_ => continue,
}
}
}
pub async fn read_frame(&mut self) -> Result<Frame, Http2Error> {
let mut length_buf = vec![0; 3];
self.stream.read_exact(&mut length_buf).await?;
length_buf.insert(0, 0);
let len = u32::from_be_bytes(length_buf.clone().try_into().unwrap()) as usize;
let mut rest = vec![0; 9 - 3 + len];
self.stream.read_exact(&mut rest).await?;
let mut content = vec![length_buf[1], length_buf[2], length_buf[3]];
content.extend_from_slice(&rest);
Frame::deserialize(&content)
}
// pub async fn multiplex_write(&mut self, stream_id: u32) -> Result<()> {}
// gets a Reader + Writer for a channel.
pub async fn write_streamid(
&mut self,
stream_id: u32,
data: Vec<u8>,
) -> Result<(), Http2Error> {
// TODO: If we ever allow concurrent writes we must not always send 'END_HEADERS'.
self.send_frame(HeadersFrame::new(stream_id, HeadersFrame::END_HEADERS))
.await?;
self.send_frame(DataFrame::new(stream_id, data, Default::default()))
.await?;
Ok(())
}
#[async_recursion]
pub async fn read_streamid(&mut self, stream_id: u32) -> Result<Vec<u8>, Http2Error> {
match self.channels.get_mut(&stream_id) {
Some((_sender, receiver)) => match receiver.try_recv().ok() {
Some(data) => Ok(data),
None => {
self.read_data().await?;
self.read_streamid(stream_id).await
}
},
None => {
self.read_data().await?;
self.read_streamid(stream_id).await
}
}
}
}

View File

@@ -1,290 +0,0 @@
//! XPC (Cross-Process Communication) Implementation
//!
//! Provides functionality for interacting with Apple's XPC protocol over HTTP/2,
//! which is used for inter-process communication between iOS/macOS components.
use std::collections::HashMap;
mod http2;
use crate::{IdeviceError, ReadWrite};
use error::XPCError;
use format::{XPCFlag, XPCMessage, XPCObject};
use http2::h2::{SettingsFrame, WindowUpdateFrame};
use log::{debug, warn};
use serde::Deserialize;
pub mod error;
mod format;
/// Represents an XPC connection to a device with available services
pub struct XPCDevice<R: ReadWrite> {
/// The underlying XPC connection
pub connection: XPCConnection<R>,
/// Map of available XPC services by name
pub services: HashMap<String, XPCService>,
}
/// Describes an available XPC service
#[derive(Debug, Clone, Deserialize)]
pub struct XPCService {
/// Required entitlement to access this service
pub entitlement: String,
/// Port number where the service is available
pub port: u16,
/// Whether the service uses remote XPC
pub uses_remote_xpc: bool,
/// Optional list of supported features
pub features: Option<Vec<String>>,
/// Optional service version number
pub service_version: Option<i64>,
}
/// Manages an active XPC connection over HTTP/2
pub struct XPCConnection<R: ReadWrite> {
pub(crate) inner: http2::Connection<R>,
root_message_id: u64,
reply_message_id: u64,
}
impl<R: ReadWrite> XPCDevice<R> {
/// Creates a new XPC device connection
///
/// # Arguments
/// * `stream` - The underlying transport stream
///
/// # Returns
/// A connected XPCDevice instance with discovered services
///
/// # Errors
/// Returns `IdeviceError` if:
/// - The connection fails
/// - The service discovery response is malformed
pub async fn new(stream: R) -> Result<Self, IdeviceError> {
let mut connection = XPCConnection::new(stream).await?;
// Read initial services message
let data = connection.read_message(http2::ROOT_CHANNEL).await?;
let data = match data.message {
Some(d) => match d
.as_dictionary()
.and_then(|x| x.get("Services"))
.and_then(|x| x.as_dictionary())
{
Some(d) => d.to_owned(),
None => return Err(IdeviceError::UnexpectedResponse),
},
None => return Err(IdeviceError::UnexpectedResponse),
};
// Parse available services
let mut services = HashMap::new();
for (name, service) in data.into_iter() {
match service.as_dictionary() {
Some(service) => {
let entitlement = match service.get("Entitlement").and_then(|x| x.as_string()) {
Some(e) => e.to_string(),
None => {
warn!("Service did not contain entitlement string");
continue;
}
};
let port = match service
.get("Port")
.and_then(|x| x.as_string())
.and_then(|x| x.parse::<u16>().ok())
{
Some(e) => e,
None => {
warn!("Service did not contain port string");
continue;
}
};
let uses_remote_xpc = match service
.get("Properties")
.and_then(|x| x.as_dictionary())
.and_then(|x| x.get("UsesRemoteXPC"))
.and_then(|x| x.as_bool())
{
Some(e) => e.to_owned(),
None => false, // default is false
};
let features = service
.get("Properties")
.and_then(|x| x.as_dictionary())
.and_then(|x| x.get("Features"))
.and_then(|x| x.as_array())
.map(|f| {
f.iter()
.filter_map(|x| x.as_string())
.map(|x| x.to_string())
.collect::<Vec<String>>()
});
let service_version = service
.get("Properties")
.and_then(|x| x.as_dictionary())
.and_then(|x| x.get("ServiceVersion"))
.and_then(|x| x.as_signed_integer())
.map(|e| e.to_owned());
services.insert(
name,
XPCService {
entitlement,
port,
uses_remote_xpc,
features,
service_version,
},
);
}
None => {
warn!("Service is not a dictionary!");
continue;
}
}
}
Ok(Self {
connection,
services,
})
}
/// Consumes the device and returns the underlying transport stream
pub fn into_inner(self) -> R {
self.connection.inner.stream
}
}
impl<R: ReadWrite> XPCConnection<R> {
/// Channel ID for root messages
pub const ROOT_CHANNEL: u32 = http2::ROOT_CHANNEL;
/// Channel ID for reply messages
pub const REPLY_CHANNEL: u32 = http2::REPLY_CHANNEL;
/// Initial stream ID for HTTP/2 connection
const INIT_STREAM: u32 = http2::INIT_STREAM;
/// Establishes a new XPC connection
///
/// # Arguments
/// * `stream` - The underlying transport stream
///
/// # Returns
/// A connected XPCConnection instance
///
/// # Errors
/// Returns `XPCError` if the connection handshake fails
pub async fn new(stream: R) -> Result<Self, XPCError> {
let mut client = http2::Connection::new(stream).await?;
// Configure HTTP/2 settings
client
.send_frame(SettingsFrame::new(
[
(SettingsFrame::MAX_CONCURRENT_STREAMS, 100),
(SettingsFrame::INITIAL_WINDOW_SIZE, 1048576),
]
.into_iter()
.collect(),
Default::default(),
))
.await?;
// Update window size
client
.send_frame(WindowUpdateFrame::new(Self::INIT_STREAM, 983041))
.await?;
let mut xpc_client = Self {
inner: client,
root_message_id: 1,
reply_message_id: 1,
};
// Perform XPC handshake
xpc_client
.send_recv_message(
Self::ROOT_CHANNEL,
XPCMessage::new(
Some(XPCFlag::AlwaysSet),
Some(XPCObject::Dictionary(Default::default())),
None,
),
)
.await?;
xpc_client
.send_recv_message(
Self::REPLY_CHANNEL,
XPCMessage::new(
Some(XPCFlag::InitHandshake | XPCFlag::AlwaysSet),
None,
None,
),
)
.await?;
xpc_client
.send_recv_message(
Self::ROOT_CHANNEL,
XPCMessage::new(Some(XPCFlag::Custom(0x201)), None, None),
)
.await?;
Ok(xpc_client)
}
/// Sends a message and waits for the response
///
/// # Arguments
/// * `stream_id` - The channel/stream to use
/// * `message` - The XPC message to send
///
/// # Returns
/// The response message
pub async fn send_recv_message(
&mut self,
stream_id: u32,
message: XPCMessage,
) -> Result<XPCMessage, XPCError> {
self.send_message(stream_id, message).await?;
self.read_message(stream_id).await
}
/// Sends an XPC message without waiting for a response
pub async fn send_message(
&mut self,
stream_id: u32,
message: XPCMessage,
) -> Result<(), XPCError> {
self.inner
.write_streamid(stream_id, message.encode(self.root_message_id)?)
.await?;
Ok(())
}
/// Reads an XPC message from the specified stream
pub async fn read_message(&mut self, stream_id: u32) -> Result<XPCMessage, XPCError> {
let mut buf = self.inner.read_streamid(stream_id).await?;
loop {
match XPCMessage::decode(&buf) {
Ok(decoded) => {
debug!("Decoded message: {:?}", decoded);
match stream_id {
1 => self.root_message_id += 1,
3 => self.reply_message_id += 1,
_ => {}
}
return Ok(decoded);
}
Err(err) => {
log::warn!("Error decoding message: {:?}", err);
buf.extend_from_slice(&self.inner.read_streamid(stream_id).await?);
}
}
}
}
}

View File

@@ -11,8 +11,7 @@
//! - Optional PCAP packet capture
//! - Implements `AsyncRead` and `AsyncWrite` for Tokio compatibility
//!
//! # Limitations
//! - Only supports one connection at a time
//! # Limitations (unecessary for CDTunnel)
//! - No proper sequence number tracking
//! - No retransmission or congestion control
//! - Requires 100% reliable underlying transport
@@ -22,8 +21,8 @@
//! ```rust,no_run
//! use std::net::{IpAddr, Ipv4Addr};
//! use tokio::io::{AsyncReadExt, AsyncWriteExt};
//! use your_crate::tcp::Adapter;
//! use your_crate::ReadWrite; // Assuming you have a ReadWrite trait
//! use idevice::tcp::{adapter::Adapter, stream::AdapterStream};
//! use idevice::ReadWrite;
//!
//! #[tokio::main]
//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
@@ -39,19 +38,19 @@
//! adapter.pcap("capture.pcap").await?;
//!
//! // Connect to remote server
//! adapter.connect(80).await?;
//! let stream = AdapterStream::new(&mut adapter, 80).await?;
//!
//! // Send HTTP request
//! adapter.write_all(b"GET / HTTP/1.1\r\nHost: example.com\r\n\r\n").await?;
//! adapter.flush().await?;
//! stream.write_all(b"GET / HTTP/1.1\r\nHost: example.com\r\n\r\n").await?;
//! stream.flush().await?;
//!
//! // Read response
//! let mut buf = vec![0; 1024];
//! let n = adapter.read(&mut buf).await?;
//! let n = stream.read(&mut buf).await?;
//! println!("Received: {}", String::from_utf8_lossy(&buf[..n]));
//!
//! // Close connection
//! adapter.close().await?;
//! stream.close().await?;
//!
//! Ok(())
//! }
@@ -59,35 +58,55 @@
//!
//! # Warning
//! This implementation makes significant simplifications and should not be used
//! in production environments or with unreliable network transports.
//! with unreliable network transports.
use std::{future::Future, net::IpAddr, path::Path, sync::Arc, task::Poll};
use std::{collections::HashMap, io::ErrorKind, net::IpAddr, path::Path, sync::Arc};
use log::trace;
use tokio::{
io::{AsyncRead, AsyncWrite, AsyncWriteExt},
sync::Mutex,
};
use tokio::{io::AsyncWriteExt, sync::Mutex};
use crate::ReadWrite;
use super::packets::{Ipv4Packet, Ipv6Packet, ProtocolNumber, TcpFlags, TcpPacket};
#[derive(Clone, Debug, PartialEq)]
enum AdapterState {
#[derive(Debug, Clone)]
struct ConnectionState {
seq: u32,
ack: u32,
host_port: u16,
peer_port: u16,
read_buffer: Vec<u8>,
write_buffer: Vec<u8>,
status: ConnectionStatus,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub(crate) enum ConnectionStatus {
WaitingForSyn,
Connected,
None,
Error(ErrorKind),
}
impl ConnectionState {
fn new(host_port: u16, peer_port: u16) -> Self {
Self {
seq: rand::random(),
ack: 0,
host_port,
peer_port,
read_buffer: Vec::new(),
write_buffer: Vec::new(),
status: ConnectionStatus::WaitingForSyn,
}
}
}
/// A simplified TCP network stack implementation.
///
/// This is an extremely naive, limited, and dangerous TCP stack implementation.
/// Key limitations:
/// - Only one connection can be active at a time
/// - ACKs aren't properly tracked and are silently ignored
/// - Should only be used when the underlying transport is 100% reliable
///
/// The adapter implements `AsyncRead` and `AsyncWrite` for convenient IO operations.
#[derive(Debug)]
pub struct Adapter {
/// The underlying transport connection
@@ -96,26 +115,11 @@ pub struct Adapter {
host_ip: IpAddr,
/// The remote peer's IP address
peer_ip: IpAddr,
/// Current connection state
state: AdapterState,
// TCP state
/// Current sequence number
seq: u32,
/// Current acknowledgement number
ack: u32,
/// Local port number
host_port: u16,
/// Remote port number
peer_port: u16,
/// The states of the connections
states: HashMap<u16, ConnectionState>, // host port by state
dropped: Vec<u16>,
// Read buffer to cache unused bytes
/// Buffer for storing unread received data
read_buffer: Vec<u8>,
/// Buffer for storing data to be sent
write_buffer: Vec<u8>,
// Logging
/// Optional PCAP file for packet logging
pcap: Option<Arc<Mutex<tokio::fs::File>>>,
}
@@ -129,19 +133,14 @@ impl Adapter {
/// * `peer_ip` - The remote IP address to connect to
///
/// # Returns
/// A new unconnected `Adapter` instance
/// A new `Adapter` instance
pub fn new(peer: Box<dyn ReadWrite>, host_ip: IpAddr, peer_ip: IpAddr) -> Self {
Self {
peer,
host_ip,
peer_ip,
state: AdapterState::None,
seq: 0,
ack: 0,
host_port: 1024,
peer_port: 1024,
read_buffer: Vec::new(),
write_buffer: Vec::new(),
states: HashMap::new(),
dropped: Vec::new(),
pcap: None,
}
}
@@ -152,32 +151,31 @@ impl Adapter {
/// * `port` - The remote port number to connect to
///
/// # Returns
/// * `Ok(())` if connection was successful
/// * `Ok(u16)` the chosen host port if successful
/// * `Err(std::io::Error)` if connection failed
///
/// # Errors
/// * Returns `InvalidData` if the SYN-ACK response is invalid
/// * Returns other IO errors if underlying transport fails
pub async fn connect(&mut self, port: u16) -> Result<(), std::io::Error> {
self.read_buffer = Vec::new();
self.write_buffer = Vec::new();
// Randomize seq
self.seq = rand::random();
self.ack = 0;
// Choose a random port
self.host_port = rand::random();
self.peer_port = port;
pub(crate) async fn connect(&mut self, port: u16) -> Result<u16, std::io::Error> {
let host_port = loop {
let host_port: u16 = rand::random();
if self.states.contains_key(&host_port) {
continue;
} else {
break host_port;
}
};
let state = ConnectionState::new(host_port, port);
// Create the TCP packet
let tcp_packet = TcpPacket::create(
self.host_ip,
self.peer_ip,
self.host_port,
self.peer_port,
self.seq,
self.ack,
state.host_port,
state.peer_port,
state.seq,
state.ack,
TcpFlags {
syn: true,
..Default::default()
@@ -187,24 +185,28 @@ impl Adapter {
);
let ip_packet = self.ip_wrap(&tcp_packet);
self.peer.write_all(&ip_packet).await?;
self.log_packet(&ip_packet).await?;
self.log_packet(&ip_packet)?;
// Wait for the syn ack
let res = self.read_tcp_packet().await?;
if !(res.flags.syn && res.flags.ack) {
log::error!("Didn't get syn ack: {res:#?}, {self:#?}");
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"No syn ack",
));
self.states.insert(host_port, state);
loop {
self.process_tcp_packet().await?;
if let Some(s) = self.states.get(&host_port) {
match s.status {
ConnectionStatus::Connected => {
break;
}
ConnectionStatus::Error(e) => {
return Err(std::io::Error::new(e, "failed to connect"))
}
ConnectionStatus::WaitingForSyn => {
continue;
}
}
}
}
self.seq = self.seq.wrapping_add(1);
// Ack back
self.ack().await?;
self.state = AdapterState::Connected;
Ok(())
Ok(host_port)
}
/// Enables packet capture to a PCAP file.
@@ -232,9 +234,9 @@ impl Adapter {
Ok(())
}
async fn log_packet(&mut self, packet: &[u8]) -> Result<(), std::io::Error> {
fn log_packet(&self, packet: &[u8]) -> Result<(), std::io::Error> {
if let Some(file) = &self.pcap {
super::log_packet(file, packet).await;
super::log_packet(file, packet);
}
Ok(())
}
@@ -247,61 +249,63 @@ impl Adapter {
///
/// # Errors
/// * Returns IO errors if underlying transport fails during close
pub async fn close(&mut self) -> Result<(), std::io::Error> {
let tcp_packet = TcpPacket::create(
self.host_ip,
self.peer_ip,
self.host_port,
self.peer_port,
self.seq,
self.ack,
TcpFlags {
fin: true,
ack: true,
..Default::default()
},
u16::MAX - 1,
&[],
);
let ip_packet = self.ip_wrap(&tcp_packet);
self.peer.write_all(&ip_packet).await?;
self.log_packet(&ip_packet).await?;
pub(crate) async fn close(&mut self, host_port: u16) -> Result<(), std::io::Error> {
if let Some(state) = self.states.remove(&host_port) {
let tcp_packet = TcpPacket::create(
self.host_ip,
self.peer_ip,
state.host_port,
state.peer_port,
state.seq,
state.ack,
TcpFlags {
fin: true,
ack: true,
..Default::default()
},
u16::MAX - 1,
&[],
);
let ip_packet = self.ip_wrap(&tcp_packet);
self.peer.write_all(&ip_packet).await?;
self.log_packet(&ip_packet)?;
loop {
let res = self.read_tcp_packet().await?;
if res.flags.psh || !res.payload.is_empty() {
self.ack().await?;
continue;
}
if res.flags.ack || res.flags.fin || res.flags.rst {
break;
}
Ok(())
} else {
Err(std::io::Error::new(
ErrorKind::NotConnected,
"not connected",
))
}
self.state = AdapterState::None;
Ok(())
}
async fn ack(&mut self) -> Result<(), std::io::Error> {
let tcp_packet = TcpPacket::create(
self.host_ip,
self.peer_ip,
self.host_port,
self.peer_port,
self.seq,
self.ack,
TcpFlags {
ack: true,
..Default::default()
},
u16::MAX - 1,
&[],
);
let ip_packet = self.ip_wrap(&tcp_packet);
self.peer.write_all(&ip_packet).await?;
self.log_packet(&ip_packet).await?;
async fn ack(&mut self, host_port: u16) -> Result<(), std::io::Error> {
if let Some(state) = self.states.get_mut(&host_port) {
let tcp_packet = TcpPacket::create(
self.host_ip,
self.peer_ip,
state.host_port,
state.peer_port,
state.seq,
state.ack,
TcpFlags {
ack: true,
..Default::default()
},
u16::MAX - 1,
&[],
);
let ip_packet = self.ip_wrap(&tcp_packet);
self.peer.write_all(&ip_packet).await?;
self.log_packet(&ip_packet)?;
Ok(())
Ok(())
} else {
Err(std::io::Error::new(
ErrorKind::NotConnected,
"not connected",
))
}
}
/// Sends a TCP packet with PSH flag set (pushing data).
@@ -315,44 +319,147 @@ impl Adapter {
///
/// # Errors
/// * Returns IO errors if underlying transport fails
pub async fn psh(&mut self, data: &[u8]) -> Result<(), std::io::Error> {
trace!("pshing {} bytes", data.len());
let tcp_packet = TcpPacket::create(
self.host_ip,
self.peer_ip,
self.host_port,
self.peer_port,
self.seq,
self.ack,
TcpFlags {
psh: true,
ack: true,
..Default::default()
},
u16::MAX - 1,
data,
);
let ip_packet = self.ip_wrap(&tcp_packet);
self.peer.write_all(&ip_packet).await?;
self.log_packet(&ip_packet).await?;
async fn psh(&mut self, data: &[u8], host_port: u16) -> Result<(), std::io::Error> {
let data_len = if let Some(state) = self.states.get(&host_port) {
// Check to make sure we haven't closed since last operation
if let ConnectionStatus::Error(e) = state.status {
return Err(std::io::Error::new(e, "socket error"));
}
trace!("pshing {} bytes", data.len());
let tcp_packet = TcpPacket::create(
self.host_ip,
self.peer_ip,
state.host_port,
state.peer_port,
state.seq,
state.ack,
TcpFlags {
psh: true,
ack: true,
..Default::default()
},
u16::MAX - 1,
data,
);
let ip_packet = self.ip_wrap(&tcp_packet);
self.peer.write_all(&ip_packet).await?;
self.log_packet(&ip_packet)?;
data.len() as u32
} else {
return Err(std::io::Error::new(
ErrorKind::NotConnected,
"not connected",
));
};
self.seq = self.seq.wrapping_add(data.len() as u32);
// We have to re-borrow, since we're mutating state
if let Some(state) = self.states.get_mut(&host_port) {
state.seq = state.seq.wrapping_add(data_len);
}
Ok(())
}
pub(crate) fn connection_drop(&mut self, host_port: u16) {
self.dropped.push(host_port);
}
/// Flushes the packets
async fn write_buffer_flush(&mut self) -> Result<(), std::io::Error> {
if self.write_buffer.is_empty() {
return Ok(());
pub(crate) async fn write_buffer_flush(&mut self) -> Result<(), std::io::Error> {
for (_, state) in self.states.clone() {
let writer_buffer = state.write_buffer.clone();
if writer_buffer.is_empty() {
continue;
}
self.psh(&writer_buffer, state.host_port).await.ok(); // don't care
// we have to borrow mutably after self.psh
if let Some(state) = self.states.get_mut(&state.host_port) {
state.write_buffer.clear();
}
}
trace!("Flushing {} bytes", self.write_buffer.len());
let write_buffer = self.write_buffer.clone();
self.psh(&write_buffer).await?;
self.write_buffer = Vec::new();
// Since we have extra clocks and we haven't been cancelled by the runtime, let's reap the
// dropped connections
for d in self.dropped.clone() {
if let Some(state) = self.states.remove(&d) {
self.close(state.host_port).await.ok();
}
}
// We can't clear until it's all done, since we can get cancelled by the runtime at any
// point.
self.dropped.clear();
Ok(())
}
pub(crate) fn queue_send(
&mut self,
payload: &[u8],
host_port: u16,
) -> Result<(), std::io::Error> {
if let Some(state) = self.states.get_mut(&host_port) {
state.write_buffer.extend_from_slice(payload);
} else {
return Err(std::io::Error::new(
ErrorKind::NotConnected,
"not connected",
));
}
Ok(())
}
pub(crate) fn uncache(
&mut self,
to_copy: usize,
host_port: u16,
) -> Result<Vec<u8>, std::io::Error> {
if let Some(state) = self.states.get_mut(&host_port) {
let to_copy = if to_copy > state.read_buffer.len() {
state.read_buffer.len()
} else {
to_copy
};
let res = state.read_buffer[..to_copy].to_vec();
state.read_buffer = state.read_buffer[to_copy..].to_vec();
Ok(res)
} else {
Err(std::io::Error::new(
ErrorKind::NotConnected,
"not connected",
))
}
}
pub(crate) fn cache_read(
&mut self,
payload: &[u8],
host_port: u16,
) -> Result<(), std::io::Error> {
if let Some(state) = self.states.get_mut(&host_port) {
state.read_buffer.extend_from_slice(payload);
Ok(())
} else {
Err(std::io::Error::new(
ErrorKind::NotConnected,
"not connected",
))
}
}
pub(crate) fn get_status(&self, host_port: u16) -> Result<ConnectionStatus, std::io::Error> {
if let Some(state) = self.states.get(&host_port) {
Ok(state.status.clone())
} else {
Err(std::io::Error::new(
ErrorKind::NotConnected,
"not connected",
))
}
}
/// Receives data from the connection.
///
/// # Returns
@@ -362,31 +469,26 @@ impl Adapter {
/// # Errors
/// * Returns `ConnectionReset` if connection was reset or closed
/// * Returns other IO errors if underlying transport fails
pub async fn recv(&mut self) -> Result<Vec<u8>, std::io::Error> {
pub(crate) async fn recv(&mut self, host_port: u16) -> Result<Vec<u8>, std::io::Error> {
loop {
let res = self.read_tcp_packet().await?;
if res.destination_port != self.host_port || res.source_port != self.peer_port {
continue;
}
if res.flags.psh || !res.payload.is_empty() {
self.ack().await?;
break Ok(res.payload);
}
if res.flags.rst {
self.state = AdapterState::None;
break Err(std::io::Error::new(
std::io::ErrorKind::ConnectionReset,
"Connection reset",
));
}
if res.flags.fin {
self.ack().await?;
self.state = AdapterState::None;
break Err(std::io::Error::new(
std::io::ErrorKind::ConnectionReset,
"Connection reset",
// Check to see if we already have some cached
if let Some(state) = self.states.get_mut(&host_port) {
if !state.read_buffer.is_empty() {
let res = state.read_buffer.clone();
state.read_buffer = Vec::new();
return Ok(res);
}
if let ConnectionStatus::Error(e) = state.status {
return Err(std::io::Error::new(e, "socket io error"));
}
} else {
return Err(std::io::Error::new(
ErrorKind::NotConnected,
"not connected",
));
}
self.process_tcp_packet().await?;
}
}
@@ -413,24 +515,40 @@ impl Adapter {
})
}
async fn read_tcp_packet(&mut self) -> Result<TcpPacket, std::io::Error> {
loop {
let ip_packet = self.read_ip_packet().await?;
let tcp_packet = TcpPacket::parse(&ip_packet)?;
if tcp_packet.destination_port != self.host_port
|| tcp_packet.source_port != self.peer_port
{
continue;
}
trace!("TCP packet: {tcp_packet:#?}");
self.ack = tcp_packet.sequence_number
+ if tcp_packet.payload.is_empty() {
async fn process_tcp_packet(&mut self) -> Result<(), std::io::Error> {
let ip_packet = self.read_ip_packet().await?;
let res = TcpPacket::parse(&ip_packet)?;
let mut ack_me = None;
if let Some(state) = self.states.get_mut(&res.destination_port) {
state.ack = res.sequence_number
+ if res.payload.is_empty() {
1
} else {
tcp_packet.payload.len() as u32
res.payload.len() as u32
};
break Ok(tcp_packet);
if res.flags.psh || !res.payload.is_empty() {
ack_me = Some(res.destination_port);
state.read_buffer.extend(res.payload)
}
if res.flags.rst {
state.status = ConnectionStatus::Error(ErrorKind::ConnectionReset);
}
if res.flags.fin {
ack_me = Some(res.destination_port);
state.status = ConnectionStatus::Error(ErrorKind::ConnectionReset);
}
if res.flags.syn && res.flags.ack {
ack_me = Some(res.destination_port);
state.seq = state.seq.wrapping_add(1);
state.status = ConnectionStatus::Connected;
}
}
// we have to ack outside of the mutable state borrow
if let Some(a) = ack_me {
self.ack(a).await?;
}
Ok(())
}
fn ip_wrap(&self, packet: &[u8]) -> Vec<u8> {
@@ -454,125 +572,3 @@ impl Adapter {
}
}
}
impl AsyncRead for Adapter {
/// Attempts to read from the connection into the provided buffer.
///
/// Uses an internal read buffer to cache any extra received data.
///
/// # Returns
/// * `Poll::Ready(Ok(()))` if data was read successfully
/// * `Poll::Ready(Err(e))` if an error occurred
/// * `Poll::Pending` if operation would block
///
/// # Errors
/// * Returns `NotConnected` if adapter isn't connected
/// * Propagates any underlying transport errors
fn poll_read(
mut self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
buf: &mut tokio::io::ReadBuf<'_>,
) -> std::task::Poll<std::io::Result<()>> {
// First, check if we have any cached data
if !self.read_buffer.is_empty() {
let to_copy = std::cmp::min(buf.remaining(), self.read_buffer.len());
buf.put_slice(&self.read_buffer[..to_copy]);
// Keep any remaining data in the buffer
if to_copy < self.read_buffer.len() {
self.read_buffer = self.read_buffer[to_copy..].to_vec();
} else {
self.read_buffer.clear();
}
return std::task::Poll::Ready(Ok(()));
}
// If no cached data and not connected, return error
if self.state != AdapterState::Connected {
return std::task::Poll::Ready(Err(std::io::Error::new(
std::io::ErrorKind::NotConnected,
"Adapter not connected",
)));
}
// If no cached data, try to receive new data
let future = async {
match self.recv().await {
Ok(data) => {
let len = std::cmp::min(buf.remaining(), data.len());
buf.put_slice(&data[..len]);
// If we received more data than needed, cache the rest
if len < data.len() {
self.read_buffer = data[len..].to_vec();
}
Ok(())
}
Err(e) => Err(e),
}
};
// Pin the future and poll it
futures::pin_mut!(future);
future.poll(cx)
}
}
impl AsyncWrite for Adapter {
/// Attempts to write data to the connection.
///
/// Data is buffered internally until flushed.
///
/// # Returns
/// * `Poll::Ready(Ok(n))` with number of bytes written
/// * `Poll::Ready(Err(e))` if an error occurred
/// * `Poll::Pending` if operation would block
///
/// # Errors
/// * Returns `NotConnected` if adapter isn't connected
fn poll_write(
mut self: std::pin::Pin<&mut Self>,
_cx: &mut std::task::Context<'_>,
buf: &[u8],
) -> std::task::Poll<Result<usize, std::io::Error>> {
trace!("poll psh {}", buf.len());
if self.state != AdapterState::Connected {
return std::task::Poll::Ready(Err(std::io::Error::new(
std::io::ErrorKind::NotConnected,
"Adapter not connected",
)));
}
self.write_buffer.extend_from_slice(buf);
Poll::Ready(Ok(buf.len()))
}
fn poll_flush(
mut self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Result<(), std::io::Error>> {
let future = async {
match self.write_buffer_flush().await {
Ok(_) => Ok(()),
Err(e) => Err(e),
}
};
// Pin the future and poll it
futures::pin_mut!(future);
future.poll(cx)
}
fn poll_shutdown(
mut self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Result<(), std::io::Error>> {
// Create a future that can be polled
let future = async { self.close().await };
// Pin the future and poll it
futures::pin_mut!(future);
future.poll(cx)
}
}

View File

@@ -6,12 +6,16 @@ use std::{
};
use log::debug;
use stream::AdapterStream;
use tokio::io::AsyncWriteExt;
use crate::provider::RsdProvider;
pub mod adapter;
pub mod packets;
pub mod stream;
pub(crate) async fn log_packet(file: &Arc<tokio::sync::Mutex<tokio::fs::File>>, packet: &[u8]) {
pub(crate) fn log_packet(file: &Arc<tokio::sync::Mutex<tokio::fs::File>>, packet: &[u8]) {
debug!("Logging {} byte packet", packet.len());
let packet = packet.to_vec();
let file = file.to_owned();
@@ -35,6 +39,18 @@ pub(crate) async fn log_packet(file: &Arc<tokio::sync::Mutex<tokio::fs::File>>,
});
}
impl<'a> RsdProvider<'a> for adapter::Adapter {
async fn connect_to_service_port(
&'a mut self,
port: u16,
) -> Result<stream::AdapterStream<'a>, crate::IdeviceError> {
let s = stream::AdapterStream::connect(self, port).await?;
Ok(s)
}
type Stream = AdapterStream<'a>;
}
#[cfg(test)]
mod tests {
use std::{
@@ -49,6 +65,7 @@ mod tests {
pin::Pin,
task::{Context, Poll},
};
use stream::AdapterStream;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tun_rs::DeviceBuilder;
@@ -186,31 +203,35 @@ mod tests {
let mut buf = Vec::new();
let _ = tokio::io::stdin().read(&mut buf).await.unwrap();
if let Err(e) = adapter.connect(SERVER_PORT).await {
println!("no connect: {e:?}");
}
let mut stream = match AdapterStream::connect(&mut adapter, SERVER_PORT).await {
Ok(s) => s,
Err(e) => {
println!("no connect: {e:?}");
return;
}
};
if let Err(e) = adapter.write_all(&[1, 2, 3, 4, 5]).await {
if let Err(e) = stream.write_all(&[1, 2, 3, 4, 5]).await {
println!("no send: {e:?}");
} else {
let mut buf = [0u8; 4];
match adapter.read_exact(&mut buf).await {
match stream.read_exact(&mut buf).await {
Ok(_) => println!("recv'd {buf:?}"),
Err(e) => println!("no recv: {e:?}"),
}
}
if let Err(e) = adapter.write_all(&[69, 69, 42, 0, 1]).await {
if let Err(e) = stream.write_all(&[69, 69, 42, 0, 1]).await {
println!("no send: {e:?}");
} else {
let mut buf = [0u8; 6];
match adapter.read_exact(&mut buf).await {
match stream.read_exact(&mut buf).await {
Ok(_) => println!("recv'd {buf:?}"),
Err(e) => println!("no recv: {e:?}"),
}
}
if let Err(e) = adapter.close().await {
if let Err(e) = stream.close().await {
println!("no close: {e:?}");
}

View File

@@ -143,7 +143,7 @@ impl Ipv4Packet {
reader.read_exact(&mut payload).await?;
if let Some(log) = log {
log_packet.extend_from_slice(&payload);
super::log_packet(log, &log_packet).await;
super::log_packet(log, &log_packet);
}
Ok(Self {
@@ -324,7 +324,7 @@ impl Ipv6Packet {
reader.read_exact(&mut payload).await?;
if let Some(log) = log {
log_packet.extend_from_slice(&payload);
super::log_packet(log, &log_packet).await;
super::log_packet(log, &log_packet);
}
Ok(Self {

177
idevice/src/tcp/stream.rs Normal file
View File

@@ -0,0 +1,177 @@
//! A stream for the adapter
use std::{future::Future, task::Poll};
use log::trace;
use tokio::io::{AsyncRead, AsyncWrite};
use crate::tcp::adapter::ConnectionStatus;
use super::adapter::Adapter;
#[derive(Debug)]
pub struct AdapterStream<'a> {
pub(crate) adapter: &'a mut Adapter,
pub host_port: u16,
pub peer_port: u16,
}
impl<'a> AdapterStream<'a> {
/// Connect to the specified port
pub async fn connect(adapter: &'a mut Adapter, port: u16) -> Result<Self, std::io::Error> {
let host_port = adapter.connect(port).await?;
Ok(Self {
adapter,
host_port,
peer_port: port,
})
}
/// Gracefully closes the stream
pub async fn close(&mut self) -> Result<(), std::io::Error> {
self.adapter.close(self.host_port).await
}
/// Sends data to the target
pub async fn psh(&mut self, payload: &[u8]) -> Result<(), std::io::Error> {
self.adapter.queue_send(payload, self.host_port)?;
self.adapter.write_buffer_flush().await?;
Ok(())
}
pub async fn recv(&mut self) -> Result<Vec<u8>, std::io::Error> {
self.adapter.recv(self.host_port).await
}
}
impl AsyncRead for AdapterStream<'_> {
/// Attempts to read from the connection into the provided buffer.
///
/// Uses an internal read buffer to cache any extra received data.
///
/// # Returns
/// * `Poll::Ready(Ok(()))` if data was read successfully
/// * `Poll::Ready(Err(e))` if an error occurred
/// * `Poll::Pending` if operation would block
///
/// # Errors
/// * Returns `NotConnected` if adapter isn't connected
/// * Propagates any underlying transport errors
fn poll_read(
mut self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
buf: &mut tokio::io::ReadBuf<'_>,
) -> std::task::Poll<std::io::Result<()>> {
match self.adapter.get_status(self.host_port) {
Ok(ConnectionStatus::Error(e)) => {
return std::task::Poll::Ready(Err(std::io::Error::new(e, "io error")));
}
Err(e) => {
return std::task::Poll::Ready(Err(e));
}
_ => {}
}
// First, check if we have any cached data
let p = self.host_port;
let cache = match self.adapter.uncache(buf.remaining(), p) {
Ok(c) => c,
Err(e) => return std::task::Poll::Ready(Err(e)),
};
if !cache.is_empty() {
buf.put_slice(&cache);
return std::task::Poll::Ready(Ok(()));
}
// If no cached data, try to receive new data
let future = async {
match self.adapter.recv(p).await {
Ok(data) => {
let len = std::cmp::min(buf.remaining(), data.len());
buf.put_slice(&data[..len]);
// If we received more data than needed, cache the rest
if len < data.len() {
self.adapter.cache_read(&data[len..], p)?
}
Ok(())
}
Err(e) => Err(e),
}
};
// Pin the future and poll it
futures::pin_mut!(future);
future.poll(cx)
}
}
impl AsyncWrite for AdapterStream<'_> {
/// Attempts to write data to the connection.
///
/// Data is buffered internally until flushed.
///
/// # Returns
/// * `Poll::Ready(Ok(n))` with number of bytes written
/// * `Poll::Ready(Err(e))` if an error occurred
/// * `Poll::Pending` if operation would block
///
/// # Errors
/// * Returns `NotConnected` if adapter isn't connected
fn poll_write(
mut self: std::pin::Pin<&mut Self>,
_cx: &mut std::task::Context<'_>,
buf: &[u8],
) -> std::task::Poll<Result<usize, std::io::Error>> {
trace!("poll psh {}", buf.len());
match self.adapter.get_status(self.host_port) {
Ok(ConnectionStatus::Error(e)) => {
return std::task::Poll::Ready(Err(std::io::Error::new(e, "io error")));
}
Err(e) => {
return std::task::Poll::Ready(Err(e));
}
_ => {}
}
let p = self.host_port;
match self.adapter.queue_send(buf, p) {
Ok(_) => Poll::Ready(Ok(buf.len())),
Err(e) => Poll::Ready(Err(e)),
}
}
fn poll_flush(
mut self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Result<(), std::io::Error>> {
let future = async {
match self.adapter.write_buffer_flush().await {
Ok(_) => Ok(()),
Err(e) => Err(e),
}
};
// Pin the future and poll it
futures::pin_mut!(future);
future.poll(cx)
}
fn poll_shutdown(
mut self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Result<(), std::io::Error>> {
// Create a future that can be polled
let future = async { self.close().await };
// Pin the future and poll it
futures::pin_mut!(future);
future.poll(cx)
}
}
impl Drop for AdapterStream<'_> {
fn drop(&mut self) {
self.adapter.connection_drop(self.host_port);
}
}

View File

@@ -1,16 +1,15 @@
// DebianArch
use std::{
ffi::CString,
io::{BufRead, Cursor, Read},
ops::{BitOr, BitOrAssign},
};
use super::error::XPCError;
use indexmap::IndexMap;
use log::debug;
use log::{debug, warn};
use serde::{Deserialize, Serialize};
use crate::IdeviceError;
#[derive(Clone, Copy, Debug)]
#[repr(u32)]
pub enum XPCFlag {
@@ -69,7 +68,7 @@ pub enum XPCType {
}
impl TryFrom<u32> for XPCType {
type Error = XPCError;
type Error = IdeviceError;
fn try_from(value: u32) -> Result<Self, Self::Error> {
match value {
@@ -81,7 +80,7 @@ impl TryFrom<u32> for XPCType {
0x00009000 => Ok(Self::String),
0x00008000 => Ok(Self::Data),
0x0000a000 => Ok(Self::Uuid),
_ => Err("Invalid XPCType")?,
_ => Err(IdeviceError::UnknownXpcType(value))?,
}
}
}
@@ -147,14 +146,7 @@ impl XPCObject {
}
}
pub fn to_value<T: Serialize>(value: &T) -> Self {
match plist::to_value(value) {
Ok(v) => Self::from(v),
Err(_) => panic!("oof"),
}
}
pub fn encode(&self) -> Result<Vec<u8>, XPCError> {
pub fn encode(&self) -> Result<Vec<u8>, IdeviceError> {
let mut buf = Vec::new();
buf.extend_from_slice(&0x42133742_u32.to_le_bytes());
buf.extend_from_slice(&0x00000005_u32.to_le_bytes());
@@ -162,7 +154,7 @@ impl XPCObject {
Ok(buf)
}
fn encode_object(&self, buf: &mut Vec<u8>) -> Result<(), XPCError> {
fn encode_object(&self, buf: &mut Vec<u8>) -> Result<(), IdeviceError> {
match self {
XPCObject::Bool(val) => {
buf.extend_from_slice(&(XPCType::Bool as u32).to_le_bytes());
@@ -224,21 +216,26 @@ impl XPCObject {
Ok(())
}
pub fn decode(buf: &[u8]) -> Result<Self, XPCError> {
let magic = u32::from_le_bytes(buf[0..4].try_into()?);
pub fn decode(buf: &[u8]) -> Result<Self, IdeviceError> {
if buf.len() < 8 {
return Err(IdeviceError::NotEnoughBytes(buf.len(), 8));
}
let magic = u32::from_le_bytes([buf[0], buf[1], buf[2], buf[3]]);
if magic != 0x42133742 {
Err("Invalid magic for XPCObject")?
warn!("Invalid magic for XPCObject");
return Err(IdeviceError::InvalidXpcMagic);
}
let version = u32::from_le_bytes(buf[4..8].try_into()?);
let version = u32::from_le_bytes([buf[4], buf[5], buf[6], buf[7]]);
if version != 0x00000005 {
Err("Unexpected version for XPCObject")?
warn!("Unexpected version for XPCObject");
return Err(IdeviceError::UnexpectedXpcVersion);
}
Self::decode_object(&mut Cursor::new(&buf[8..]))
}
fn decode_object(mut cursor: &mut Cursor<&[u8]>) -> Result<Self, XPCError> {
fn decode_object(mut cursor: &mut Cursor<&[u8]>) -> Result<Self, IdeviceError> {
let mut buf_32: [u8; 4] = Default::default();
cursor.read_exact(&mut buf_32)?;
let xpc_type = u32::from_le_bytes(buf_32);
@@ -251,10 +248,18 @@ impl XPCObject {
let _l = u32::from_le_bytes(buf_32);
cursor.read_exact(&mut buf_32)?;
let num_entries = u32::from_le_bytes(buf_32);
for _i in 0..num_entries {
for _ in 0..num_entries {
let mut key_buf = Vec::new();
BufRead::read_until(&mut cursor, 0, &mut key_buf)?;
let key = CString::from_vec_with_nul(key_buf)?.to_str()?.to_string();
let key = match CString::from_vec_with_nul(key_buf)
.ok()
.and_then(|x| x.to_str().ok().map(|x| x.to_string()))
{
Some(k) => k,
None => {
return Err(IdeviceError::InvalidCString);
}
};
let padding = Self::calculate_padding(key.len() + 1);
BufRead::consume(&mut cursor, padding);
@@ -292,7 +297,13 @@ impl XPCObject {
let mut key_buf = vec![0; l];
cursor.read_exact(&mut key_buf)?;
let key = CString::from_vec_with_nul(key_buf)?.to_str()?.to_string();
let key = match CString::from_vec_with_nul(key_buf)
.ok()
.and_then(|x| x.to_str().ok().map(|x| x.to_string()))
{
Some(k) => k,
None => return Err(IdeviceError::InvalidCString),
};
BufRead::consume(&mut cursor, padding);
Ok(XPCObject::String(key))
}
@@ -395,40 +406,48 @@ impl XPCMessage {
}
}
pub fn decode(data: &[u8]) -> Result<XPCMessage, XPCError> {
pub fn decode(data: &[u8]) -> Result<XPCMessage, IdeviceError> {
if data.len() < 24 {
Err("XPCMessage must be at least 24 bytes.")?
Err(IdeviceError::NotEnoughBytes(data.len(), 24))?
}
let magic = u32::from_le_bytes(data[0..4].try_into()?);
let magic = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
if magic != 0x29b00b92_u32 {
Err("XPCMessage magic is invalid.")?
warn!("XPCMessage magic is invalid.");
Err(IdeviceError::MalformedXpc)?
}
let flags = u32::from_le_bytes(data[4..8].try_into()?);
let body_len = u64::from_le_bytes(data[8..16].try_into()?);
let message_id = u64::from_le_bytes(data[16..24].try_into()?);
if body_len + 24 > data.len().try_into()? {
Err("XPCMessage body length given is incorrect.")?
let flags = u32::from_le_bytes([data[4], data[5], data[6], data[7]]);
let body_len = u64::from_le_bytes([
data[8], data[9], data[10], data[11], data[12], data[13], data[14], data[15],
]);
debug!("Body_len: {body_len}");
let message_id = u64::from_le_bytes([
data[16], data[17], data[18], data[19], data[20], data[21], data[22], data[23],
]);
if body_len + 24 > data.len() as u64 {
warn!(
"Body length is {body_len}, but received bytes is {}",
data.len()
);
Err(IdeviceError::PacketSizeMismatch)?
}
// for some reason the above if check doesn't work ???
debug!("Body length {} : {}", body_len, data.len());
if body_len == 0 {
return Ok(XPCMessage {
flags,
message: None,
message_id: Some(message_id),
});
}
Ok(XPCMessage {
let res = XPCMessage {
flags,
message: Some(XPCObject::decode(&data[24..24 + body_len as usize])?),
message: if body_len > 0 {
Some(XPCObject::decode(&data[24..24 + body_len as usize])?)
} else {
None
},
message_id: Some(message_id),
})
};
debug!("Decoded {res:#?}");
Ok(res)
}
pub fn encode(self, message_id: u64) -> Result<Vec<u8>, XPCError> {
pub fn encode(self, message_id: u64) -> Result<Vec<u8>, IdeviceError> {
let mut out = 0x29b00b92_u32.to_le_bytes().to_vec();
out.extend_from_slice(&self.flags.to_le_bytes());
match self.message {

View File

@@ -0,0 +1,196 @@
// Jackson Coxson
use crate::{IdeviceError, ReadWrite};
use tokio::io::AsyncReadExt;
pub trait HttpFrame {
fn serialize(&self) -> Vec<u8>;
}
#[derive(Debug)]
#[allow(dead_code)] // we don't care about frames from the device
pub enum Frame {
Settings(SettingsFrame),
WindowUpdate(WindowUpdateFrame),
Headers(HeadersFrame),
Data(DataFrame),
}
impl Frame {
pub async fn next(mut socket: &mut impl ReadWrite) -> Result<Self, IdeviceError> {
// Read the len of the frame
let mut buf = [0u8; 3];
tokio::io::AsyncReadExt::read_exact(&mut socket, &mut buf).await?;
let frame_len = u32::from_be_bytes([0x00, buf[0], buf[1], buf[2]]);
// Read the fields
let frame_type = socket.read_u8().await?;
let flags = socket.read_u8().await?;
let stream_id = socket.read_u32().await?;
let mut body = vec![0; frame_len as usize];
socket.read_exact(&mut body).await?;
Ok(match frame_type {
0x00 => {
// data
Self::Data(DataFrame {
stream_id,
payload: body,
})
}
0x01 => {
// headers
Self::Headers(HeadersFrame { stream_id })
}
0x03 => return Err(IdeviceError::HttpStreamReset),
0x04 => {
// settings
let mut body = std::io::Cursor::new(body);
let mut settings = Vec::new();
while let Ok(setting_type) = body.read_u16().await {
settings.push(match setting_type {
0x03 => {
let max_streams = body.read_u32().await?;
Setting::MaxConcurrentStreams(max_streams)
}
0x04 => {
let window_size = body.read_u32().await?;
Setting::InitialWindowSize(window_size)
}
_ => {
return Err(IdeviceError::UnknownHttpSetting(setting_type));
}
});
}
Self::Settings(SettingsFrame {
settings,
stream_id,
flags,
})
}
0x07 => {
let msg = if body.len() < 8 {
"<MISSING>".to_string()
} else {
String::from_utf8_lossy(&body[8..]).to_string()
};
return Err(IdeviceError::HttpGoAway(msg));
}
0x08 => {
// window update
if body.len() != 4 {
return Err(IdeviceError::UnexpectedResponse);
}
let window = u32::from_be_bytes([body[0], body[1], body[2], body[3]]);
Self::WindowUpdate(WindowUpdateFrame {
increment_size: window,
stream_id,
})
}
_ => {
return Err(IdeviceError::UnknownFrame(frame_type));
}
})
}
}
#[derive(Debug, Clone)]
pub struct SettingsFrame {
pub settings: Vec<Setting>,
pub stream_id: u32,
pub flags: u8,
}
#[derive(Debug, Clone)]
pub enum Setting {
MaxConcurrentStreams(u32),
InitialWindowSize(u32),
}
impl Setting {
fn serialize(&self) -> Vec<u8> {
match self {
Setting::MaxConcurrentStreams(m) => {
let mut res = vec![0x00, 0x03];
res.extend(m.to_be_bytes());
res
}
Setting::InitialWindowSize(s) => {
let mut res = vec![0x00, 0x04];
res.extend(s.to_be_bytes());
res
}
}
}
}
impl HttpFrame for SettingsFrame {
fn serialize(&self) -> Vec<u8> {
let settings = self
.settings
.iter()
.map(|x| x.serialize())
.collect::<Vec<Vec<u8>>>()
.concat();
let settings_len = (settings.len() as u32).to_be_bytes();
let mut res = vec![
settings_len[1],
settings_len[2],
settings_len[3],
0x04,
self.flags,
];
res.extend(self.stream_id.to_be_bytes());
res.extend(settings);
res
}
}
#[derive(Debug, Clone)]
pub struct WindowUpdateFrame {
pub increment_size: u32,
pub stream_id: u32,
}
impl HttpFrame for WindowUpdateFrame {
fn serialize(&self) -> Vec<u8> {
let mut res = vec![0x00, 0x00, 0x04, 0x08, 0x00]; // size, frame ID, flags
res.extend(self.stream_id.to_be_bytes());
res.extend(self.increment_size.to_be_bytes());
res
}
}
#[derive(Debug, Clone)]
/// We don't actually care about this frame according to spec. This is just to open new channels.
pub struct HeadersFrame {
pub stream_id: u32,
}
impl HttpFrame for HeadersFrame {
fn serialize(&self) -> Vec<u8> {
let mut res = vec![0x00, 0x00, 0x00, 0x01, 0x04];
res.extend(self.stream_id.to_be_bytes());
res
}
}
#[derive(Debug, Clone)]
pub struct DataFrame {
pub stream_id: u32,
pub payload: Vec<u8>,
}
impl HttpFrame for DataFrame {
fn serialize(&self) -> Vec<u8> {
let mut res = (self.payload.len() as u32).to_be_bytes().to_vec();
res.remove(0); // only 3 significant bytes
res.extend([0x00, 0x00]); // frame type, flags
res.extend(self.stream_id.to_be_bytes());
res.extend(self.payload.clone());
res
}
}

View File

@@ -0,0 +1,136 @@
// Jackson Coxson
use frame::HttpFrame;
use log::{debug, warn};
use std::collections::{HashMap, VecDeque};
use tokio::io::AsyncWriteExt;
use crate::{IdeviceError, ReadWrite};
pub mod frame;
pub use frame::Setting;
const HTTP2_MAGIC: &[u8] = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n".as_bytes();
pub struct Http2Client<R: ReadWrite> {
inner: R,
cache: HashMap<u32, VecDeque<Vec<u8>>>,
}
impl<R: ReadWrite> Http2Client<R> {
/// Writes the magic and inits the caches
pub async fn new(mut inner: R) -> Result<Self, IdeviceError> {
inner.write_all(HTTP2_MAGIC).await?;
inner.flush().await?;
Ok(Self {
inner,
cache: HashMap::new(),
})
}
pub async fn set_settings(
&mut self,
settings: Vec<frame::Setting>,
stream_id: u32,
) -> Result<(), IdeviceError> {
let frame = frame::SettingsFrame {
settings,
stream_id,
flags: 0,
}
.serialize();
self.inner.write_all(&frame).await?;
self.inner.flush().await?;
Ok(())
}
pub async fn window_update(
&mut self,
increment_size: u32,
stream_id: u32,
) -> Result<(), IdeviceError> {
let frame = frame::WindowUpdateFrame {
increment_size,
stream_id,
}
.serialize();
self.inner.write_all(&frame).await?;
self.inner.flush().await?;
Ok(())
}
pub async fn open_stream(&mut self, stream_id: u32) -> Result<(), IdeviceError> {
self.cache.insert(stream_id, VecDeque::new());
let frame = frame::HeadersFrame { stream_id }.serialize();
self.inner.write_all(&frame).await?;
self.inner.flush().await?;
Ok(())
}
pub async fn send(&mut self, payload: Vec<u8>, stream_id: u32) -> Result<(), IdeviceError> {
let frame = frame::DataFrame { stream_id, payload }.serialize();
self.inner.write_all(&frame).await?;
self.inner.flush().await?;
Ok(())
}
pub async fn read(&mut self, stream_id: u32) -> Result<Vec<u8>, IdeviceError> {
// See if we already have a cached message from another read
match self.cache.get_mut(&stream_id) {
Some(c) => {
if let Some(d) = c.pop_front() {
return Ok(d);
}
}
None => {
self.cache.insert(stream_id, VecDeque::new());
}
};
// handle packets until we get what we want
loop {
let frame = frame::Frame::next(&mut self.inner).await?;
// debug!("Got frame: {frame:#?}");
match frame {
frame::Frame::Settings(settings_frame) => {
if settings_frame.flags != 1 {
// ack that
let frame = frame::SettingsFrame {
settings: Vec::new(),
stream_id: settings_frame.stream_id,
flags: 1,
}
.serialize();
self.inner.write_all(&frame).await?;
self.inner.flush().await?;
}
}
frame::Frame::Data(data_frame) => {
debug!(
"Got data frame for {} with {} bytes",
data_frame.stream_id,
data_frame.payload.len()
);
if data_frame.stream_id == stream_id {
return Ok(data_frame.payload);
} else {
let c = match self.cache.get_mut(&data_frame.stream_id) {
Some(c) => c,
None => {
warn!(
"Received message for stream ID {} not in cache",
data_frame.stream_id
);
continue;
}
};
c.push_back(data_frame.payload);
}
}
_ => {
// do nothing, we shouldn't receive these frames
}
}
}
}
}

130
idevice/src/xpc/mod.rs Normal file
View File

@@ -0,0 +1,130 @@
// Jackson Coxson
use http2::Setting;
use log::debug;
use crate::{IdeviceError, ReadWrite};
mod format;
mod http2;
pub use format::XPCMessage;
use format::{XPCFlag, XPCObject};
const ROOT_CHANNEL: u32 = 1;
const REPLY_CHANNEL: u32 = 3;
pub struct RemoteXpcClient<R: ReadWrite> {
h2_client: http2::Http2Client<R>,
root_id: u64,
reply_id: u64,
}
impl<R: ReadWrite> RemoteXpcClient<R> {
pub async fn new(socket: R) -> Result<Self, IdeviceError> {
Ok(Self {
h2_client: http2::Http2Client::new(socket).await?,
root_id: 1,
reply_id: 1,
})
}
pub async fn do_handshake(&mut self) -> Result<plist::Value, IdeviceError> {
self.h2_client
.set_settings(
vec![
Setting::MaxConcurrentStreams(10),
Setting::InitialWindowSize(1048576),
],
0,
)
.await?;
self.h2_client.window_update(983041, 0).await?;
self.h2_client.open_stream(1).await?; // root channel
debug!("Sending empty dictionary");
self.send_root(XPCMessage::new(
Some(XPCFlag::AlwaysSet),
Some(XPCObject::Dictionary(Default::default())),
None,
))
.await?;
self.recv_root().await?;
self.recv_root().await?;
debug!("Sending weird flags");
self.send_root(XPCMessage::new(Some(XPCFlag::Custom(0x201)), None, None))
.await?;
debug!("Opening reply stream");
self.h2_client.open_stream(REPLY_CHANNEL).await?;
self.send_reply(XPCMessage::new(
Some(XPCFlag::InitHandshake | XPCFlag::AlwaysSet),
None,
None,
))
.await?;
let mut total_msg = Vec::new();
loop {
// We receive from the root channel for this message
total_msg.extend(self.h2_client.read(ROOT_CHANNEL).await?);
let msg = match XPCMessage::decode(&total_msg) {
Ok(m) => m,
Err(IdeviceError::PacketSizeMismatch) => {
continue;
}
Err(e) => {
return Err(e);
}
};
match msg.message {
Some(msg) => {
return Ok(msg.to_plist());
}
None => {
return Err(IdeviceError::UnexpectedResponse);
}
};
}
}
pub async fn recv(&mut self) -> Result<plist::Value, IdeviceError> {
loop {
let msg = self.h2_client.read(REPLY_CHANNEL).await?;
let msg = XPCMessage::decode(&msg)?;
if let Some(msg) = msg.message {
return Ok(msg.to_plist());
}
self.reply_id += 1;
}
}
pub async fn recv_root(&mut self) -> Result<Option<plist::Value>, IdeviceError> {
let msg = self.h2_client.read(ROOT_CHANNEL).await?;
let msg = XPCMessage::decode(&msg)?;
if let Some(msg) = msg.message {
Ok(Some(msg.to_plist()))
} else {
Ok(None)
}
}
async fn send_root(&mut self, msg: XPCMessage) -> Result<(), IdeviceError> {
self.h2_client
.send(msg.encode(self.root_id)?, ROOT_CHANNEL)
.await?;
Ok(())
}
async fn send_reply(&mut self, msg: XPCMessage) -> Result<(), IdeviceError> {
self.h2_client
.send(msg.encode(self.root_id)?, REPLY_CHANNEL)
.await?;
Ok(())
}
}

View File

@@ -96,7 +96,7 @@ async fn main() {
println!("rsd port: {}", tun_proxy.handshake.server_rsd_port);
println!("-----------------------------");
let mut buf = vec![0; 1500];
let mut buf = vec![0; 20_000]; // XPC is big lol
loop {
tokio::select! {
Ok(len) = async_dev.recv(&mut buf) => {

View File

@@ -1,17 +1,12 @@
// Jackson Coxson
use std::{
io::Write,
net::{IpAddr, SocketAddr},
str::FromStr,
};
use std::io::Write;
use clap::{Arg, Command};
use idevice::{
core_device_proxy::CoreDeviceProxy, debug_proxy::DebugProxyClient,
tunneld::get_tunneld_devices, xpc::XPCDevice, IdeviceService, ReadWrite,
core_device_proxy::CoreDeviceProxy, debug_proxy::DebugProxyClient, rsd::RsdHandshake,
tcp::stream::AdapterStream, IdeviceService, RsdService,
};
use tokio::net::TcpStream;
mod common;
@@ -63,79 +58,31 @@ async fn main() {
let pairing_file = matches.get_one::<String>("pairing_file");
let host = matches.get_one::<String>("host");
let mut dp: DebugProxyClient<Box<dyn ReadWrite>> = if matches.get_flag("tunneld") {
let socket = SocketAddr::new(
IpAddr::from_str("127.0.0.1").unwrap(),
idevice::tunneld::DEFAULT_PORT,
);
let mut devices = get_tunneld_devices(socket)
.await
.expect("Failed to get tunneld devices");
let (_udid, device) = match udid {
Some(u) => (
u.to_owned(),
devices.remove(u).expect("Device not in tunneld"),
),
None => devices.into_iter().next().expect("No devices"),
let provider =
match common::get_provider(udid, host, pairing_file, "debug-proxy-jkcoxson").await {
Ok(p) => p,
Err(e) => {
eprintln!("{e}");
return;
}
};
// Make the connection to RemoteXPC
let client = XPCDevice::new(Box::new(
TcpStream::connect((device.tunnel_address.as_str(), device.tunnel_port))
.await
.unwrap(),
))
let proxy = CoreDeviceProxy::connect(&*provider)
.await
.unwrap();
.expect("no core proxy");
let rsd_port = proxy.handshake.server_rsd_port;
// Get the debug proxy
let service = client
.services
.get(idevice::debug_proxy::SERVICE_NAME)
.expect("Client did not contain debug proxy service");
let stream = TcpStream::connect(SocketAddr::new(
IpAddr::from_str(&device.tunnel_address).unwrap(),
service.port,
))
let mut adapter = proxy.create_software_tunnel().expect("no software tunnel");
let stream = AdapterStream::connect(&mut adapter, rsd_port)
.await
.expect("Failed to connect");
.expect("no RSD connect");
DebugProxyClient::new(Box::new(stream))
} else {
let provider =
match common::get_provider(udid, host, pairing_file, "debug-proxy-jkcoxson").await {
Ok(p) => p,
Err(e) => {
eprintln!("{e}");
return;
}
};
let proxy = CoreDeviceProxy::connect(&*provider)
.await
.expect("no core proxy");
let rsd_port = proxy.handshake.server_rsd_port;
// Make the connection to RemoteXPC
let mut handshake = RsdHandshake::new(stream).await.unwrap();
println!("{:?}", handshake.services);
let mut adapter = proxy.create_software_tunnel().expect("no software tunnel");
adapter.connect(rsd_port).await.expect("no RSD connect");
// Make the connection to RemoteXPC
let client = XPCDevice::new(Box::new(adapter)).await.unwrap();
// Get the debug proxy
let service = client
.services
.get(idevice::debug_proxy::SERVICE_NAME)
.expect("Client did not contain debug proxy service")
.to_owned();
let mut adapter = client.into_inner();
adapter.close().await.unwrap();
adapter.connect(service.port).await.unwrap();
DebugProxyClient::new(Box::new(adapter))
};
let mut dp = DebugProxyClient::connect_rsd(&mut adapter, &mut handshake)
.await
.expect("no connect");
println!("Shell connected!");
loop {

View File

@@ -2,7 +2,10 @@
// Just lists apps for now
use clap::{Arg, Command};
use idevice::{core_device_proxy::CoreDeviceProxy, xpc::XPCDevice, IdeviceService};
use idevice::{
core_device_proxy::CoreDeviceProxy, rsd::RsdHandshake, tcp::stream::AdapterStream,
IdeviceService, RsdService,
};
mod common;
@@ -69,26 +72,21 @@ async fn main() {
let rsd_port = proxy.handshake.server_rsd_port;
let mut adapter = proxy.create_software_tunnel().expect("no software tunnel");
adapter.connect(rsd_port).await.expect("no RSD connect");
let stream = AdapterStream::connect(&mut adapter, rsd_port)
.await
.expect("no RSD connect");
// Make the connection to RemoteXPC
let client = XPCDevice::new(Box::new(adapter)).await.unwrap();
// Get the debug proxy
let service = client
.services
.get(idevice::dvt::SERVICE_NAME)
.expect("Client did not contain DVT service")
.to_owned();
let mut adapter = client.into_inner();
adapter.connect(service.port).await.unwrap();
let mut rs_client = idevice::dvt::remote_server::RemoteServerClient::new(Box::new(adapter));
rs_client.read_message(0).await.expect("no read??");
let mut handshake = RsdHandshake::new(stream).await.unwrap();
let mut ls_client =
idevice::dvt::location_simulation::LocationSimulationClient::new(&mut rs_client)
idevice::dvt::remote_server::RemoteServerClient::connect_rsd(&mut adapter, &mut handshake)
.await
.expect("Failed to connect");
ls_client.read_message(0).await.expect("no read??");
let mut ls_client =
idevice::dvt::location_simulation::LocationSimulationClient::new(&mut ls_client)
.await
.expect("Unable to get channel for location simulation");

View File

@@ -1,16 +1,10 @@
// Jackson Coxson
use std::{
net::{IpAddr, SocketAddr},
str::FromStr,
};
use clap::{Arg, Command};
use idevice::{
core_device_proxy::CoreDeviceProxy, tunneld::get_tunneld_devices, xpc::XPCDevice,
IdeviceService,
core_device_proxy::CoreDeviceProxy, rsd::RsdHandshake, tcp::stream::AdapterStream,
IdeviceService, RsdService,
};
use tokio::net::TcpStream;
mod common;
@@ -71,70 +65,8 @@ async fn main() {
.get_one::<String>("bundle_id")
.expect("No bundle ID specified");
if matches.get_flag("tunneld") {
let socket = SocketAddr::new(
IpAddr::from_str("127.0.0.1").unwrap(),
idevice::tunneld::DEFAULT_PORT,
);
let mut devices = get_tunneld_devices(socket)
.await
.expect("Failed to get tunneld devices");
let (_udid, device) = match udid {
Some(u) => (
u.to_owned(),
devices.remove(u).expect("Device not in tunneld"),
),
None => devices.into_iter().next().expect("No devices"),
};
// Make the connection to RemoteXPC
let client = XPCDevice::new(Box::new(
TcpStream::connect((device.tunnel_address.as_str(), device.tunnel_port))
.await
.unwrap(),
))
.await
.unwrap();
// Get the debug proxy
let service = client
.services
.get(idevice::dvt::SERVICE_NAME)
.expect("Client did not contain DVT service");
let stream = TcpStream::connect(SocketAddr::new(
IpAddr::from_str(&device.tunnel_address).unwrap(),
service.port,
))
.await
.expect("Failed to connect");
let mut rs_client = idevice::dvt::remote_server::RemoteServerClient::new(Box::new(stream));
rs_client.read_message(0).await.expect("no read??");
let mut pc_client =
idevice::dvt::process_control::ProcessControlClient::new(&mut rs_client)
.await
.unwrap();
let pid = pc_client
.launch_app(bundle_id, None, None, true, false)
.await
.expect("no launch??");
pc_client
.disable_memory_limit(pid)
.await
.expect("no disable??");
println!("PID: {pid}");
} else {
let provider = match common::get_provider(
udid,
host,
pairing_file,
"process_control-jkcoxson",
)
.await
{
let provider =
match common::get_provider(udid, host, pairing_file, "process_control-jkcoxson").await {
Ok(p) => p,
Err(e) => {
eprintln!("{e}");
@@ -142,45 +74,35 @@ async fn main() {
}
};
let proxy = CoreDeviceProxy::connect(&*provider)
let proxy = CoreDeviceProxy::connect(&*provider)
.await
.expect("no core proxy");
let rsd_port = proxy.handshake.server_rsd_port;
let mut adapter = proxy.create_software_tunnel().expect("no software tunnel");
let stream = AdapterStream::connect(&mut adapter, rsd_port)
.await
.expect("no RSD connect");
// Make the connection to RemoteXPC
let mut handshake = RsdHandshake::new(stream).await.unwrap();
let mut rs_client =
idevice::dvt::remote_server::RemoteServerClient::connect_rsd(&mut adapter, &mut handshake)
.await
.expect("no core proxy");
let rsd_port = proxy.handshake.server_rsd_port;
.expect("no connect");
rs_client.read_message(0).await.expect("no read??");
let mut pc_client = idevice::dvt::process_control::ProcessControlClient::new(&mut rs_client)
.await
.unwrap();
let mut adapter = proxy.create_software_tunnel().expect("no software tunnel");
adapter.connect(rsd_port).await.expect("no RSD connect");
// Make the connection to RemoteXPC
let client = XPCDevice::new(Box::new(adapter)).await.unwrap();
// Get the debug proxy
let service = client
.services
.get(idevice::dvt::SERVICE_NAME)
.expect("Client did not contain DVT service")
.to_owned();
let mut adapter = client.into_inner();
adapter.connect(service.port).await.unwrap();
let mut rs_client = idevice::dvt::remote_server::RemoteServerClient::new(Box::new(adapter));
rs_client.read_message(0).await.expect("no read??");
let mut pc_client =
idevice::dvt::process_control::ProcessControlClient::new(&mut rs_client)
.await
.unwrap();
let pid = pc_client
.launch_app(bundle_id, None, None, true, false)
.await
.expect("no launch??");
pc_client
.disable_memory_limit(pid)
.await
.expect("no disable??");
println!("PID: {pid}");
// let mut adapter = rs_client.into_inner();
// adapter.close().await.expect("no close??");
}
let pid = pc_client
.launch_app(bundle_id, None, None, true, false)
.await
.expect("no launch??");
pc_client
.disable_memory_limit(pid)
.await
.expect("no disable??");
println!("PID: {pid}");
}

View File

@@ -2,7 +2,10 @@
// Print out all the RemoteXPC services
use clap::{Arg, Command};
use idevice::{core_device_proxy::CoreDeviceProxy, xpc::XPCDevice, IdeviceService};
use idevice::{
core_device_proxy::CoreDeviceProxy, tcp::stream::AdapterStream, xpc::RemoteXpcClient,
IdeviceService,
};
mod common;
@@ -63,10 +66,13 @@ async fn main() {
let rsd_port = proxy.handshake.server_rsd_port;
let mut adapter = proxy.create_software_tunnel().expect("no software tunnel");
adapter.connect(rsd_port).await.expect("no RSD connect");
adapter.pcap("new_xpc.pcap").await.unwrap();
let conn = AdapterStream::connect(&mut adapter, rsd_port)
.await
.expect("no RSD connect");
// Make the connection to RemoteXPC
let client = XPCDevice::new(Box::new(adapter)).await.unwrap();
let mut client = RemoteXpcClient::new(Box::new(conn)).await.unwrap();
println!("{:#?}", client.services);
println!("{:#?}", client.do_handshake().await);
}