3 Commits

Author SHA1 Message Date
Jackson Coxson
aefda7e6f1 Merge branch 'master' into rppairing 2025-10-02 09:07:56 -06:00
Jackson Coxson
b4caa3b271 Merge branch 'master' into rppairing
a
2025-09-05 12:00:25 -06:00
Jackson Coxson
81a644170e Initial scafolding for RPPairing 2025-07-21 07:57:49 -06:00
185 changed files with 5696 additions and 12363 deletions

View File

@@ -30,14 +30,8 @@ jobs:
- name: Install rustup targets
run: |
rustup target add aarch64-apple-ios && \
rustup target add x86_64-apple-ios && \
rustup target add aarch64-apple-ios-sim && \
rustup target add aarch64-apple-darwin && \
rustup target add x86_64-apple-darwin && \
rustup target add aarch64-apple-ios-macabi && \
rustup target add x86_64-apple-ios-macabi && \
cargo install --force --locked bindgen-cli
rustup target add x86_64-apple-darwin && cargo install --force --locked bindgen-cli
- name: Build all Apple targets and examples/tools
run: |
@@ -50,12 +44,6 @@ jobs:
path: |
target/*apple*/release/libidevice_ffi.a
- name: Upload macOS+iOS XCFramework
uses: actions/upload-artifact@v4
with:
name: idevice-xcframework
path: swift/bundle.zip
- name: Upload C examples/tools
uses: actions/upload-artifact@v4
with:

1889
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -47,8 +47,6 @@ To keep dependency bloat and compile time down, everything is contained in featu
|------------------------|-----------------------------------------------------------------------------|
| `afc` | Apple File Conduit for file system access.|
| `amfi` | Apple mobile file integrity service |
| `bt_packet_logger` | Capture Bluetooth packets. |
| `companion_proxy` | Manage paired Apple Watches. |
| `core_device_proxy` | Start a secure tunnel to access protected services. |
| `crashreportcopymobile`| Copy crash reports.|
| `debug_proxy` | Send GDB commands to the device.|
@@ -57,19 +55,13 @@ To keep dependency bloat and compile time down, everything is contained in featu
| `heartbeat` | Maintain a heartbeat connection.|
| `house_arrest` | Manage files in app containers |
| `installation_proxy` | Manage app installation and uninstallation.|
| `installcoordination_proxy` | Manage app installation coordination.|
| `location_simulation` | Simulate GPS locations on the device.|
| `springboardservices` | Control SpringBoard (e.g. UI interactions). Partial support.|
| `misagent` | Manage provisioning profiles on the device.|
| `mobile_image_mounter` | Manage DDI images.|
| `mobileactivationd` | Activate/Deactivate device.|
| `mobilebackup2` | Manage backups.|
| `mobile_image_mounter` | Manage DDI images.|
| `location_simulation` | Simulate GPS locations on the device.|
| `pair` | Pair the device.|
| `pcapd` | Capture network packets.|
| `preboard_service` | Interface with Preboard.|
| `restore_service` | Restore service (recovery/reboot).|
| `screenshotr` | Take screenshots.|
| `springboardservices` | Control SpringBoard (icons, wallpaper, orientation, etc.).|
| `syslog_relay` | Relay system logs and OS trace logs from the device. |
| `syslog_relay` | Relay system logs from the device |
| `tcp` | Connect to devices over TCP.|
| `tunnel_tcp_stack` | Naive in-process TCP stack for `core_device_proxy`.|
| `tss` | Make requests to Apple's TSS servers. Partial support.|
@@ -81,11 +73,16 @@ To keep dependency bloat and compile time down, everything is contained in featu
Finish the following:
- webinspector
- springboard
Implement the following:
- companion_proxy
- diagnostics
- mobilebackup2
- notification_proxy
- screenshot
- webinspector
As this project is done in my free time within my busy schedule, there
is no ETA for any of these. Feel free to contribute or donate!

View File

@@ -32,8 +32,8 @@ endif()
# ---- Build the C++ wrapper library -----------------------------------------
# Collect your .cpps
file(GLOB_RECURSE IDEVICE_CPP_SOURCES
"${IDEVICE_CPP_SRC_DIR}/*.cpp"
file(GLOB IDEVICE_CPP_SOURCES
${IDEVICE_CPP_SRC_DIR}/*.cpp
)
file(GLOB PLIST_CPP_SOURCES

View File

@@ -6,11 +6,11 @@
#include <thread>
#include <idevice++/core_device_proxy.hpp>
#include <idevice++/dvt/location_simulation.hpp>
#include <idevice++/dvt/remote_server.hpp>
#include <idevice++/ffi.hpp>
#include <idevice++/location_simulation.hpp>
#include <idevice++/provider.hpp>
#include <idevice++/readwrite.hpp>
#include <idevice++/remote_server.hpp>
#include <idevice++/rsd.hpp>
#include <idevice++/usbmuxd.hpp>

View File

@@ -1,108 +0,0 @@
// Jackson Coxson
#include <cstdlib>
#include <fstream>
#include <iostream>
#include <string>
#include <idevice++/bindings.hpp>
#include <idevice++/core_device_proxy.hpp>
#include <idevice++/dvt/remote_server.hpp>
#include <idevice++/dvt/screenshot.hpp>
#include <idevice++/ffi.hpp>
#include <idevice++/provider.hpp>
#include <idevice++/readwrite.hpp>
#include <idevice++/rsd.hpp>
#include <idevice++/usbmuxd.hpp>
using namespace IdeviceFFI;
[[noreturn]]
static void die(const char* msg, const FfiError& e) {
std::cerr << msg << ": " << e.message << "\n";
std::exit(1);
}
int main(int argc, char** argv) {
idevice_init_logger(Debug, Disabled, NULL);
// Usage:
// take_screenshot <output.png>
if (argc != 2) {
std::cerr << "Usage:\n"
<< " " << argv[0] << " <output.png>\n";
return 2;
}
std::string out_path = argv[1];
// 1) Connect to usbmuxd and pick first device
auto mux = UsbmuxdConnection::default_new(/*tag*/ 0).expect("failed to connect to usbmuxd");
auto devices = mux.get_devices().expect("failed to list devices");
if (devices.empty()) {
std::cerr << "no devices connected\n";
return 1;
}
auto& dev = (devices)[0];
auto udid = dev.get_udid();
if (udid.is_none()) {
std::cerr << "device has no UDID\n";
return 1;
}
auto mux_id = dev.get_id();
if (mux_id.is_none()) {
std::cerr << "device has no mux id\n";
return 1;
}
// 2) Provider via default usbmuxd addr
auto addr = UsbmuxdAddr::default_new();
const uint32_t tag = 0;
const std::string label = "screenshot-client";
auto provider =
Provider::usbmuxd_new(std::move(addr), tag, udid.unwrap(), mux_id.unwrap(), label)
.expect("failed to create provider");
// 3) CoreDeviceProxy
auto cdp = CoreDeviceProxy::connect(provider).unwrap_or_else(
[](FfiError e) -> CoreDeviceProxy { die("failed to connect CoreDeviceProxy", e); });
auto rsd_port = cdp.get_server_rsd_port().unwrap_or_else(
[](FfiError err) -> uint16_t { die("failed to get server RSD port", err); });
// 4) Create software tunnel adapter (consumes proxy)
auto adapter =
std::move(cdp).create_tcp_adapter().expect("failed to create software tunnel adapter");
// 5) Connect adapter to RSD → ReadWrite stream
auto stream = adapter.connect(rsd_port).expect("failed to connect RSD stream");
// 6) RSD handshake (consumes stream)
auto rsd = RsdHandshake::from_socket(std::move(stream)).expect("failed RSD handshake");
// 7) RemoteServer over RSD (borrows adapter + handshake)
auto rs = RemoteServer::connect_rsd(adapter, rsd).expect("failed to connect to RemoteServer");
// 8) ScreenshotClient (borrows RemoteServer)
auto ss = ScreenshotClient::create(rs).unwrap_or_else(
[](FfiError e) -> ScreenshotClient { die("failed to create ScreenshotClient", e); });
// 9) Capture screenshot
auto buf = ss.take_screenshot().unwrap_or_else(
[](FfiError e) -> std::vector<uint8_t> { die("failed to capture screenshot", e); });
// 10) Write PNG file
std::ofstream out(out_path, std::ios::binary);
if (!out.is_open()) {
std::cerr << "failed to open output file: " << out_path << "\n";
return 1;
}
out.write(reinterpret_cast<const char*>(buf.data()), static_cast<std::streamsize>(buf.size()));
out.close();
std::cout << "Screenshot saved to " << out_path << " (" << buf.size() << " bytes)\n";
return 0;
}

View File

@@ -7,101 +7,81 @@
objects = {
/* Begin PBXBuildFile section */
196C57452EA7F5DC00188F4B /* location_simulation.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 196C56B42EA7F53000188F4B /* location_simulation.cpp */; };
196C57462EA7F5DC00188F4B /* process_control.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 196C56B52EA7F53000188F4B /* process_control.cpp */; };
196C57472EA7F5DC00188F4B /* remote_server.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 196C56B62EA7F53000188F4B /* remote_server.cpp */; };
196C57482EA7F5DC00188F4B /* screenshot.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 196C56B72EA7F53000188F4B /* screenshot.cpp */; };
196C57492EA7F5DC00188F4B /* adapter_stream.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 196C56B92EA7F53000188F4B /* adapter_stream.cpp */; };
196C574A2EA7F5DC00188F4B /* app_service.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 196C56BA2EA7F53000188F4B /* app_service.cpp */; };
196C574B2EA7F5DC00188F4B /* core_device.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 196C56BB2EA7F53000188F4B /* core_device.cpp */; };
196C574C2EA7F5DC00188F4B /* debug_proxy.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 196C56BC2EA7F53000188F4B /* debug_proxy.cpp */; };
196C574D2EA7F5DC00188F4B /* diagnosticsservice.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 196C56BD2EA7F53000188F4B /* diagnosticsservice.cpp */; };
196C574E2EA7F5DC00188F4B /* ffi.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 196C56BE2EA7F53000188F4B /* ffi.cpp */; };
196C574F2EA7F5DC00188F4B /* heartbeat.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 196C56BF2EA7F53000188F4B /* heartbeat.cpp */; };
196C57502EA7F5DC00188F4B /* idevice.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 196C56C02EA7F53000188F4B /* idevice.cpp */; };
196C57512EA7F5DC00188F4B /* installation_proxy.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 196C56C12EA7F53000188F4B /* installation_proxy.cpp */; };
196C57522EA7F5DC00188F4B /* lockdown.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 196C56C22EA7F53000188F4B /* lockdown.cpp */; };
196C57532EA7F5DC00188F4B /* mobile_image_mounter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 196C56C32EA7F53000188F4B /* mobile_image_mounter.cpp */; };
196C57542EA7F5DC00188F4B /* pairing_file.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 196C56C42EA7F53000188F4B /* pairing_file.cpp */; };
196C57552EA7F5DC00188F4B /* provider.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 196C56C52EA7F53000188F4B /* provider.cpp */; };
196C57562EA7F5DC00188F4B /* rsd.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 196C56C62EA7F53000188F4B /* rsd.cpp */; };
196C57572EA7F5DC00188F4B /* tcp_callback_feeder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 196C56C72EA7F53000188F4B /* tcp_callback_feeder.cpp */; };
196C57582EA7F5DC00188F4B /* usbmuxd.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 196C56C82EA7F53000188F4B /* usbmuxd.cpp */; };
196C57592EA7F5F400188F4B /* location_simulation.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 196C56DE2EA7F54900188F4B /* location_simulation.hpp */; };
196C575A2EA7F5F400188F4B /* process_control.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 196C56DF2EA7F54900188F4B /* process_control.hpp */; };
196C575B2EA7F5F400188F4B /* remote_server.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 196C56E02EA7F54900188F4B /* remote_server.hpp */; };
196C575C2EA7F5F400188F4B /* screenshot.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 196C56E12EA7F54900188F4B /* screenshot.hpp */; };
196C575D2EA7F5F400188F4B /* adapter_stream.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 196C56E32EA7F54900188F4B /* adapter_stream.hpp */; };
196C575E2EA7F5F400188F4B /* app_service.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 196C56E42EA7F54900188F4B /* app_service.hpp */; };
196C575F2EA7F5F400188F4B /* bindings.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 196C56E52EA7F54900188F4B /* bindings.hpp */; };
196C57602EA7F5F400188F4B /* core_device_proxy.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 196C56E62EA7F54900188F4B /* core_device_proxy.hpp */; };
196C57612EA7F5F400188F4B /* debug_proxy.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 196C56E72EA7F54900188F4B /* debug_proxy.hpp */; };
196C57622EA7F5F400188F4B /* diagnosticsservice.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 196C56E82EA7F54900188F4B /* diagnosticsservice.hpp */; };
196C57632EA7F5F400188F4B /* ffi.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 196C56E92EA7F54900188F4B /* ffi.hpp */; };
196C57642EA7F5F400188F4B /* heartbeat.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 196C56EA2EA7F54900188F4B /* heartbeat.hpp */; };
196C57652EA7F5F400188F4B /* idevice.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 196C56EB2EA7F54900188F4B /* idevice.hpp */; };
196C57662EA7F5F400188F4B /* installation_proxy.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 196C56EC2EA7F54900188F4B /* installation_proxy.hpp */; };
196C57672EA7F5F400188F4B /* lockdown.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 196C56ED2EA7F54900188F4B /* lockdown.hpp */; };
196C57682EA7F5F400188F4B /* mobile_image_mounter.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 196C56EE2EA7F54900188F4B /* mobile_image_mounter.hpp */; };
196C57692EA7F5F400188F4B /* option.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 196C56EF2EA7F54900188F4B /* option.hpp */; };
196C576A2EA7F5F400188F4B /* pairing_file.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 196C56F02EA7F54900188F4B /* pairing_file.hpp */; };
196C576B2EA7F5F400188F4B /* provider.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 196C56F12EA7F54900188F4B /* provider.hpp */; };
196C576C2EA7F5F400188F4B /* readwrite.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 196C56F22EA7F54900188F4B /* readwrite.hpp */; };
196C576D2EA7F5F400188F4B /* result.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 196C56F32EA7F54900188F4B /* result.hpp */; };
196C576E2EA7F5F400188F4B /* rsd.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 196C56F42EA7F54900188F4B /* rsd.hpp */; };
196C576F2EA7F5F400188F4B /* tcp_object_stack.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 196C56F52EA7F54900188F4B /* tcp_object_stack.hpp */; };
196C57702EA7F5F400188F4B /* usbmuxd.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 196C56F62EA7F54900188F4B /* usbmuxd.hpp */; };
196C57712EA7F5F400188F4B /* idevice.h in Headers */ = {isa = PBXBuildFile; fileRef = 196C56F82EA7F54900188F4B /* idevice.h */; };
1914C7972E623CC2002EAB6E /* option.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 1914C7962E623CC2002EAB6E /* option.hpp */; };
1914C7992E623CC8002EAB6E /* result.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 1914C7982E623CC8002EAB6E /* result.hpp */; };
198077932E5CA6EF00CB501E /* adapter_stream.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1980776C2E5CA69800CB501E /* adapter_stream.cpp */; };
198077942E5CA6EF00CB501E /* app_service.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1980776D2E5CA69800CB501E /* app_service.cpp */; };
198077952E5CA6EF00CB501E /* core_device.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1980776E2E5CA69800CB501E /* core_device.cpp */; };
198077962E5CA6EF00CB501E /* debug_proxy.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1980776F2E5CA69800CB501E /* debug_proxy.cpp */; };
198077972E5CA6EF00CB501E /* diagnosticsservice.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 198077702E5CA69800CB501E /* diagnosticsservice.cpp */; };
198077982E5CA6EF00CB501E /* ffi.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 198077712E5CA69800CB501E /* ffi.cpp */; };
198077992E5CA6EF00CB501E /* idevice.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 198077722E5CA69800CB501E /* idevice.cpp */; };
1980779A2E5CA6EF00CB501E /* location_simulation.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 198077732E5CA69800CB501E /* location_simulation.cpp */; };
1980779B2E5CA6EF00CB501E /* lockdown.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 198077742E5CA69800CB501E /* lockdown.cpp */; };
1980779C2E5CA6EF00CB501E /* pairing_file.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 198077752E5CA69800CB501E /* pairing_file.cpp */; };
1980779D2E5CA6EF00CB501E /* provider.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 198077762E5CA69800CB501E /* provider.cpp */; };
1980779E2E5CA6EF00CB501E /* remote_server.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 198077772E5CA69800CB501E /* remote_server.cpp */; };
1980779F2E5CA6EF00CB501E /* rsd.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 198077782E5CA69800CB501E /* rsd.cpp */; };
198077A02E5CA6EF00CB501E /* tcp_callback_feeder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 198077792E5CA69800CB501E /* tcp_callback_feeder.cpp */; };
198077A12E5CA6EF00CB501E /* usbmuxd.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1980777A2E5CA69800CB501E /* usbmuxd.cpp */; };
198077B72E5CA6FC00CB501E /* core_device_proxy.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 198077A62E5CA6FC00CB501E /* core_device_proxy.hpp */; };
198077B82E5CA6FC00CB501E /* pairing_file.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 198077AD2E5CA6FC00CB501E /* pairing_file.hpp */; };
198077B92E5CA6FC00CB501E /* bindings.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 198077A52E5CA6FC00CB501E /* bindings.hpp */; };
198077BA2E5CA6FC00CB501E /* diagnosticsservice.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 198077A82E5CA6FC00CB501E /* diagnosticsservice.hpp */; };
198077BB2E5CA6FC00CB501E /* idevice.h in Headers */ = {isa = PBXBuildFile; fileRef = 198077B52E5CA6FC00CB501E /* idevice.h */; };
198077BC2E5CA6FC00CB501E /* debug_proxy.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 198077A72E5CA6FC00CB501E /* debug_proxy.hpp */; };
198077BD2E5CA6FC00CB501E /* ffi.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 198077A92E5CA6FC00CB501E /* ffi.hpp */; };
198077BE2E5CA6FC00CB501E /* rsd.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 198077B12E5CA6FC00CB501E /* rsd.hpp */; };
198077BF2E5CA6FC00CB501E /* tcp_object_stack.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 198077B22E5CA6FC00CB501E /* tcp_object_stack.hpp */; };
198077C02E5CA6FC00CB501E /* remote_server.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 198077B02E5CA6FC00CB501E /* remote_server.hpp */; };
198077C12E5CA6FC00CB501E /* readwrite.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 198077AF2E5CA6FC00CB501E /* readwrite.hpp */; };
198077C22E5CA6FC00CB501E /* location_simulation.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 198077AB2E5CA6FC00CB501E /* location_simulation.hpp */; };
198077C32E5CA6FC00CB501E /* adapter_stream.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 198077A32E5CA6FC00CB501E /* adapter_stream.hpp */; };
198077C42E5CA6FC00CB501E /* lockdown.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 198077AC2E5CA6FC00CB501E /* lockdown.hpp */; };
198077C52E5CA6FC00CB501E /* usbmuxd.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 198077B32E5CA6FC00CB501E /* usbmuxd.hpp */; };
198077C62E5CA6FC00CB501E /* app_service.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 198077A42E5CA6FC00CB501E /* app_service.hpp */; };
198077C72E5CA6FC00CB501E /* idevice.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 198077AA2E5CA6FC00CB501E /* idevice.hpp */; };
198077C82E5CA6FC00CB501E /* provider.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 198077AE2E5CA6FC00CB501E /* provider.hpp */; };
198077DF2E5CCA2900CB501E /* libidevice_ffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 198077DB2E5CC33000CB501E /* libidevice_ffi.a */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
196C56B42EA7F53000188F4B /* location_simulation.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = location_simulation.cpp; sourceTree = "<group>"; };
196C56B52EA7F53000188F4B /* process_control.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = process_control.cpp; sourceTree = "<group>"; };
196C56B62EA7F53000188F4B /* remote_server.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = remote_server.cpp; sourceTree = "<group>"; };
196C56B72EA7F53000188F4B /* screenshot.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = screenshot.cpp; sourceTree = "<group>"; };
196C56B92EA7F53000188F4B /* adapter_stream.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = adapter_stream.cpp; sourceTree = "<group>"; };
196C56BA2EA7F53000188F4B /* app_service.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = app_service.cpp; sourceTree = "<group>"; };
196C56BB2EA7F53000188F4B /* core_device.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = core_device.cpp; sourceTree = "<group>"; };
196C56BC2EA7F53000188F4B /* debug_proxy.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = debug_proxy.cpp; sourceTree = "<group>"; };
196C56BD2EA7F53000188F4B /* diagnosticsservice.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = diagnosticsservice.cpp; sourceTree = "<group>"; };
196C56BE2EA7F53000188F4B /* ffi.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = ffi.cpp; sourceTree = "<group>"; };
196C56BF2EA7F53000188F4B /* heartbeat.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = heartbeat.cpp; sourceTree = "<group>"; };
196C56C02EA7F53000188F4B /* idevice.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = idevice.cpp; sourceTree = "<group>"; };
196C56C12EA7F53000188F4B /* installation_proxy.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = installation_proxy.cpp; sourceTree = "<group>"; };
196C56C22EA7F53000188F4B /* lockdown.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = lockdown.cpp; sourceTree = "<group>"; };
196C56C32EA7F53000188F4B /* mobile_image_mounter.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = mobile_image_mounter.cpp; sourceTree = "<group>"; };
196C56C42EA7F53000188F4B /* pairing_file.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = pairing_file.cpp; sourceTree = "<group>"; };
196C56C52EA7F53000188F4B /* provider.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = provider.cpp; sourceTree = "<group>"; };
196C56C62EA7F53000188F4B /* rsd.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = rsd.cpp; sourceTree = "<group>"; };
196C56C72EA7F53000188F4B /* tcp_callback_feeder.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = tcp_callback_feeder.cpp; sourceTree = "<group>"; };
196C56C82EA7F53000188F4B /* usbmuxd.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = usbmuxd.cpp; sourceTree = "<group>"; };
196C56DE2EA7F54900188F4B /* location_simulation.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = location_simulation.hpp; sourceTree = "<group>"; };
196C56DF2EA7F54900188F4B /* process_control.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = process_control.hpp; sourceTree = "<group>"; };
196C56E02EA7F54900188F4B /* remote_server.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = remote_server.hpp; sourceTree = "<group>"; };
196C56E12EA7F54900188F4B /* screenshot.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = screenshot.hpp; sourceTree = "<group>"; };
196C56E32EA7F54900188F4B /* adapter_stream.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = adapter_stream.hpp; sourceTree = "<group>"; };
196C56E42EA7F54900188F4B /* app_service.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = app_service.hpp; sourceTree = "<group>"; };
196C56E52EA7F54900188F4B /* bindings.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = bindings.hpp; sourceTree = "<group>"; };
196C56E62EA7F54900188F4B /* core_device_proxy.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = core_device_proxy.hpp; sourceTree = "<group>"; };
196C56E72EA7F54900188F4B /* debug_proxy.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = debug_proxy.hpp; sourceTree = "<group>"; };
196C56E82EA7F54900188F4B /* diagnosticsservice.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = diagnosticsservice.hpp; sourceTree = "<group>"; };
196C56E92EA7F54900188F4B /* ffi.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ffi.hpp; sourceTree = "<group>"; };
196C56EA2EA7F54900188F4B /* heartbeat.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = heartbeat.hpp; sourceTree = "<group>"; };
196C56EB2EA7F54900188F4B /* idevice.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = idevice.hpp; sourceTree = "<group>"; };
196C56EC2EA7F54900188F4B /* installation_proxy.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = installation_proxy.hpp; sourceTree = "<group>"; };
196C56ED2EA7F54900188F4B /* lockdown.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = lockdown.hpp; sourceTree = "<group>"; };
196C56EE2EA7F54900188F4B /* mobile_image_mounter.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = mobile_image_mounter.hpp; sourceTree = "<group>"; };
196C56EF2EA7F54900188F4B /* option.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = option.hpp; sourceTree = "<group>"; };
196C56F02EA7F54900188F4B /* pairing_file.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = pairing_file.hpp; sourceTree = "<group>"; };
196C56F12EA7F54900188F4B /* provider.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = provider.hpp; sourceTree = "<group>"; };
196C56F22EA7F54900188F4B /* readwrite.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = readwrite.hpp; sourceTree = "<group>"; };
196C56F32EA7F54900188F4B /* result.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = result.hpp; sourceTree = "<group>"; };
196C56F42EA7F54900188F4B /* rsd.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = rsd.hpp; sourceTree = "<group>"; };
196C56F52EA7F54900188F4B /* tcp_object_stack.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = tcp_object_stack.hpp; sourceTree = "<group>"; };
196C56F62EA7F54900188F4B /* usbmuxd.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = usbmuxd.hpp; sourceTree = "<group>"; };
196C56F82EA7F54900188F4B /* idevice.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = idevice.h; sourceTree = "<group>"; };
1914C7962E623CC2002EAB6E /* option.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = option.hpp; sourceTree = "<group>"; };
1914C7982E623CC8002EAB6E /* result.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = result.hpp; sourceTree = "<group>"; };
1980776C2E5CA69800CB501E /* adapter_stream.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = adapter_stream.cpp; sourceTree = "<group>"; };
1980776D2E5CA69800CB501E /* app_service.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = app_service.cpp; sourceTree = "<group>"; };
1980776E2E5CA69800CB501E /* core_device.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = core_device.cpp; sourceTree = "<group>"; };
1980776F2E5CA69800CB501E /* debug_proxy.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = debug_proxy.cpp; sourceTree = "<group>"; };
198077702E5CA69800CB501E /* diagnosticsservice.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = diagnosticsservice.cpp; sourceTree = "<group>"; };
198077712E5CA69800CB501E /* ffi.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = ffi.cpp; sourceTree = "<group>"; };
198077722E5CA69800CB501E /* idevice.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = idevice.cpp; sourceTree = "<group>"; };
198077732E5CA69800CB501E /* location_simulation.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = location_simulation.cpp; sourceTree = "<group>"; };
198077742E5CA69800CB501E /* lockdown.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = lockdown.cpp; sourceTree = "<group>"; };
198077752E5CA69800CB501E /* pairing_file.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = pairing_file.cpp; sourceTree = "<group>"; };
198077762E5CA69800CB501E /* provider.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = provider.cpp; sourceTree = "<group>"; };
198077772E5CA69800CB501E /* remote_server.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = remote_server.cpp; sourceTree = "<group>"; };
198077782E5CA69800CB501E /* rsd.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = rsd.cpp; sourceTree = "<group>"; };
198077792E5CA69800CB501E /* tcp_callback_feeder.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = tcp_callback_feeder.cpp; sourceTree = "<group>"; };
1980777A2E5CA69800CB501E /* usbmuxd.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = usbmuxd.cpp; sourceTree = "<group>"; };
1980778F2E5CA6C700CB501E /* libidevice++.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libidevice++.a"; sourceTree = BUILT_PRODUCTS_DIR; };
198077A32E5CA6FC00CB501E /* adapter_stream.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = adapter_stream.hpp; sourceTree = "<group>"; };
198077A42E5CA6FC00CB501E /* app_service.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = app_service.hpp; sourceTree = "<group>"; };
198077A52E5CA6FC00CB501E /* bindings.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = bindings.hpp; sourceTree = "<group>"; };
198077A62E5CA6FC00CB501E /* core_device_proxy.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = core_device_proxy.hpp; sourceTree = "<group>"; };
198077A72E5CA6FC00CB501E /* debug_proxy.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = debug_proxy.hpp; sourceTree = "<group>"; };
198077A82E5CA6FC00CB501E /* diagnosticsservice.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = diagnosticsservice.hpp; sourceTree = "<group>"; };
198077A92E5CA6FC00CB501E /* ffi.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ffi.hpp; sourceTree = "<group>"; };
198077AA2E5CA6FC00CB501E /* idevice.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = idevice.hpp; sourceTree = "<group>"; };
198077AB2E5CA6FC00CB501E /* location_simulation.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = location_simulation.hpp; sourceTree = "<group>"; };
198077AC2E5CA6FC00CB501E /* lockdown.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = lockdown.hpp; sourceTree = "<group>"; };
198077AD2E5CA6FC00CB501E /* pairing_file.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = pairing_file.hpp; sourceTree = "<group>"; };
198077AE2E5CA6FC00CB501E /* provider.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = provider.hpp; sourceTree = "<group>"; };
198077AF2E5CA6FC00CB501E /* readwrite.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = readwrite.hpp; sourceTree = "<group>"; };
198077B02E5CA6FC00CB501E /* remote_server.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = remote_server.hpp; sourceTree = "<group>"; };
198077B12E5CA6FC00CB501E /* rsd.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = rsd.hpp; sourceTree = "<group>"; };
198077B22E5CA6FC00CB501E /* tcp_object_stack.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = tcp_object_stack.hpp; sourceTree = "<group>"; };
198077B32E5CA6FC00CB501E /* usbmuxd.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = usbmuxd.hpp; sourceTree = "<group>"; };
198077B52E5CA6FC00CB501E /* idevice.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = idevice.h; sourceTree = "<group>"; };
198077DB2E5CC33000CB501E /* libidevice_ffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libidevice_ffi.a; path = "${DESTINATION_PATH}/libidevice_ffi.a"; sourceTree = "<group>"; };
198077E02E5CD49200CB501E /* xcode_build_rust.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = xcode_build_rust.sh; sourceTree = "<group>"; };
/* End PBXFileReference section */
@@ -118,95 +98,12 @@
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
196C56B82EA7F53000188F4B /* dvt */ = {
isa = PBXGroup;
children = (
196C56B42EA7F53000188F4B /* location_simulation.cpp */,
196C56B52EA7F53000188F4B /* process_control.cpp */,
196C56B62EA7F53000188F4B /* remote_server.cpp */,
196C56B72EA7F53000188F4B /* screenshot.cpp */,
);
path = dvt;
sourceTree = "<group>";
};
196C56C92EA7F53000188F4B /* src */ = {
isa = PBXGroup;
children = (
196C56B82EA7F53000188F4B /* dvt */,
196C56B92EA7F53000188F4B /* adapter_stream.cpp */,
196C56BA2EA7F53000188F4B /* app_service.cpp */,
196C56BB2EA7F53000188F4B /* core_device.cpp */,
196C56BC2EA7F53000188F4B /* debug_proxy.cpp */,
196C56BD2EA7F53000188F4B /* diagnosticsservice.cpp */,
196C56BE2EA7F53000188F4B /* ffi.cpp */,
196C56BF2EA7F53000188F4B /* heartbeat.cpp */,
196C56C02EA7F53000188F4B /* idevice.cpp */,
196C56C12EA7F53000188F4B /* installation_proxy.cpp */,
196C56C22EA7F53000188F4B /* lockdown.cpp */,
196C56C32EA7F53000188F4B /* mobile_image_mounter.cpp */,
196C56C42EA7F53000188F4B /* pairing_file.cpp */,
196C56C52EA7F53000188F4B /* provider.cpp */,
196C56C62EA7F53000188F4B /* rsd.cpp */,
196C56C72EA7F53000188F4B /* tcp_callback_feeder.cpp */,
196C56C82EA7F53000188F4B /* usbmuxd.cpp */,
);
path = src;
sourceTree = "<group>";
};
196C56E22EA7F54900188F4B /* dvt */ = {
isa = PBXGroup;
children = (
196C56DE2EA7F54900188F4B /* location_simulation.hpp */,
196C56DF2EA7F54900188F4B /* process_control.hpp */,
196C56E02EA7F54900188F4B /* remote_server.hpp */,
196C56E12EA7F54900188F4B /* screenshot.hpp */,
);
path = dvt;
sourceTree = "<group>";
};
196C56F72EA7F54900188F4B /* idevice++ */ = {
isa = PBXGroup;
children = (
196C56E22EA7F54900188F4B /* dvt */,
196C56E32EA7F54900188F4B /* adapter_stream.hpp */,
196C56E42EA7F54900188F4B /* app_service.hpp */,
196C56E52EA7F54900188F4B /* bindings.hpp */,
196C56E62EA7F54900188F4B /* core_device_proxy.hpp */,
196C56E72EA7F54900188F4B /* debug_proxy.hpp */,
196C56E82EA7F54900188F4B /* diagnosticsservice.hpp */,
196C56E92EA7F54900188F4B /* ffi.hpp */,
196C56EA2EA7F54900188F4B /* heartbeat.hpp */,
196C56EB2EA7F54900188F4B /* idevice.hpp */,
196C56EC2EA7F54900188F4B /* installation_proxy.hpp */,
196C56ED2EA7F54900188F4B /* lockdown.hpp */,
196C56EE2EA7F54900188F4B /* mobile_image_mounter.hpp */,
196C56EF2EA7F54900188F4B /* option.hpp */,
196C56F02EA7F54900188F4B /* pairing_file.hpp */,
196C56F12EA7F54900188F4B /* provider.hpp */,
196C56F22EA7F54900188F4B /* readwrite.hpp */,
196C56F32EA7F54900188F4B /* result.hpp */,
196C56F42EA7F54900188F4B /* rsd.hpp */,
196C56F52EA7F54900188F4B /* tcp_object_stack.hpp */,
196C56F62EA7F54900188F4B /* usbmuxd.hpp */,
);
path = "idevice++";
sourceTree = "<group>";
};
196C56F92EA7F54900188F4B /* include */ = {
isa = PBXGroup;
children = (
196C56F72EA7F54900188F4B /* idevice++ */,
196C56F82EA7F54900188F4B /* idevice.h */,
);
path = include;
sourceTree = "<group>";
};
198077562E5CA62F00CB501E = {
isa = PBXGroup;
children = (
196C56C92EA7F53000188F4B /* src */,
196C56F92EA7F54900188F4B /* include */,
1980777B2E5CA69800CB501E /* src */,
198077612E5CA62F00CB501E /* Products */,
198077B62E5CA6FC00CB501E /* include */,
198077D92E5CC31100CB501E /* Frameworks */,
198077E02E5CD49200CB501E /* xcode_build_rust.sh */,
);
@@ -220,6 +117,63 @@
name = Products;
sourceTree = "<group>";
};
1980777B2E5CA69800CB501E /* src */ = {
isa = PBXGroup;
children = (
1980776C2E5CA69800CB501E /* adapter_stream.cpp */,
1980776D2E5CA69800CB501E /* app_service.cpp */,
1980776E2E5CA69800CB501E /* core_device.cpp */,
1980776F2E5CA69800CB501E /* debug_proxy.cpp */,
198077702E5CA69800CB501E /* diagnosticsservice.cpp */,
198077712E5CA69800CB501E /* ffi.cpp */,
198077722E5CA69800CB501E /* idevice.cpp */,
198077732E5CA69800CB501E /* location_simulation.cpp */,
198077742E5CA69800CB501E /* lockdown.cpp */,
198077752E5CA69800CB501E /* pairing_file.cpp */,
198077762E5CA69800CB501E /* provider.cpp */,
198077772E5CA69800CB501E /* remote_server.cpp */,
198077782E5CA69800CB501E /* rsd.cpp */,
198077792E5CA69800CB501E /* tcp_callback_feeder.cpp */,
1980777A2E5CA69800CB501E /* usbmuxd.cpp */,
);
path = src;
sourceTree = "<group>";
};
198077B42E5CA6FC00CB501E /* idevice++ */ = {
isa = PBXGroup;
children = (
198077A32E5CA6FC00CB501E /* adapter_stream.hpp */,
198077A42E5CA6FC00CB501E /* app_service.hpp */,
198077A52E5CA6FC00CB501E /* bindings.hpp */,
198077A62E5CA6FC00CB501E /* core_device_proxy.hpp */,
198077A72E5CA6FC00CB501E /* debug_proxy.hpp */,
198077A82E5CA6FC00CB501E /* diagnosticsservice.hpp */,
198077A92E5CA6FC00CB501E /* ffi.hpp */,
198077AA2E5CA6FC00CB501E /* idevice.hpp */,
198077AB2E5CA6FC00CB501E /* location_simulation.hpp */,
198077AC2E5CA6FC00CB501E /* lockdown.hpp */,
198077AD2E5CA6FC00CB501E /* pairing_file.hpp */,
198077AE2E5CA6FC00CB501E /* provider.hpp */,
198077AF2E5CA6FC00CB501E /* readwrite.hpp */,
198077B02E5CA6FC00CB501E /* remote_server.hpp */,
198077B12E5CA6FC00CB501E /* rsd.hpp */,
198077B22E5CA6FC00CB501E /* tcp_object_stack.hpp */,
198077B32E5CA6FC00CB501E /* usbmuxd.hpp */,
1914C7962E623CC2002EAB6E /* option.hpp */,
1914C7982E623CC8002EAB6E /* result.hpp */,
);
path = "idevice++";
sourceTree = "<group>";
};
198077B62E5CA6FC00CB501E /* include */ = {
isa = PBXGroup;
children = (
198077B42E5CA6FC00CB501E /* idevice++ */,
198077B52E5CA6FC00CB501E /* idevice.h */,
);
path = include;
sourceTree = "<group>";
};
198077D92E5CC31100CB501E /* Frameworks */ = {
isa = PBXGroup;
children = (
@@ -235,31 +189,26 @@
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
196C57592EA7F5F400188F4B /* location_simulation.hpp in Headers */,
196C575A2EA7F5F400188F4B /* process_control.hpp in Headers */,
196C575B2EA7F5F400188F4B /* remote_server.hpp in Headers */,
196C575C2EA7F5F400188F4B /* screenshot.hpp in Headers */,
196C575D2EA7F5F400188F4B /* adapter_stream.hpp in Headers */,
196C575E2EA7F5F400188F4B /* app_service.hpp in Headers */,
196C575F2EA7F5F400188F4B /* bindings.hpp in Headers */,
196C57602EA7F5F400188F4B /* core_device_proxy.hpp in Headers */,
196C57612EA7F5F400188F4B /* debug_proxy.hpp in Headers */,
196C57622EA7F5F400188F4B /* diagnosticsservice.hpp in Headers */,
196C57632EA7F5F400188F4B /* ffi.hpp in Headers */,
196C57642EA7F5F400188F4B /* heartbeat.hpp in Headers */,
196C57652EA7F5F400188F4B /* idevice.hpp in Headers */,
196C57662EA7F5F400188F4B /* installation_proxy.hpp in Headers */,
196C57672EA7F5F400188F4B /* lockdown.hpp in Headers */,
196C57682EA7F5F400188F4B /* mobile_image_mounter.hpp in Headers */,
196C57692EA7F5F400188F4B /* option.hpp in Headers */,
196C576A2EA7F5F400188F4B /* pairing_file.hpp in Headers */,
196C576B2EA7F5F400188F4B /* provider.hpp in Headers */,
196C576C2EA7F5F400188F4B /* readwrite.hpp in Headers */,
196C576D2EA7F5F400188F4B /* result.hpp in Headers */,
196C576E2EA7F5F400188F4B /* rsd.hpp in Headers */,
196C576F2EA7F5F400188F4B /* tcp_object_stack.hpp in Headers */,
196C57702EA7F5F400188F4B /* usbmuxd.hpp in Headers */,
196C57712EA7F5F400188F4B /* idevice.h in Headers */,
198077B72E5CA6FC00CB501E /* core_device_proxy.hpp in Headers */,
198077B82E5CA6FC00CB501E /* pairing_file.hpp in Headers */,
198077B92E5CA6FC00CB501E /* bindings.hpp in Headers */,
198077BA2E5CA6FC00CB501E /* diagnosticsservice.hpp in Headers */,
198077BB2E5CA6FC00CB501E /* idevice.h in Headers */,
198077BC2E5CA6FC00CB501E /* debug_proxy.hpp in Headers */,
198077BD2E5CA6FC00CB501E /* ffi.hpp in Headers */,
198077BE2E5CA6FC00CB501E /* rsd.hpp in Headers */,
198077BF2E5CA6FC00CB501E /* tcp_object_stack.hpp in Headers */,
198077C02E5CA6FC00CB501E /* remote_server.hpp in Headers */,
198077C12E5CA6FC00CB501E /* readwrite.hpp in Headers */,
198077C22E5CA6FC00CB501E /* location_simulation.hpp in Headers */,
198077C32E5CA6FC00CB501E /* adapter_stream.hpp in Headers */,
1914C7972E623CC2002EAB6E /* option.hpp in Headers */,
198077C42E5CA6FC00CB501E /* lockdown.hpp in Headers */,
198077C52E5CA6FC00CB501E /* usbmuxd.hpp in Headers */,
198077C62E5CA6FC00CB501E /* app_service.hpp in Headers */,
198077C72E5CA6FC00CB501E /* idevice.hpp in Headers */,
198077C82E5CA6FC00CB501E /* provider.hpp in Headers */,
1914C7992E623CC8002EAB6E /* result.hpp in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -346,26 +295,21 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
196C57452EA7F5DC00188F4B /* location_simulation.cpp in Sources */,
196C57462EA7F5DC00188F4B /* process_control.cpp in Sources */,
196C57472EA7F5DC00188F4B /* remote_server.cpp in Sources */,
196C57482EA7F5DC00188F4B /* screenshot.cpp in Sources */,
196C57492EA7F5DC00188F4B /* adapter_stream.cpp in Sources */,
196C574A2EA7F5DC00188F4B /* app_service.cpp in Sources */,
196C574B2EA7F5DC00188F4B /* core_device.cpp in Sources */,
196C574C2EA7F5DC00188F4B /* debug_proxy.cpp in Sources */,
196C574D2EA7F5DC00188F4B /* diagnosticsservice.cpp in Sources */,
196C574E2EA7F5DC00188F4B /* ffi.cpp in Sources */,
196C574F2EA7F5DC00188F4B /* heartbeat.cpp in Sources */,
196C57502EA7F5DC00188F4B /* idevice.cpp in Sources */,
196C57512EA7F5DC00188F4B /* installation_proxy.cpp in Sources */,
196C57522EA7F5DC00188F4B /* lockdown.cpp in Sources */,
196C57532EA7F5DC00188F4B /* mobile_image_mounter.cpp in Sources */,
196C57542EA7F5DC00188F4B /* pairing_file.cpp in Sources */,
196C57552EA7F5DC00188F4B /* provider.cpp in Sources */,
196C57562EA7F5DC00188F4B /* rsd.cpp in Sources */,
196C57572EA7F5DC00188F4B /* tcp_callback_feeder.cpp in Sources */,
196C57582EA7F5DC00188F4B /* usbmuxd.cpp in Sources */,
198077932E5CA6EF00CB501E /* adapter_stream.cpp in Sources */,
198077942E5CA6EF00CB501E /* app_service.cpp in Sources */,
198077952E5CA6EF00CB501E /* core_device.cpp in Sources */,
198077962E5CA6EF00CB501E /* debug_proxy.cpp in Sources */,
198077972E5CA6EF00CB501E /* diagnosticsservice.cpp in Sources */,
198077982E5CA6EF00CB501E /* ffi.cpp in Sources */,
198077992E5CA6EF00CB501E /* idevice.cpp in Sources */,
1980779A2E5CA6EF00CB501E /* location_simulation.cpp in Sources */,
1980779B2E5CA6EF00CB501E /* lockdown.cpp in Sources */,
1980779C2E5CA6EF00CB501E /* pairing_file.cpp in Sources */,
1980779D2E5CA6EF00CB501E /* provider.cpp in Sources */,
1980779E2E5CA6EF00CB501E /* remote_server.cpp in Sources */,
1980779F2E5CA6EF00CB501E /* rsd.cpp in Sources */,
198077A02E5CA6EF00CB501E /* tcp_callback_feeder.cpp in Sources */,
198077A12E5CA6EF00CB501E /* usbmuxd.cpp in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -507,12 +451,11 @@
GCC_WARN_UNKNOWN_PRAGMAS = YES;
GCC_WARN_UNUSED_LABEL = YES;
GCC_WARN_UNUSED_PARAMETER = YES;
HEADER_SEARCH_PATHS = "include/**";
LIBRARY_SEARCH_PATHS = (
"${TARGET_BUILD_DIR}/**",
"$(PROJECT_DIR)/${DESTINATION_PATH}",
);
MACOSX_DEPLOYMENT_TARGET = 10.12;
MACOSX_DEPLOYMENT_TARGET = 15.5;
OTHER_LDFLAGS = "-Wall";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = macosx;
@@ -536,12 +479,11 @@
GCC_WARN_UNKNOWN_PRAGMAS = YES;
GCC_WARN_UNUSED_LABEL = YES;
GCC_WARN_UNUSED_PARAMETER = YES;
HEADER_SEARCH_PATHS = "include/**";
LIBRARY_SEARCH_PATHS = (
"${TARGET_BUILD_DIR}/**",
"$(PROJECT_DIR)/${DESTINATION_PATH}",
);
MACOSX_DEPLOYMENT_TARGET = 10.12;
MACOSX_DEPLOYMENT_TARGET = 15.5;
OTHER_LDFLAGS = "-Wall";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = macosx;

View File

@@ -53,14 +53,6 @@ class Adapter {
return Ok(ReadWrite::adopt(s));
}
Result<void, FfiError> close() {
FfiError e(::adapter_close(handle_.get()));
if (e) {
return Err(e);
}
return Ok();
}
private:
explicit Adapter(AdapterHandle* h) noexcept : handle_(h) {}
AdapterPtr handle_{};

View File

@@ -1,60 +0,0 @@
// Jackson Coxson
#pragma once
#include <idevice++/bindings.hpp>
#include <idevice++/ffi.hpp>
#include <idevice++/provider.hpp>
#include <idevice++/result.hpp>
#include <string>
#include <vector>
namespace IdeviceFFI {
using DiagnosticsRelayPtr =
std::unique_ptr<DiagnosticsRelayClientHandle,
FnDeleter<DiagnosticsRelayClientHandle, diagnostics_relay_client_free>>;
class DiagnosticsRelay {
public:
// Factory: connect via Provider
static Result<DiagnosticsRelay, FfiError> connect(Provider& provider);
// Factory: wrap an existing Idevice socket (consumes it on success)
static Result<DiagnosticsRelay, FfiError> from_socket(Idevice&& socket);
// API Methods - queries returning optional plist
Result<Option<plist_t>, FfiError> ioregistry(Option<std::string> current_plane,
Option<std::string> entry_name,
Option<std::string> entry_class) const;
Result<Option<plist_t>, FfiError> mobilegestalt(Option<std::vector<char*>> keys) const;
Result<Option<plist_t>, FfiError> gasguage() const;
Result<Option<plist_t>, FfiError> nand() const;
Result<Option<plist_t>, FfiError> all() const;
Result<Option<plist_t>, FfiError> wifi() const;
// API Methods - actions
Result<void, FfiError> restart();
Result<void, FfiError> shutdown();
Result<void, FfiError> sleep();
Result<void, FfiError> goodbye();
// RAII / moves
~DiagnosticsRelay() noexcept = default;
DiagnosticsRelay(DiagnosticsRelay&&) noexcept = default;
DiagnosticsRelay& operator=(DiagnosticsRelay&&) noexcept = default;
DiagnosticsRelay(const DiagnosticsRelay&) = delete;
DiagnosticsRelay& operator=(const DiagnosticsRelay&) = delete;
DiagnosticsRelayClientHandle* raw() const noexcept { return handle_.get(); }
static DiagnosticsRelay adopt(DiagnosticsRelayClientHandle* h) noexcept {
return DiagnosticsRelay(h);
}
private:
explicit DiagnosticsRelay(DiagnosticsRelayClientHandle* h) noexcept : handle_(h) {}
DiagnosticsRelayPtr handle_{};
};
} // namespace IdeviceFFI

View File

@@ -1,49 +0,0 @@
// Jackson Coxson
#pragma once
#include <cstring>
#include <idevice++/bindings.hpp>
#include <idevice++/dvt/remote_server.hpp>
#include <idevice++/result.hpp>
#include <memory>
#include <vector>
namespace IdeviceFFI {
using ScreenshotClientPtr =
std::unique_ptr<ScreenshotClientHandle,
FnDeleter<ScreenshotClientHandle, screenshot_client_free>>;
/// C++ wrapper around the ScreenshotClient FFI handle
///
/// Provides a high-level interface for capturing screenshots
/// from a connected iOS device through the DVT service.
class ScreenshotClient {
public:
/// Creates a new ScreenshotClient using an existing RemoteServer.
///
/// The RemoteServer is borrowed, not consumed.
static Result<ScreenshotClient, FfiError> create(RemoteServer& server);
/// Captures a screenshot and returns it as a PNG buffer.
///
/// On success, returns a vector containing PNG-encoded bytes.
Result<std::vector<uint8_t>, FfiError> take_screenshot();
~ScreenshotClient() noexcept = default;
ScreenshotClient(ScreenshotClient&&) noexcept = default;
ScreenshotClient& operator=(ScreenshotClient&&) noexcept = default;
ScreenshotClient(const ScreenshotClient&) = delete;
ScreenshotClient& operator=(const ScreenshotClient&) = delete;
ScreenshotClientHandle* raw() const noexcept { return handle_.get(); }
static ScreenshotClient adopt(ScreenshotClientHandle* h) noexcept {
return ScreenshotClient(h);
}
private:
explicit ScreenshotClient(ScreenshotClientHandle* h) noexcept : handle_(h) {}
ScreenshotClientPtr handle_{};
};
} // namespace IdeviceFFI

View File

@@ -3,6 +3,7 @@
#include <idevice++/ffi.hpp>
#include <idevice++/provider.hpp>
#include <memory>
#include <sys/_types/_u_int64_t.h>
namespace IdeviceFFI {

View File

@@ -32,9 +32,6 @@ using IdevicePtr = std::unique_ptr<IdeviceHandle, FnDeleter<IdeviceHandle, idevi
class Idevice {
public:
static Result<Idevice, FfiError> create(IdeviceSocketHandle* socket, const std::string& label);
#if defined(__unix__) || defined(__APPLE__)
static Result<Idevice, FfiError> from_fd(int fd, const std::string& label);
#endif
static Result<Idevice, FfiError>
create_tcp(const sockaddr* addr, socklen_t addr_len, const std::string& label);
@@ -42,7 +39,7 @@ class Idevice {
// Methods
Result<std::string, FfiError> get_type() const;
Result<void, FfiError> rsd_checkin();
Result<void, FfiError> start_session(const PairingFile& pairing_file, bool legacy);
Result<void, FfiError> start_session(const PairingFile& pairing_file);
// Ownership/RAII
~Idevice() noexcept = default;

View File

@@ -4,6 +4,7 @@
#include <idevice++/ffi.hpp>
#include <idevice++/provider.hpp>
#include <memory>
#include <sys/_types/_u_int64_t.h>
namespace IdeviceFFI {

View File

@@ -2,7 +2,7 @@
#pragma once
#include <idevice++/bindings.hpp>
#include <idevice++/dvt/remote_server.hpp>
#include <idevice++/remote_server.hpp>
#include <idevice++/result.hpp>
#include <memory>

View File

@@ -1,46 +0,0 @@
#pragma once
#include <idevice++/bindings.hpp>
#include <idevice++/ffi.hpp>
#include <idevice++/provider.hpp>
#include <memory>
#include <string>
#include <vector>
namespace IdeviceFFI {
using NotificationProxyPtr = std::unique_ptr<NotificationProxyClientHandle,
FnDeleter<NotificationProxyClientHandle, notification_proxy_client_free>>;
class NotificationProxy {
public:
// Factory: connect via Provider
static Result<NotificationProxy, FfiError> connect(Provider& provider);
// Factory: wrap an existing Idevice socket (consumes it on success)
static Result<NotificationProxy, FfiError> from_socket(Idevice&& socket);
// Ops
Result<void, FfiError> post_notification(const std::string& name);
Result<void, FfiError> observe_notification(const std::string& name);
Result<void, FfiError> observe_notifications(const std::vector<std::string>& names);
Result<std::string, FfiError> receive_notification();
Result<std::string, FfiError> receive_notification_with_timeout(u_int64_t interval);
// RAII / moves
~NotificationProxy() noexcept = default;
NotificationProxy(NotificationProxy&&) noexcept = default;
NotificationProxy& operator=(NotificationProxy&&) noexcept = default;
NotificationProxy(const NotificationProxy&) = delete;
NotificationProxy& operator=(const NotificationProxy&) = delete;
NotificationProxyClientHandle* raw() const noexcept { return handle_.get(); }
static NotificationProxy adopt(NotificationProxyClientHandle* h) noexcept {
return NotificationProxy(h);
}
private:
explicit NotificationProxy(NotificationProxyClientHandle* h) noexcept : handle_(h) {}
NotificationProxyPtr handle_{};
};
} // namespace IdeviceFFI

View File

@@ -2,7 +2,7 @@
#pragma once
#include <idevice++/bindings.hpp>
#include <idevice++/dvt/remote_server.hpp>
#include <idevice++/remote_server.hpp>
#include <idevice++/result.hpp>
#include <memory>

View File

@@ -27,53 +27,43 @@
</ProjectConfiguration>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\..\ffi\idevice.h" />
<ClCompile Include="..\src\adapter_stream.cpp" />
<ClCompile Include="..\src\app_service.cpp" />
<ClCompile Include="..\src\core_device.cpp" />
<ClCompile Include="..\src\debug_proxy.cpp" />
<ClCompile Include="..\src\diagnosticsservice.cpp" />
<ClCompile Include="..\src\ffi.cpp" />
<ClCompile Include="..\src\idevice.cpp" />
<ClCompile Include="..\src\location_simulation.cpp" />
<ClCompile Include="..\src\lockdown.cpp" />
<ClCompile Include="..\src\pairing_file.cpp" />
<ClCompile Include="..\src\provider.cpp" />
<ClCompile Include="..\src\remote_server.cpp" />
<ClCompile Include="..\src\rsd.cpp" />
<ClCompile Include="..\src\tcp_callback_feeder.cpp" />
<ClCompile Include="..\src\usbmuxd.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\include\idevice++\adapter_stream.hpp" />
<ClInclude Include="..\include\idevice++\app_service.hpp" />
<ClInclude Include="..\include\idevice++\bindings.hpp" />
<ClInclude Include="..\include\idevice++\core_device_proxy.hpp" />
<ClInclude Include="..\include\idevice++\debug_proxy.hpp" />
<ClInclude Include="..\include\idevice++\diagnosticsservice.hpp" />
<ClInclude Include="..\include\idevice++\dvt\location_simulation.hpp" />
<ClInclude Include="..\include\idevice++\dvt\process_control.hpp" />
<ClInclude Include="..\include\idevice++\dvt\remote_server.hpp" />
<ClInclude Include="..\include\idevice++\dvt\screenshot.hpp" />
<ClInclude Include="..\include\idevice++\ffi.hpp" />
<ClInclude Include="..\include\idevice++\heartbeat.hpp" />
<ClInclude Include="..\include\idevice++\idevice.hpp" />
<ClInclude Include="..\include\idevice++\installation_proxy.hpp" />
<ClInclude Include="..\include\idevice++\location_simulation.hpp" />
<ClInclude Include="..\include\idevice++\lockdown.hpp" />
<ClInclude Include="..\include\idevice++\mobile_image_mounter.hpp" />
<ClInclude Include="..\include\idevice++\option.hpp" />
<ClInclude Include="..\include\idevice++\pairing_file.hpp" />
<ClInclude Include="..\include\idevice++\provider.hpp" />
<ClInclude Include="..\include\idevice++\readwrite.hpp" />
<ClInclude Include="..\include\idevice++\remote_server.hpp" />
<ClInclude Include="..\include\idevice++\result.hpp" />
<ClInclude Include="..\include\idevice++\rsd.hpp" />
<ClInclude Include="..\include\idevice++\tcp_object_stack.hpp" />
<ClInclude Include="..\include\idevice++\usbmuxd.hpp" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\src\adapter_stream.cpp" />
<ClCompile Include="..\src\app_service.cpp" />
<ClCompile Include="..\src\core_device.cpp" />
<ClCompile Include="..\src\debug_proxy.cpp" />
<ClCompile Include="..\src\diagnosticsservice.cpp" />
<ClCompile Include="..\src\dvt\location_simulation.cpp" />
<ClCompile Include="..\src\dvt\process_control.cpp" />
<ClCompile Include="..\src\dvt\remote_server.cpp" />
<ClCompile Include="..\src\dvt\screenshot.cpp" />
<ClCompile Include="..\src\ffi.cpp" />
<ClCompile Include="..\src\heartbeat.cpp" />
<ClCompile Include="..\src\idevice.cpp" />
<ClCompile Include="..\src\installation_proxy.cpp" />
<ClCompile Include="..\src\lockdown.cpp" />
<ClCompile Include="..\src\mobile_image_mounter.cpp" />
<ClCompile Include="..\src\pairing_file.cpp" />
<ClCompile Include="..\src\provider.cpp" />
<ClCompile Include="..\src\rsd.cpp" />
<ClCompile Include="..\src\tcp_callback_feeder.cpp" />
<ClCompile Include="..\src\usbmuxd.cpp" />
<ClInclude Include="..\..\ffi\idevice.h" />
</ItemGroup>
<PropertyGroup Label="Globals">
<VCProjectVersion>17.0</VCProjectVersion>

View File

@@ -18,21 +18,53 @@
</Filter>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\..\ffi\idevice.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\include\idevice++\dvt\location_simulation.hpp">
<Filter>Header Files\idevice++</Filter>
</ClInclude>
<ClInclude Include="..\include\idevice++\dvt\process_control.hpp">
<Filter>Header Files\idevice++</Filter>
</ClInclude>
<ClInclude Include="..\include\idevice++\dvt\remote_server.hpp">
<Filter>Header Files\idevice++</Filter>
</ClInclude>
<ClInclude Include="..\include\idevice++\dvt\screenshot.hpp">
<Filter>Header Files\idevice++</Filter>
</ClInclude>
<ClCompile Include="..\src\adapter_stream.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\src\app_service.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\src\core_device.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\src\debug_proxy.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\src\diagnosticsservice.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\src\ffi.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\src\idevice.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\src\location_simulation.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\src\lockdown.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\src\pairing_file.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\src\provider.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\src\remote_server.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\src\rsd.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\src\tcp_callback_feeder.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\src\usbmuxd.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\include\idevice++\adapter_stream.hpp">
<Filter>Header Files\idevice++</Filter>
</ClInclude>
@@ -54,24 +86,15 @@
<ClInclude Include="..\include\idevice++\ffi.hpp">
<Filter>Header Files\idevice++</Filter>
</ClInclude>
<ClInclude Include="..\include\idevice++\heartbeat.hpp">
<Filter>Header Files\idevice++</Filter>
</ClInclude>
<ClInclude Include="..\include\idevice++\idevice.hpp">
<Filter>Header Files\idevice++</Filter>
</ClInclude>
<ClInclude Include="..\include\idevice++\installation_proxy.hpp">
<ClInclude Include="..\include\idevice++\location_simulation.hpp">
<Filter>Header Files\idevice++</Filter>
</ClInclude>
<ClInclude Include="..\include\idevice++\lockdown.hpp">
<Filter>Header Files\idevice++</Filter>
</ClInclude>
<ClInclude Include="..\include\idevice++\mobile_image_mounter.hpp">
<Filter>Header Files\idevice++</Filter>
</ClInclude>
<ClInclude Include="..\include\idevice++\option.hpp">
<Filter>Header Files\idevice++</Filter>
</ClInclude>
<ClInclude Include="..\include\idevice++\pairing_file.hpp">
<Filter>Header Files\idevice++</Filter>
</ClInclude>
@@ -81,7 +104,7 @@
<ClInclude Include="..\include\idevice++\readwrite.hpp">
<Filter>Header Files\idevice++</Filter>
</ClInclude>
<ClInclude Include="..\include\idevice++\result.hpp">
<ClInclude Include="..\include\idevice++\remote_server.hpp">
<Filter>Header Files\idevice++</Filter>
</ClInclude>
<ClInclude Include="..\include\idevice++\rsd.hpp">
@@ -93,67 +116,14 @@
<ClInclude Include="..\include\idevice++\usbmuxd.hpp">
<Filter>Header Files\idevice++</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\src\adapter_stream.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\src\app_service.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\src\core_device.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\src\debug_proxy.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\src\diagnosticsservice.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\src\ffi.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\src\heartbeat.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\src\idevice.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\src\installation_proxy.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\src\lockdown.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\src\mobile_image_mounter.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\src\pairing_file.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\src\provider.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\src\rsd.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\src\tcp_callback_feeder.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\src\usbmuxd.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\src\dvt\location_simulation.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\src\dvt\process_control.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\src\dvt\remote_server.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\src\dvt\screenshot.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClInclude Include="..\..\ffi\idevice.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\include\idevice++\option.hpp">
<Filter>Header Files\idevice++</Filter>
</ClInclude>
<ClInclude Include="..\include\idevice++\result.hpp">
<Filter>Header Files\idevice++</Filter>
</ClInclude>
</ItemGroup>
</Project>

View File

@@ -6,11 +6,10 @@
namespace IdeviceFFI {
Result<void, FfiError> AdapterStream::close() {
if (!h_) {
if (!h_)
return Ok();
}
FfiError e(::adapter_stream_close(h_));
FfiError e(::adapter_close(h_));
if (e) {
return Err(e);
}
@@ -19,10 +18,9 @@ Result<void, FfiError> AdapterStream::close() {
return Ok();
}
Result<void, FfiError> AdapterStream::send(const uint8_t* data, size_t len) {
if (!h_) {
Result<void, FfiError> AdapterStream::send(const uint8_t *data, size_t len) {
if (!h_)
return Err(FfiError::NotConnected());
}
FfiError e(::adapter_send(h_, data, len));
if (e) {
return Err(e);
@@ -31,13 +29,11 @@ Result<void, FfiError> AdapterStream::send(const uint8_t* data, size_t len) {
}
Result<std::vector<uint8_t>, FfiError> AdapterStream::recv(size_t max_hint) {
if (!h_) {
if (!h_)
return Err(FfiError::NotConnected());
}
if (max_hint == 0) {
if (max_hint == 0)
max_hint = 2048;
}
std::vector<uint8_t> out(max_hint);
size_t actual = 0;

View File

@@ -1,159 +0,0 @@
// Jackson Coxson
#include <idevice++/bindings.hpp>
#include <idevice++/diagnostics_relay.hpp>
#include <idevice++/ffi.hpp>
#include <idevice++/provider.hpp>
namespace IdeviceFFI {
// -------- Factory Methods --------
Result<DiagnosticsRelay, FfiError> DiagnosticsRelay::connect(Provider& provider) {
DiagnosticsRelayClientHandle* out = nullptr;
FfiError e(::diagnostics_relay_client_connect(provider.raw(), &out));
if (e) {
return Err(e);
}
return Ok(DiagnosticsRelay::adopt(out));
}
Result<DiagnosticsRelay, FfiError> DiagnosticsRelay::from_socket(Idevice&& socket) {
DiagnosticsRelayClientHandle* out = nullptr;
FfiError e(::diagnostics_relay_client_new(socket.raw(), &out));
if (e) {
return Err(e);
}
socket.release();
return Ok(DiagnosticsRelay::adopt(out));
}
// -------- API Methods --------
Result<Option<plist_t>, FfiError>
DiagnosticsRelay::ioregistry(Option<std::string> current_plane,
Option<std::string> entry_name,
Option<std::string> entry_class) const {
plist_t res = nullptr;
const char* plane_ptr = current_plane.is_some() ? current_plane.unwrap().c_str() : nullptr;
const char* name_ptr = entry_name.is_some() ? entry_name.unwrap().c_str() : nullptr;
const char* class_ptr = entry_class.is_some() ? entry_class.unwrap().c_str() : nullptr;
FfiError e(
::diagnostics_relay_client_ioregistry(handle_.get(), plane_ptr, name_ptr, class_ptr, &res));
if (e) {
return Err(e);
}
if (res == nullptr) {
return Ok(Option<plist_t>(None));
}
return Ok(Some(res));
}
Result<Option<plist_t>, FfiError>
DiagnosticsRelay::mobilegestalt(Option<std::vector<char*>> keys) const {
plist_t res = nullptr;
if (!keys.is_some() || keys.unwrap().empty()) {
return Err(FfiError::InvalidArgument());
}
FfiError e(::diagnostics_relay_client_mobilegestalt(
handle_.get(), keys.unwrap().data(), keys.unwrap().size(), &res));
if (e) {
return Err(e);
}
if (res == nullptr) {
return Ok(Option<plist_t>(None));
}
return Ok(Some(res));
}
Result<Option<plist_t>, FfiError> DiagnosticsRelay::gasguage() const {
plist_t res = nullptr;
FfiError e(::diagnostics_relay_client_gasguage(handle_.get(), &res));
if (e) {
return Err(e);
}
if (res == nullptr) {
return Ok(Option<plist_t>(None));
}
return Ok(Some(res));
}
Result<Option<plist_t>, FfiError> DiagnosticsRelay::nand() const {
plist_t res = nullptr;
FfiError e(::diagnostics_relay_client_nand(handle_.get(), &res));
if (e) {
return Err(e);
}
if (res == nullptr) {
return Ok(Option<plist_t>(None));
}
return Ok(Some(res));
}
Result<Option<plist_t>, FfiError> DiagnosticsRelay::all() const {
plist_t res = nullptr;
FfiError e(::diagnostics_relay_client_all(handle_.get(), &res));
if (e) {
return Err(e);
}
if (res == nullptr) {
return Ok(Option<plist_t>(None));
}
return Ok(Some(res));
}
Result<Option<plist_t>, FfiError> DiagnosticsRelay::wifi() const {
plist_t res = nullptr;
FfiError e(::diagnostics_relay_client_wifi(handle_.get(), &res));
if (e) {
return Err(e);
}
if (res == nullptr) {
return Ok(Option<plist_t>(None));
}
return Ok(Some(res));
}
Result<void, FfiError> DiagnosticsRelay::restart() {
FfiError e(::diagnostics_relay_client_restart(handle_.get()));
if (e) {
return Err(e);
}
return Ok();
}
Result<void, FfiError> DiagnosticsRelay::shutdown() {
FfiError e(::diagnostics_relay_client_shutdown(handle_.get()));
if (e) {
return Err(e);
}
return Ok();
}
Result<void, FfiError> DiagnosticsRelay::sleep() {
FfiError e(::diagnostics_relay_client_sleep(handle_.get()));
if (e) {
return Err(e);
}
return Ok();
}
Result<void, FfiError> DiagnosticsRelay::goodbye() {
FfiError e(::diagnostics_relay_client_goodbye(handle_.get()));
if (e) {
return Err(e);
}
return Ok();
}
} // namespace IdeviceFFI

View File

@@ -1,37 +0,0 @@
// Jackson Coxson
#include <idevice++/dvt/screenshot.hpp>
namespace IdeviceFFI {
Result<ScreenshotClient, FfiError> ScreenshotClient::create(RemoteServer& server) {
ScreenshotClientHandle* out = nullptr;
FfiError e(::screenshot_client_new(server.raw(), &out));
if (e) {
return Err(e);
}
return Ok(ScreenshotClient::adopt(out));
}
Result<std::vector<uint8_t>, FfiError> ScreenshotClient::take_screenshot() {
uint8_t* data = nullptr;
size_t len = 0;
FfiError e(::screenshot_client_take_screenshot(handle_.get(), &data, &len));
if (e) {
return Err(e);
}
// Copy into a C++ buffer
std::vector<uint8_t> out(len);
if (len > 0 && data != nullptr) {
std::memcpy(out.data(), data, len);
}
// Free Rust-allocated data
::idevice_data_free(data, len);
return Ok(std::move(out));
}
} // namespace IdeviceFFI

View File

@@ -13,17 +13,6 @@ Result<Idevice, FfiError> Idevice::create(IdeviceSocketHandle* socket, const std
return Ok(Idevice(h));
}
#if defined(__unix__) || defined(__APPLE__)
Result<Idevice, FfiError> Idevice::from_fd(int fd, const std::string& label) {
IdeviceHandle* h = nullptr;
FfiError e(idevice_from_fd(fd, label.c_str(), &h));
if (e) {
return Err(e);
}
return Ok(Idevice(h));
}
#endif
Result<Idevice, FfiError>
Idevice::create_tcp(const sockaddr* addr, socklen_t addr_len, const std::string& label) {
IdeviceHandle* h = nullptr;
@@ -53,8 +42,8 @@ Result<void, FfiError> Idevice::rsd_checkin() {
return Ok();
}
Result<void, FfiError> Idevice::start_session(const PairingFile& pairing_file, bool legacy) {
FfiError e(idevice_start_session(handle_.get(), pairing_file.raw(), legacy));
Result<void, FfiError> Idevice::start_session(const PairingFile& pairing_file) {
FfiError e(idevice_start_session(handle_.get(), pairing_file.raw()));
if (e) {
return Err(e);
}

View File

@@ -3,6 +3,7 @@
#include <cstring>
#include <idevice++/bindings.hpp>
#include <idevice++/installation_proxy.hpp>
#include <sys/_types/_u_int64_t.h>
#include <vector>
namespace IdeviceFFI {

View File

@@ -1,6 +1,6 @@
// Jackson Coxson
#include <idevice++/dvt/location_simulation.hpp>
#include <idevice++/location_simulation.hpp>
namespace IdeviceFFI {

View File

@@ -1,82 +0,0 @@
// Jackson Coxson
#include <idevice++/bindings.hpp>
#include <idevice++/ffi.hpp>
#include <idevice++/notification_proxy.hpp>
#include <idevice++/provider.hpp>
namespace IdeviceFFI {
Result<NotificationProxy, FfiError> NotificationProxy::connect(Provider& provider) {
NotificationProxyClientHandle* out = nullptr;
FfiError e(::notification_proxy_connect(provider.raw(), &out));
if (e) {
provider.release();
return Err(e);
}
return Ok(NotificationProxy::adopt(out));
}
Result<NotificationProxy, FfiError> NotificationProxy::from_socket(Idevice&& socket) {
NotificationProxyClientHandle* out = nullptr;
FfiError e(::notification_proxy_new(socket.raw(), &out));
if (e) {
return Err(e);
}
socket.release();
return Ok(NotificationProxy::adopt(out));
}
Result<void, FfiError> NotificationProxy::post_notification(const std::string& name) {
FfiError e(::notification_proxy_post(handle_.get(), name.c_str()));
if (e) {
return Err(e);
}
return Ok();
}
Result<void, FfiError> NotificationProxy::observe_notification(const std::string& name) {
FfiError e(::notification_proxy_observe(handle_.get(), name.c_str()));
if (e) {
return Err(e);
}
return Ok();
}
Result<void, FfiError> NotificationProxy::observe_notifications(const std::vector<std::string>& names) {
std::vector<const char*> ptrs;
ptrs.reserve(names.size() + 1);
for (const auto& n : names) {
ptrs.push_back(n.c_str());
}
ptrs.push_back(nullptr);
FfiError e(::notification_proxy_observe_multiple(handle_.get(), ptrs.data()));
if (e) {
return Err(e);
}
return Ok();
}
Result<std::string, FfiError> NotificationProxy::receive_notification() {
char* name_ptr = nullptr;
FfiError e(::notification_proxy_receive(handle_.get(), &name_ptr));
if (e) {
return Err(e);
}
std::string name(name_ptr);
::notification_proxy_free_string(name_ptr);
return Ok(std::move(name));
}
Result<std::string, FfiError> NotificationProxy::receive_notification_with_timeout(u_int64_t interval) {
char* name_ptr = nullptr;
FfiError e(::notification_proxy_receive_with_timeout(handle_.get(), interval, &name_ptr));
if (e) {
return Err(e);
}
std::string name(name_ptr);
::notification_proxy_free_string(name_ptr);
return Ok(std::move(name));
}
} // namespace IdeviceFFI

View File

@@ -1,6 +1,6 @@
// Jackson Coxson
#include <idevice++/dvt/process_control.hpp>
#include <idevice++/process_control.hpp>
namespace IdeviceFFI {

View File

@@ -1,6 +1,6 @@
// Jackson Coxson
#include <idevice++/dvt/remote_server.hpp>
#include <idevice++/remote_server.hpp>
namespace IdeviceFFI {

View File

@@ -117,7 +117,6 @@ Result<std::vector<UsbmuxdDevice>, FfiError> UsbmuxdConnection::get_devices() co
for (int i = 0; i < count; ++i) {
out.emplace_back(UsbmuxdDevice::adopt(list[i]));
}
idevice_outer_slice_free(list, count);
return Ok(std::move(out));
}

View File

@@ -7,9 +7,8 @@ edition = "2024"
[dependencies]
idevice = { path = "../idevice", default-features = false }
futures = { version = "0.3", optional = true }
tracing = { version = "0.1.41" }
tracing-subscriber = { version = "0.3.20", features = ["env-filter"] }
tracing-appender = { version = "0.2" }
log = "0.4.26"
simplelog = "0.12.2"
once_cell = "1.21.1"
tokio = { version = "1.44.1", features = ["full"] }
libc = "0.2.171"
@@ -18,7 +17,7 @@ plist_ffi = { version = "0.1.6" }
uuid = { version = "1.12", features = ["v4"], optional = true }
[target.'cfg(windows)'.dependencies]
windows-sys = { version = "0.61", features = ["Win32_Networking_WinSock"] }
windows-sys = { version = "0.60", features = ["Win32_Networking_WinSock"] }
[features]
aws-lc = ["idevice/aws-lc"]
@@ -31,10 +30,8 @@ core_device = ["idevice/core_device", "dep:futures", "dep:uuid"]
core_device_proxy = ["idevice/core_device_proxy"]
crashreportcopymobile = ["idevice/crashreportcopymobile"]
debug_proxy = ["idevice/debug_proxy"]
diagnostics_relay = ["idevice/diagnostics_relay"]
dvt = ["idevice/dvt"]
heartbeat = ["idevice/heartbeat"]
notification_proxy = ["idevice/notification_proxy"]
house_arrest = ["idevice/house_arrest"]
installation_proxy = ["idevice/installation_proxy"]
springboardservices = ["idevice/springboardservices"]
@@ -51,7 +48,6 @@ tss = ["idevice/tss"]
tunneld = ["idevice/tunneld"]
usbmuxd = ["idevice/usbmuxd"]
xpc = ["idevice/xpc"]
screenshotr = ["idevice/screenshotr"]
full = [
"afc",
"amfi",
@@ -59,10 +55,8 @@ full = [
"core_device_proxy",
"crashreportcopymobile",
"debug_proxy",
"diagnostics_relay",
"dvt",
"heartbeat",
"notification_proxy",
"house_arrest",
"installation_proxy",
"misagent",
@@ -78,7 +72,6 @@ full = [
"tunneld",
"springboardservices",
"syslog_relay",
"screenshotr",
]
default = ["full", "aws-lc"]
@@ -87,4 +80,4 @@ cbindgen = "0.29.0"
ureq = "3"
[lib]
crate-type = ["staticlib", "cdylib", "rlib"]
crate-type = ["staticlib", "cdylib"]

View File

@@ -35,25 +35,16 @@ fn main() {
.expect("Unable to generate bindings")
.write_to_file("idevice.h");
// Check if plist.h exists locally first, otherwise download
let plist_h_path = "plist.h";
let h = if std::path::Path::new(plist_h_path).exists() {
std::fs::read_to_string(plist_h_path).expect("failed to read plist.h")
} else {
// download plist.h
let h = ureq::get("https://raw.githubusercontent.com/libimobiledevice/libplist/refs/heads/master/include/plist/plist.h")
.call()
.expect("failed to download plist.h");
h.into_body()
let h = h
.into_body()
.read_to_string()
.expect("failed to get string content")
};
.expect("failed to get string content");
let mut f = OpenOptions::new().append(true).open("idevice.h").unwrap();
f.write_all(b"\n\n\n").unwrap();
f.write_all(&h.into_bytes())
.expect("failed to append plist.h");
let f = std::fs::read_to_string("idevice.h").unwrap();
std::fs::write("../cpp/include/idevice.h", f).unwrap();
}

View File

@@ -254,7 +254,7 @@ int main(int argc, char **argv) {
} else {
uint8_t *data = NULL;
size_t length = 0;
err = afc_file_read_entire(file, &data, &length);
err = afc_file_read(file, &data, &length);
if (err == NULL) {
if (write_file(dest_path, data, length)) {
printf("File downloaded successfully\n");

View File

@@ -179,7 +179,7 @@ int main(int argc, char **argv) {
// Connect to installation proxy
InstallationProxyClientHandle *instproxy_client = NULL;
err = installation_proxy_connect(provider, &instproxy_client);
err = installation_proxy_connect_tcp(provider, &instproxy_client);
if (err != NULL) {
fprintf(stderr, "Failed to connect to installation proxy: [%d] %s",
err->code, err->message);

View File

@@ -42,7 +42,7 @@ int main() {
// Connect to installation proxy
InstallationProxyClientHandle *client = NULL;
err = installation_proxy_connect(provider, &client);
err = installation_proxy_connect_tcp(provider, &client);
if (err != NULL) {
fprintf(stderr, "Failed to connect to installation proxy: [%d] %s",
err->code, err->message);

View File

@@ -131,6 +131,7 @@ int main(int argc, char **argv) {
fprintf(stderr, "Failed to connect to image mounter: [%d] %s", err->code,
err->message);
idevice_error_free(err);
idevice_provider_free(provider);
return 1;
}
idevice_provider_free(provider);

View File

@@ -7,7 +7,7 @@ use idevice::tcp::handle::StreamHandle;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use crate::core_device_proxy::AdapterHandle;
use crate::{IdeviceFfiError, ReadWriteOpaque, ffi_err, run_sync};
use crate::{IdeviceFfiError, RUNTIME, ReadWriteOpaque, ffi_err};
pub struct AdapterStreamHandle(pub StreamHandle);
@@ -36,7 +36,7 @@ pub unsafe extern "C" fn adapter_connect(
}
let adapter = unsafe { &mut (*adapter_handle).0 };
let res = run_sync(async move { adapter.connect(port).await });
let res = RUNTIME.block_on(async move { adapter.connect(port).await });
match res {
Ok(r) => {
@@ -47,7 +47,7 @@ pub unsafe extern "C" fn adapter_connect(
null_mut()
}
Err(e) => {
tracing::error!("Adapter connect failed: {e}");
log::error!("Adapter connect failed: {e}");
ffi_err!(e)
}
}
@@ -81,12 +81,12 @@ pub unsafe extern "C" fn adapter_pcap(
Err(_) => return ffi_err!(IdeviceError::FfiInvalidString),
};
let res = run_sync(async move { adapter.pcap(path_str).await });
let res = RUNTIME.block_on(async move { adapter.pcap(path_str).await });
match res {
Ok(_) => null_mut(),
Err(e) => {
tracing::error!("Adapter pcap failed: {e}");
log::error!("Adapter pcap failed: {e}");
ffi_err!(e)
}
}
@@ -103,37 +103,13 @@ 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_stream_close(
handle: *mut AdapterStreamHandle,
) -> *mut IdeviceFfiError {
pub unsafe extern "C" fn adapter_close(handle: *mut AdapterStreamHandle) -> *mut IdeviceFfiError {
if handle.is_null() {
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let adapter = unsafe { &mut (*handle).0 };
run_sync(async move { adapter.close() });
null_mut()
}
/// Stops the entire adapter TCP stack
///
/// # Arguments
/// * [`handle`] - The adapter handle
///
/// # Returns
/// Null on success, an IdeviceFfiError otherwise
///
/// # 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) -> *mut IdeviceFfiError {
if handle.is_null() {
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let adapter = unsafe { &mut (*handle).0 };
run_sync(async move { adapter.close().await.ok() });
RUNTIME.block_on(async move { adapter.close() });
null_mut()
}
@@ -164,12 +140,12 @@ pub unsafe extern "C" fn adapter_send(
let adapter = unsafe { &mut (*handle).0 };
let data_slice = unsafe { std::slice::from_raw_parts(data, length) };
let res = run_sync(async move { adapter.write_all(data_slice).await });
let res = RUNTIME.block_on(async move { adapter.write_all(data_slice).await });
match res {
Ok(_) => null_mut(),
Err(e) => {
tracing::error!("Adapter send failed: {e}");
log::error!("Adapter send failed: {e}");
ffi_err!(e)
}
}
@@ -202,7 +178,7 @@ pub unsafe extern "C" fn adapter_recv(
}
let adapter = unsafe { &mut (*handle).0 };
let res: Result<Vec<u8>, std::io::Error> = run_sync(async move {
let res: Result<Vec<u8>, std::io::Error> = RUNTIME.block_on(async move {
let mut buf = [0; 2048];
let res = adapter.read(&mut buf).await?;
Ok(buf[..res].to_vec())
@@ -223,7 +199,7 @@ pub unsafe extern "C" fn adapter_recv(
null_mut()
}
Err(e) => {
tracing::error!("Adapter recv failed: {e}");
log::error!("Adapter recv failed: {e}");
ffi_err!(e)
}
}

View File

@@ -1,18 +1,14 @@
// Jackson Coxson
use std::{io::SeekFrom, ptr::null_mut};
use std::ptr::null_mut;
use idevice::{
IdeviceError, IdeviceService,
afc::{AfcClient, DeviceInfo, FileInfo, file::FileDescriptor},
afc::{AfcClient, DeviceInfo, FileInfo},
provider::IdeviceProvider,
};
use tokio::io::{AsyncReadExt, AsyncSeekExt};
use crate::{
IdeviceFfiError, IdeviceHandle, LOCAL_RUNTIME, ffi_err, provider::IdeviceProviderHandle,
run_sync, run_sync_local,
};
use crate::{IdeviceFfiError, IdeviceHandle, RUNTIME, ffi_err, provider::IdeviceProviderHandle};
pub struct AfcClientHandle(pub AfcClient);
@@ -34,11 +30,11 @@ pub unsafe extern "C" fn afc_client_connect(
client: *mut *mut AfcClientHandle,
) -> *mut IdeviceFfiError {
if provider.is_null() || client.is_null() {
tracing::error!("Null pointer provided");
log::error!("Null pointer provided");
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let res = run_sync_local(async {
let res = RUNTIME.block_on(async {
let provider_ref: &dyn IdeviceProvider = unsafe { &*(*provider).0 };
AfcClient::connect(provider_ref).await
@@ -54,44 +50,6 @@ pub unsafe extern "C" fn afc_client_connect(
}
}
/// Connects to the AFC2 service using a TCP provider
///
/// # Arguments
/// * [`provider`] - An IdeviceProvider
/// * [`client`] - On success, will be set to point to a newly allocated AfcClient handle
///
/// # Returns
/// An IdeviceFfiError on error, null on success
///
/// # 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 afc2_client_connect(
provider: *mut IdeviceProviderHandle,
client: *mut *mut AfcClientHandle,
) -> *mut IdeviceFfiError {
if provider.is_null() || client.is_null() {
tracing::error!("Null pointer provided");
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let res = run_sync_local(async {
let provider_ref: &dyn IdeviceProvider = unsafe { &*(*provider).0 };
AfcClient::new_afc2(provider_ref).await
});
match res {
Ok(r) => {
let boxed = Box::new(AfcClientHandle(r));
unsafe { *client = Box::into_raw(boxed) };
null_mut()
}
Err(e) => ffi_err!(e),
}
}
/// Creates a new AfcClient from an existing Idevice connection
///
/// # Arguments
@@ -130,7 +88,7 @@ pub unsafe extern "C" fn afc_client_new(
#[unsafe(no_mangle)]
pub unsafe extern "C" fn afc_client_free(handle: *mut AfcClientHandle) {
if !handle.is_null() {
tracing::debug!("Freeing afc_client");
log::debug!("Freeing afc_client");
let _ = unsafe { Box::from_raw(handle) };
}
}
@@ -164,7 +122,7 @@ pub unsafe extern "C" fn afc_list_directory(
// Use to_string_lossy to handle non-UTF8 paths
let path = path_cstr.to_string_lossy();
let res: Result<Vec<String>, IdeviceError> = run_sync_local(async move {
let res: Result<Vec<String>, IdeviceError> = RUNTIME.block_on(async move {
// SAFETY: We're assuming client is a valid pointer here
let client_ref = unsafe { &mut (*client).0 };
client_ref.list_dir(&path.to_string()).await
@@ -236,7 +194,7 @@ pub unsafe extern "C" fn afc_make_directory(
Err(_) => return ffi_err!(IdeviceError::FfiInvalidArg),
};
let res: Result<(), IdeviceError> = run_sync_local(async move {
let res: Result<(), IdeviceError> = RUNTIME.block_on(async move {
let client_ref = unsafe { &mut (*client).0 };
client_ref.mk_dir(path).await
});
@@ -288,7 +246,7 @@ pub unsafe extern "C" fn afc_get_file_info(
Err(_) => return ffi_err!(IdeviceError::FfiInvalidArg),
};
let res: Result<FileInfo, IdeviceError> = run_sync_local(async move {
let res: Result<FileInfo, IdeviceError> = RUNTIME.block_on(async move {
let client_ref = unsafe { &mut (*client).0 };
client_ref.get_file_info(path).await
});
@@ -373,7 +331,7 @@ pub unsafe extern "C" fn afc_get_device_info(
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let res: Result<DeviceInfo, IdeviceError> = run_sync_local(async move {
let res: Result<DeviceInfo, IdeviceError> = RUNTIME.block_on(async move {
let client_ref = unsafe { &mut (*client).0 };
client_ref.get_device_info().await
});
@@ -437,7 +395,7 @@ pub unsafe extern "C" fn afc_remove_path(
Err(_) => return ffi_err!(IdeviceError::FfiInvalidArg),
};
let res: Result<(), IdeviceError> = run_sync_local(async move {
let res: Result<(), IdeviceError> = RUNTIME.block_on(async move {
let client_ref = unsafe { &mut (*client).0 };
client_ref.remove(path).await
});
@@ -475,7 +433,7 @@ pub unsafe extern "C" fn afc_remove_path_and_contents(
Err(_) => return ffi_err!(IdeviceError::FfiInvalidArg),
};
let res: Result<(), IdeviceError> = run_sync_local(async move {
let res: Result<(), IdeviceError> = RUNTIME.block_on(async move {
let client_ref = unsafe { &mut (*client).0 };
client_ref.remove_all(path).await
});
@@ -548,7 +506,7 @@ pub unsafe extern "C" fn afc_file_open(
let mode = mode.into();
let res: Result<*mut AfcFileHandle, IdeviceError> = LOCAL_RUNTIME.block_on(async move {
let res: Result<*mut AfcFileHandle, IdeviceError> = RUNTIME.block_on(async move {
let client_ref = unsafe { &mut (*client).0 };
let result = client_ref.open(path, mode).await;
match result {
@@ -586,7 +544,7 @@ pub unsafe extern "C" fn afc_file_close(handle: *mut AfcFileHandle) -> *mut Idev
}
let fd = unsafe { Box::from_raw(handle as *mut idevice::afc::file::FileDescriptor) };
let res: Result<(), IdeviceError> = run_sync(async move { fd.close().await });
let res: Result<(), IdeviceError> = RUNTIME.block_on(async move { fd.close().await });
match res {
Ok(_) => null_mut(),
@@ -594,13 +552,12 @@ pub unsafe extern "C" fn afc_file_close(handle: *mut AfcFileHandle) -> *mut Idev
}
}
/// Reads data from an open file. This advances the cursor of the file.
/// Reads data from an open file
///
/// # Arguments
/// * [`handle`] - File handle to read from
/// * [`data`] - Will be set to point to the read data
/// * [`len`] - Number of bytes to read from the file
/// * [`bytes_read`] - The number of bytes read from the file
/// * [`length`] - Will be set to the length of the read data
///
/// # Returns
/// An IdeviceFfiError on error, null on success
@@ -609,53 +566,6 @@ pub unsafe extern "C" fn afc_file_close(handle: *mut AfcFileHandle) -> *mut Idev
/// All pointers must be valid and non-null
#[unsafe(no_mangle)]
pub unsafe extern "C" fn afc_file_read(
handle: *mut AfcFileHandle,
data: *mut *mut u8,
len: usize,
bytes_read: *mut libc::size_t,
) -> *mut IdeviceFfiError {
if handle.is_null() || data.is_null() || bytes_read.is_null() {
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let fd = unsafe { &mut *(handle as *mut FileDescriptor) };
let res: Result<Vec<u8>, IdeviceError> = run_sync({
let mut buf = vec![0u8; len];
async move {
let r = fd.read(&mut buf).await?;
buf.truncate(r);
Ok(buf)
}
});
match res {
Ok(bytes) => {
let mut boxed = bytes.into_boxed_slice();
unsafe {
*data = boxed.as_mut_ptr();
*bytes_read = boxed.len();
}
std::mem::forget(boxed);
null_mut()
}
Err(e) => ffi_err!(e),
}
}
/// Reads all data from an open file.
///
/// # Arguments
/// * [`handle`] - File handle to read from
/// * [`data`] - Will be set to point to the read data
/// * [`length`] - The number of bytes read from the file
///
/// # Returns
/// An IdeviceFfiError on error, null on success
///
/// # Safety
/// All pointers must be valid and non-null
#[unsafe(no_mangle)]
pub unsafe extern "C" fn afc_file_read_entire(
handle: *mut AfcFileHandle,
data: *mut *mut u8,
length: *mut libc::size_t,
@@ -665,7 +575,7 @@ pub unsafe extern "C" fn afc_file_read_entire(
}
let fd = unsafe { &mut *(handle as *mut idevice::afc::file::FileDescriptor) };
let res: Result<Vec<u8>, IdeviceError> = run_sync(async move { fd.read_entire().await });
let res: Result<Vec<u8>, IdeviceError> = RUNTIME.block_on(async move { fd.read().await });
match res {
Ok(bytes) => {
@@ -681,100 +591,6 @@ pub unsafe extern "C" fn afc_file_read_entire(
}
}
/// Moves the read/write cursor in an open file.
///
/// # Arguments
/// * [`handle`] - File handle whose cursor should be moved
/// * [`offset`] - Distance to move the cursor, interpreted based on `whence`
/// * [`whence`] - Origin used for the seek operation:
/// * `0` — Seek from the start of the file (`SeekFrom::Start`)
/// * `1` — Seek from the current cursor position (`SeekFrom::Current`)
/// * `2` — Seek from the end of the file (`SeekFrom::End`)
/// * [`new_pos`] - Output parameter; will be set to the new absolute cursor position
///
/// # Returns
/// An [`IdeviceFfiError`] on error, or null on success.
///
/// # Safety
/// All pointers must be valid and non-null.
///
/// # Notes
/// * If `whence` is invalid, this function returns `FfiInvalidArg`.
/// * The AFC protocol may restrict seeking beyond certain bounds; such errors
/// are reported through the returned [`IdeviceFfiError`].
#[unsafe(no_mangle)]
pub unsafe extern "C" fn afc_file_seek(
handle: *mut AfcFileHandle,
offset: i64,
whence: libc::c_int,
new_pos: *mut i64,
) -> *mut IdeviceFfiError {
if handle.is_null() || new_pos.is_null() {
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let fd = unsafe { &mut *(handle as *mut FileDescriptor) };
let seek_from = match whence {
0 => SeekFrom::Start(offset as u64),
1 => SeekFrom::Current(offset),
2 => SeekFrom::End(offset),
_ => return ffi_err!(IdeviceError::FfiInvalidArg),
};
let res: Result<u64, IdeviceError> = run_sync(async move { Ok(fd.seek(seek_from).await?) });
match res {
Ok(pos) => {
unsafe {
*new_pos = pos as i64;
}
null_mut()
}
Err(e) => ffi_err!(e),
}
}
/// Returns the current read/write cursor position of an open file.
///
/// # Arguments
/// * [`handle`] - File handle whose cursor should be queried
/// * [`pos`] - Output parameter; will be set to the current absolute cursor position
///
/// # Returns
/// An [`IdeviceFfiError`] on error, or null on success.
///
/// # Safety
/// All pointers must be valid and non-null.
///
/// # Notes
/// This function is equivalent to performing a seek operation with
/// `SeekFrom::Current(0)` internally.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn afc_file_tell(
handle: *mut AfcFileHandle,
pos: *mut i64,
) -> *mut IdeviceFfiError {
if handle.is_null() || pos.is_null() {
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let fd = unsafe { &mut *(handle as *mut FileDescriptor) };
let res: Result<u64, IdeviceError> =
run_sync(async { Ok(fd.seek(SeekFrom::Current(0)).await?) });
match res {
Ok(cur) => {
unsafe {
*pos = cur as i64;
}
null_mut()
}
Err(e) => ffi_err!(e),
}
}
/// Writes data to an open file
///
/// # Arguments
@@ -801,7 +617,7 @@ pub unsafe extern "C" fn afc_file_write(
let fd = unsafe { &mut *(handle as *mut idevice::afc::file::FileDescriptor) };
let data_slice = unsafe { std::slice::from_raw_parts(data, length) };
let res: Result<(), IdeviceError> = run_sync(async move { fd.write_entire(data_slice).await });
let res: Result<(), IdeviceError> = RUNTIME.block_on(async move { fd.write(data_slice).await });
match res {
Ok(_) => null_mut(),
@@ -858,7 +674,7 @@ pub unsafe extern "C" fn afc_make_link(
AfcLinkType::Symbolic => idevice::afc::opcode::LinkType::Symlink,
};
let res: Result<(), IdeviceError> = run_sync_local(async move {
let res: Result<(), IdeviceError> = RUNTIME.block_on(async move {
let client_ref = unsafe { &mut (*client).0 };
client_ref.link(target, source, link_type).await
});
@@ -904,7 +720,7 @@ pub unsafe extern "C" fn afc_rename_path(
Err(_) => return ffi_err!(IdeviceError::FfiInvalidArg),
};
let res: Result<(), IdeviceError> = run_sync_local(async move {
let res: Result<(), IdeviceError> = RUNTIME.block_on(async move {
let client_ref = unsafe { &mut (*client).0 };
client_ref.rename(source, target).await
});
@@ -925,7 +741,7 @@ pub unsafe extern "C" fn afc_rename_path(
#[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::ptr::slice_from_raw_parts_mut(data, length)) };
let boxed = unsafe { Box::from_raw(std::slice::from_raw_parts_mut(data, length)) };
drop(boxed);
}
}

View File

@@ -4,9 +4,7 @@ use std::ptr::null_mut;
use idevice::{IdeviceError, IdeviceService, amfi::AmfiClient, provider::IdeviceProvider};
use crate::{
IdeviceFfiError, IdeviceHandle, ffi_err, provider::IdeviceProviderHandle, run_sync_local,
};
use crate::{IdeviceFfiError, IdeviceHandle, RUNTIME, ffi_err, provider::IdeviceProviderHandle};
pub struct AmfiClientHandle(pub AmfiClient);
@@ -28,11 +26,11 @@ pub unsafe extern "C" fn amfi_connect(
client: *mut *mut AmfiClientHandle,
) -> *mut IdeviceFfiError {
if provider.is_null() || client.is_null() {
tracing::error!("Null pointer provided");
log::error!("Null pointer provided");
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let res: Result<AmfiClient, IdeviceError> = run_sync_local(async move {
let res: Result<AmfiClient, IdeviceError> = RUNTIME.block_on(async move {
let provider_ref: &dyn IdeviceProvider = unsafe { &*(*provider).0 };
// Connect using the reference
@@ -96,7 +94,7 @@ pub unsafe extern "C" fn amfi_reveal_developer_mode_option_in_ui(
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let res: Result<(), IdeviceError> = run_sync_local(async move {
let res: Result<(), IdeviceError> = RUNTIME.block_on(async move {
let client_ref = unsafe { &mut (*client).0 };
client_ref.reveal_developer_mode_option_in_ui().await
});
@@ -124,7 +122,7 @@ pub unsafe extern "C" fn amfi_enable_developer_mode(
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let res: Result<(), IdeviceError> = run_sync_local(async move {
let res: Result<(), IdeviceError> = RUNTIME.block_on(async move {
let client_ref = unsafe { &mut (*client).0 };
client_ref.enable_developer_mode().await
});
@@ -152,7 +150,7 @@ pub unsafe extern "C" fn amfi_accept_developer_mode(
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let res: Result<(), IdeviceError> = run_sync_local(async move {
let res: Result<(), IdeviceError> = RUNTIME.block_on(async move {
let client_ref = unsafe { &mut (*client).0 };
client_ref.accept_developer_mode().await
});
@@ -173,7 +171,7 @@ pub unsafe extern "C" fn amfi_accept_developer_mode(
#[unsafe(no_mangle)]
pub unsafe extern "C" fn amfi_client_free(handle: *mut AmfiClientHandle) {
if !handle.is_null() {
tracing::debug!("Freeing AmfiClient handle");
log::debug!("Freeing AmfiClient handle");
let _ = unsafe { Box::from_raw(handle) };
}
}

View File

@@ -9,7 +9,7 @@ use idevice::{IdeviceError, ReadWrite, RsdService};
use crate::core_device_proxy::AdapterHandle;
use crate::rsd::RsdHandshakeHandle;
use crate::{IdeviceFfiError, ReadWriteOpaque, ffi_err, run_sync, run_sync_local};
use crate::{IdeviceFfiError, RUNTIME, ReadWriteOpaque, ffi_err};
/// Opaque handle to an AppServiceClient
pub struct AppServiceHandle(pub AppServiceClient<Box<dyn ReadWrite>>);
@@ -91,7 +91,7 @@ pub unsafe extern "C" fn app_service_connect_rsd(
}
let res: Result<AppServiceClient<Box<dyn ReadWrite>>, IdeviceError> =
run_sync_local(async move {
RUNTIME.block_on(async move {
let provider_ref = unsafe { &mut (*provider).0 };
let handshake_ref = unsafe { &mut (*handshake).0 };
@@ -130,7 +130,7 @@ pub unsafe extern "C" fn app_service_new(
}
let socket = unsafe { Box::from_raw(socket) };
let res = run_sync(async move { AppServiceClient::new(socket.inner.unwrap()).await });
let res = RUNTIME.block_on(async move { AppServiceClient::new(socket.inner.unwrap()).await });
match res {
Ok(client) => {
@@ -186,7 +186,7 @@ pub unsafe extern "C" fn app_service_list_apps(
}
let client = unsafe { &mut (*handle).0 };
let res = run_sync(async move {
let res = RUNTIME.block_on(async move {
client
.list_apps(
app_clips != 0,
@@ -347,7 +347,7 @@ pub unsafe extern "C" fn app_service_launch_app(
};
let client = unsafe { &mut (*handle).0 };
let res = run_sync(async move {
let res = RUNTIME.block_on(async move {
client
.launch_application(
bundle_id_str,
@@ -430,7 +430,7 @@ pub unsafe extern "C" fn app_service_list_processes(
}
let client = unsafe { &mut (*handle).0 };
let res = run_sync(async move { client.list_processes().await });
let res = RUNTIME.block_on(async move { client.list_processes().await });
match res {
Ok(process_list) => {
@@ -477,7 +477,7 @@ pub unsafe extern "C" fn app_service_free_process_list(
count: usize,
) {
if !processes.is_null() && count > 0 {
let processes_slice = unsafe { Vec::from_raw_parts(processes, count, count) };
let processes_slice = unsafe { std::slice::from_raw_parts_mut(processes, count) };
for process in processes_slice {
if !process.executable_url.is_null() {
let _ = unsafe { CString::from_raw(process.executable_url) };
@@ -513,7 +513,7 @@ pub unsafe extern "C" fn app_service_uninstall_app(
};
let client = unsafe { &mut (*handle).0 };
let res = run_sync(async move { client.uninstall_app(bundle_id_str).await });
let res = RUNTIME.block_on(async move { client.uninstall_app(bundle_id_str).await });
match res {
Ok(_) => null_mut(),
@@ -546,7 +546,7 @@ pub unsafe extern "C" fn app_service_send_signal(
}
let client = unsafe { &mut (*handle).0 };
let res = run_sync(async move { client.send_signal(pid, signal).await });
let res = RUNTIME.block_on(async move { client.send_signal(pid, signal).await });
match res {
Ok(signal_response) => {
@@ -628,7 +628,7 @@ pub unsafe extern "C" fn app_service_fetch_app_icon(
};
let client = unsafe { &mut (*handle).0 };
let res = run_sync(async move {
let res = RUNTIME.block_on(async move {
client
.fetch_app_icon(bundle_id_str, width, height, scale, allow_placeholder != 0)
.await

View File

@@ -7,11 +7,11 @@ use std::ptr::null_mut;
use futures::{Stream, StreamExt};
use idevice::core_device::DiagnostisServiceClient;
use idevice::{IdeviceError, ReadWrite, RsdService};
use tracing::debug;
use log::debug;
use crate::core_device_proxy::AdapterHandle;
use crate::rsd::RsdHandshakeHandle;
use crate::{IdeviceFfiError, ReadWriteOpaque, ffi_err, run_sync, run_sync_local};
use crate::{IdeviceFfiError, RUNTIME, ReadWriteOpaque, ffi_err};
/// Opaque handle to an AppServiceClient
pub struct DiagnosticsServiceHandle(pub DiagnostisServiceClient<Box<dyn ReadWrite>>);
@@ -43,7 +43,7 @@ pub unsafe extern "C" fn diagnostics_service_connect_rsd(
}
let res: Result<DiagnostisServiceClient<Box<dyn ReadWrite>>, IdeviceError> =
run_sync_local(async move {
RUNTIME.block_on(async move {
let provider_ref = unsafe { &mut (*provider).0 };
let handshake_ref = unsafe { &mut (*handshake).0 };
debug!(
@@ -87,8 +87,8 @@ pub unsafe extern "C" fn diagnostics_service_new(
}
let socket = unsafe { Box::from_raw(socket) };
let res =
run_sync(async move { DiagnostisServiceClient::from_stream(socket.inner.unwrap()).await });
let res = RUNTIME
.block_on(async move { DiagnostisServiceClient::from_stream(socket.inner.unwrap()).await });
match res {
Ok(client) => {
@@ -134,7 +134,7 @@ pub unsafe extern "C" fn diagnostics_service_capture_sysdiagnose(
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let handle = unsafe { &mut *handle };
let res = run_sync_local(async move { handle.0.capture_sysdiagnose(dry_run).await });
let res = RUNTIME.block_on(async move { handle.0.capture_sysdiagnose(dry_run).await });
match res {
Ok(res) => {
let filename = CString::new(res.preferred_filename).unwrap();
@@ -172,7 +172,7 @@ pub unsafe extern "C" fn sysdiagnose_stream_next(
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let handle = unsafe { &mut *handle };
let res = run_sync_local(async move { handle.0.next().await });
let res = RUNTIME.block_on(async move { handle.0.next().await });
match res {
Some(Ok(res)) => {
let mut res = res.into_boxed_slice();

View File

@@ -9,10 +9,7 @@ use idevice::{
IdeviceError, IdeviceService, core_device_proxy::CoreDeviceProxy, provider::IdeviceProvider,
};
use crate::{
IdeviceFfiError, IdeviceHandle, ffi_err, provider::IdeviceProviderHandle, run_sync,
run_sync_local,
};
use crate::{IdeviceFfiError, IdeviceHandle, RUNTIME, ffi_err, provider::IdeviceProviderHandle};
pub struct CoreDeviceProxyHandle(pub CoreDeviceProxy);
pub struct AdapterHandle(pub idevice::tcp::handle::AdapterHandle);
@@ -35,11 +32,11 @@ pub unsafe extern "C" fn core_device_proxy_connect(
client: *mut *mut CoreDeviceProxyHandle,
) -> *mut IdeviceFfiError {
if provider.is_null() || client.is_null() {
tracing::error!("Null pointer provided");
log::error!("Null pointer provided");
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let res: Result<CoreDeviceProxy, IdeviceError> = run_sync_local(async move {
let res: Result<CoreDeviceProxy, IdeviceError> = RUNTIME.block_on(async move {
let provider_ref: &dyn IdeviceProvider = unsafe { &*(*provider).0 };
// Connect using the reference
@@ -79,7 +76,7 @@ pub unsafe extern "C" fn core_device_proxy_new(
}
let socket = unsafe { Box::from_raw(socket) }.0;
let r: Result<CoreDeviceProxy, IdeviceError> =
run_sync(async move { CoreDeviceProxy::new(socket).await });
RUNTIME.block_on(async move { CoreDeviceProxy::new(socket).await });
match r {
Ok(r) => {
let boxed = Box::new(CoreDeviceProxyHandle(r));
@@ -116,7 +113,7 @@ pub unsafe extern "C" fn core_device_proxy_send(
let proxy = unsafe { &mut (*handle).0 };
let data_slice = unsafe { std::slice::from_raw_parts(data, length) };
let res = run_sync(async move { proxy.send(data_slice).await });
let res = RUNTIME.block_on(async move { proxy.send(data_slice).await });
match res {
Ok(_) => null_mut(),
@@ -152,7 +149,7 @@ pub unsafe extern "C" fn core_device_proxy_recv(
let proxy = unsafe { &mut (*handle).0 };
let res = run_sync(async move { proxy.recv().await });
let res = RUNTIME.block_on(async move { proxy.recv().await });
match res {
Ok(received_data) => {
@@ -195,7 +192,7 @@ pub unsafe extern "C" fn core_device_proxy_get_client_parameters(
netmask: *mut *mut c_char,
) -> *mut IdeviceFfiError {
if handle.is_null() {
tracing::error!("Passed null handle");
log::error!("Passed null handle");
return ffi_err!(IdeviceError::FfiInvalidArg);
}
@@ -315,7 +312,7 @@ pub unsafe extern "C" fn core_device_proxy_create_tcp_adapter(
match result {
Ok(adapter_obj) => {
// We have to run this in the RUNTIME since we're spawning a new thread
let adapter_handle = run_sync(async move { adapter_obj.to_async_handle() });
let adapter_handle = RUNTIME.block_on(async move { adapter_obj.to_async_handle() });
let boxed = Box::new(AdapterHandle(adapter_handle));
unsafe { *adapter = Box::into_raw(boxed) };
@@ -336,7 +333,7 @@ pub unsafe extern "C" fn core_device_proxy_create_tcp_adapter(
#[unsafe(no_mangle)]
pub unsafe extern "C" fn core_device_proxy_free(handle: *mut CoreDeviceProxyHandle) {
if !handle.is_null() {
tracing::debug!("Freeing core_device_proxy");
log::debug!("Freeing core_device_proxy");
let _ = unsafe { Box::from_raw(handle) };
}
}
@@ -352,7 +349,7 @@ pub unsafe extern "C" fn core_device_proxy_free(handle: *mut CoreDeviceProxyHand
#[unsafe(no_mangle)]
pub unsafe extern "C" fn adapter_free(handle: *mut AdapterHandle) {
if !handle.is_null() {
tracing::debug!("Freeing adapter");
log::debug!("Freeing adapter");
let _ = unsafe { Box::from_raw(handle) };
}
}

View File

@@ -9,7 +9,7 @@ use idevice::{IdeviceError, ReadWrite, RsdService};
use crate::core_device_proxy::AdapterHandle;
use crate::rsd::RsdHandshakeHandle;
use crate::{IdeviceFfiError, ReadWriteOpaque, ffi_err, run_sync, run_sync_local};
use crate::{IdeviceFfiError, RUNTIME, ReadWriteOpaque, ffi_err};
/// Opaque handle to a DebugProxyClient
pub struct DebugProxyHandle(pub DebugProxyClient<Box<dyn ReadWrite>>);
@@ -136,7 +136,7 @@ pub unsafe extern "C" fn debug_proxy_connect_rsd(
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let res: Result<DebugProxyClient<Box<dyn ReadWrite>>, IdeviceError> =
run_sync_local(async move {
RUNTIME.block_on(async move {
let provider_ref = unsafe { &mut (*provider).0 };
let handshake_ref = unsafe { &mut (*handshake).0 };
@@ -241,7 +241,7 @@ pub unsafe extern "C" fn debug_proxy_send_command(
},
};
let res = run_sync(async move { client.send_command(cmd).await });
let res = RUNTIME.block_on(async move { client.send_command(cmd).await });
match res {
Ok(Some(r)) => {
@@ -282,7 +282,7 @@ pub unsafe extern "C" fn debug_proxy_read_response(
}
let client = unsafe { &mut (*handle).0 };
let res = run_sync(async move { client.read_response().await });
let res = RUNTIME.block_on(async move { client.read_response().await });
match res {
Ok(Some(r)) => {
@@ -326,7 +326,7 @@ pub unsafe extern "C" fn debug_proxy_send_raw(
let client = unsafe { &mut (*handle).0 };
let data_slice = unsafe { std::slice::from_raw_parts(data, len) };
let res = run_sync(async move { client.send_raw(data_slice).await });
let res = RUNTIME.block_on(async move { client.send_raw(data_slice).await });
match res {
Ok(_) => null_mut(),
@@ -358,7 +358,7 @@ pub unsafe extern "C" fn debug_proxy_read(
}
let client = unsafe { &mut (*handle).0 };
let res = run_sync(async move { client.read(len).await });
let res = RUNTIME.block_on(async move { client.read(len).await });
match res {
Ok(r) => {
@@ -416,7 +416,7 @@ pub unsafe extern "C" fn debug_proxy_set_argv(
.collect()
};
let res = run_sync(async move { client.set_argv(argv_vec).await });
let res = RUNTIME.block_on(async move { client.set_argv(argv_vec).await });
match res {
Ok(r) => {
@@ -450,7 +450,7 @@ pub unsafe extern "C" fn debug_proxy_send_ack(
}
let client = unsafe { &mut (*handle).0 };
let res = run_sync(async move { client.send_ack().await });
let res = RUNTIME.block_on(async move { client.send_ack().await });
match res {
Ok(_) => null_mut(),
@@ -477,7 +477,7 @@ pub unsafe extern "C" fn debug_proxy_send_nack(
}
let client = unsafe { &mut (*handle).0 };
let res = run_sync(async move { client.send_noack().await });
let res = RUNTIME.block_on(async move { client.send_noack().await });
match res {
Ok(_) => null_mut(),

View File

@@ -1,533 +0,0 @@
// Jackson Coxson
use std::{
ffi::{CStr, c_char},
ptr::null_mut,
};
use idevice::{
IdeviceError, IdeviceService, diagnostics_relay::DiagnosticsRelayClient,
provider::IdeviceProvider,
};
use plist_ffi::plist_t;
use crate::{
IdeviceFfiError, IdeviceHandle, ffi_err, provider::IdeviceProviderHandle, run_sync_local,
};
pub struct DiagnosticsRelayClientHandle(pub DiagnosticsRelayClient);
/// Automatically creates and connects to Diagnostics Relay, returning a client handle
///
/// # Arguments
/// * [`provider`] - An IdeviceProvider
/// * [`client`] - On success, will be set to point to a newly allocated DiagnosticsRelayClient handle
///
/// # Returns
/// An IdeviceFfiError on error, null on success
///
/// # 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 diagnostics_relay_client_connect(
provider: *mut IdeviceProviderHandle,
client: *mut *mut DiagnosticsRelayClientHandle,
) -> *mut IdeviceFfiError {
if provider.is_null() || client.is_null() {
tracing::error!("Null pointer provided");
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let res: Result<DiagnosticsRelayClient, IdeviceError> = run_sync_local(async move {
let provider_ref: &dyn IdeviceProvider = unsafe { &*(*provider).0 };
// Connect using the reference
DiagnosticsRelayClient::connect(provider_ref).await
});
match res {
Ok(r) => {
let boxed = Box::new(DiagnosticsRelayClientHandle(r));
unsafe { *client = Box::into_raw(boxed) };
null_mut()
}
Err(e) => {
ffi_err!(e)
}
}
}
/// Automatically creates and connects to Diagnostics Relay, returning a client handle
///
/// # Arguments
/// * [`socket`] - An IdeviceSocket handle
/// * [`client`] - On success, will be set to point to a newly allocated DiagnosticsRelayClient handle
///
/// # Returns
/// An IdeviceFfiError on error, null on success
///
/// # Safety
/// `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 diagnostics_relay_client_new(
socket: *mut IdeviceHandle,
client: *mut *mut DiagnosticsRelayClientHandle,
) -> *mut IdeviceFfiError {
if socket.is_null() || client.is_null() {
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let socket = unsafe { Box::from_raw(socket) }.0;
let r = DiagnosticsRelayClient::new(socket);
let boxed = Box::new(DiagnosticsRelayClientHandle(r));
unsafe { *client = Box::into_raw(boxed) };
null_mut()
}
/// Queries the device IO registry
///
/// # Arguments
/// * `client` - A valid DiagnosticsRelayClient handle
/// * `current_plane` - A string to search by or null
/// * `entry_name` - A string to search by or null
/// * `entry_class` - A string to search by or null
/// * `res` - Will be set to a pointer of a plist dictionary node on search success
///
/// # Returns
/// An IdeviceFfiError on error, null on success. Note that res can be null on success
/// if the search resulted in no values.
///
/// # Safety
/// `client` must be a valid pointer to a handle allocated by this library
#[unsafe(no_mangle)]
pub unsafe extern "C" fn diagnostics_relay_client_ioregistry(
client: *mut DiagnosticsRelayClientHandle,
current_plane: *const c_char,
entry_name: *const c_char,
entry_class: *const c_char,
res: *mut plist_t,
) -> *mut IdeviceFfiError {
if client.is_null() {
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let current_plane = if current_plane.is_null() {
None
} else {
Some(match unsafe { CStr::from_ptr(current_plane) }.to_str() {
Ok(s) => s,
Err(_) => {
return ffi_err!(IdeviceError::FfiInvalidString);
}
})
};
let entry_name = if entry_name.is_null() {
None
} else {
Some(match unsafe { CStr::from_ptr(entry_name) }.to_str() {
Ok(s) => s,
Err(_) => {
return ffi_err!(IdeviceError::FfiInvalidString);
}
})
};
let entry_class = if entry_class.is_null() {
None
} else {
Some(match unsafe { CStr::from_ptr(entry_class) }.to_str() {
Ok(s) => s,
Err(_) => {
return ffi_err!(IdeviceError::FfiInvalidString);
}
})
};
let output: Result<Option<plist::Dictionary>, IdeviceError> = run_sync_local(async move {
let client_ref = unsafe { &mut (*client).0 };
client_ref
.ioregistry(current_plane, entry_name, entry_class)
.await
});
match output {
Ok(output) => {
let output = match output {
Some(res) => {
plist_ffi::PlistWrapper::new_node(plist::Value::Dictionary(res)).into_ptr()
}
None => null_mut(),
};
unsafe { *res = output }
null_mut()
}
Err(e) => ffi_err!(e),
}
}
/// Requests MobileGestalt information from the device
///
/// # Arguments
/// * `client` - A valid DiagnosticsRelayClient handle
/// * `keys` - Optional list of specific keys to request. If None, requests all available keys
/// * `res` - Will be set to a pointer of a plist dictionary node on search success
///
/// # Returns
/// An IdeviceFfiError on error, null on success. Note that res can be null on success
/// if the search resulted in no values.
///
/// # Safety
/// `client` must be a valid pointer to a handle allocated by this library
#[unsafe(no_mangle)]
pub unsafe extern "C" fn diagnostics_relay_client_mobilegestalt(
client: *mut DiagnosticsRelayClientHandle,
keys: *const *const c_char,
keys_len: usize,
res: *mut plist_t,
) -> *mut IdeviceFfiError {
if client.is_null() {
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let keys = if !keys.is_null() {
let keys = unsafe { std::slice::from_raw_parts(keys, keys_len) };
Some(
keys.iter()
.filter_map(|x| unsafe {
match CStr::from_ptr(*x).to_str() {
Ok(s) => Some(s.to_string()),
Err(_) => None,
}
})
.collect::<Vec<String>>(),
)
} else {
None
};
let output: Result<Option<plist::Dictionary>, IdeviceError> = run_sync_local(async move {
let client_ref = unsafe { &mut (*client).0 };
client_ref.mobilegestalt(keys).await
});
match output {
Ok(output) => {
let output = match output {
Some(res) => {
plist_ffi::PlistWrapper::new_node(plist::Value::Dictionary(res)).into_ptr()
}
None => null_mut(),
};
unsafe { *res = output }
null_mut()
}
Err(e) => ffi_err!(e),
}
}
/// Requests gas gauge information from the device
///
/// # Arguments
/// * `client` - A valid DiagnosticsRelayClient handle
/// * `res` - Will be set to a pointer of a plist dictionary node on search success
///
/// # Returns
/// An IdeviceFfiError on error, null on success. Note that res can be null on success
/// if the search resulted in no values.
///
/// # Safety
/// `client` must be a valid pointer to a handle allocated by this library
#[unsafe(no_mangle)]
pub unsafe extern "C" fn diagnostics_relay_client_gasguage(
client: *mut DiagnosticsRelayClientHandle,
res: *mut plist_t,
) -> *mut IdeviceFfiError {
if client.is_null() {
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let output: Result<Option<plist::Dictionary>, IdeviceError> = run_sync_local(async move {
let client_ref = unsafe { &mut (*client).0 };
client_ref.gasguage().await
});
match output {
Ok(output) => {
let output = match output {
Some(res) => {
plist_ffi::PlistWrapper::new_node(plist::Value::Dictionary(res)).into_ptr()
}
None => null_mut(),
};
unsafe { *res = output }
null_mut()
}
Err(e) => ffi_err!(e),
}
}
/// Requests nand information from the device
///
/// # Arguments
/// * `client` - A valid DiagnosticsRelayClient handle
/// * `res` - Will be set to a pointer of a plist dictionary node on search success
///
/// # Returns
/// An IdeviceFfiError on error, null on success. Note that res can be null on success
/// if the search resulted in no values.
///
/// # Safety
/// `client` must be a valid pointer to a handle allocated by this library
#[unsafe(no_mangle)]
pub unsafe extern "C" fn diagnostics_relay_client_nand(
client: *mut DiagnosticsRelayClientHandle,
res: *mut plist_t,
) -> *mut IdeviceFfiError {
if client.is_null() {
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let output: Result<Option<plist::Dictionary>, IdeviceError> = run_sync_local(async move {
let client_ref = unsafe { &mut (*client).0 };
client_ref.nand().await
});
match output {
Ok(output) => {
let output = match output {
Some(res) => {
plist_ffi::PlistWrapper::new_node(plist::Value::Dictionary(res)).into_ptr()
}
None => null_mut(),
};
unsafe { *res = output }
null_mut()
}
Err(e) => ffi_err!(e),
}
}
/// Requests all available information from the device
///
/// # Arguments
/// * `client` - A valid DiagnosticsRelayClient handle
/// * `res` - Will be set to a pointer of a plist dictionary node on search success
///
/// # Returns
/// An IdeviceFfiError on error, null on success. Note that res can be null on success
/// if the search resulted in no values.
///
/// # Safety
/// `client` must be a valid pointer to a handle allocated by this library
#[unsafe(no_mangle)]
pub unsafe extern "C" fn diagnostics_relay_client_all(
client: *mut DiagnosticsRelayClientHandle,
res: *mut plist_t,
) -> *mut IdeviceFfiError {
if client.is_null() {
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let output: Result<Option<plist::Dictionary>, IdeviceError> = run_sync_local(async move {
let client_ref = unsafe { &mut (*client).0 };
client_ref.all().await
});
match output {
Ok(output) => {
let output = match output {
Some(res) => {
plist_ffi::PlistWrapper::new_node(plist::Value::Dictionary(res)).into_ptr()
}
None => null_mut(),
};
unsafe { *res = output }
null_mut()
}
Err(e) => ffi_err!(e),
}
}
/// Restarts the device
///
/// # Arguments
/// * `client` - A valid DiagnosticsRelayClient handle
///
/// # Returns
/// An IdeviceFfiError on error, null on success.
///
/// # Safety
/// `client` must be a valid pointer to a handle allocated by this library
#[unsafe(no_mangle)]
pub unsafe extern "C" fn diagnostics_relay_client_restart(
client: *mut DiagnosticsRelayClientHandle,
) -> *mut IdeviceFfiError {
if client.is_null() {
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let output: Result<(), IdeviceError> = run_sync_local(async move {
let client_ref = unsafe { &mut (*client).0 };
client_ref.restart().await
});
match output {
Ok(_) => null_mut(),
Err(e) => ffi_err!(e),
}
}
/// Shuts down the device
///
/// # Arguments
/// * `client` - A valid DiagnosticsRelayClient handle
///
/// # Returns
/// An IdeviceFfiError on error, null on success.
///
/// # Safety
/// `client` must be a valid pointer to a handle allocated by this library
#[unsafe(no_mangle)]
pub unsafe extern "C" fn diagnostics_relay_client_shutdown(
client: *mut DiagnosticsRelayClientHandle,
) -> *mut IdeviceFfiError {
if client.is_null() {
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let output: Result<(), IdeviceError> = run_sync_local(async move {
let client_ref = unsafe { &mut (*client).0 };
client_ref.shutdown().await
});
match output {
Ok(_) => null_mut(),
Err(e) => ffi_err!(e),
}
}
/// Puts the device to sleep
///
/// # Arguments
/// * `client` - A valid DiagnosticsRelayClient handle
///
/// # Returns
/// An IdeviceFfiError on error, null on success.
///
/// # Safety
/// `client` must be a valid pointer to a handle allocated by this library
#[unsafe(no_mangle)]
pub unsafe extern "C" fn diagnostics_relay_client_sleep(
client: *mut DiagnosticsRelayClientHandle,
) -> *mut IdeviceFfiError {
if client.is_null() {
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let output: Result<(), IdeviceError> = run_sync_local(async move {
let client_ref = unsafe { &mut (*client).0 };
client_ref.sleep().await
});
match output {
Ok(_) => null_mut(),
Err(e) => ffi_err!(e),
}
}
/// Requests WiFi diagnostics from the device
///
/// # Arguments
/// * `client` - A valid DiagnosticsRelayClient handle
/// * `res` - Will be set to a pointer of a plist dictionary node on search success
///
/// # Returns
/// An IdeviceFfiError on error, null on success. Note that res can be null on success
/// if the search resulted in no values.
///
/// # Safety
/// `client` must be a valid pointer to a handle allocated by this library
#[unsafe(no_mangle)]
pub unsafe extern "C" fn diagnostics_relay_client_wifi(
client: *mut DiagnosticsRelayClientHandle,
res: *mut plist_t,
) -> *mut IdeviceFfiError {
if client.is_null() {
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let output: Result<Option<plist::Dictionary>, IdeviceError> = run_sync_local(async move {
let client_ref = unsafe { &mut (*client).0 };
client_ref.wifi().await
});
match output {
Ok(output) => {
let output = match output {
Some(res) => {
plist_ffi::PlistWrapper::new_node(plist::Value::Dictionary(res)).into_ptr()
}
None => null_mut(),
};
unsafe { *res = output }
null_mut()
}
Err(e) => ffi_err!(e),
}
}
/// Puts the device to sleep
///
/// # Arguments
/// * `client` - A valid DiagnosticsRelayClient handle
///
/// # Returns
/// An IdeviceFfiError on error, null on success.
///
/// # Safety
/// `client` must be a valid pointer to a handle allocated by this library
#[unsafe(no_mangle)]
pub unsafe extern "C" fn diagnostics_relay_client_goodbye(
client: *mut DiagnosticsRelayClientHandle,
) -> *mut IdeviceFfiError {
if client.is_null() {
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let output: Result<(), IdeviceError> = run_sync_local(async move {
let client_ref = unsafe { &mut (*client).0 };
client_ref.goodbye().await
});
match output {
Ok(_) => null_mut(),
Err(e) => ffi_err!(e),
}
}
/// Frees a handle
///
/// # Arguments
/// * [`handle`] - The handle to free
///
/// # Safety
/// `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 unsafe extern "C" fn diagnostics_relay_client_free(handle: *mut DiagnosticsRelayClientHandle) {
if !handle.is_null() {
tracing::debug!("Freeing DiagnosticsRelayClientHandle");
let _ = unsafe { Box::from_raw(handle) };
}
}

View File

@@ -1,8 +0,0 @@
// Jackson Coxson
#[cfg(feature = "location_simulation")]
pub mod location_simulation;
pub mod process_control;
pub mod remote_server;
pub mod screenshot;

View File

@@ -1,113 +0,0 @@
// Jackson Coxson
use std::ptr::null_mut;
use idevice::{ReadWrite, dvt::screenshot::ScreenshotClient};
use crate::{IdeviceFfiError, dvt::remote_server::RemoteServerHandle, ffi_err, run_sync};
/// An opaque FFI handle for a [`ScreenshotClient`].
///
/// This type wraps a [`ScreenshotClient`] that communicates with
/// a connected device to capture screenshots through the DVT (Device Virtualization Toolkit) service.
pub struct ScreenshotClientHandle<'a>(pub ScreenshotClient<'a, Box<dyn ReadWrite>>);
/// Creates a new [`ScreenshotClient`] associated with a given [`RemoteServerHandle`].
///
/// # Arguments
/// * `server` - A pointer to a valid [`RemoteServerHandle`], previously created by this library.
/// * `handle` - A pointer to a location where the newly created [`ScreenshotClientHandle`] will be stored.
///
/// # Returns
/// * `null_mut()` on success.
/// * A pointer to an [`IdeviceFfiError`] on failure.
///
/// # Safety
/// - `server` must be a non-null pointer to a valid remote server handle allocated by this library.
/// - `handle` must be a non-null pointer to a writable memory location where the handle will be stored.
/// - The returned handle must later be freed using [`screenshot_client_free`].
#[unsafe(no_mangle)]
pub unsafe extern "C" fn screenshot_client_new(
server: *mut RemoteServerHandle,
handle: *mut *mut ScreenshotClientHandle<'static>,
) -> *mut IdeviceFfiError {
if server.is_null() || handle.is_null() {
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let server = unsafe { &mut (*server).0 };
let res = run_sync(async move { ScreenshotClient::new(server).await });
match res {
Ok(client) => {
let boxed = Box::new(ScreenshotClientHandle(client));
unsafe { *handle = Box::into_raw(boxed) };
null_mut()
}
Err(e) => ffi_err!(e),
}
}
/// Frees a [`ScreenshotClientHandle`].
///
/// This releases all memory associated with the handle.
/// After calling this function, the handle pointer must not be used again.
///
/// # Arguments
/// * `handle` - Pointer to a [`ScreenshotClientHandle`] previously returned by [`screenshot_client_new`].
///
/// # Safety
/// - `handle` must either be `NULL` or a valid pointer created by this library.
/// - Double-freeing or using the handle after freeing causes undefined behavior.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn screenshot_client_free(handle: *mut ScreenshotClientHandle<'static>) {
if !handle.is_null() {
let _ = unsafe { Box::from_raw(handle) };
}
}
/// Captures a screenshot from the connected device.
///
/// On success, this function writes a pointer to the PNG-encoded screenshot data and its length
/// into the provided output arguments. The caller is responsible for freeing this data using
/// `idevice_data_free`.
///
/// # Arguments
/// * `handle` - A pointer to a valid [`ScreenshotClientHandle`].
/// * `data` - Output pointer where the screenshot buffer pointer will be written.
/// * `len` - Output pointer where the buffer length (in bytes) will be written.
///
/// # Returns
/// * `null_mut()` on success.
/// * A pointer to an [`IdeviceFfiError`] on failure.
///
/// # Safety
/// - `handle` must be a valid pointer to a [`ScreenshotClientHandle`].
/// - `data` and `len` must be valid writable pointers.
/// - The data returned through `*data` must be freed by the caller with `idevice_data_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn screenshot_client_take_screenshot(
handle: *mut ScreenshotClientHandle<'static>,
data: *mut *mut u8,
len: *mut usize,
) -> *mut IdeviceFfiError {
if handle.is_null() || data.is_null() || len.is_null() {
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let client = unsafe { &mut (*handle).0 };
let res = run_sync(async move { client.take_screenshot().await });
match res {
Ok(r) => {
let mut r = r.into_boxed_slice();
unsafe {
*data = r.as_mut_ptr();
*len = r.len();
}
std::mem::forget(r); // Prevent Rust from freeing the buffer
null_mut()
}
Err(e) => ffi_err!(e),
}
}

View File

@@ -35,7 +35,7 @@ macro_rules! ffi_err {
let err: IdeviceError = $err.into();
let code = err.code();
let msg = CString::new(format!("{:?}", err))
let msg = CString::new(err.to_string())
.unwrap_or_else(|_| CString::new("invalid error").unwrap());
let raw_msg = msg.into_raw();

View File

@@ -6,9 +6,7 @@ use idevice::{
IdeviceError, IdeviceService, heartbeat::HeartbeatClient, provider::IdeviceProvider,
};
use crate::{
IdeviceFfiError, IdeviceHandle, ffi_err, provider::IdeviceProviderHandle, run_sync_local,
};
use crate::{IdeviceFfiError, IdeviceHandle, RUNTIME, ffi_err, provider::IdeviceProviderHandle};
pub struct HeartbeatClientHandle(pub HeartbeatClient);
@@ -30,11 +28,11 @@ pub unsafe extern "C" fn heartbeat_connect(
client: *mut *mut HeartbeatClientHandle,
) -> *mut IdeviceFfiError {
if provider.is_null() || client.is_null() {
tracing::error!("Null pointer provided");
log::error!("Null pointer provided");
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let res: Result<HeartbeatClient, IdeviceError> = run_sync_local(async move {
let res: Result<HeartbeatClient, IdeviceError> = RUNTIME.block_on(async move {
let provider_ref: &dyn IdeviceProvider = unsafe { &*(*provider).0 };
// Connect using the reference
HeartbeatClient::connect(provider_ref).await
@@ -97,7 +95,7 @@ pub unsafe extern "C" fn heartbeat_send_polo(
if client.is_null() {
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let res: Result<(), IdeviceError> = run_sync_local(async move {
let res: Result<(), IdeviceError> = RUNTIME.block_on(async move {
let client_ref = unsafe { &mut (*client).0 };
client_ref.send_polo().await
});
@@ -128,7 +126,7 @@ pub unsafe extern "C" fn heartbeat_get_marco(
if client.is_null() || new_interval.is_null() {
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let res: Result<u64, IdeviceError> = run_sync_local(async move {
let res: Result<u64, IdeviceError> = RUNTIME.block_on(async move {
let client_ref = unsafe { &mut (*client).0 };
client_ref.get_marco(interval).await
});
@@ -152,7 +150,7 @@ pub unsafe extern "C" fn heartbeat_get_marco(
#[unsafe(no_mangle)]
pub unsafe extern "C" fn heartbeat_client_free(handle: *mut HeartbeatClientHandle) {
if !handle.is_null() {
tracing::debug!("Freeing installation_proxy_client");
log::debug!("Freeing installation_proxy_client");
let _ = unsafe { Box::from_raw(handle) };
}
}

View File

@@ -1,183 +0,0 @@
// Jackson Coxson
use std::{
ffi::{CStr, c_char},
ptr::null_mut,
};
use idevice::{
IdeviceError, IdeviceService, afc::AfcClient, house_arrest::HouseArrestClient,
provider::IdeviceProvider,
};
use crate::{
IdeviceFfiError, IdeviceHandle, afc::AfcClientHandle, ffi_err, provider::IdeviceProviderHandle,
run_sync_local,
};
pub struct HouseArrestClientHandle(pub HouseArrestClient);
/// Connects to the House Arrest service using a TCP provider
///
/// # Arguments
/// * [`provider`] - An IdeviceProvider
/// * [`client`] - On success, will be set to point to a newly allocated HouseArrestClient handle
///
/// # Returns
/// An IdeviceFfiError on error, null on success
///
/// # 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 house_arrest_client_connect(
provider: *mut IdeviceProviderHandle,
client: *mut *mut HouseArrestClientHandle,
) -> *mut IdeviceFfiError {
if provider.is_null() || client.is_null() {
tracing::error!("Null pointer provided");
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let res = run_sync_local(async {
let provider_ref: &dyn IdeviceProvider = unsafe { &*(*provider).0 };
HouseArrestClient::connect(provider_ref).await
});
match res {
Ok(r) => {
let boxed = Box::new(HouseArrestClientHandle(r));
unsafe { *client = Box::into_raw(boxed) };
null_mut()
}
Err(e) => ffi_err!(e),
}
}
/// Creates a new HouseArrestClient from an existing Idevice connection
///
/// # Arguments
/// * [`socket`] - An IdeviceSocket handle
/// * [`client`] - On success, will be set to point to a newly allocated HouseArrestClient handle
///
/// # Returns
/// An IdeviceFfiError on error, null on success
///
/// # Safety
/// `socket` 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 house_arrest_client_new(
socket: *mut IdeviceHandle,
client: *mut *mut HouseArrestClientHandle,
) -> *mut IdeviceFfiError {
if socket.is_null() || client.is_null() {
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let socket = unsafe { Box::from_raw(socket) }.0;
let r = HouseArrestClient::new(socket);
let boxed = Box::new(HouseArrestClientHandle(r));
unsafe { *client = Box::into_raw(boxed) };
null_mut()
}
/// Vends a container for an app
///
/// # Arguments
/// * [`client`] - The House Arrest client
/// * [`bundle_id`] - The bundle ID to vend for
/// * [`afc_client`] - The new AFC client for the underlying connection
///
/// # Returns
/// An IdeviceFfiError on error, null on success
///
/// # Safety
/// `client` must be a allocated by this library
/// `bundle_id` must be a NULL-terminated string
/// `afc_client` must be a valid, non-null pointer where the new AFC client will be stored
#[unsafe(no_mangle)]
pub unsafe extern "C" fn house_arrest_vend_container(
client: *mut HouseArrestClientHandle,
bundle_id: *const c_char,
afc_client: *mut *mut AfcClientHandle,
) -> *mut IdeviceFfiError {
if client.is_null() || bundle_id.is_null() || afc_client.is_null() {
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let bundle_id = unsafe { CStr::from_ptr(bundle_id) }.to_string_lossy();
let client_ref = unsafe { Box::from_raw(client) }.0; // take ownership and drop
let res: Result<AfcClient, IdeviceError> =
run_sync_local(async move { client_ref.vend_container(bundle_id).await });
match res {
Ok(a) => {
let a = Box::into_raw(Box::new(AfcClientHandle(a)));
unsafe { *afc_client = a };
null_mut()
}
Err(e) => {
ffi_err!(e)
}
}
}
/// Vends documents for an app
///
/// # Arguments
/// * [`client`] - The House Arrest client
/// * [`bundle_id`] - The bundle ID to vend for
/// * [`afc_client`] - The new AFC client for the underlying connection
///
/// # Returns
/// An IdeviceFfiError on error, null on success
///
/// # Safety
/// `client` must be a allocated by this library
/// `bundle_id` must be a NULL-terminated string
/// `afc_client` must be a valid, non-null pointer where the new AFC client will be stored
#[unsafe(no_mangle)]
pub unsafe extern "C" fn house_arrest_vend_documents(
client: *mut HouseArrestClientHandle,
bundle_id: *const c_char,
afc_client: *mut *mut AfcClientHandle,
) -> *mut IdeviceFfiError {
if client.is_null() || bundle_id.is_null() || afc_client.is_null() {
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let bundle_id = unsafe { CStr::from_ptr(bundle_id) }.to_string_lossy();
let client_ref = unsafe { Box::from_raw(client) }.0; // take ownership and drop
let res: Result<AfcClient, IdeviceError> =
run_sync_local(async move { client_ref.vend_documents(bundle_id).await });
match res {
Ok(a) => {
let a = Box::into_raw(Box::new(AfcClientHandle(a)));
unsafe { *afc_client = a };
null_mut()
}
Err(e) => {
ffi_err!(e)
}
}
}
/// Frees an HouseArrestClient handle
///
/// # Arguments
/// * [`handle`] - The handle to free
///
/// # Safety
/// `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 unsafe extern "C" fn house_arrest_client_free(handle: *mut HouseArrestClientHandle) {
if !handle.is_null() {
tracing::debug!("Freeing house_arrest_client");
let _ = unsafe { Box::from_raw(handle) };
}
}

View File

@@ -8,9 +8,7 @@ use idevice::{
};
use plist_ffi::{PlistWrapper, plist_t};
use crate::{
IdeviceFfiError, IdeviceHandle, ffi_err, provider::IdeviceProviderHandle, run_sync_local,
};
use crate::{IdeviceFfiError, IdeviceHandle, RUNTIME, ffi_err, provider::IdeviceProviderHandle};
pub struct InstallationProxyClientHandle(pub InstallationProxyClient);
@@ -32,11 +30,11 @@ pub unsafe extern "C" fn installation_proxy_connect(
client: *mut *mut InstallationProxyClientHandle,
) -> *mut IdeviceFfiError {
if provider.is_null() || client.is_null() {
tracing::error!("Null pointer provided");
log::error!("Null pointer provided");
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let res: Result<InstallationProxyClient, IdeviceError> = run_sync_local(async move {
let res: Result<InstallationProxyClient, IdeviceError> = RUNTIME.block_on(async move {
let provider_ref: &dyn IdeviceProvider = unsafe { &*(*provider).0 };
InstallationProxyClient::connect(provider_ref).await
});
@@ -103,7 +101,7 @@ pub unsafe extern "C" fn installation_proxy_get_apps(
out_result_len: *mut libc::size_t,
) -> *mut IdeviceFfiError {
if client.is_null() || out_result.is_null() || out_result_len.is_null() {
tracing::error!("Invalid arguments: {client:?}, {out_result:?}");
log::error!("Invalid arguments: {client:?}, {out_result:?}");
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let client = unsafe { &mut *client };
@@ -136,7 +134,7 @@ pub unsafe extern "C" fn installation_proxy_get_apps(
)
};
let res: Result<Vec<plist_t>, IdeviceError> = run_sync_local(async {
let res: Result<Vec<plist_t>, IdeviceError> = RUNTIME.block_on(async {
client.0.get_apps(app_type, bundle_ids).await.map(|apps| {
apps.into_values()
.map(|v| PlistWrapper::new_node(v).into_ptr())
@@ -145,8 +143,7 @@ pub unsafe extern "C" fn installation_proxy_get_apps(
});
match res {
Ok(r) => {
let mut r = r.into_boxed_slice();
Ok(mut r) => {
let ptr = r.as_mut_ptr();
let len = r.len();
std::mem::forget(r);
@@ -174,7 +171,7 @@ pub unsafe extern "C" fn installation_proxy_client_free(
handle: *mut InstallationProxyClientHandle,
) {
if !handle.is_null() {
tracing::debug!("Freeing installation_proxy_client");
log::debug!("Freeing installation_proxy_client");
let _ = unsafe { Box::from_raw(handle) };
}
}
@@ -213,7 +210,7 @@ pub unsafe extern "C" fn installation_proxy_install(
}
.map(|x| x.borrow_self().clone());
let res = run_sync_local(async {
let res = RUNTIME.block_on(async {
unsafe { &mut *client }
.0
.install(package_path, options)
@@ -264,7 +261,7 @@ pub unsafe extern "C" fn installation_proxy_install_with_callback(
}
.map(|x| x.borrow_self().clone());
let res = run_sync_local(async {
let res = RUNTIME.block_on(async {
let callback_wrapper = |(progress, context)| async move {
callback(progress, context);
};
@@ -315,7 +312,7 @@ pub unsafe extern "C" fn installation_proxy_upgrade(
}
.map(|x| x.borrow_self().clone());
let res = run_sync_local(async {
let res = RUNTIME.block_on(async {
unsafe { &mut *client }
.0
.upgrade(package_path, options)
@@ -366,7 +363,7 @@ pub unsafe extern "C" fn installation_proxy_upgrade_with_callback(
}
.map(|x| x.borrow_self().clone());
let res = run_sync_local(async {
let res = RUNTIME.block_on(async {
let callback_wrapper = |(progress, context)| async move {
callback(progress, context);
};
@@ -417,7 +414,7 @@ pub unsafe extern "C" fn installation_proxy_uninstall(
}
.map(|x| x.borrow_self().clone());
let res = run_sync_local(async {
let res = RUNTIME.block_on(async {
unsafe { &mut *client }
.0
.uninstall(bundle_id, options)
@@ -468,7 +465,7 @@ pub unsafe extern "C" fn installation_proxy_uninstall_with_callback(
}
.map(|x| x.borrow_self().clone());
let res = run_sync_local(async {
let res = RUNTIME.block_on(async {
let callback_wrapper = |(progress, context)| async move {
callback(progress, context);
};
@@ -530,7 +527,7 @@ pub unsafe extern "C" fn installation_proxy_check_capabilities_match(
}
.map(|x| x.borrow_self().clone());
let res = run_sync_local(async {
let res = RUNTIME.block_on(async {
unsafe { &mut *client }
.0
.check_capabilities_match(capabilities, options)
@@ -580,7 +577,7 @@ pub unsafe extern "C" fn installation_proxy_browse(
}
.map(|x| x.borrow_self().clone());
let res: Result<Vec<plist_t>, IdeviceError> = run_sync_local(async {
let res: Result<Vec<plist_t>, IdeviceError> = RUNTIME.block_on(async {
unsafe { &mut *client }.0.browse(options).await.map(|apps| {
apps.into_iter()
.map(|v| PlistWrapper::new_node(v).into_ptr())

View File

@@ -12,33 +12,29 @@ pub mod core_device;
pub mod core_device_proxy;
#[cfg(feature = "debug_proxy")]
pub mod debug_proxy;
#[cfg(feature = "diagnostics_relay")]
pub mod diagnostics_relay;
#[cfg(feature = "dvt")]
pub mod dvt;
mod errors;
#[cfg(feature = "heartbeat")]
pub mod heartbeat;
#[cfg(feature = "house_arrest")]
pub mod house_arrest;
#[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;
#[cfg(feature = "mobile_image_mounter")]
pub mod mobile_image_mounter;
#[cfg(feature = "notification_proxy")]
pub mod notification_proxy;
#[cfg(feature = "syslog_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;
#[cfg(feature = "xpc")]
pub mod rsd;
#[cfg(feature = "screenshotr")]
pub mod screenshotr;
#[cfg(feature = "springboardservices")]
pub mod springboardservices;
#[cfg(feature = "syslog_relay")]
@@ -54,9 +50,8 @@ pub use pairing_file::*;
use idevice::{Idevice, IdeviceSocket, ReadWrite};
use once_cell::sync::Lazy;
use plist_ffi::PlistWrapper;
use std::{
ffi::{CStr, CString, c_char, c_void},
ffi::{CStr, CString, c_char},
ptr::null_mut,
};
use tokio::runtime::{self, Runtime};
@@ -64,7 +59,7 @@ use tokio::runtime::{self, Runtime};
#[cfg(unix)]
use crate::util::{idevice_sockaddr, idevice_socklen_t};
static GLOBAL_RUNTIME: Lazy<Runtime> = Lazy::new(|| {
static RUNTIME: Lazy<Runtime> = Lazy::new(|| {
runtime::Builder::new_multi_thread()
.enable_io()
.enable_time()
@@ -72,39 +67,6 @@ static GLOBAL_RUNTIME: Lazy<Runtime> = Lazy::new(|| {
.unwrap()
});
static LOCAL_RUNTIME: Lazy<Runtime> = Lazy::new(|| {
runtime::Builder::new_multi_thread()
.enable_all()
.build()
.unwrap()
});
/// Spawn the future on the global runtime and block current (FFI) thread until result.
/// F and R must be Send + 'static.
pub fn run_sync<F, R>(fut: F) -> R
where
F: std::future::Future<Output = R> + Send + 'static,
R: Send + 'static,
{
let (tx, rx) = std::sync::mpsc::sync_channel(1);
GLOBAL_RUNTIME.handle().spawn(async move {
let res = fut.await;
// best-effort send; ignore if receiver dropped
let _ = tx.send(res);
});
rx.recv().expect("runtime worker panicked")
}
pub fn run_sync_local<F, R>(fut: F) -> R
where
F: std::future::Future<Output = R>,
R: 'static,
{
LOCAL_RUNTIME.block_on(fut)
}
pub const LOCKDOWN_PORT: u16 = 62078;
#[repr(C)]
@@ -164,47 +126,6 @@ pub unsafe extern "C" fn idevice_new(
null_mut()
}
/// Creates an Idevice object from a socket file descriptor
///
/// # Safety
/// The socket FD must be valid.
/// The pointers must be valid and non-null.
#[cfg(unix)]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn idevice_from_fd(
fd: i32,
label: *const c_char,
idevice: *mut *mut IdeviceHandle,
) -> *mut IdeviceFfiError {
if label.is_null() || idevice.is_null() || fd == 0 {
return ffi_err!(IdeviceError::FfiInvalidArg);
}
// Get socket ownership
let fd = unsafe { libc::dup(fd) };
let socket = unsafe { <std::net::TcpStream as std::os::fd::FromRawFd>::from_raw_fd(fd) };
if let Err(e) = socket.set_nonblocking(true) {
return ffi_err!(e);
}
let socket = match run_sync(async move { tokio::net::TcpStream::from_std(socket) }) {
Ok(s) => s,
Err(e) => return ffi_err!(e),
};
// Convert C string to Rust string
let c_str = match unsafe { CStr::from_ptr(label).to_str() } {
Ok(s) => s,
Err(_) => return ffi_err!(IdeviceError::FfiInvalidString),
};
// Create new Idevice instance
let dev = Idevice::new(Box::new(socket), c_str);
let boxed = Box::new(IdeviceHandle(dev));
unsafe { *idevice = Box::into_raw(boxed) };
null_mut()
}
/// Creates a new Idevice connection
///
/// # Arguments
@@ -231,7 +152,7 @@ pub unsafe extern "C" fn idevice_new_tcp_socket(
use crate::util::SockAddr;
if addr.is_null() || label.is_null() || idevice.is_null() {
tracing::error!("null pointer(s) to idevice_new_tcp_socket");
log::error!("null pointer(s) to idevice_new_tcp_socket");
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let addr = addr as *const SockAddr;
@@ -246,7 +167,7 @@ pub unsafe extern "C" fn idevice_new_tcp_socket(
Err(e) => return ffi_err!(e),
};
let device = run_sync(async move {
let device = RUNTIME.block_on(async move {
let stream = tokio::net::TcpStream::connect(addr).await?;
Ok::<idevice::Idevice, idevice::IdeviceError>(idevice::Idevice::new(
Box::new(stream),
@@ -289,7 +210,7 @@ pub unsafe extern "C" fn idevice_get_type(
let dev = unsafe { &mut (*idevice).0 };
// Run the get_type method in the runtime
let result = run_sync(async { dev.get_type().await });
let result = RUNTIME.block_on(async { dev.get_type().await });
match result {
Ok(type_str) => match CString::new(type_str) {
@@ -323,7 +244,7 @@ pub unsafe extern "C" fn idevice_rsd_checkin(idevice: *mut IdeviceHandle) -> *mu
let dev = unsafe { &mut (*idevice).0 };
// Run the rsd_checkin method in the runtime
let result = run_sync(async { dev.rsd_checkin().await });
let result = RUNTIME.block_on(async { dev.rsd_checkin().await });
match result {
Ok(_) => null_mut(),
@@ -347,7 +268,6 @@ pub unsafe extern "C" fn idevice_rsd_checkin(idevice: *mut IdeviceHandle) -> *mu
pub unsafe extern "C" fn idevice_start_session(
idevice: *mut IdeviceHandle,
pairing_file: *const IdevicePairingFile,
legacy: bool,
) -> *mut IdeviceFfiError {
if idevice.is_null() || pairing_file.is_null() {
return ffi_err!(IdeviceError::FfiInvalidArg);
@@ -360,7 +280,7 @@ pub unsafe extern "C" fn idevice_start_session(
let pf = unsafe { &(*pairing_file).0 };
// Run the start_session method in the runtime
let result = run_sync(async move { dev.start_session(pf, legacy).await });
let result = RUNTIME.block_on(async { dev.start_session(pf).await });
match result {
Ok(_) => null_mut(),
@@ -383,17 +303,6 @@ pub unsafe extern "C" fn idevice_free(idevice: *mut IdeviceHandle) {
}
}
/// Frees a stream handle
///
/// # Safety
/// Pass a valid handle allocated by this library
#[unsafe(no_mangle)]
pub unsafe extern "C" fn idevice_stream_free(stream_handle: *mut ReadWriteOpaque) {
if !stream_handle.is_null() {
let _ = unsafe { Box::from_raw(stream_handle) };
}
}
/// Frees a string allocated by this library
///
/// # Arguments
@@ -420,36 +329,6 @@ pub unsafe extern "C" fn idevice_string_free(string: *mut c_char) {
#[unsafe(no_mangle)]
pub unsafe extern "C" fn idevice_data_free(data: *mut u8, len: usize) {
if !data.is_null() {
let _ = unsafe { Vec::from_raw_parts(data, len, len) };
}
}
/// Frees an array of plists allocated by this library
///
/// # Safety
/// `data` must be a pointer to data allocated by this library,
/// NOT data allocated by libplist.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn idevice_plist_array_free(plists: *mut plist_t, len: usize) {
if !plists.is_null() {
let data = unsafe { std::slice::from_raw_parts(plists, len) };
for x in data {
unsafe { plist_ffi::creation::plist_free((*x) as *mut PlistWrapper) };
}
}
}
/// Frees a slice of pointers allocated by this library that had an underlying
/// vec creation.
///
/// The following functions use an underlying vec and are safe to use:
/// - idevice_usbmuxd_get_devices
///
/// # Safety
/// Pass a valid pointer passed by the Vec creating functions
#[unsafe(no_mangle)]
pub unsafe extern "C" fn idevice_outer_slice_free(slice: *mut c_void, len: usize) {
if !slice.is_null() {
let _ = unsafe { Vec::from_raw_parts(slice, len, len) };
let _ = unsafe { std::slice::from_raw_parts(data, len) };
}
}

View File

@@ -4,7 +4,7 @@ use std::ptr::null_mut;
use idevice::{ReadWrite, dvt::location_simulation::LocationSimulationClient};
use crate::{IdeviceFfiError, dvt::remote_server::RemoteServerHandle, ffi_err, run_sync};
use crate::{IdeviceFfiError, RUNTIME, ffi_err, remote_server::RemoteServerHandle};
/// Opaque handle to a ProcessControlClient
pub struct LocationSimulationHandle<'a>(pub LocationSimulationClient<'a, Box<dyn ReadWrite>>);
@@ -31,7 +31,7 @@ pub unsafe extern "C" fn location_simulation_new(
}
let server = unsafe { &mut (*server).0 };
let res = run_sync(async move { LocationSimulationClient::new(server).await });
let res = RUNTIME.block_on(async move { LocationSimulationClient::new(server).await });
match res {
Ok(client) => {
@@ -76,7 +76,7 @@ pub unsafe extern "C" fn location_simulation_clear(
}
let client = unsafe { &mut (*handle).0 };
let res = run_sync(async move { client.clear().await });
let res = RUNTIME.block_on(async move { client.clear().await });
match res {
Ok(_) => null_mut(),
@@ -107,7 +107,7 @@ pub unsafe extern "C" fn location_simulation_set(
}
let client = unsafe { &mut (*handle).0 };
let res = run_sync(async move { client.set(latitude, longitude).await });
let res = RUNTIME.block_on(async move { client.set(latitude, longitude).await });
match res {
Ok(_) => null_mut(),

View File

@@ -6,8 +6,8 @@ use idevice::{IdeviceError, IdeviceService, lockdown::LockdownClient, provider::
use plist_ffi::plist_t;
use crate::{
IdeviceFfiError, IdeviceHandle, IdevicePairingFile, ffi_err, provider::IdeviceProviderHandle,
run_sync_local,
IdeviceFfiError, IdeviceHandle, IdevicePairingFile, RUNTIME, ffi_err,
provider::IdeviceProviderHandle,
};
pub struct LockdowndClientHandle(pub LockdownClient);
@@ -30,11 +30,11 @@ pub unsafe extern "C" fn lockdownd_connect(
client: *mut *mut LockdowndClientHandle,
) -> *mut IdeviceFfiError {
if provider.is_null() || client.is_null() {
tracing::error!("Null pointer provided");
log::error!("Null pointer provided");
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let res: Result<LockdownClient, IdeviceError> = run_sync_local(async move {
let res: Result<LockdownClient, IdeviceError> = RUNTIME.block_on(async move {
let provider_ref: &dyn IdeviceProvider = unsafe { &*(*provider).0 };
LockdownClient::connect(provider_ref).await
});
@@ -97,7 +97,7 @@ pub unsafe extern "C" fn lockdownd_start_session(
client: *mut LockdowndClientHandle,
pairing_file: *mut IdevicePairingFile,
) -> *mut IdeviceFfiError {
let res: Result<(), IdeviceError> = run_sync_local(async move {
let res: Result<(), IdeviceError> = RUNTIME.block_on(async move {
let client_ref = unsafe { &mut (*client).0 };
let pairing_file_ref = unsafe { &(*pairing_file).0 };
@@ -140,7 +140,7 @@ pub unsafe extern "C" fn lockdownd_start_service(
.to_string_lossy()
.into_owned();
let res: Result<(u16, bool), IdeviceError> = run_sync_local(async move {
let res: Result<(u16, bool), IdeviceError> = RUNTIME.block_on(async move {
let client_ref = unsafe { &mut (*client).0 };
client_ref.start_service(identifier).await
});
@@ -179,7 +179,7 @@ pub unsafe extern "C" fn lockdownd_get_value(
domain: *const libc::c_char,
out_plist: *mut plist_t,
) -> *mut IdeviceFfiError {
if client.is_null() || out_plist.is_null() {
if out_plist.is_null() {
return ffi_err!(IdeviceError::FfiInvalidArg);
}
@@ -205,7 +205,7 @@ pub unsafe extern "C" fn lockdownd_get_value(
})
};
let res: Result<plist::Value, IdeviceError> = run_sync_local(async move {
let res: Result<plist::Value, IdeviceError> = RUNTIME.block_on(async move {
let client_ref = unsafe { &mut (*client).0 };
client_ref.get_value(value, domain).await
});
@@ -221,89 +221,6 @@ pub unsafe extern "C" fn lockdownd_get_value(
}
}
/// Tells the device to enter recovery mode
///
/// # Arguments
/// * `client` - A valid LockdowndClient handle
///
/// # Returns
/// An IdeviceFfiError on error, null on success
///
/// # Safety
/// `client` must be a valid pointer to a handle allocated by this library
#[unsafe(no_mangle)]
pub unsafe extern "C" fn lockdownd_enter_recovery(
client: *mut LockdowndClientHandle,
) -> *mut IdeviceFfiError {
if client.is_null() {
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let res: Result<(), IdeviceError> = run_sync_local(async move {
let client_ref = unsafe { &mut (*client).0 };
client_ref.enter_recovery().await
});
match res {
Ok(_) => null_mut(),
Err(e) => ffi_err!(e),
}
}
/// Sets a value in lockdownd
///
/// # Arguments
/// * `client` - A valid LockdowndClient handle
/// * `key` - The key to set (null-terminated string)
/// * `value` - The value to set as a plist
/// * `domain` - The domain to set in (null-terminated string, optional)
///
/// # Returns
/// An IdeviceFfiError on error, null on success
///
/// # Safety
/// `client` must be a valid pointer to a handle allocated by this library
/// `key` must be a valid null-terminated string
/// `value` must be a valid plist
/// `domain` must be a valid null-terminated string or NULL
#[unsafe(no_mangle)]
pub unsafe extern "C" fn lockdownd_set_value(
client: *mut LockdowndClientHandle,
key: *const libc::c_char,
value: plist_t,
domain: *const libc::c_char,
) -> *mut IdeviceFfiError {
if client.is_null() || key.is_null() || value.is_null() {
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let key = match unsafe { std::ffi::CStr::from_ptr(key) }.to_str() {
Ok(k) => k,
Err(_) => return ffi_err!(IdeviceError::InvalidCString),
};
let domain = if domain.is_null() {
None
} else {
Some(match unsafe { std::ffi::CStr::from_ptr(domain) }.to_str() {
Ok(d) => d,
Err(_) => return ffi_err!(IdeviceError::InvalidCString),
})
};
let value = unsafe { &mut *value }.borrow_self().clone();
let res: Result<(), IdeviceError> = run_sync_local(async move {
let client_ref = unsafe { &mut (*client).0 };
client_ref.set_value(key, value, domain).await
});
match res {
Ok(_) => null_mut(),
Err(e) => ffi_err!(e),
}
}
/// Frees a LockdowndClient handle
///
/// # Arguments
@@ -315,7 +232,7 @@ pub unsafe extern "C" fn lockdownd_set_value(
#[unsafe(no_mangle)]
pub unsafe extern "C" fn lockdownd_client_free(handle: *mut LockdowndClientHandle) {
if !handle.is_null() {
tracing::debug!("Freeing lockdownd_client");
log::debug!("Freeing lockdownd_client");
let _ = unsafe { Box::from_raw(handle) };
}
}

View File

@@ -3,13 +3,75 @@
use std::{
ffi::{CStr, c_char},
fs::File,
sync::Once,
};
use tracing::Level;
use tracing_appender::non_blocking;
use tracing_subscriber::{EnvFilter, Layer};
use tracing_subscriber::{Registry, fmt, layer::SubscriberExt, util::SubscriberInitExt};
use log::LevelFilter;
use simplelog::{
ColorChoice, CombinedLogger, Config, SharedLogger, TermLogger, TerminalMode, WriteLogger,
};
/// Initializes the logger
///
/// # Arguments
/// * [`console_level`] - The level to log to the file
/// * [`file_level`] - The level to log to the file
/// * [`file_path`] - If not null, the file to write logs to
///
/// ## Log Level
/// 0. Disabled
/// 1. Error
/// 2. Warn
/// 3. Info
/// 4. Debug
/// 5. Trace
///
/// # Returns
/// 0 for success, -1 if the file couldn't be created, -2 if a logger has been initialized, -3 for invalid path string
///
/// # Safety
/// Pass a valid CString for file_path. Pass valid log levels according to the enum
#[unsafe(no_mangle)]
pub unsafe extern "C" fn idevice_init_logger(
console_level: IdeviceLogLevel,
file_level: IdeviceLogLevel,
file_path: *mut c_char,
) -> IdeviceLoggerError {
let mut loggers: Vec<Box<dyn SharedLogger>> = Vec::new();
let level: LevelFilter = console_level.into();
loggers.push(TermLogger::new(
level,
Config::default(),
TerminalMode::Mixed,
ColorChoice::Auto,
));
if !file_path.is_null() {
let file_path = match unsafe { CStr::from_ptr(file_path) }.to_str() {
Ok(f) => f.to_string(),
Err(_) => {
return IdeviceLoggerError::InvalidPathString;
}
};
let level: LevelFilter = file_level.into();
loggers.push(WriteLogger::new(
level,
Config::default(),
match File::create(file_path) {
Ok(f) => f,
Err(e) => {
println!("Failed to create path: {e:?}");
return IdeviceLoggerError::FileError;
}
},
));
}
if CombinedLogger::init(loggers).is_err() {
IdeviceLoggerError::AlreadyInitialized
} else {
IdeviceLoggerError::Success
}
}
#[repr(C)]
pub enum IdeviceLoggerError {
@@ -30,113 +92,33 @@ pub enum IdeviceLogLevel {
Trace = 5,
}
impl From<IdeviceLogLevel> for Level {
impl TryFrom<u8> for IdeviceLogLevel {
type Error = ();
fn try_from(value: u8) -> Result<Self, Self::Error> {
Ok(match value {
0 => Self::Disabled,
1 => Self::ErrorLevel,
2 => Self::Warn,
3 => Self::Info,
4 => Self::Debug,
5 => Self::Trace,
_ => {
return Err(());
}
})
}
}
impl From<IdeviceLogLevel> for LevelFilter {
fn from(value: IdeviceLogLevel) -> Self {
match value {
IdeviceLogLevel::Disabled => Level::ERROR, // won't matter, filter will disable
IdeviceLogLevel::ErrorLevel => Level::ERROR,
IdeviceLogLevel::Warn => Level::WARN,
IdeviceLogLevel::Info => Level::INFO,
IdeviceLogLevel::Debug => Level::DEBUG,
IdeviceLogLevel::Trace => Level::TRACE,
IdeviceLogLevel::Disabled => LevelFilter::Off,
IdeviceLogLevel::ErrorLevel => LevelFilter::Error,
IdeviceLogLevel::Warn => LevelFilter::Warn,
IdeviceLogLevel::Info => LevelFilter::Info,
IdeviceLogLevel::Debug => LevelFilter::Debug,
IdeviceLogLevel::Trace => LevelFilter::Trace,
}
}
}
impl IdeviceLogLevel {
fn as_filter(&self) -> String {
match self {
IdeviceLogLevel::Disabled => "off",
IdeviceLogLevel::ErrorLevel => "error",
IdeviceLogLevel::Warn => "warn",
IdeviceLogLevel::Info => "info",
IdeviceLogLevel::Debug => "debug",
IdeviceLogLevel::Trace => "trace",
}
.to_string()
}
}
// ensures we only init once
static INIT: Once = Once::new();
static mut GUARDS: Option<(
Option<tracing_appender::non_blocking::WorkerGuard>,
Option<tracing_appender::non_blocking::WorkerGuard>,
)> = None;
/// Initializes the global logger
///
/// # Safety
/// Pass a valid file path string
#[unsafe(no_mangle)]
pub unsafe extern "C" fn idevice_init_logger(
console_level: IdeviceLogLevel,
file_level: IdeviceLogLevel,
file_path: *mut c_char,
) -> IdeviceLoggerError {
let mut init_result = IdeviceLoggerError::Success;
INIT.call_once(|| {
let console_filter = console_level.as_filter();
let file_filter = file_level.as_filter();
let mut layers = Vec::new();
let mut console_guard = None;
let mut file_guard = None;
// ---- Console layer ----
if console_level != IdeviceLogLevel::Disabled {
let (non_blocking, guard) = non_blocking(std::io::stdout());
console_guard = Some(guard);
let console_layer = fmt::layer()
.with_writer(non_blocking)
.with_ansi(true)
.with_target(false)
.with_target(true)
.with_filter(EnvFilter::new(console_filter));
layers.push(console_layer.boxed());
}
// ---- File layer ----
if !file_path.is_null() && file_level != IdeviceLogLevel::Disabled {
let path = match unsafe { CStr::from_ptr(file_path).to_str() } {
Ok(p) => p,
Err(_) => {
init_result = IdeviceLoggerError::InvalidPathString;
return;
}
};
match File::create(path) {
Ok(f) => {
let (non_blocking, guard) = non_blocking(f);
file_guard = Some(guard);
let file_layer = fmt::layer()
.with_writer(non_blocking)
.with_ansi(false)
.with_target(false)
.with_filter(EnvFilter::new(file_filter));
layers.push(file_layer.boxed());
}
Err(_) => {
init_result = IdeviceLoggerError::FileError;
return;
}
};
}
// Keep guards alive (otherwise background threads die)
unsafe {
GUARDS = Some((console_guard, file_guard));
}
let subscriber = Registry::default().with(layers);
subscriber.init();
});
if !INIT.is_completed() {
IdeviceLoggerError::AlreadyInitialized
} else {
init_result
}
}

View File

@@ -6,7 +6,7 @@ use std::ptr::null_mut;
use idevice::{IdeviceError, IdeviceService, misagent::MisagentClient, provider::IdeviceProvider};
use crate::{IdeviceFfiError, ffi_err, provider::IdeviceProviderHandle, run_sync_local};
use crate::{IdeviceFfiError, RUNTIME, ffi_err, provider::IdeviceProviderHandle};
pub struct MisagentClientHandle(pub MisagentClient);
@@ -28,11 +28,11 @@ pub unsafe extern "C" fn misagent_connect(
client: *mut *mut MisagentClientHandle,
) -> *mut IdeviceFfiError {
if provider.is_null() || client.is_null() {
tracing::error!("Null pointer provided");
log::error!("Null pointer provided");
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let res: Result<MisagentClient, IdeviceError> = run_sync_local(async move {
let res: Result<MisagentClient, IdeviceError> = RUNTIME.block_on(async move {
let provider_ref: &dyn IdeviceProvider = unsafe { &*(*provider).0 };
MisagentClient::connect(provider_ref).await
});
@@ -72,7 +72,7 @@ pub unsafe extern "C" fn misagent_install(
let profile = unsafe { std::slice::from_raw_parts(profile_data, profile_len) }.to_vec();
let res = run_sync_local(async { unsafe { &mut *client }.0.install(profile).await });
let res = RUNTIME.block_on(async { unsafe { &mut *client }.0.install(profile).await });
match res {
Ok(_) => null_mut(),
@@ -105,7 +105,7 @@ pub unsafe extern "C" fn misagent_remove(
.to_string_lossy()
.into_owned();
let res = run_sync_local(async { unsafe { &mut *client }.0.remove(&id).await });
let res = RUNTIME.block_on(async { unsafe { &mut *client }.0.remove(&id).await });
match res {
Ok(_) => null_mut(),
@@ -143,7 +143,7 @@ pub unsafe extern "C" fn misagent_copy_all(
}
let res: Result<Vec<Vec<u8>>, IdeviceError> =
run_sync_local(async { unsafe { &mut *client }.0.copy_all().await });
RUNTIME.block_on(async { unsafe { &mut *client }.0.copy_all().await });
match res {
Ok(profiles) => {
@@ -199,7 +199,7 @@ pub unsafe extern "C" fn misagent_free_profiles(
for (ptr, len) in profiles.iter_mut().zip(lens.iter()) {
if !ptr.is_null() {
let _ = unsafe { Box::from_raw(std::ptr::slice_from_raw_parts_mut(*ptr, *len)) };
let _ = unsafe { Box::from_raw(std::slice::from_raw_parts_mut(*ptr, *len)) };
}
}
@@ -218,7 +218,7 @@ pub unsafe extern "C" fn misagent_free_profiles(
#[unsafe(no_mangle)]
pub unsafe extern "C" fn misagent_client_free(handle: *mut MisagentClientHandle) {
if !handle.is_null() {
tracing::debug!("Freeing misagent_client");
log::debug!("Freeing misagent_client");
let _ = unsafe { Box::from_raw(handle) };
}
}

View File

@@ -8,9 +8,7 @@ use idevice::{
use plist::Value;
use plist_ffi::{PlistWrapper, plist_t};
use crate::{
IdeviceFfiError, IdeviceHandle, ffi_err, provider::IdeviceProviderHandle, run_sync_local,
};
use crate::{IdeviceFfiError, IdeviceHandle, RUNTIME, ffi_err, provider::IdeviceProviderHandle};
pub struct ImageMounterHandle(pub ImageMounter);
@@ -32,11 +30,11 @@ pub unsafe extern "C" fn image_mounter_connect(
client: *mut *mut ImageMounterHandle,
) -> *mut IdeviceFfiError {
if provider.is_null() || client.is_null() {
tracing::error!("Null pointer provided");
log::error!("Null pointer provided");
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let res: Result<ImageMounter, IdeviceError> = run_sync_local(async move {
let res: Result<ImageMounter, IdeviceError> = RUNTIME.block_on(async move {
let provider_ref: &dyn IdeviceProvider = unsafe { &*(*provider).0 };
ImageMounter::connect(provider_ref).await
});
@@ -92,7 +90,7 @@ pub unsafe extern "C" fn image_mounter_new(
#[unsafe(no_mangle)]
pub unsafe extern "C" fn image_mounter_free(handle: *mut ImageMounterHandle) {
if !handle.is_null() {
tracing::debug!("Freeing image_mounter_client");
log::debug!("Freeing image_mounter_client");
let _ = unsafe { Box::from_raw(handle) };
}
}
@@ -116,7 +114,7 @@ pub unsafe extern "C" fn image_mounter_copy_devices(
devices: *mut *mut plist_t,
devices_len: *mut libc::size_t,
) -> *mut IdeviceFfiError {
let res: Result<Vec<Value>, IdeviceError> = run_sync_local(async move {
let res: Result<Vec<Value>, IdeviceError> = RUNTIME.block_on(async move {
let client_ref = unsafe { &mut (*client).0 };
client_ref.copy_devices().await
});
@@ -173,7 +171,7 @@ pub unsafe extern "C" fn image_mounter_lookup_image(
Err(_) => return ffi_err!(IdeviceError::FfiInvalidArg),
};
let res: Result<Vec<u8>, IdeviceError> = run_sync_local(async move {
let res: Result<Vec<u8>, IdeviceError> = RUNTIME.block_on(async move {
let client_ref = unsafe { &mut (*client).0 };
client_ref.lookup_image(image_type).await
});
@@ -230,7 +228,7 @@ pub unsafe extern "C" fn image_mounter_upload_image(
let image_slice = unsafe { std::slice::from_raw_parts(image, image_len) };
let signature_slice = unsafe { std::slice::from_raw_parts(signature, signature_len) };
let res: Result<(), IdeviceError> = run_sync_local(async move {
let res: Result<(), IdeviceError> = RUNTIME.block_on(async move {
let client_ref = unsafe { &mut (*client).0 };
client_ref
.upload_image(image_type, image_slice, signature_slice.to_vec())
@@ -297,7 +295,7 @@ pub unsafe extern "C" fn image_mounter_mount_image(
None
};
let res: Result<(), IdeviceError> = run_sync_local(async move {
let res: Result<(), IdeviceError> = RUNTIME.block_on(async move {
let client_ref = unsafe { &mut (*client).0 };
client_ref
.mount_image(
@@ -342,7 +340,7 @@ pub unsafe extern "C" fn image_mounter_unmount_image(
Err(_) => return ffi_err!(IdeviceError::FfiInvalidArg),
};
let res: Result<(), IdeviceError> = run_sync_local(async move {
let res: Result<(), IdeviceError> = RUNTIME.block_on(async move {
let client_ref = unsafe { &mut (*client).0 };
client_ref.unmount_image(mount_path).await
});
@@ -374,7 +372,7 @@ pub unsafe extern "C" fn image_mounter_query_developer_mode_status(
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let res: Result<bool, IdeviceError> = run_sync_local(async move {
let res: Result<bool, IdeviceError> = RUNTIME.block_on(async move {
let client_ref = unsafe { &mut (*client).0 };
client_ref.query_developer_mode_status().await
});
@@ -417,7 +415,7 @@ pub unsafe extern "C" fn image_mounter_mount_developer(
let image_slice = unsafe { std::slice::from_raw_parts(image, image_len) };
let signature_slice = unsafe { std::slice::from_raw_parts(signature, signature_len) };
let res: Result<(), IdeviceError> = run_sync_local(async move {
let res: Result<(), IdeviceError> = RUNTIME.block_on(async move {
let client_ref = unsafe { &mut (*client).0 };
client_ref
.mount_developer(image_slice, signature_slice.to_vec())
@@ -467,7 +465,7 @@ 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> = run_sync_local(async move {
let res: Result<Vec<u8>, IdeviceError> = RUNTIME.block_on(async move {
let client_ref = unsafe { &mut (*client).0 };
client_ref
.query_personalization_manifest(image_type, signature_slice.to_vec())
@@ -523,7 +521,7 @@ pub unsafe extern "C" fn image_mounter_query_nonce(
None
};
let res: Result<Vec<u8>, IdeviceError> = run_sync_local(async move {
let res: Result<Vec<u8>, IdeviceError> = RUNTIME.block_on(async move {
let client_ref = unsafe { &mut (*client).0 };
client_ref.query_nonce(image_type).await
});
@@ -575,7 +573,7 @@ pub unsafe extern "C" fn image_mounter_query_personalization_identifiers(
None
};
let res: Result<plist::Dictionary, IdeviceError> = run_sync_local(async move {
let res: Result<plist::Dictionary, IdeviceError> = RUNTIME.block_on(async move {
let client_ref = unsafe { &mut (*client).0 };
client_ref
.query_personalization_identifiers(image_type)
@@ -606,7 +604,7 @@ pub unsafe extern "C" fn image_mounter_query_personalization_identifiers(
pub unsafe extern "C" fn image_mounter_roll_personalization_nonce(
client: *mut ImageMounterHandle,
) -> *mut IdeviceFfiError {
let res: Result<(), IdeviceError> = run_sync_local(async move {
let res: Result<(), IdeviceError> = RUNTIME.block_on(async move {
let client_ref = unsafe { &mut (*client).0 };
client_ref.roll_personalization_nonce().await
});
@@ -631,7 +629,7 @@ pub unsafe extern "C" fn image_mounter_roll_personalization_nonce(
pub unsafe extern "C" fn image_mounter_roll_cryptex_nonce(
client: *mut ImageMounterHandle,
) -> *mut IdeviceFfiError {
let res: Result<(), IdeviceError> = run_sync_local(async move {
let res: Result<(), IdeviceError> = RUNTIME.block_on(async move {
let client_ref = unsafe { &mut (*client).0 };
client_ref.roll_cryptex_nonce().await
});
@@ -694,7 +692,7 @@ pub unsafe extern "C" fn image_mounter_mount_personalized(
None
};
let res: Result<(), IdeviceError> = run_sync_local(async move {
let res: Result<(), IdeviceError> = RUNTIME.block_on(async move {
let client_ref = unsafe { &mut (*client).0 };
let provider_ref: &dyn IdeviceProvider = unsafe { &*(*provider).0 };
client_ref
@@ -771,7 +769,7 @@ pub unsafe extern "C" fn image_mounter_mount_personalized_with_callback(
None
};
let res: Result<(), IdeviceError> = run_sync_local(async move {
let res: Result<(), IdeviceError> = RUNTIME.block_on(async move {
let client_ref = unsafe { &mut (*client).0 };
let provider_ref: &dyn IdeviceProvider = unsafe { &*(*provider).0 };

View File

@@ -1,311 +0,0 @@
// Jackson Coxson
use std::ffi::{CStr, CString, c_char};
use std::ptr::null_mut;
use idevice::{
IdeviceError, IdeviceService, notification_proxy::NotificationProxyClient,
provider::IdeviceProvider,
};
use crate::{
IdeviceFfiError, IdeviceHandle, ffi_err, provider::IdeviceProviderHandle, run_sync_local,
};
pub struct NotificationProxyClientHandle(pub NotificationProxyClient);
/// Automatically creates and connects to Notification Proxy, returning a client handle
///
/// # Arguments
/// * [`provider`] - An IdeviceProvider
/// * [`client`] - On success, will be set to point to a newly allocated NotificationProxyClient handle
///
/// # Returns
/// An IdeviceFfiError on error, null on success
///
/// # 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 notification_proxy_connect(
provider: *mut IdeviceProviderHandle,
client: *mut *mut NotificationProxyClientHandle,
) -> *mut IdeviceFfiError {
if provider.is_null() || client.is_null() {
tracing::error!("Null pointer provided");
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let res: Result<NotificationProxyClient, IdeviceError> = run_sync_local(async move {
let provider_ref: &dyn IdeviceProvider = unsafe { &*(*provider).0 };
NotificationProxyClient::connect(provider_ref).await
});
match res {
Ok(r) => {
let boxed = Box::new(NotificationProxyClientHandle(r));
unsafe { *client = Box::into_raw(boxed) };
null_mut()
}
Err(e) => {
ffi_err!(e)
}
}
}
/// Creates a new NotificationProxyClient from an existing Idevice connection
///
/// # Arguments
/// * [`socket`] - An IdeviceSocket handle
/// * [`client`] - On success, will be set to point to a newly allocated NotificationProxyClient handle
///
/// # Returns
/// An IdeviceFfiError on error, null on success
///
/// # Safety
/// `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 notification_proxy_new(
socket: *mut IdeviceHandle,
client: *mut *mut NotificationProxyClientHandle,
) -> *mut IdeviceFfiError {
if socket.is_null() || client.is_null() {
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let socket = unsafe { Box::from_raw(socket) }.0;
let r = NotificationProxyClient::new(socket);
let boxed = Box::new(NotificationProxyClientHandle(r));
unsafe { *client = Box::into_raw(boxed) };
null_mut()
}
/// Posts a notification to the device
///
/// # Arguments
/// * `client` - A valid NotificationProxyClient handle
/// * `name` - C string containing the notification name
///
/// # Returns
/// An IdeviceFfiError on error, null on success
///
/// # Safety
/// `client` must be a valid pointer to a handle allocated by this library
/// `name` must be a valid null-terminated C string
#[unsafe(no_mangle)]
pub unsafe extern "C" fn notification_proxy_post(
client: *mut NotificationProxyClientHandle,
name: *const c_char,
) -> *mut IdeviceFfiError {
if client.is_null() || name.is_null() {
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let name_str = match unsafe { CStr::from_ptr(name) }.to_str() {
Ok(s) => s.to_string(),
Err(_) => return ffi_err!(IdeviceError::FfiInvalidString),
};
let res: Result<(), IdeviceError> = run_sync_local(async move {
let client_ref = unsafe { &mut (*client).0 };
client_ref.post_notification(name_str).await
});
match res {
Ok(_) => null_mut(),
Err(e) => ffi_err!(e),
}
}
/// Observes a specific notification
///
/// # Arguments
/// * `client` - A valid NotificationProxyClient handle
/// * `name` - C string containing the notification name to observe
///
/// # Returns
/// An IdeviceFfiError on error, null on success
///
/// # Safety
/// `client` must be a valid pointer to a handle allocated by this library
/// `name` must be a valid null-terminated C string
#[unsafe(no_mangle)]
pub unsafe extern "C" fn notification_proxy_observe(
client: *mut NotificationProxyClientHandle,
name: *const c_char,
) -> *mut IdeviceFfiError {
if client.is_null() || name.is_null() {
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let name_str = match unsafe { CStr::from_ptr(name) }.to_str() {
Ok(s) => s.to_string(),
Err(_) => return ffi_err!(IdeviceError::FfiInvalidString),
};
let res: Result<(), IdeviceError> = run_sync_local(async move {
let client_ref = unsafe { &mut (*client).0 };
client_ref.observe_notification(name_str).await
});
match res {
Ok(_) => null_mut(),
Err(e) => ffi_err!(e),
}
}
/// Observes multiple notifications at once
///
/// # Arguments
/// * `client` - A valid NotificationProxyClient handle
/// * `names` - A null-terminated array of C strings containing notification names
///
/// # Returns
/// An IdeviceFfiError on error, null on success
///
/// # Safety
/// `client` must be a valid pointer to a handle allocated by this library
/// `names` must be a valid pointer to a null-terminated array of null-terminated C strings
#[unsafe(no_mangle)]
pub unsafe extern "C" fn notification_proxy_observe_multiple(
client: *mut NotificationProxyClientHandle,
names: *const *const c_char,
) -> *mut IdeviceFfiError {
if client.is_null() || names.is_null() {
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let mut notification_names: Vec<String> = Vec::new();
let mut i = 0;
loop {
let ptr = unsafe { *names.add(i) };
if ptr.is_null() {
break;
}
match unsafe { CStr::from_ptr(ptr) }.to_str() {
Ok(s) => notification_names.push(s.to_string()),
Err(_) => return ffi_err!(IdeviceError::FfiInvalidString),
}
i += 1;
}
let refs: Vec<&str> = notification_names.iter().map(|s| s.as_str()).collect();
let res: Result<(), IdeviceError> = run_sync_local(async move {
let client_ref = unsafe { &mut (*client).0 };
client_ref.observe_notifications(&refs).await
});
match res {
Ok(_) => null_mut(),
Err(e) => ffi_err!(e),
}
}
/// Receives the next notification from the device
///
/// # Arguments
/// * `client` - A valid NotificationProxyClient handle
/// * `name_out` - On success, will be set to a newly allocated C string containing the notification name
///
/// # Returns
/// An IdeviceFfiError on error, null on success
///
/// # Safety
/// `client` must be a valid pointer to a handle allocated by this library
/// `name_out` must be a valid pointer. The returned string must be freed with `notification_proxy_free_string`
#[unsafe(no_mangle)]
pub unsafe extern "C" fn notification_proxy_receive(
client: *mut NotificationProxyClientHandle,
name_out: *mut *mut c_char,
) -> *mut IdeviceFfiError {
if client.is_null() || name_out.is_null() {
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let res: Result<String, IdeviceError> = run_sync_local(async move {
let client_ref = unsafe { &mut (*client).0 };
client_ref.receive_notification().await
});
match res {
Ok(name) => match CString::new(name) {
Ok(c_string) => {
unsafe { *name_out = c_string.into_raw() };
null_mut()
}
Err(_) => ffi_err!(IdeviceError::FfiInvalidString),
},
Err(e) => ffi_err!(e),
}
}
/// Receives the next notification with a timeout
///
/// # Arguments
/// * `client` - A valid NotificationProxyClient handle
/// * `interval` - Timeout in seconds to wait for a notification
/// * `name_out` - On success, will be set to a newly allocated C string containing the notification name
///
/// # Returns
/// An IdeviceFfiError on error, null on success
///
/// # Safety
/// `client` must be a valid pointer to a handle allocated by this library
/// `name_out` must be a valid pointer. The returned string must be freed with `notification_proxy_free_string`
#[unsafe(no_mangle)]
pub unsafe extern "C" fn notification_proxy_receive_with_timeout(
client: *mut NotificationProxyClientHandle,
interval: u64,
name_out: *mut *mut c_char,
) -> *mut IdeviceFfiError {
if client.is_null() || name_out.is_null() {
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let res: Result<String, IdeviceError> = run_sync_local(async move {
let client_ref = unsafe { &mut (*client).0 };
client_ref.receive_notification_with_timeout(interval).await
});
match res {
Ok(name) => match CString::new(name) {
Ok(c_string) => {
unsafe { *name_out = c_string.into_raw() };
null_mut()
}
Err(_) => ffi_err!(IdeviceError::FfiInvalidString),
},
Err(e) => ffi_err!(e),
}
}
/// Frees a string returned by notification_proxy_receive
///
/// # Safety
/// `s` must be a valid pointer returned from `notification_proxy_receive`
#[unsafe(no_mangle)]
pub unsafe extern "C" fn notification_proxy_free_string(s: *mut c_char) {
if !s.is_null() {
let _ = unsafe { CString::from_raw(s) };
}
}
/// Frees a handle
///
/// # Arguments
/// * [`handle`] - The handle to free
///
/// # Safety
/// `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 unsafe extern "C" fn notification_proxy_client_free(
handle: *mut NotificationProxyClientHandle,
) {
if !handle.is_null() {
tracing::debug!("Freeing notification_proxy_client");
let _ = unsafe { Box::from_raw(handle) };
}
}

View File

@@ -5,8 +5,7 @@ use idevice::{
IdeviceError, IdeviceService, os_trace_relay::OsTraceRelayClient, provider::IdeviceProvider,
};
use crate::run_sync_local;
use crate::{IdeviceFfiError, ffi_err, provider::IdeviceProviderHandle};
use crate::{IdeviceFfiError, RUNTIME, ffi_err, provider::IdeviceProviderHandle};
pub struct OsTraceRelayClientHandle(pub OsTraceRelayClient);
pub struct OsTraceRelayReceiverHandle(pub idevice::os_trace_relay::OsTraceRelayReceiver);
@@ -47,11 +46,11 @@ pub unsafe extern "C" fn os_trace_relay_connect(
client: *mut *mut OsTraceRelayClientHandle,
) -> *mut IdeviceFfiError {
if provider.is_null() {
tracing::error!("Null pointer provided");
log::error!("Null pointer provided");
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let res: Result<OsTraceRelayClient, IdeviceError> = run_sync_local(async move {
let res: Result<OsTraceRelayClient, IdeviceError> = RUNTIME.block_on(async move {
let provider_ref: &dyn IdeviceProvider = unsafe { &*(*provider).0 };
OsTraceRelayClient::connect(provider_ref).await
});
@@ -79,7 +78,7 @@ pub unsafe extern "C" fn os_trace_relay_connect(
#[unsafe(no_mangle)]
pub unsafe extern "C" fn os_trace_relay_free(handle: *mut OsTraceRelayClientHandle) {
if !handle.is_null() {
tracing::debug!("Freeing os trace relay client");
log::debug!("Freeing os trace relay client");
let _ = unsafe { Box::from_raw(handle) };
}
}
@@ -103,7 +102,7 @@ pub unsafe extern "C" fn os_trace_relay_start_trace(
pid: *const u32,
) -> *mut IdeviceFfiError {
if receiver.is_null() || client.is_null() {
tracing::error!("Null pointer provided");
log::error!("Null pointer provided");
return ffi_err!(IdeviceError::FfiInvalidArg);
}
@@ -115,7 +114,7 @@ pub unsafe extern "C" fn os_trace_relay_start_trace(
let client_owned = unsafe { Box::from_raw(client) };
let res = run_sync_local(async { client_owned.0.start_trace(pid_option).await });
let res = RUNTIME.block_on(async { client_owned.0.start_trace(pid_option).await });
match res {
Ok(relay) => {
@@ -138,7 +137,7 @@ pub unsafe extern "C" fn os_trace_relay_start_trace(
#[unsafe(no_mangle)]
pub unsafe extern "C" fn os_trace_relay_receiver_free(handle: *mut OsTraceRelayReceiverHandle) {
if !handle.is_null() {
tracing::debug!("Freeing syslog relay client");
log::debug!("Freeing syslog relay client");
let _ = unsafe { Box::from_raw(handle) };
}
}
@@ -159,7 +158,7 @@ pub unsafe extern "C" fn os_trace_relay_get_pid_list(
client: *mut OsTraceRelayClientHandle,
list: *mut *mut Vec<u64>,
) -> *mut IdeviceFfiError {
let res = run_sync_local(async { unsafe { &mut *client }.0.get_pid_list().await });
let res = RUNTIME.block_on(async { unsafe { &mut *client }.0.get_pid_list().await });
match res {
Ok(r) => {
@@ -187,11 +186,11 @@ pub unsafe extern "C" fn os_trace_relay_next(
log: *mut *mut OsTraceLog,
) -> *mut IdeviceFfiError {
if client.is_null() {
tracing::error!("Null pointer provided");
log::error!("Null pointer provided");
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let res = run_sync_local(async { unsafe { &mut *client }.0.next().await });
let res = RUNTIME.block_on(async { unsafe { &mut *client }.0.next().await });
match res {
Ok(r) => {

View File

@@ -139,7 +139,7 @@ pub unsafe extern "C" fn idevice_pairing_file_serialize(
#[unsafe(no_mangle)]
pub unsafe extern "C" fn idevice_pairing_file_free(pairing_file: *mut IdevicePairingFile) {
if !pairing_file.is_null() {
tracing::debug!("Freeing pairing file");
log::debug!("Freeing pairing file");
let _ = unsafe { Box::from_raw(pairing_file) };
}
}

View File

@@ -8,7 +8,7 @@ use std::{
use idevice::{ReadWrite, dvt::process_control::ProcessControlClient};
use plist::{Dictionary, Value};
use crate::{IdeviceFfiError, dvt::remote_server::RemoteServerHandle, ffi_err, run_sync};
use crate::{IdeviceFfiError, RUNTIME, ffi_err, remote_server::RemoteServerHandle};
/// Opaque handle to a ProcessControlClient
pub struct ProcessControlHandle<'a>(pub ProcessControlClient<'a, Box<dyn ReadWrite>>);
@@ -35,7 +35,7 @@ pub unsafe extern "C" fn process_control_new(
}
let server = unsafe { &mut (*server).0 };
let res = run_sync(async move { ProcessControlClient::new(server).await });
let res = RUNTIME.block_on(async move { ProcessControlClient::new(server).await });
match res {
Ok(client) => {
@@ -128,7 +128,7 @@ pub unsafe extern "C" fn process_control_launch_app(
}
let client = unsafe { &mut (*handle).0 };
let res = run_sync(async move {
let res = RUNTIME.block_on(async move {
client
.launch_app(
bundle_id,
@@ -170,7 +170,7 @@ pub unsafe extern "C" fn process_control_kill_app(
}
let client = unsafe { &mut (*handle).0 };
let res = run_sync(async move { client.kill_app(pid).await });
let res = RUNTIME.block_on(async move { client.kill_app(pid).await });
match res {
Ok(_) => null_mut(),
@@ -199,7 +199,7 @@ pub unsafe extern "C" fn process_control_disable_memory_limit(
}
let client = unsafe { &mut (*handle).0 };
let res = run_sync(async move { client.disable_memory_limit(pid).await });
let res = RUNTIME.block_on(async move { client.disable_memory_limit(pid).await });
match res {
Ok(_) => null_mut(),

View File

@@ -7,7 +7,7 @@ use std::{ffi::CStr, ptr::null_mut};
use crate::util::{SockAddr, idevice_sockaddr};
use crate::{IdeviceFfiError, ffi_err, usbmuxd::UsbmuxdAddrHandle, util};
use crate::{IdevicePairingFile, run_sync};
use crate::{IdevicePairingFile, RUNTIME};
pub struct IdeviceProviderHandle(pub Box<dyn IdeviceProvider>);
@@ -70,7 +70,7 @@ pub unsafe extern "C" fn idevice_tcp_provider_new(
#[unsafe(no_mangle)]
pub unsafe extern "C" fn idevice_provider_free(provider: *mut IdeviceProviderHandle) {
if !provider.is_null() {
tracing::debug!("Freeing provider");
log::debug!("Freeing provider");
unsafe { drop(Box::from_raw(provider)) };
}
}
@@ -109,7 +109,7 @@ pub unsafe extern "C" fn usbmuxd_provider_new(
let udid = match unsafe { CStr::from_ptr(udid) }.to_str() {
Ok(u) => u.to_string(),
Err(e) => {
tracing::error!("Invalid UDID string: {e:?}");
log::error!("Invalid UDID string: {e:?}");
return ffi_err!(IdeviceError::FfiInvalidString);
}
};
@@ -117,7 +117,7 @@ pub unsafe extern "C" fn usbmuxd_provider_new(
let label = match unsafe { CStr::from_ptr(label) }.to_str() {
Ok(l) => l.to_string(),
Err(e) => {
tracing::error!("Invalid label string: {e:?}");
log::error!("Invalid label string: {e:?}");
return ffi_err!(IdeviceError::FfiInvalidArg);
}
};
@@ -156,7 +156,7 @@ pub unsafe extern "C" fn idevice_provider_get_pairing_file(
) -> *mut IdeviceFfiError {
let provider = unsafe { &mut *provider };
let res = run_sync(async move { provider.0.get_pairing_file().await });
let res = RUNTIME.block_on(async move { provider.0.get_pairing_file().await });
match res {
Ok(pf) => {
let pf = Box::new(IdevicePairingFile(pf));

View File

@@ -4,7 +4,7 @@ use std::ptr::null_mut;
use crate::core_device_proxy::AdapterHandle;
use crate::rsd::RsdHandshakeHandle;
use crate::{IdeviceFfiError, ReadWriteOpaque, ffi_err, run_sync, run_sync_local};
use crate::{IdeviceFfiError, RUNTIME, ReadWriteOpaque, ffi_err};
use idevice::dvt::remote_server::RemoteServerClient;
use idevice::{IdeviceError, ReadWrite, RsdService};
@@ -37,7 +37,7 @@ pub unsafe extern "C" fn remote_server_new(
let res: Result<RemoteServerClient<Box<dyn ReadWrite>>, IdeviceError> =
match wrapper.inner.take() {
Some(stream) => run_sync(async move {
Some(stream) => RUNTIME.block_on(async move {
let mut client = RemoteServerClient::new(stream);
client.read_message(0).await?;
Ok(client)
@@ -77,7 +77,7 @@ pub unsafe extern "C" fn remote_server_connect_rsd(
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let res: Result<RemoteServerClient<Box<dyn ReadWrite>>, IdeviceError> =
run_sync_local(async move {
RUNTIME.block_on(async move {
let provider_ref = unsafe { &mut (*provider).0 };
let handshake_ref = unsafe { &mut (*handshake).0 };

View File

@@ -7,7 +7,7 @@ use std::ptr::{self, null_mut};
use idevice::rsd::RsdHandshake;
use crate::{IdeviceFfiError, ReadWriteOpaque, ffi_err, run_sync};
use crate::{IdeviceFfiError, RUNTIME, ReadWriteOpaque, ffi_err};
/// Opaque handle to an RsdHandshake
#[derive(Clone)]
@@ -63,10 +63,10 @@ pub unsafe extern "C" fn rsd_handshake_new(
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let mut wrapper = unsafe { Box::from_raw(socket) };
let wrapper = unsafe { &mut *socket };
let res = match wrapper.inner.take() {
Some(mut w) => run_sync(async move { RsdHandshake::new(w.as_mut()).await }),
Some(mut w) => RUNTIME.block_on(async move { RsdHandshake::new(w.as_mut()).await }),
None => {
return ffi_err!(IdeviceError::FfiInvalidArg);
}

View File

@@ -1,132 +0,0 @@
// Jackson Coxson
use std::ptr::null_mut;
use idevice::{
IdeviceError, IdeviceService, provider::IdeviceProvider,
services::screenshotr::ScreenshotService,
};
use crate::{IdeviceFfiError, ffi_err, provider::IdeviceProviderHandle, run_sync_local};
pub struct ScreenshotrClientHandle(pub ScreenshotService);
/// Represents a screenshot data buffer
#[repr(C)]
pub struct ScreenshotData {
pub data: *mut u8,
pub length: usize,
}
/// Connects to screenshotr service using provider
///
/// # Arguments
/// * [`provider`] - An IdeviceProvider
/// * [`client`] - On success, will be set to point to a newly allocated ScreenshotrClient handle
///
/// # Returns
/// An IdeviceFfiError on error, null on success
///
/// # 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 screenshotr_connect(
provider: *mut IdeviceProviderHandle,
client: *mut *mut ScreenshotrClientHandle,
) -> *mut IdeviceFfiError {
if provider.is_null() || client.is_null() {
tracing::error!("Null pointer provided");
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let res: Result<ScreenshotService, IdeviceError> = run_sync_local(async move {
let provider_ref: &dyn IdeviceProvider = unsafe { &*(*provider).0 };
ScreenshotService::connect(provider_ref).await
});
match res {
Ok(r) => {
let boxed = Box::new(ScreenshotrClientHandle(r));
unsafe { *client = Box::into_raw(boxed) };
null_mut()
}
Err(e) => ffi_err!(e),
}
}
/// Takes a screenshot from the device
///
/// # Arguments
/// * `client` - A valid ScreenshotrClient handle
/// * `screenshot` - Pointer to store the screenshot data
///
/// # Returns
/// An IdeviceFfiError on error, null on success
///
/// # Safety
/// `client` must be a valid pointer to a handle allocated by this library
/// `screenshot` must be a valid pointer to store the screenshot data
/// The caller is responsible for freeing the screenshot data using screenshotr_screenshot_free
#[unsafe(no_mangle)]
pub unsafe extern "C" fn screenshotr_take_screenshot(
client: *mut ScreenshotrClientHandle,
screenshot: *mut ScreenshotData,
) -> *mut IdeviceFfiError {
if client.is_null() || screenshot.is_null() {
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let res: Result<Vec<u8>, IdeviceError> = run_sync_local(async move {
let client_ref = unsafe { &mut (*client).0 };
client_ref.take_screenshot().await
});
match res {
Ok(data) => {
let len = data.len();
let boxed = data.into_boxed_slice();
let ptr = Box::into_raw(boxed) as *mut u8;
unsafe {
(*screenshot).data = ptr;
(*screenshot).length = len;
}
null_mut()
}
Err(e) => ffi_err!(e),
}
}
/// Frees screenshot data
///
/// # Arguments
/// * `screenshot` - The screenshot data to free
///
/// # Safety
/// `screenshot` must be a valid ScreenshotData that was allocated by screenshotr_take_screenshot
/// or NULL (in which case this function does nothing)
#[unsafe(no_mangle)]
pub unsafe extern "C" fn screenshotr_screenshot_free(screenshot: ScreenshotData) {
if !screenshot.data.is_null() && screenshot.length > 0 {
tracing::debug!("Freeing screenshot data");
let _ =
unsafe { Vec::from_raw_parts(screenshot.data, screenshot.length, screenshot.length) };
}
}
/// Frees a ScreenshotrClient handle
///
/// # Arguments
/// * [`handle`] - The handle to free
///
/// # Safety
/// `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 unsafe extern "C" fn screenshotr_client_free(handle: *mut ScreenshotrClientHandle) {
if !handle.is_null() {
tracing::debug!("Freeing screenshotr_client");
let _ = unsafe { Box::from_raw(handle) };
}
}

View File

@@ -7,12 +7,8 @@ use idevice::{
IdeviceError, IdeviceService, provider::IdeviceProvider,
springboardservices::SpringBoardServicesClient,
};
use plist_ffi::plist_t;
use crate::{
IdeviceFfiError, IdeviceHandle, ffi_err, provider::IdeviceProviderHandle, run_sync,
run_sync_local,
};
use crate::{IdeviceFfiError, IdeviceHandle, RUNTIME, ffi_err, provider::IdeviceProviderHandle};
pub struct SpringBoardServicesClientHandle(pub SpringBoardServicesClient);
@@ -34,11 +30,11 @@ pub unsafe extern "C" fn springboard_services_connect(
client: *mut *mut SpringBoardServicesClientHandle,
) -> *mut IdeviceFfiError {
if provider.is_null() || client.is_null() {
tracing::error!("Null pointer provided");
log::error!("Null pointer provided");
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let res: Result<SpringBoardServicesClient, IdeviceError> = run_sync_local(async move {
let res: Result<SpringBoardServicesClient, IdeviceError> = RUNTIME.block_on(async move {
let provider_ref: &dyn IdeviceProvider = unsafe { &*(*provider).0 };
SpringBoardServicesClient::connect(provider_ref).await
});
@@ -107,7 +103,7 @@ pub unsafe extern "C" fn springboard_services_get_icon(
out_result_len: *mut libc::size_t,
) -> *mut IdeviceFfiError {
if client.is_null() || out_result.is_null() || out_result_len.is_null() {
tracing::error!("Invalid arguments: {client:?}, {out_result:?}");
log::error!("Invalid arguments: {client:?}, {out_result:?}");
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let client = unsafe { &mut *client };
@@ -119,7 +115,7 @@ pub unsafe extern "C" fn springboard_services_get_icon(
};
let res: Result<Vec<u8>, IdeviceError> =
run_sync(async { client.0.get_icon_pngdata(bundle_id).await });
RUNTIME.block_on(async { client.0.get_icon_pngdata(bundle_id).await });
match res {
Ok(r) => {
@@ -138,169 +134,6 @@ pub unsafe extern "C" fn springboard_services_get_icon(
}
}
/// Gets the home screen wallpaper preview as PNG image
///
/// # Arguments
/// * `client` - A valid SpringBoardServicesClient handle
/// * `out_result` - On success, will be set to point to newly allocated png image
/// * `out_result_len` - On success, will contain the size of the data in bytes
///
/// # Returns
/// An IdeviceFfiError on error, null on success
///
/// # Safety
/// `client` must be a valid pointer to a handle allocated by this library
/// `out_result` and `out_result_len` must be valid, non-null pointers
#[unsafe(no_mangle)]
pub unsafe extern "C" fn springboard_services_get_home_screen_wallpaper_preview(
client: *mut SpringBoardServicesClientHandle,
out_result: *mut *mut c_void,
out_result_len: *mut libc::size_t,
) -> *mut IdeviceFfiError {
if client.is_null() || out_result.is_null() || out_result_len.is_null() {
tracing::error!("Invalid arguments: {client:?}, {out_result:?}");
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let client = unsafe { &mut *client };
let res: Result<Vec<u8>, IdeviceError> =
run_sync(async { client.0.get_home_screen_wallpaper_preview_pngdata().await });
match res {
Ok(r) => {
let len = r.len();
let boxed_slice = r.into_boxed_slice();
let ptr = boxed_slice.as_ptr();
std::mem::forget(boxed_slice);
unsafe {
*out_result = ptr as *mut c_void;
*out_result_len = len;
}
null_mut()
}
Err(e) => ffi_err!(e),
}
}
/// Gets the lock screen wallpaper preview as PNG image
///
/// # Arguments
/// * `client` - A valid SpringBoardServicesClient handle
/// * `out_result` - On success, will be set to point to newly allocated png image
/// * `out_result_len` - On success, will contain the size of the data in bytes
///
/// # Returns
/// An IdeviceFfiError on error, null on success
///
/// # Safety
/// `client` must be a valid pointer to a handle allocated by this library
/// `out_result` and `out_result_len` must be valid, non-null pointers
#[unsafe(no_mangle)]
pub unsafe extern "C" fn springboard_services_get_lock_screen_wallpaper_preview(
client: *mut SpringBoardServicesClientHandle,
out_result: *mut *mut c_void,
out_result_len: *mut libc::size_t,
) -> *mut IdeviceFfiError {
if client.is_null() || out_result.is_null() || out_result_len.is_null() {
tracing::error!("Invalid arguments: {client:?}, {out_result:?}");
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let client = unsafe { &mut *client };
let res: Result<Vec<u8>, IdeviceError> =
run_sync(async { client.0.get_lock_screen_wallpaper_preview_pngdata().await });
match res {
Ok(r) => {
let len = r.len();
let boxed_slice = r.into_boxed_slice();
let ptr = boxed_slice.as_ptr();
std::mem::forget(boxed_slice);
unsafe {
*out_result = ptr as *mut c_void;
*out_result_len = len;
}
null_mut()
}
Err(e) => ffi_err!(e),
}
}
/// Gets the current interface orientation of the device
///
/// # Arguments
/// * `client` - A valid SpringBoardServicesClient handle
/// * `out_orientation` - On success, will contain the orientation value (0-4)
///
/// # Returns
/// An IdeviceFfiError on error, null on success
///
/// # Safety
/// `client` must be a valid pointer to a handle allocated by this library
/// `out_orientation` must be a valid, non-null pointer
#[unsafe(no_mangle)]
pub unsafe extern "C" fn springboard_services_get_interface_orientation(
client: *mut SpringBoardServicesClientHandle,
out_orientation: *mut u8,
) -> *mut IdeviceFfiError {
if client.is_null() || out_orientation.is_null() {
tracing::error!("Invalid arguments: {client:?}, {out_orientation:?}");
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let client = unsafe { &mut *client };
let res = run_sync(async { client.0.get_interface_orientation().await });
match res {
Ok(orientation) => {
unsafe {
*out_orientation = orientation as u8;
}
null_mut()
}
Err(e) => ffi_err!(e),
}
}
/// Gets the home screen icon layout metrics
///
/// # Arguments
/// * `client` - A valid SpringBoardServicesClient handle
/// * `res` - On success, will point to a plist dictionary node containing the metrics
///
/// # Returns
/// An IdeviceFfiError on error, null on success
///
/// # Safety
/// `client` must be a valid pointer to a handle allocated by this library
/// `res` must be a valid, non-null pointer
#[unsafe(no_mangle)]
pub unsafe extern "C" fn springboard_services_get_homescreen_icon_metrics(
client: *mut SpringBoardServicesClientHandle,
res: *mut plist_t,
) -> *mut IdeviceFfiError {
if client.is_null() || res.is_null() {
tracing::error!("Invalid arguments: {client:?}, {res:?}");
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let client = unsafe { &mut *client };
let output = run_sync(async { client.0.get_homescreen_icon_metrics().await });
match output {
Ok(metrics) => {
unsafe {
*res =
plist_ffi::PlistWrapper::new_node(plist::Value::Dictionary(metrics)).into_ptr();
}
null_mut()
}
Err(e) => ffi_err!(e),
}
}
/// Frees an SpringBoardServicesClient handle
///
/// # Arguments
@@ -312,7 +145,7 @@ pub unsafe extern "C" fn springboard_services_get_homescreen_icon_metrics(
#[unsafe(no_mangle)]
pub unsafe extern "C" fn springboard_services_free(handle: *mut SpringBoardServicesClientHandle) {
if !handle.is_null() {
tracing::debug!("Freeing springboard_services_client");
log::debug!("Freeing springboard_services_client");
let _ = unsafe { Box::from_raw(handle) };
}
}

View File

@@ -4,7 +4,7 @@ use idevice::{
IdeviceError, IdeviceService, provider::IdeviceProvider, syslog_relay::SyslogRelayClient,
};
use crate::{IdeviceFfiError, ffi_err, provider::IdeviceProviderHandle, run_sync_local};
use crate::{IdeviceFfiError, RUNTIME, ffi_err, provider::IdeviceProviderHandle};
pub struct SyslogRelayClientHandle(pub SyslogRelayClient);
@@ -23,11 +23,11 @@ pub unsafe extern "C" fn syslog_relay_connect_tcp(
client: *mut *mut SyslogRelayClientHandle,
) -> *mut IdeviceFfiError {
if provider.is_null() {
tracing::error!("Null pointer provided");
log::error!("Null pointer provided");
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let res: Result<SyslogRelayClient, IdeviceError> = run_sync_local(async move {
let res: Result<SyslogRelayClient, IdeviceError> = RUNTIME.block_on(async move {
let provider_ref: &dyn IdeviceProvider = unsafe { &*(*provider).0 };
SyslogRelayClient::connect(provider_ref).await
});
@@ -58,7 +58,7 @@ pub unsafe extern "C" fn syslog_relay_connect_tcp(
#[unsafe(no_mangle)]
pub unsafe extern "C" fn syslog_relay_client_free(handle: *mut SyslogRelayClientHandle) {
if !handle.is_null() {
tracing::debug!("Freeing syslog relay client");
log::debug!("Freeing syslog relay client");
let _ = unsafe { Box::from_raw(handle) };
}
}
@@ -81,7 +81,7 @@ pub unsafe extern "C" fn syslog_relay_next(
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let res = run_sync_local(async { unsafe { &mut *client }.0.next().await });
let res = RUNTIME.block_on(async { unsafe { &mut *client }.0.next().await });
match res {
Ok(log) => {
@@ -96,7 +96,7 @@ pub unsafe extern "C" fn syslog_relay_next(
null_mut()
}
Err(_) => {
tracing::error!("Failed to convert log message to C string");
log::error!("Failed to convert log message to C string");
ffi_err!(IdeviceError::FfiInvalidString)
}
}

View File

@@ -14,7 +14,7 @@ use tokio::{
net::tcp::{OwnedReadHalf, OwnedWriteHalf},
};
use crate::{IdeviceFfiError, core_device_proxy::AdapterHandle, ffi_err, run_sync, run_sync_local};
use crate::{IdeviceFfiError, RUNTIME, core_device_proxy::AdapterHandle, ffi_err};
pub struct TcpFeedObject {
sender: Arc<Mutex<OwnedWriteHalf>>,
@@ -63,7 +63,7 @@ pub unsafe extern "C" fn idevice_tcp_stack_into_sync_objects(
}
};
let res = run_sync(async {
let res = RUNTIME.block_on(async {
let mut port = 4000;
loop {
if port > 4050 {
@@ -105,7 +105,7 @@ pub unsafe extern "C" fn idevice_tcp_stack_into_sync_objects(
let eat_object = TcpEatObject { receiver: r };
// we must be inside the runtime for the inner function to spawn threads
let new_adapter = run_sync_local(async {
let new_adapter = RUNTIME.block_on(async {
idevice::tcp::adapter::Adapter::new(Box::new(stream), our_ip, their_ip).to_async_handle()
});
// this object can now be used with the rest of the idevice FFI library
@@ -133,7 +133,7 @@ pub unsafe extern "C" fn idevice_tcp_feed_object_write(
}
let object = unsafe { &mut *object };
let data = unsafe { std::slice::from_raw_parts(data, len) };
run_sync_local(async move {
RUNTIME.block_on(async move {
let mut lock = object.sender.lock().await;
match lock.write_all(data).await {
Ok(_) => {
@@ -163,7 +163,7 @@ pub unsafe extern "C" fn idevice_tcp_eat_object_read(
) -> *mut IdeviceFfiError {
let object = unsafe { &mut *object };
let mut buf = [0; 2048];
run_sync_local(async {
RUNTIME.block_on(async {
let lock = object.receiver.lock().await;
match lock.try_read(&mut buf) {
Ok(size) => {

View File

@@ -2,27 +2,22 @@
use std::{
ffi::{CStr, CString, c_char},
pin::Pin,
ptr::null_mut,
};
use crate::{
IdeviceFfiError, IdeviceHandle, IdevicePairingFile, ffi_err, run_sync, run_sync_local,
IdeviceFfiError, IdeviceHandle, IdevicePairingFile, RUNTIME, ffi_err,
util::{SockAddr, c_socket_to_rust, idevice_sockaddr, idevice_socklen_t},
};
use futures::{Stream, StreamExt};
use idevice::{
IdeviceError,
usbmuxd::{UsbmuxdAddr, UsbmuxdConnection, UsbmuxdDevice, UsbmuxdListenEvent},
usbmuxd::{UsbmuxdAddr, UsbmuxdConnection, UsbmuxdDevice},
};
use tracing::error;
use log::error;
pub struct UsbmuxdConnectionHandle(pub UsbmuxdConnection);
pub struct UsbmuxdAddrHandle(pub UsbmuxdAddr);
pub struct UsbmuxdDeviceHandle(pub UsbmuxdDevice);
pub struct UsbmuxdListenerHandle<'a>(
Pin<Box<dyn Stream<Item = Result<UsbmuxdListenEvent, IdeviceError>> + 'a>>,
);
/// Connects to a usbmuxd instance over TCP
///
@@ -57,7 +52,7 @@ pub unsafe extern "C" fn idevice_usbmuxd_new_tcp_connection(
Err(e) => return ffi_err!(e),
};
let res = run_sync(async move {
let res = RUNTIME.block_on(async move {
let stream = tokio::net::TcpStream::connect(addr).await?;
Ok::<_, IdeviceError>(UsbmuxdConnection::new(Box::new(stream), tag))
});
@@ -96,7 +91,7 @@ pub unsafe extern "C" fn idevice_usbmuxd_new_unix_socket_connection(
Err(_) => return ffi_err!(IdeviceError::FfiInvalidArg),
};
let res: Result<UsbmuxdConnection, IdeviceError> = run_sync(async move {
let res: Result<UsbmuxdConnection, IdeviceError> = RUNTIME.block_on(async move {
let stream = tokio::net::UnixStream::connect(addr).await?;
Ok(UsbmuxdConnection::new(Box::new(stream), tag))
});
@@ -132,13 +127,13 @@ pub unsafe extern "C" fn idevice_usbmuxd_new_default_connection(
let addr = match UsbmuxdAddr::from_env_var() {
Ok(a) => a,
Err(e) => {
tracing::error!("Invalid address set: {e:?}");
log::error!("Invalid address set: {e:?}");
return ffi_err!(IdeviceError::FfiInvalidArg);
}
};
let res: Result<UsbmuxdConnection, IdeviceError> =
run_sync(async move { addr.connect(tag).await });
RUNTIME.block_on(async move { addr.connect(tag).await });
match res {
Ok(r) => {
@@ -176,7 +171,7 @@ pub unsafe extern "C" fn idevice_usbmuxd_get_devices(
}
let conn = unsafe { &mut (*usbmuxd_conn).0 };
let res = run_sync(async { conn.get_devices().await });
let res = RUNTIME.block_on(async { conn.get_devices().await });
match res {
Ok(device_vec) => {
@@ -244,7 +239,7 @@ pub unsafe extern "C" fn idevice_usbmuxd_connect_to_device(
}
};
let res = run_sync(async move { conn.connect_to_device(device_id, port, label).await });
let res = RUNTIME.block_on(async move { conn.connect_to_device(device_id, port, label).await });
match res {
Ok(device_conn) => {
@@ -292,7 +287,7 @@ pub unsafe extern "C" fn idevice_usbmuxd_get_pair_record(
}
};
let res = run_sync(async { conn.get_pair_record(udid_str).await });
let res = RUNTIME.block_on(async { conn.get_pair_record(udid_str).await });
match res {
Ok(pf) => {
@@ -306,149 +301,6 @@ pub unsafe extern "C" fn idevice_usbmuxd_get_pair_record(
}
}
/// Saves the pairing record for a given device UDID.
///
/// # Arguments
/// * `usbmuxd_conn` - A valid connection to usbmuxd.
/// * `device_id` - The muxer ID for the device
/// * `udid` - The UDID of the device.
/// * `pair_record` - The bytes of the pairing record plist to save
/// * `pair_record_len` - the length of the pairing record bytes
///
/// # Returns
/// An `IdeviceFfiError` on error, `null` on success.
///
/// # Safety
/// * `usbmuxd_conn` must be a valid pointer.
/// * `udid` must be a valid, null-terminated C string.
/// * `pair_record` must be a valid, non-null pointer.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn idevice_usbmuxd_save_pair_record(
usbmuxd_conn: *mut UsbmuxdConnectionHandle,
udid: *const c_char,
pair_record: *mut u8,
pair_record_len: usize,
) -> *mut IdeviceFfiError {
if usbmuxd_conn.is_null() || pair_record.is_null() {
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let conn = unsafe { &mut (*usbmuxd_conn).0 };
let pair_record =
unsafe { std::slice::from_raw_parts_mut(pair_record, pair_record_len) }.to_vec();
let udid_str = unsafe {
match CStr::from_ptr(udid).to_str() {
Ok(s) => s,
Err(_) => return ffi_err!(IdeviceError::FfiInvalidArg),
}
};
let res = run_sync_local(async { conn.save_pair_record(udid_str, pair_record).await });
match res {
Ok(_) => null_mut(),
Err(e) => ffi_err!(e),
}
}
/// Listens on the socket for connections and disconnections
///
/// # Safety
/// Pass valid pointers. Free the stream with ``idevice_usbmuxd_listener_handle_free``.
/// The stream must outlive the usbmuxd connection, and the usbmuxd connection cannot
/// be used for other requests.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn idevice_usbmuxd_listen(
usbmuxd_conn: *mut UsbmuxdConnectionHandle,
stream_handle: *mut *mut UsbmuxdListenerHandle,
) -> *mut IdeviceFfiError {
if usbmuxd_conn.is_null() || stream_handle.is_null() {
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let conn = unsafe { &mut (*usbmuxd_conn).0 };
let res = run_sync_local(async { conn.listen().await });
match res {
Ok(s) => {
unsafe { *stream_handle = Box::into_raw(Box::new(UsbmuxdListenerHandle(s))) };
null_mut()
}
Err(e) => ffi_err!(e),
}
}
/// Frees a stream created by ``listen`` or does nothing on null
///
/// # Safety
/// Pass a valid pointer.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn idevice_usbmuxd_listener_handle_free(
stream_handle: *mut UsbmuxdListenerHandle,
) {
if stream_handle.is_null() {
return;
}
let _ = unsafe { Box::from_raw(stream_handle) };
}
/// Gets the next event from the stream.
/// Connect will be set to true if the event is a connection event,
/// and the connection_device will be filled with the device information.
/// If connection is false, the mux ID of the device will be filled.
///
/// # Arguments
/// * `stream_handle` - The handle to the stream returned by listen
/// * `connect` - The bool that will be set
/// * `connection_device` - The pointer that will be filled on a connect event
/// * `disconnection_id` - The mux ID that will be set on a disconnect event
///
/// # Safety
/// Pass valid pointers
#[unsafe(no_mangle)]
pub unsafe extern "C" fn idevice_usbmuxd_listener_next(
stream_handle: *mut UsbmuxdListenerHandle,
connect: *mut bool,
connection_device: *mut *mut UsbmuxdDeviceHandle,
disconnection_id: *mut u32,
) -> *mut IdeviceFfiError {
if stream_handle.is_null()
|| connect.is_null()
|| connection_device.is_null()
|| disconnection_id.is_null()
{
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let stream = unsafe { &mut (*stream_handle).0 };
let res = run_sync_local(async { stream.next().await });
match res {
Some(res) => match res {
Ok(s) => {
match s {
UsbmuxdListenEvent::Connected(usbmuxd_device) => {
unsafe { *connect = true };
unsafe {
*connection_device =
Box::into_raw(Box::new(UsbmuxdDeviceHandle(usbmuxd_device)))
};
}
UsbmuxdListenEvent::Disconnected(id) => unsafe { *disconnection_id = id },
}
null_mut()
}
Err(e) => ffi_err!(e),
},
None => {
ffi_err!(IdeviceError::Socket(std::io::Error::new(
std::io::ErrorKind::BrokenPipe,
"end of stream"
)))
}
}
}
/// Reads the BUID (Boot-Unique ID) from usbmuxd.
///
/// The returned string must be freed with `idevice_string_free`.
@@ -473,7 +325,7 @@ pub unsafe extern "C" fn idevice_usbmuxd_get_buid(
}
let conn = unsafe { &mut (*usbmuxd_conn).0 };
let res = run_sync(async { conn.get_buid().await });
let res = RUNTIME.block_on(async { conn.get_buid().await });
match res {
Ok(buid_str) => match CString::new(buid_str) {

View File

@@ -48,7 +48,7 @@ pub(crate) fn c_socket_to_rust(
addr_len: SockLen,
) -> Result<SocketAddr, IdeviceError> {
if addr.is_null() {
tracing::error!("null sockaddr");
log::error!("null sockaddr");
return invalid_arg();
}
@@ -59,7 +59,7 @@ pub(crate) fn c_socket_to_rust(
match family as i32 {
libc::AF_INET => {
if (addr_len as usize) < std::mem::size_of::<sockaddr_in>() {
tracing::error!("Invalid sockaddr_in size");
log::error!("Invalid sockaddr_in size");
return invalid_arg();
}
let a = &*(addr as *const sockaddr_in);
@@ -69,7 +69,7 @@ pub(crate) fn c_socket_to_rust(
}
libc::AF_INET6 => {
if (addr_len as usize) < std::mem::size_of::<sockaddr_in6>() {
tracing::error!("Invalid sockaddr_in6 size");
log::error!("Invalid sockaddr_in6 size");
return invalid_arg();
}
let a = &*(addr as *const sockaddr_in6);
@@ -83,7 +83,7 @@ pub(crate) fn c_socket_to_rust(
)))
}
_ => {
tracing::error!(
log::error!(
"Unsupported socket address family: {}",
(*addr).sa_family as i32
);
@@ -95,7 +95,7 @@ pub(crate) fn c_socket_to_rust(
match family {
AF_INET => {
if (addr_len as usize) < std::mem::size_of::<sockaddr_in>() {
tracing::error!("Invalid SOCKADDR_IN size");
log::error!("Invalid SOCKADDR_IN size");
return invalid_arg();
}
let a = &*(addr as *const sockaddr_in);
@@ -107,7 +107,7 @@ pub(crate) fn c_socket_to_rust(
}
AF_INET6 => {
if (addr_len as usize) < std::mem::size_of::<sockaddr_in6>() {
tracing::error!("Invalid SOCKADDR_IN6 size");
log::error!("Invalid SOCKADDR_IN6 size");
return invalid_arg();
}
let a = &*(addr as *const sockaddr_in6);
@@ -124,7 +124,7 @@ pub(crate) fn c_socket_to_rust(
)))
}
_ => {
tracing::error!("Unsupported socket address family: {}", (*addr).sa_family);
log::error!("Unsupported socket address family: {}", (*addr).sa_family);
invalid_arg()
}
}
@@ -133,7 +133,7 @@ pub(crate) fn c_socket_to_rust(
pub(crate) fn c_addr_to_rust(addr: *const SockAddr) -> Result<IpAddr, IdeviceError> {
if addr.is_null() {
tracing::error!("null sockaddr");
log::error!("null sockaddr");
return invalid_arg();
}
@@ -157,7 +157,7 @@ pub(crate) fn c_addr_to_rust(addr: *const SockAddr) -> Result<IpAddr, IdeviceErr
Ok(IpAddr::V6(Ipv6Addr::from(a.sin6_addr.s6_addr)))
}
_ => {
tracing::error!(
log::error!(
"Unsupported socket address family: {}",
(*addr).sa_family as i32
);
@@ -178,7 +178,7 @@ pub(crate) fn c_addr_to_rust(addr: *const SockAddr) -> Result<IpAddr, IdeviceErr
Ok(IpAddr::V6(Ipv6Addr::from(bytes)))
}
_ => {
tracing::error!("Unsupported socket address family: {}", (*addr).sa_family);
log::error!("Unsupported socket address family: {}", (*addr).sa_family);
invalid_arg()
}
}

View File

@@ -2,7 +2,7 @@
name = "idevice"
description = "A Rust library to interact with services on iOS devices."
authors = ["Jackson Coxson"]
version = "0.1.53"
version = "0.1.42"
edition = "2024"
license = "MIT"
documentation = "https://docs.rs/idevice"
@@ -11,28 +11,26 @@ keywords = ["lockdownd", "ios"]
[dependencies]
tokio = { version = "1", features = ["io-util"] }
tokio-rustls = { version = "0.26", default-features = false, optional = true }
tokio = { version = "1.43", features = ["io-util"] }
tokio-rustls = { version = "0.26", default-features = false }
rustls = { version = "0.23", default-features = false, features = [
"std",
"tls12",
], optional = true }
tokio-openssl = { version = "0.6", optional = true }
openssl = { version = "0.10", optional = true }
] }
crossfire = { version = "2.0", optional = true } # TODO: update to 2.1 when it comes out
plist = { version = "1.8" }
plist-macro = { version = "0.1.6" }
plist = { version = "1.7" }
serde = { version = "1", features = ["derive"] }
ns-keyed-archive = { version = "0.1.4", optional = true }
crossfire = { version = "2.1", optional = true }
ns-keyed-archive = { version = "0.1.3", optional = true }
thiserror = { version = "2" }
tracing = { version = "0.1.41" }
log = { version = "0.4" }
env_logger = { version = "0.11" }
base64 = { version = "0.22" }
indexmap = { version = "2.11", features = ["serde"], optional = true }
uuid = { version = "1.18", features = ["serde", "v3", "v4"], optional = true }
chrono = { version = "0.4", optional = true, default-features = false, features = [
indexmap = { version = "2.7", features = ["serde"], optional = true }
uuid = { version = "1.12", features = ["serde", "v4"], optional = true }
chrono = { version = "0.4.40", optional = true, default-features = false, features = [
"serde",
] }
@@ -41,7 +39,7 @@ json = { version = "0.12", optional = true }
byteorder = { version = "1.5", optional = true }
bytes = { version = "1.10", optional = true }
reqwest = { version = "0.13", features = [
reqwest = { version = "0.12", features = [
"json",
], optional = true, default-features = false }
rand = { version = "0.9", optional = true }
@@ -59,7 +57,6 @@ x25519-dalek = { version = "2", optional = true }
ed25519-dalek = { version = "2", features = ["rand_core"], optional = true }
hkdf = { version = "0.12", optional = true }
chacha20poly1305 = { version = "0.10", optional = true }
idevice-srp = { version = "0.6", optional = true }
obfstr = { version = "0.4", optional = true }
@@ -72,16 +69,14 @@ bytes = "1.10.1"
[features]
default = ["aws-lc"]
aws-lc = ["rustls", "rustls/aws-lc-rs", "tokio-rustls/aws-lc-rs"]
ring = ["rustls", "rustls/ring", "tokio-rustls/ring"]
rustls = ["dep:rustls", "dep:tokio-rustls"]
openssl = ["dep:openssl", "dep:tokio-openssl"]
aws-lc = ["rustls/aws-lc-rs", "tokio-rustls/aws-lc-rs"]
ring = ["rustls/ring", "tokio-rustls/ring"]
afc = ["dep:chrono", "dep:futures"]
afc = ["dep:chrono"]
amfi = []
bt_packet_logger = []
companion_proxy = []
core_device = ["xpc", "dep:uuid", "dep:ns-keyed-archive"]
core_device = ["xpc", "dep:uuid"]
core_device_proxy = ["dep:serde_json", "dep:json", "dep:byteorder"]
crashreportcopymobile = ["afc"]
debug_proxy = []
@@ -96,34 +91,25 @@ installation_proxy = [
"async_zip/deflate",
"tokio/fs",
]
installcoordination_proxy = []
springboardservices = []
misagent = []
mobile_image_mounter = ["dep:sha2"]
mobileactivationd = ["dep:reqwest"]
mobilebackup2 = []
notification_proxy = [
"tokio/macros",
"tokio/time",
"dep:async-stream",
"dep:futures",
]
location_simulation = []
pair = ["chrono/default", "tokio/time", "dep:sha2", "dep:rsa", "dep:x509-cert"]
pcapd = []
preboard_service = []
obfuscate = ["dep:obfstr"]
restore_service = []
remote_pairing = [
"dep:serde_json",
"dep:json",
"dep:x25519-dalek",
"dep:ed25519-dalek",
"dep:hkdf",
"dep:chacha20poly1305",
"dep:idevice-srp",
"dep:uuid",
]
restore_service = []
rsd = ["xpc"]
screenshotr = []
syslog_relay = ["dep:bytes"]
@@ -137,7 +123,7 @@ tunnel_tcp_stack = [
]
tss = ["dep:uuid", "dep:reqwest"]
tunneld = ["dep:serde_json", "dep:json", "dep:reqwest"]
usbmuxd = ["tokio/net", "dep:futures"]
usbmuxd = ["tokio/net"]
xpc = ["dep:indexmap", "dep:uuid", "dep:async-stream"]
full = [
"afc",
@@ -153,18 +139,19 @@ full = [
"heartbeat",
"house_arrest",
"installation_proxy",
"installcoordination_proxy",
"location_simulation",
"misagent",
"mobile_image_mounter",
"mobileactivationd",
"mobilebackup2",
"notification_proxy",
"pair",
"pcapd",
"preboard_service",
"remote_pairing",
"restore_service",
"usbmuxd",
"xpc",
"location_simulation",
"remote_pairing",
"rsd",
"screenshotr",
"springboardservices",

View File

@@ -1,334 +0,0 @@
// Jackson Coxson
#[derive(Clone, Debug)]
pub struct Cursor<'a> {
inner: &'a [u8],
pos: usize,
}
impl<'a> Cursor<'a> {
/// Creates a new cursor
pub fn new(inner: &'a [u8]) -> Self {
Self { inner, pos: 0 }
}
pub fn len(&self) -> usize {
self.inner.len()
}
pub fn is_empty(&self) -> bool {
self.inner.is_empty()
}
pub fn at_end(&self) -> bool {
self.pos == self.inner.len()
}
pub fn read(&mut self, to_read: usize) -> Option<&'a [u8]> {
// Check if the end of the slice (self.pos + to_read) is beyond the buffer length
if self
.pos
.checked_add(to_read)
.is_none_or(|end_pos| end_pos > self.inner.len())
{
return None;
}
// The end of the slice is self.pos + to_read
let end_pos = self.pos + to_read;
let res = Some(&self.inner[self.pos..end_pos]);
self.pos = end_pos;
res
}
pub fn back(&mut self, to_back: usize) {
let to_back = if to_back > self.pos {
self.pos
} else {
to_back
};
self.pos -= to_back;
}
/// True if actually all zeroes
pub fn read_assert_zero(&mut self, to_read: usize) -> Option<()> {
let bytes = self.read(to_read)?;
#[cfg(debug_assertions)]
for b in bytes.iter() {
if *b > 0 {
eprintln!("Zero read contained non-zero values!");
eprintln!("{bytes:02X?}");
return None;
}
}
Some(())
}
pub fn read_to(&mut self, end: usize) -> Option<&'a [u8]> {
if end > self.inner.len() {
return None;
}
let res = Some(&self.inner[self.pos..end]);
self.pos = end;
res
}
pub fn peek_to(&mut self, end: usize) -> Option<&'a [u8]> {
if end > self.inner.len() {
return None;
}
Some(&self.inner[self.pos..end])
}
pub fn peek(&self, to_read: usize) -> Option<&'a [u8]> {
if self
.pos
.checked_add(to_read)
.is_none_or(|end_pos| end_pos > self.inner.len())
{
return None;
}
let end_pos = self.pos + to_read;
Some(&self.inner[self.pos..end_pos])
}
pub fn reveal(&self, surrounding: usize) {
let len = self.inner.len();
if self.pos > len {
println!("Cursor is past end of buffer");
return;
}
let start = self.pos.saturating_sub(surrounding);
let end = (self.pos + surrounding + 1).min(len);
// HEADER
println!("Reveal around pos {} ({} bytes):", self.pos, surrounding);
// --- HEX LINE ---
print!("Hex: ");
for i in start..end {
if i == self.pos {
print!("[{:02X}] ", self.inner[i]);
} else {
print!("{:02X} ", self.inner[i]);
}
}
println!();
// --- ASCII LINE ---
print!("Ascii: ");
for i in start..end {
let b = self.inner[i];
let c = if b.is_ascii_graphic() || b == b' ' {
b as char
} else {
'.'
};
if i == self.pos {
print!("[{}] ", c);
} else {
print!("{} ", c);
}
}
println!();
// --- OFFSET LINE ---
print!("Offset: ");
for i in start..end {
let off = i as isize - self.pos as isize;
if i == self.pos {
print!("[{}] ", off);
} else {
print!("{:<3} ", off);
}
}
println!();
}
pub fn remaining(&mut self) -> &'a [u8] {
let res = &self.inner[self.pos..];
self.pos = self.inner.len();
res
}
pub fn read_u8(&mut self) -> Option<u8> {
if self.pos == self.inner.len() {
return None;
}
let res = Some(self.inner[self.pos]);
self.pos += 1;
res
}
pub fn read_le_u16(&mut self) -> Option<u16> {
const SIZE: usize = 2;
let bytes = self.read(SIZE)?;
let bytes: [u8; SIZE] = bytes.try_into().unwrap();
Some(u16::from_le_bytes(bytes))
}
pub fn read_be_u16(&mut self) -> Option<u16> {
const SIZE: usize = 2;
let bytes = self.read(SIZE)?;
let bytes: [u8; SIZE] = bytes.try_into().unwrap();
Some(u16::from_be_bytes(bytes))
}
pub fn read_le_u32(&mut self) -> Option<u32> {
const SIZE: usize = 4;
let bytes = self.read(SIZE)?;
let bytes: [u8; SIZE] = bytes.try_into().unwrap();
Some(u32::from_le_bytes(bytes))
}
pub fn read_be_u32(&mut self) -> Option<u32> {
const SIZE: usize = 4;
let bytes = self.read(SIZE)?;
let bytes: [u8; SIZE] = bytes.try_into().unwrap();
Some(u32::from_be_bytes(bytes))
}
pub fn read_le_u64(&mut self) -> Option<u64> {
const SIZE: usize = 8;
let bytes = self.read(SIZE)?;
let bytes: [u8; SIZE] = bytes.try_into().unwrap();
Some(u64::from_le_bytes(bytes))
}
pub fn read_be_u64(&mut self) -> Option<u64> {
const SIZE: usize = 8;
let bytes = self.read(SIZE)?;
let bytes: [u8; SIZE] = bytes.try_into().unwrap();
Some(u64::from_be_bytes(bytes))
}
pub fn read_le_u128(&mut self) -> Option<u128> {
const SIZE: usize = 16;
let bytes = self.read(SIZE)?;
let bytes: [u8; SIZE] = bytes.try_into().unwrap();
Some(u128::from_le_bytes(bytes))
}
pub fn read_be_u128(&mut self) -> Option<u128> {
const SIZE: usize = 16;
let bytes = self.read(SIZE)?;
let bytes: [u8; SIZE] = bytes.try_into().unwrap();
Some(u128::from_be_bytes(bytes))
}
pub fn read_le_f32(&mut self) -> Option<f32> {
const SIZE: usize = 4;
let bytes = self.read(SIZE)?;
let bytes: [u8; SIZE] = bytes.try_into().unwrap();
Some(f32::from_le_bytes(bytes))
}
pub fn read_be_f32(&mut self) -> Option<f32> {
const SIZE: usize = 4;
let bytes = self.read(SIZE)?;
let bytes: [u8; SIZE] = bytes.try_into().unwrap();
Some(f32::from_be_bytes(bytes))
}
pub fn read_i8(&mut self) -> Option<i8> {
if self.pos == self.inner.len() {
return None;
}
let res = Some(self.inner[self.pos]).map(|x| x as i8);
self.pos += 1;
res
}
pub fn read_le_i16(&mut self) -> Option<i16> {
const SIZE: usize = 2;
let bytes = self.read(SIZE)?;
let bytes: [u8; SIZE] = bytes.try_into().unwrap();
Some(i16::from_le_bytes(bytes))
}
pub fn read_be_i16(&mut self) -> Option<i16> {
const SIZE: usize = 2;
let bytes = self.read(SIZE)?;
let bytes: [u8; SIZE] = bytes.try_into().unwrap();
Some(i16::from_be_bytes(bytes))
}
pub fn read_le_i32(&mut self) -> Option<i32> {
const SIZE: usize = 4;
let bytes = self.read(SIZE)?;
let bytes: [u8; SIZE] = bytes.try_into().unwrap();
Some(i32::from_le_bytes(bytes))
}
pub fn read_be_i32(&mut self) -> Option<i32> {
const SIZE: usize = 4;
let bytes = self.read(SIZE)?;
let bytes: [u8; SIZE] = bytes.try_into().unwrap();
Some(i32::from_be_bytes(bytes))
}
pub fn read_le_i64(&mut self) -> Option<i64> {
const SIZE: usize = 8;
let bytes = self.read(SIZE)?;
let bytes: [u8; SIZE] = bytes.try_into().unwrap();
Some(i64::from_le_bytes(bytes))
}
pub fn read_be_i64(&mut self) -> Option<i64> {
const SIZE: usize = 8;
let bytes = self.read(SIZE)?;
let bytes: [u8; SIZE] = bytes.try_into().unwrap();
Some(i64::from_be_bytes(bytes))
}
pub fn read_le_i128(&mut self) -> Option<i128> {
const SIZE: usize = 16;
let bytes = self.read(SIZE)?;
let bytes: [u8; SIZE] = bytes.try_into().unwrap();
Some(i128::from_le_bytes(bytes))
}
pub fn read_be_i128(&mut self) -> Option<i128> {
const SIZE: usize = 16;
let bytes = self.read(SIZE)?;
let bytes: [u8; SIZE] = bytes.try_into().unwrap();
Some(i128::from_be_bytes(bytes))
}
pub fn take_2(&mut self) -> Option<[u8; 2]> {
let bytes = self.read(2)?;
Some(bytes.to_owned().try_into().unwrap())
}
pub fn take_3(&mut self) -> Option<[u8; 3]> {
let bytes = self.read(3)?;
Some(bytes.to_owned().try_into().unwrap())
}
pub fn take_4(&mut self) -> Option<[u8; 4]> {
let bytes = self.read(4)?;
Some(bytes.to_owned().try_into().unwrap())
}
pub fn take_8(&mut self) -> Option<[u8; 8]> {
let bytes = self.read(8)?;
Some(bytes.to_owned().try_into().unwrap())
}
pub fn take_20(&mut self) -> Option<[u8; 20]> {
let bytes = self.read(20)?;
Some(bytes.to_owned().try_into().unwrap())
}
pub fn take_32(&mut self) -> Option<[u8; 32]> {
let bytes = self.read(32)?;
Some(bytes.to_owned().try_into().unwrap())
}
}

View File

@@ -1,17 +1,11 @@
#![doc = include_str!("../README.md")]
#![warn(missing_debug_implementations)]
#![warn(missing_copy_implementations)]
// Jackson Coxson
#[cfg(all(feature = "pair", feature = "rustls"))]
#[cfg(feature = "pair")]
mod ca;
pub mod cursor;
mod obfuscation;
pub mod pairing_file;
mod plist_macro;
pub mod provider;
#[cfg(feature = "remote_pairing")]
pub mod remote_pairing;
#[cfg(feature = "rustls")]
mod sni;
#[cfg(feature = "tunnel_tcp_stack")]
pub mod tcp;
@@ -21,6 +15,7 @@ pub mod tss;
pub mod tunneld;
#[cfg(feature = "usbmuxd")]
pub mod usbmuxd;
mod util;
pub mod utils;
#[cfg(feature = "xpc")]
pub mod xpc;
@@ -31,9 +26,8 @@ pub use services::*;
#[cfg(feature = "xpc")]
pub use xpc::RemoteXpcClient;
use plist_macro::{plist, pretty_print_dictionary, pretty_print_plist};
use log::{debug, error, trace};
use provider::{IdeviceProvider, RsdProvider};
#[cfg(feature = "rustls")]
use rustls::{crypto::CryptoProvider, pki_types::ServerName};
use std::{
io::{self, BufWriter},
@@ -41,7 +35,8 @@ use std::{
};
use thiserror::Error;
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
use tracing::{debug, trace};
pub use util::{pretty_print_dictionary, pretty_print_plist};
use crate::services::lockdown::LockdownClient;
@@ -77,22 +72,6 @@ pub trait IdeviceService: Sized {
#[allow(async_fn_in_trait)]
async fn connect(provider: &dyn IdeviceProvider) -> Result<Self, IdeviceError> {
let mut lockdown = LockdownClient::connect(provider).await?;
#[cfg(feature = "openssl")]
let legacy = lockdown
.get_value(Some("ProductVersion"), None)
.await
.ok()
.as_ref()
.and_then(|x| x.as_string())
.and_then(|x| x.split(".").next())
.and_then(|x| x.parse::<u8>().ok())
.map(|x| x < 5)
.unwrap_or(false);
#[cfg(not(feature = "openssl"))]
let legacy = false;
lockdown
.start_session(&provider.get_pairing_file().await?)
.await?;
@@ -107,7 +86,7 @@ pub trait IdeviceService: Sized {
let mut idevice = provider.connect(port).await?;
if ssl {
idevice
.start_session(&provider.get_pairing_file().await?, legacy)
.start_session(&provider.get_pairing_file().await?)
.await?;
}
@@ -149,7 +128,6 @@ pub type IdeviceSocket = Box<dyn ReadWrite>;
///
/// Manages the connection socket and provides methods for common device operations
/// and message exchange.
#[derive(Debug)]
pub struct Idevice {
/// The underlying connection socket, boxed for dynamic dispatch
socket: Option<Box<dyn ReadWrite>>,
@@ -197,7 +175,7 @@ impl Idevice {
/// # Errors
/// Returns `IdeviceError` if communication fails or response is invalid
pub async fn get_type(&mut self) -> Result<String, IdeviceError> {
let req = plist!({
let req = crate::plist!({
"Label": self.label.clone(),
"Request": "QueryType",
});
@@ -217,7 +195,7 @@ impl Idevice {
/// # Errors
/// Returns `IdeviceError` if the protocol sequence isn't followed correctly
pub async fn rsd_checkin(&mut self) -> Result<(), IdeviceError> {
let req = plist!({
let req = crate::plist!({
"Label": self.label.clone(),
"ProtocolVersion": "2",
"Request": "RSDCheckin",
@@ -309,75 +287,6 @@ impl Idevice {
self.send_raw_with_progress(message, |_| async {}, ()).await
}
/// Sends raw binary data via vectored I/O
///
/// # Arguments
/// * `bufs` - The buffers to send
///
/// # Errors
/// Returns `IdeviceError` if transmission fails
pub async fn send_raw_vectored(
&mut self,
bufs: &[std::io::IoSlice<'_>],
) -> Result<(), IdeviceError> {
if let Some(socket) = &mut self.socket {
let mut curr_idx = 0;
let mut curr_offset = 0;
while curr_idx < bufs.len() {
let mut iovec = Vec::new();
let mut accumulated_len = 0;
let max_chunk = 1024 * 64;
// Add partial first slice
let first_avail = bufs[curr_idx].len() - curr_offset;
let to_take_first = std::cmp::min(first_avail, max_chunk);
iovec.push(std::io::IoSlice::new(
&bufs[curr_idx][curr_offset..curr_offset + to_take_first],
));
accumulated_len += to_take_first;
// Add others up to max_chunk
let mut temp_idx = curr_idx + 1;
while temp_idx < bufs.len() && accumulated_len < max_chunk {
let needed = max_chunk - accumulated_len;
let avail = bufs[temp_idx].len();
let take = std::cmp::min(avail, needed);
iovec.push(std::io::IoSlice::new(&bufs[temp_idx][..take]));
accumulated_len += take;
temp_idx += 1;
}
let n = socket.write_vectored(&iovec).await?;
if n == 0 {
return Err(io::Error::new(
io::ErrorKind::WriteZero,
"failed to write whole buffer",
)
.into());
}
// Advance cursor by n
let mut advanced = n;
while advanced > 0 && curr_idx < bufs.len() {
let available = bufs[curr_idx].len() - curr_offset;
if advanced < available {
curr_offset += advanced;
advanced = 0;
} else {
advanced -= available;
curr_idx += 1;
curr_offset = 0;
}
}
}
socket.flush().await?;
Ok(())
} else {
Err(IdeviceError::NoEstablishedConnection)
}
}
/// Sends raw binary data with progress callbacks
///
/// # Arguments
@@ -480,20 +389,14 @@ impl Idevice {
}
}
_ => {
tracing::error!("Error is not a string or integer from read_plist: {e:?}");
log::error!("Error is not a string or integer from read_plist: {e:?}");
return Err(IdeviceError::UnexpectedResponse);
}
};
if let Some(e) = IdeviceError::from_device_error_type(e.as_str(), &res) {
return Err(e);
} else {
let msg =
if let Some(desc) = res.get("ErrorDescription").and_then(|x| x.as_string()) {
format!("{} ({})", e, desc)
} else {
e
};
return Err(IdeviceError::UnknownErrorType(msg));
return Err(IdeviceError::UnknownErrorType(e));
}
}
Ok(res)
@@ -556,16 +459,7 @@ impl Idevice {
pub async fn start_session(
&mut self,
pairing_file: &pairing_file::PairingFile,
legacy: bool,
) -> Result<(), IdeviceError> {
#[cfg(feature = "rustls")]
{
if legacy {
tracing::warn!(
"Compiled with rustls, but connecting to legacy device! rustls does not support old SSL, this will fail."
);
}
if CryptoProvider::get_default().is_none() {
// rust-analyzer will choke on this block, don't worry about it
let crypto_provider: CryptoProvider = {
@@ -594,9 +488,7 @@ impl Idevice {
// My sanity while debugging the workspace crates are more important.
debug!("Using ring crypto backend, because both were passed");
tracing::warn!(
"Both ring && aws-lc are selected as idevice crypto backends!"
);
log::warn!("Both ring && aws-lc are selected as idevice crypto backends!");
rustls::crypto::ring::default_provider()
}
};
@@ -605,7 +497,7 @@ impl Idevice {
// For whatever reason, getting the default provider will return None on iOS at
// random. Installing the default provider a second time will return an error, so
// we will log it but not propogate it. An issue should be opened with rustls.
tracing::error!("Failed to set crypto provider: {e:?}");
log::error!("Failed to set crypto provider: {e:?}");
}
}
let config = sni::create_client_config(pairing_file)?;
@@ -620,30 +512,6 @@ impl Idevice {
Ok(())
}
#[cfg(all(feature = "openssl", not(feature = "rustls")))]
{
let mut connector =
openssl::ssl::SslConnector::builder(openssl::ssl::SslMethod::tls())?;
if legacy {
connector.set_min_proto_version(Some(openssl::ssl::SslVersion::SSL3))?;
connector.set_max_proto_version(Some(openssl::ssl::SslVersion::TLS1))?;
connector.set_cipher_list("ALL:!aNULL:!eNULL:@SECLEVEL=0")?;
connector.set_options(openssl::ssl::SslOptions::ALLOW_UNSAFE_LEGACY_RENEGOTIATION);
}
let mut connector = connector.build().configure()?.into_ssl("ur mom")?;
connector.set_certificate(&pairing_file.host_certificate)?;
connector.set_private_key(&pairing_file.host_private_key)?;
connector.set_verify(openssl::ssl::SslVerifyMode::empty());
let socket = self.socket.take().unwrap();
let mut ssl_stream = tokio_openssl::SslStream::new(connector, socket)?;
std::pin::Pin::new(&mut ssl_stream).connect().await?;
self.socket = Some(Box::new(ssl_stream));
Ok(())
}
}
}
/// Comprehensive error type for all device communication failures
@@ -653,24 +521,12 @@ impl Idevice {
pub enum IdeviceError {
#[error("device socket io failed")]
Socket(#[from] io::Error) = -1,
#[cfg(feature = "rustls")]
#[error("PEM parse failed")]
PemParseFailed(#[from] rustls::pki_types::pem::Error) = -2,
#[cfg(feature = "rustls")]
#[error("TLS error")]
Rustls(#[from] rustls::Error) = -3,
#[cfg(all(feature = "openssl", not(feature = "rustls")))]
#[error("TLS error")]
Rustls(#[from] openssl::ssl::Error) = -3,
#[cfg(feature = "rustls")]
#[error("TLS verifiction build failed")]
TlsBuilderFailed(#[from] rustls::server::VerifierBuilderError) = -4,
#[cfg(all(feature = "openssl", not(feature = "rustls")))]
#[error("TLS verifiction build failed")]
TlsBuilderFailed(#[from] openssl::error::ErrorStack) = -4,
#[error("io on plist")]
Plist(#[from] plist::Error) = -5,
#[error("can't convert bytes to utf8")]
@@ -868,25 +724,24 @@ pub enum IdeviceError {
#[error("Developer mode is not enabled")]
DeveloperModeNotEnabled = -68,
#[error("Unknown TLV {0}")]
UnknownTlv(u8) = -69,
#[error("Malformed TLV")]
MalformedTlv = -70,
#[error("Pairing rejected: {0}")]
PairingRejected(String) = -71,
#[cfg(feature = "remote_pairing")]
#[error("Base64 decode error")]
Base64DecodeError(#[from] base64::DecodeError) = -72,
#[error("Pair verified failed")]
#[error("could not parse as JSON")]
JsonParseFailed(#[from] json::Error) = -69,
#[cfg(feature = "remote_pairing")]
#[error("unknown TLV type: {0}")]
UnknownTlv(u8) = -70,
#[cfg(feature = "remote_pairing")]
#[error("malformed TLV")]
MalformedTlv = -71,
#[error("failed to decode base64 string")]
Base64Decode(#[from] base64::DecodeError) = -72,
#[cfg(feature = "remote_pairing")]
#[error("pair verify failed")]
PairVerifyFailed = -73,
#[error("SRP auth failed")]
SrpAuthFailed = -74,
#[cfg(feature = "remote_pairing")]
#[error("Chacha encryption error")]
ChachaEncryption(chacha20poly1305::Error) = -75,
#[cfg(feature = "notification_proxy")]
#[error("notification proxy died")]
NotificationProxyDeath = -76,
}
impl IdeviceError {
@@ -937,7 +792,6 @@ impl IdeviceError {
pub fn code(&self) -> i32 {
match self {
IdeviceError::Socket(_) => -1,
#[cfg(feature = "rustls")]
IdeviceError::PemParseFailed(_) => -2,
IdeviceError::Rustls(_) => -3,
IdeviceError::TlsBuilderFailed(_) => -4,
@@ -1052,17 +906,15 @@ impl IdeviceError {
#[cfg(feature = "installation_proxy")]
IdeviceError::MalformedPackageArchive(_) => -67,
IdeviceError::DeveloperModeNotEnabled => -68,
IdeviceError::UnknownTlv(_) => -69,
IdeviceError::MalformedTlv => -70,
IdeviceError::PairingRejected(_) => -71,
#[cfg(feature = "remote_pairing")]
IdeviceError::Base64DecodeError(_) => -72,
IdeviceError::JsonParseFailed(_) => -69,
#[cfg(feature = "remote_pairing")]
IdeviceError::UnknownTlv(_) => -70,
#[cfg(feature = "remote_pairing")]
IdeviceError::MalformedTlv => -71,
IdeviceError::Base64Decode(_) => -72,
#[cfg(feature = "remote_pairing")]
IdeviceError::PairVerifyFailed => -73,
IdeviceError::SrpAuthFailed => -74,
IdeviceError::ChachaEncryption(_) => -75,
#[cfg(feature = "notification_proxy")]
IdeviceError::NotificationProxyDeath => -76,
}
}
}

View File

@@ -1,15 +0,0 @@
// Jackson Coxson
#[macro_export]
macro_rules! obf {
($lit:literal) => {{
#[cfg(feature = "obfuscate")]
{
std::borrow::Cow::Owned(obfstr::obfstr!($lit).to_string())
}
#[cfg(not(feature = "obfuscate"))]
{
std::borrow::Cow::Borrowed($lit)
}
}};
}

View File

@@ -5,22 +5,15 @@
use std::path::Path;
#[cfg(all(feature = "openssl", not(feature = "rustls")))]
use openssl::{
pkey::{PKey, Private},
x509::X509,
};
use log::warn;
use plist::Data;
#[cfg(feature = "rustls")]
use rustls::pki_types::{CertificateDer, pem::PemObject};
use serde::{Deserialize, Serialize};
use tracing::warn;
/// Represents a complete iOS device pairing record
///
/// Contains all cryptographic materials and identifiers needed for secure communication
/// with an iOS device, including certificates, private keys, and device identifiers.
#[cfg(feature = "rustls")]
#[derive(Clone, Debug)]
pub struct PairingFile {
/// Device's certificate in DER format
@@ -38,28 +31,13 @@ pub struct PairingFile {
/// Host identifier
pub host_id: String,
/// Escrow bag allowing for access while locked
pub escrow_bag: Option<Vec<u8>>,
pub escrow_bag: Vec<u8>,
/// Device's WiFi MAC address
pub wifi_mac_address: String,
/// Device's Unique Device Identifier (optional)
pub udid: Option<String>,
}
#[cfg(all(feature = "openssl", not(feature = "rustls")))]
#[derive(Clone, Debug)]
pub struct PairingFile {
pub device_certificate: X509,
pub host_private_key: PKey<Private>,
pub host_certificate: X509,
pub root_private_key: PKey<Private>,
pub root_certificate: X509,
pub system_buid: String,
pub host_id: String,
pub escrow_bag: Vec<u8>,
pub wifi_mac_address: String,
pub udid: Option<String>,
}
/// Internal representation of a pairing file for serialization/deserialization
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "PascalCase")]
@@ -73,7 +51,7 @@ struct RawPairingFile {
system_buid: String,
#[serde(rename = "HostID")]
host_id: String,
escrow_bag: Option<Data>, // None on Apple Watch
escrow_bag: Data,
#[serde(rename = "WiFiMACAddress")]
wifi_mac_address: String,
#[serde(rename = "UDID")]
@@ -155,7 +133,6 @@ impl PairingFile {
///
/// # Errors
/// Returns `IdeviceError` if serialization fails
#[cfg(feature = "rustls")]
pub fn serialize(self) -> Result<Vec<u8>, crate::IdeviceError> {
let raw = RawPairingFile::from(self);
@@ -163,18 +140,8 @@ impl PairingFile {
plist::to_writer_xml(&mut buf, &raw)?;
Ok(buf)
}
#[cfg(all(feature = "openssl", not(feature = "rustls")))]
pub fn serialize(self) -> Result<Vec<u8>, crate::IdeviceError> {
let raw = RawPairingFile::try_from(self)?;
let mut buf = Vec::new();
plist::to_writer_xml(&mut buf, &raw)?;
Ok(buf)
}
}
#[cfg(feature = "rustls")]
impl TryFrom<RawPairingFile> for PairingFile {
type Error = rustls::pki_types::pem::Error;
@@ -206,38 +173,13 @@ impl TryFrom<RawPairingFile> for PairingFile {
root_certificate: CertificateDer::from_pem_slice(&root_certificate_pem)?,
system_buid: value.system_buid,
host_id: value.host_id,
escrow_bag: value.escrow_bag.map(|x| x.into()),
escrow_bag: value.escrow_bag.into(),
wifi_mac_address: value.wifi_mac_address,
udid: value.udid,
})
}
}
#[cfg(all(feature = "openssl", not(feature = "rustls")))]
impl TryFrom<RawPairingFile> for PairingFile {
type Error = openssl::error::ErrorStack;
fn try_from(value: RawPairingFile) -> Result<Self, Self::Error> {
Ok(Self {
device_certificate: X509::from_pem(&Into::<Vec<u8>>::into(value.device_certificate))?,
host_private_key: PKey::private_key_from_pem(&Into::<Vec<u8>>::into(
value.host_private_key,
))?,
host_certificate: X509::from_pem(&Into::<Vec<u8>>::into(value.host_certificate))?,
root_private_key: PKey::private_key_from_pem(&Into::<Vec<u8>>::into(
value.root_private_key,
))?,
root_certificate: X509::from_pem(&Into::<Vec<u8>>::into(value.root_certificate))?,
system_buid: value.system_buid,
host_id: value.host_id,
escrow_bag: value.escrow_bag.map(|x| x.into()),
wifi_mac_address: value.wifi_mac_address,
udid: value.udid,
})
}
}
#[cfg(feature = "rustls")]
impl From<PairingFile> for RawPairingFile {
/// Converts a structured pairing file into a raw pairing file for serialization
fn from(value: PairingFile) -> Self {
@@ -258,33 +200,13 @@ impl From<PairingFile> for RawPairingFile {
root_certificate: Data::new(root_cert_data),
system_buid: value.system_buid,
host_id: value.host_id.clone(),
escrow_bag: value.escrow_bag.map(Data::new),
escrow_bag: Data::new(value.escrow_bag),
wifi_mac_address: value.wifi_mac_address,
udid: value.udid,
}
}
}
#[cfg(all(feature = "openssl", not(feature = "rustls")))]
impl TryFrom<PairingFile> for RawPairingFile {
type Error = openssl::error::ErrorStack;
fn try_from(value: PairingFile) -> Result<Self, Self::Error> {
Ok(Self {
device_certificate: Data::new(value.device_certificate.to_pem()?),
host_private_key: Data::new(value.host_private_key.private_key_to_pem_pkcs8()?),
host_certificate: Data::new(value.host_certificate.to_pem()?),
root_private_key: Data::new(value.root_private_key.private_key_to_pem_pkcs8()?),
root_certificate: Data::new(value.root_certificate.to_pem()?),
system_buid: value.system_buid,
host_id: value.host_id.clone(),
escrow_bag: value.escrow_bag.map(Data::new),
wifi_mac_address: value.wifi_mac_address,
udid: value.udid,
})
}
}
/// Helper function to ensure data has proper PEM headers
/// If the data already has headers, it returns it as is
/// If not, it adds the appropriate BEGIN and END headers

714
idevice/src/plist_macro.rs Normal file
View File

@@ -0,0 +1,714 @@
// Jackson Coxson
// Ported from serde's json!
/// Construct a `plist::Value` from a JSON-like literal.
///
/// ```
/// # use idevice::plist;
/// #
/// let value = plist!({
/// "code": 200,
/// "success": true,
/// "payload": {
/// "features": [
/// "serde",
/// "plist"
/// ],
/// "homepage": null
/// }
/// });
/// ```
///
/// Variables or expressions can be interpolated into the plist literal. Any type
/// interpolated into an array element or object value must implement `Into<plist::Value>`.
/// If the conversion fails, the `plist!` macro will panic.
///
/// ```
/// # use idevice::plist;
/// #
/// let code = 200;
/// let features = vec!["serde", "plist"];
///
/// let value = plist!({
/// "code": code,
/// "success": code == 200,
/// "payload": {
/// features[0]: features[1]
/// }
/// });
/// ```
///
/// Trailing commas are allowed inside both arrays and objects.
///
/// ```
/// # use idevice::plist;
/// #
/// let value = plist!([
/// "notice",
/// "the",
/// "trailing",
/// "comma -->",
/// ]);
/// ```
#[macro_export]
macro_rules! plist {
// Force: dictionary out
(dict { $($tt:tt)+ }) => {{
let mut object = plist::Dictionary::new();
$crate::plist_internal!(@object object () ($($tt)+) ($($tt)+));
object
}};
// Force: value out (explicit, though default already does this)
(value { $($tt:tt)+ }) => {
$crate::plist_internal!({ $($tt)+ })
};
// Force: raw vec of plist::Value out
(array [ $($tt:tt)+ ]) => {
$crate::plist_internal!(@array [] $($tt)+)
};
// Hide distracting implementation details from the generated rustdoc.
($($plist:tt)+) => {
$crate::plist_internal!($($plist)+)
};
}
#[macro_export]
#[doc(hidden)]
macro_rules! plist_internal {
//////////////////////////////////////////////////////////////////////////
// TT muncher for parsing the inside of an array [...]. Produces a vec![...]
// of the elements.
//
// Must be invoked as: plist_internal!(@array [] $($tt)*)
//////////////////////////////////////////////////////////////////////////
// Done with trailing comma.
(@array [$($elems:expr,)*]) => {
vec![$($elems,)*]
};
// Done without trailing comma.
(@array [$($elems:expr),*]) => {
vec![$($elems),*]
};
// Next element is `null`.
(@array [$($elems:expr,)*] null $($rest:tt)*) => {
$crate::plist_internal!(@array [$($elems,)* $crate::plist_internal!(null)] $($rest)*)
};
// Next element is `true`.
(@array [$($elems:expr,)*] true $($rest:tt)*) => {
$crate::plist_internal!(@array [$($elems,)* $crate::plist_internal!(true)] $($rest)*)
};
// Next element is `false`.
(@array [$($elems:expr,)*] false $($rest:tt)*) => {
$crate::plist_internal!(@array [$($elems,)* $crate::plist_internal!(false)] $($rest)*)
};
// Next element is an array.
(@array [$($elems:expr,)*] [$($array:tt)*] $($rest:tt)*) => {
$crate::plist_internal!(@array [$($elems,)* $crate::plist_internal!([$($array)*])] $($rest)*)
};
// Next element is a map.
(@array [$($elems:expr,)*] {$($map:tt)*} $($rest:tt)*) => {
$crate::plist_internal!(@array [$($elems,)* $crate::plist_internal!({$($map)*})] $($rest)*)
};
// Next element is an expression followed by comma.
(@array [$($elems:expr,)*] $next:expr, $($rest:tt)*) => {
$crate::plist_internal!(@array [$($elems,)* $crate::plist_internal!($next),] $($rest)*)
};
// Last element is an expression with no trailing comma.
(@array [$($elems:expr,)*] $last:expr) => {
$crate::plist_internal!(@array [$($elems,)* $crate::plist_internal!($last)])
};
// Comma after the most recent element.
(@array [$($elems:expr),*] , $($rest:tt)*) => {
$crate::plist_internal!(@array [$($elems,)*] $($rest)*)
};
// Unexpected token after most recent element.
(@array [$($elems:expr),*] $unexpected:tt $($rest:tt)*) => {
$crate::plist_unexpected!($unexpected)
};
(@array [$($elems:expr,)*] ? $maybe:expr , $($rest:tt)*) => {
if let Some(__v) = $crate::plist_macro::plist_maybe($maybe) {
$crate::plist_internal!(@array [$($elems,)* __v,] $($rest)*)
} else {
$crate::plist_internal!(@array [$($elems,)*] $($rest)*)
}
};
(@array [$($elems:expr,)*] ? $maybe:expr) => {
if let Some(__v) = $crate::plist_macro::plist_maybe($maybe) {
$crate::plist_internal!(@array [$($elems,)* __v])
} else {
$crate::plist_internal!(@array [$($elems,)*])
}
};
//////////////////////////////////////////////////////////////////////////
// TT muncher for parsing the inside of an object {...}. Each entry is
// inserted into the given map variable.
//
// Must be invoked as: plist_internal!(@object $map () ($($tt)*) ($($tt)*))
//
// We require two copies of the input tokens so that we can match on one
// copy and trigger errors on the other copy.
//////////////////////////////////////////////////////////////////////////
// Done.
(@object $object:ident () () ()) => {};
// Insert the current entry followed by trailing comma.
(@object $object:ident [$($key:tt)+] ($value:expr) , $($rest:tt)*) => {
let _ = $object.insert(($($key)+).into(), $value);
$crate::plist_internal!(@object $object () ($($rest)*) ($($rest)*));
};
// Current entry followed by unexpected token.
(@object $object:ident [$($key:tt)+] ($value:expr) $unexpected:tt $($rest:tt)*) => {
$crate::plist_unexpected!($unexpected);
};
// Insert the last entry without trailing comma.
(@object $object:ident [$($key:tt)+] ($value:expr)) => {
let _ = $object.insert(($($key)+).into(), $value);
};
// Next value is `null`.
(@object $object:ident ($($key:tt)+) (: null $($rest:tt)*) $copy:tt) => {
$crate::plist_internal!(@object $object [$($key)+] ($crate::plist_internal!(null)) $($rest)*);
};
// Next value is `true`.
(@object $object:ident ($($key:tt)+) (: true $($rest:tt)*) $copy:tt) => {
$crate::plist_internal!(@object $object [$($key)+] ($crate::plist_internal!(true)) $($rest)*);
};
// Next value is `false`.
(@object $object:ident ($($key:tt)+) (: false $($rest:tt)*) $copy:tt) => {
$crate::plist_internal!(@object $object [$($key)+] ($crate::plist_internal!(false)) $($rest)*);
};
// Next value is an array.
(@object $object:ident ($($key:tt)+) (: [$($array:tt)*] $($rest:tt)*) $copy:tt) => {
$crate::plist_internal!(@object $object [$($key)+] ($crate::plist_internal!([$($array)*])) $($rest)*);
};
// Next value is a map.
(@object $object:ident ($($key:tt)+) (: {$($map:tt)*} $($rest:tt)*) $copy:tt) => {
$crate::plist_internal!(@object $object [$($key)+] ($crate::plist_internal!({$($map)*})) $($rest)*);
};
// Optional insert with trailing comma: key?: expr,
(@object $object:ident ($($key:tt)+) (:? $value:expr , $($rest:tt)*) $copy:tt) => {
if let Some(__v) = $crate::plist_macro::plist_maybe($value) {
let _ = $object.insert(($($key)+).into(), __v);
}
$crate::plist_internal!(@object $object () ($($rest)*) ($($rest)*));
};
// Optional insert, last entry: key?: expr
(@object $object:ident ($($key:tt)+) (:? $value:expr) $copy:tt) => {
if let Some(__v) = $crate::plist_macro::plist_maybe($value) {
let _ = $object.insert(($($key)+).into(), __v);
}
};
(@object $object:ident () ( :< $value:expr , $($rest:tt)*) $copy:tt) => {
{
let __v = $crate::plist_internal!($value);
let __dict = $crate::plist_macro::IntoPlistDict::into_plist_dict(__v);
for (__k, __val) in __dict {
let _ = $object.insert(__k, __val);
}
}
$crate::plist_internal!(@object $object () ($($rest)*) ($($rest)*));
};
// Merge: last entry `:< expr`
(@object $object:ident () ( :< $value:expr ) $copy:tt) => {
{
let __v = $crate::plist_internal!($value);
let __dict = $crate::plist_macro::IntoPlistDict::into_plist_dict(__v);
for (__k, __val) in __dict {
let _ = $object.insert(__k, __val);
}
}
};
// Optional merge: `:< ? expr,` — only merge if Some(...)
(@object $object:ident () ( :< ? $value:expr , $($rest:tt)*) $copy:tt) => {
if let Some(__dict) = $crate::plist_macro::maybe_into_dict($value) {
for (__k, __val) in __dict {
let _ = $object.insert(__k, __val);
}
}
$crate::plist_internal!(@object $object () ($($rest)*) ($($rest)*));
};
// Optional merge: last entry `:< ? expr`
(@object $object:ident () ( :< ? $value:expr ) $copy:tt) => {
if let Some(__dict) = $crate::plist_macro::maybe_into_dict($value) {
for (__k, __val) in __dict {
let _ = $object.insert(__k, __val);
}
}
};
// Next value is an expression followed by comma.
(@object $object:ident ($($key:tt)+) (: $value:expr , $($rest:tt)*) $copy:tt) => {
$crate::plist_internal!(@object $object [$($key)+] ($crate::plist_internal!($value)) , $($rest)*);
};
// Last value is an expression with no trailing comma.
(@object $object:ident ($($key:tt)+) (: $value:expr) $copy:tt) => {
$crate::plist_internal!(@object $object [$($key)+] ($crate::plist_internal!($value)));
};
// Missing value for last entry. Trigger a reasonable error message.
(@object $object:ident ($($key:tt)+) (:) $copy:tt) => {
// "unexpected end of macro invocation"
$crate::plist_internal!();
};
// Missing colon and value for last entry. Trigger a reasonable error
// message.
(@object $object:ident ($($key:tt)+) () $copy:tt) => {
// "unexpected end of macro invocation"
$crate::plist_internal!();
};
// Misplaced colon. Trigger a reasonable error message.
(@object $object:ident () (: $($rest:tt)*) ($colon:tt $($copy:tt)*)) => {
// Takes no arguments so "no rules expected the token `:`".
$crate::plist_unexpected!($colon);
};
// Found a comma inside a key. Trigger a reasonable error message.
(@object $object:ident ($($key:tt)*) (, $($rest:tt)*) ($comma:tt $($copy:tt)*)) => {
// Takes no arguments so "no rules expected the token `,`".
$crate::plist_unexpected!($comma);
};
// Key is fully parenthesized. This avoids clippy double_parens false
// positives because the parenthesization may be necessary here.
(@object $object:ident () (($key:expr) : $($rest:tt)*) $copy:tt) => {
$crate::plist_internal!(@object $object ($key) (: $($rest)*) (: $($rest)*));
};
// Refuse to absorb colon token into key expression.
(@object $object:ident ($($key:tt)*) (: $($unexpected:tt)+) $copy:tt) => {
$crate::plist_expect_expr_comma!($($unexpected)+);
};
// Munch a token into the current key.
(@object $object:ident ($($key:tt)*) ($tt:tt $($rest:tt)*) $copy:tt) => {
$crate::plist_internal!(@object $object ($($key)* $tt) ($($rest)*) ($($rest)*));
};
//////////////////////////////////////////////////////////////////////////
// The main implementation.
//
// Must be invoked as: plist_internal!($($plist)+)
//////////////////////////////////////////////////////////////////////////
(null) => {
plist::Value::String("".to_string()) // plist doesn't have null, use empty string or consider other representation
};
(true) => {
plist::Value::Boolean(true)
};
(false) => {
plist::Value::Boolean(false)
};
([]) => {
plist::Value::Array(vec![])
};
([ $($tt:tt)+ ]) => {
plist::Value::Array($crate::plist_internal!(@array [] $($tt)+))
};
({}) => {
plist::Value::Dictionary(plist::Dictionary::new())
};
({ $($tt:tt)+ }) => {
plist::Value::Dictionary({
let mut object = plist::Dictionary::new();
$crate::plist_internal!(@object object () ($($tt)+) ($($tt)+));
object
})
};
// Any type that can be converted to plist::Value: numbers, strings, variables etc.
// Must be below every other rule.
($other:expr) => {
$crate::plist_macro::plist_to_value($other)
};
}
#[macro_export]
#[doc(hidden)]
macro_rules! plist_unexpected {
() => {};
}
#[macro_export]
#[doc(hidden)]
macro_rules! plist_expect_expr_comma {
($e:expr , $($tt:tt)*) => {};
}
// Helper function to convert various types to plist::Value
#[doc(hidden)]
pub fn plist_to_value<T: PlistConvertible>(value: T) -> plist::Value {
value.to_plist_value()
}
// Trait for types that can be converted to plist::Value
pub trait PlistConvertible {
fn to_plist_value(self) -> plist::Value;
}
// Implementations for common types
impl PlistConvertible for plist::Value {
fn to_plist_value(self) -> plist::Value {
self
}
}
impl PlistConvertible for String {
fn to_plist_value(self) -> plist::Value {
plist::Value::String(self)
}
}
impl PlistConvertible for &str {
fn to_plist_value(self) -> plist::Value {
plist::Value::String(self.to_string())
}
}
impl PlistConvertible for i16 {
fn to_plist_value(self) -> plist::Value {
plist::Value::Integer(self.into())
}
}
impl PlistConvertible for i32 {
fn to_plist_value(self) -> plist::Value {
plist::Value::Integer(self.into())
}
}
impl PlistConvertible for i64 {
fn to_plist_value(self) -> plist::Value {
plist::Value::Integer(self.into())
}
}
impl PlistConvertible for u16 {
fn to_plist_value(self) -> plist::Value {
plist::Value::Integer((self as i64).into())
}
}
impl PlistConvertible for u32 {
fn to_plist_value(self) -> plist::Value {
plist::Value::Integer((self as i64).into())
}
}
impl PlistConvertible for u64 {
fn to_plist_value(self) -> plist::Value {
plist::Value::Integer((self as i64).into())
}
}
impl PlistConvertible for f32 {
fn to_plist_value(self) -> plist::Value {
plist::Value::Real(self as f64)
}
}
impl PlistConvertible for f64 {
fn to_plist_value(self) -> plist::Value {
plist::Value::Real(self)
}
}
impl PlistConvertible for bool {
fn to_plist_value(self) -> plist::Value {
plist::Value::Boolean(self)
}
}
impl<'a> PlistConvertible for std::borrow::Cow<'a, str> {
fn to_plist_value(self) -> plist::Value {
plist::Value::String(self.into_owned())
}
}
impl PlistConvertible for Vec<u8> {
fn to_plist_value(self) -> plist::Value {
plist::Value::Data(self)
}
}
impl PlistConvertible for &[u8] {
fn to_plist_value(self) -> plist::Value {
plist::Value::Data(self.to_vec())
}
}
impl PlistConvertible for std::time::SystemTime {
fn to_plist_value(self) -> plist::Value {
plist::Value::Date(self.into())
}
}
impl<T: PlistConvertible> PlistConvertible for Vec<T> {
fn to_plist_value(self) -> plist::Value {
plist::Value::Array(self.into_iter().map(|item| item.to_plist_value()).collect())
}
}
impl<T: PlistConvertible + Clone> PlistConvertible for &[T] {
fn to_plist_value(self) -> plist::Value {
plist::Value::Array(
self.iter()
.map(|item| item.clone().to_plist_value())
.collect(),
)
}
}
impl<T: PlistConvertible + Clone, const N: usize> PlistConvertible for [T; N] {
fn to_plist_value(self) -> plist::Value {
plist::Value::Array(self.into_iter().map(|item| item.to_plist_value()).collect())
}
}
impl<T: PlistConvertible + Clone, const N: usize> PlistConvertible for &[T; N] {
fn to_plist_value(self) -> plist::Value {
plist::Value::Array(
self.iter()
.map(|item| item.clone().to_plist_value())
.collect(),
)
}
}
impl PlistConvertible for plist::Dictionary {
fn to_plist_value(self) -> plist::Value {
plist::Value::Dictionary(self)
}
}
impl<K, V> PlistConvertible for std::collections::HashMap<K, V>
where
K: Into<String>,
V: PlistConvertible,
{
fn to_plist_value(self) -> plist::Value {
let mut dict = plist::Dictionary::new();
for (key, value) in self {
dict.insert(key.into(), value.to_plist_value());
}
plist::Value::Dictionary(dict)
}
}
impl<K, V> PlistConvertible for std::collections::BTreeMap<K, V>
where
K: Into<String>,
V: PlistConvertible,
{
fn to_plist_value(self) -> plist::Value {
let mut dict = plist::Dictionary::new();
for (key, value) in self {
dict.insert(key.into(), value.to_plist_value());
}
plist::Value::Dictionary(dict)
}
}
// Treat plain T as Some(T) and Option<T> as-is.
pub trait MaybePlist {
fn into_option_value(self) -> Option<plist::Value>;
}
impl<T: PlistConvertible> MaybePlist for T {
fn into_option_value(self) -> Option<plist::Value> {
Some(self.to_plist_value())
}
}
impl<T: PlistConvertible> MaybePlist for Option<T> {
fn into_option_value(self) -> Option<plist::Value> {
self.map(|v| v.to_plist_value())
}
}
#[doc(hidden)]
pub fn plist_maybe<T: MaybePlist>(v: T) -> Option<plist::Value> {
v.into_option_value()
}
// Convert things into a Dictionary we can merge.
pub trait IntoPlistDict {
fn into_plist_dict(self) -> plist::Dictionary;
}
impl IntoPlistDict for plist::Dictionary {
fn into_plist_dict(self) -> plist::Dictionary {
self
}
}
impl IntoPlistDict for plist::Value {
fn into_plist_dict(self) -> plist::Dictionary {
match self {
plist::Value::Dictionary(d) => d,
other => panic!("plist :< expects a dictionary, got {other:?}"),
}
}
}
impl<K, V> IntoPlistDict for std::collections::HashMap<K, V>
where
K: Into<String>,
V: PlistConvertible,
{
fn into_plist_dict(self) -> plist::Dictionary {
let mut d = plist::Dictionary::new();
for (k, v) in self {
d.insert(k.into(), v.to_plist_value());
}
d
}
}
impl<K, V> IntoPlistDict for std::collections::BTreeMap<K, V>
where
K: Into<String>,
V: PlistConvertible,
{
fn into_plist_dict(self) -> plist::Dictionary {
let mut d = plist::Dictionary::new();
for (k, v) in self {
d.insert(k.into(), v.to_plist_value());
}
d
}
}
// Optional version: T or Option<T>.
pub trait MaybeIntoPlistDict {
fn into_option_plist_dict(self) -> Option<plist::Dictionary>;
}
impl<T: IntoPlistDict> MaybeIntoPlistDict for T {
fn into_option_plist_dict(self) -> Option<plist::Dictionary> {
Some(self.into_plist_dict())
}
}
impl<T: IntoPlistDict> MaybeIntoPlistDict for Option<T> {
fn into_option_plist_dict(self) -> Option<plist::Dictionary> {
self.map(|t| t.into_plist_dict())
}
}
#[doc(hidden)]
pub fn maybe_into_dict<T: MaybeIntoPlistDict>(v: T) -> Option<plist::Dictionary> {
v.into_option_plist_dict()
}
#[cfg(test)]
mod tests {
#[test]
fn test_plist_macro_basic() {
let value = plist!({
"name": "test",
"count": 42,
"active": true,
"items": ["a", ?"b", "c"]
});
if let plist::Value::Dictionary(dict) = value {
assert_eq!(
dict.get("name"),
Some(&plist::Value::String("test".to_string()))
);
assert_eq!(dict.get("count"), Some(&plist::Value::Integer(42.into())));
assert_eq!(dict.get("active"), Some(&plist::Value::Boolean(true)));
} else {
panic!("Expected dictionary");
}
}
#[test]
fn test_plist_macro_with_variables() {
let name = "dynamic";
let count = 100;
let items = vec!["x", "y"];
let none: Option<u64> = None;
let to_merge = plist!({
"reee": "cool beans"
});
let maybe_merge = Some(plist!({
"yeppers": "what did I say about yeppers",
"replace me": 2,
}));
let value = plist!({
"name": name,
"count": count,
"items": items,
"omit me":? none,
"keep me":? Some(123),
"replace me": 1,
:< to_merge,
:<? maybe_merge
});
if let plist::Value::Dictionary(dict) = value {
assert_eq!(
dict.get("name"),
Some(&plist::Value::String("dynamic".to_string()))
);
assert_eq!(dict.get("count"), Some(&plist::Value::Integer(100.into())));
assert!(dict.get("omit me").is_none());
assert_eq!(
dict.get("keep me"),
Some(&plist::Value::Integer(123.into()))
);
assert_eq!(
dict.get("reee"),
Some(&plist::Value::String("cool beans".to_string()))
);
assert_eq!(
dict.get("yeppers"),
Some(&plist::Value::String(
"what did I say about yeppers".to_string()
))
);
assert_eq!(
dict.get("replace me"),
Some(&plist::Value::Integer(2.into()))
);
} else {
panic!("Expected dictionary");
}
}
}

View File

@@ -1,720 +0,0 @@
//! Remote Pairing
use crate::IdeviceError;
use chacha20poly1305::{
ChaCha20Poly1305, Key, KeyInit, Nonce,
aead::{Aead, Payload},
};
use ed25519_dalek::Signature;
use hkdf::Hkdf;
use idevice_srp::{client::SrpClient, groups::G_3072};
use plist_macro::plist;
use plist_macro::{PlistConvertible, PlistExt};
use rand::RngCore;
use rsa::{rand_core::OsRng, signature::SignerMut};
use serde::Serialize;
use sha2::Sha512;
use tracing::{debug, warn};
use x25519_dalek::{EphemeralSecret, PublicKey as X25519PublicKey};
mod opack;
mod rp_pairing_file;
mod socket;
mod tlv;
// export
pub use rp_pairing_file::RpPairingFile;
pub use socket::{RpPairingSocket, RpPairingSocketProvider};
const RPPAIRING_MAGIC: &[u8] = b"RPPairing";
const WIRE_PROTOCOL_VERSION: u8 = 19;
pub struct RemotePairingClient<'a, R: RpPairingSocketProvider> {
inner: R,
sequence_number: usize,
pairing_file: &'a mut RpPairingFile,
sending_host: String,
client_cipher: ChaCha20Poly1305,
server_cipher: ChaCha20Poly1305,
}
impl<'a, R: RpPairingSocketProvider> RemotePairingClient<'a, R> {
pub fn new(inner: R, sending_host: &str, pairing_file: &'a mut RpPairingFile) -> Self {
let hk = Hkdf::<sha2::Sha512>::new(None, pairing_file.e_private_key.as_bytes());
let mut okm = [0u8; 32];
hk.expand(b"ClientEncrypt-main", &mut okm).unwrap();
let client_cipher = ChaCha20Poly1305::new(chacha20poly1305::Key::from_slice(&okm));
let hk = Hkdf::<sha2::Sha512>::new(None, pairing_file.e_private_key.as_bytes());
let mut okm = [0u8; 32];
hk.expand(b"ServerEncrypt-main", &mut okm).unwrap();
let server_cipher = ChaCha20Poly1305::new(chacha20poly1305::Key::from_slice(&okm));
Self {
inner,
sequence_number: 0,
pairing_file,
sending_host: sending_host.to_string(),
client_cipher,
server_cipher,
}
}
pub async fn connect<Fut, S>(
&mut self,
pin_callback: impl Fn(S) -> Fut,
state: S,
) -> Result<(), IdeviceError>
where
Fut: std::future::Future<Output = String>,
{
self.attempt_pair_verify().await?;
if self.validate_pairing().await.is_err() {
self.pair(pin_callback, state).await?;
}
Ok(())
}
pub async fn validate_pairing(&mut self) -> Result<(), IdeviceError> {
let x_private_key = EphemeralSecret::random_from_rng(OsRng);
let x_public_key = X25519PublicKey::from(&x_private_key);
let pairing_data = tlv::serialize_tlv8(&[
tlv::TLV8Entry {
tlv_type: tlv::PairingDataComponentType::State,
data: vec![0x01],
},
tlv::TLV8Entry {
tlv_type: tlv::PairingDataComponentType::PublicKey,
data: x_public_key.to_bytes().to_vec(),
},
]);
let pairing_data = R::serialize_bytes(&pairing_data);
self.send_pairing_data(plist!({
"data": pairing_data,
"kind": "verifyManualPairing",
"startNewSession": true
}))
.await?;
debug!("Waiting for response from verifyManualPairing");
let pairing_data = self.receive_pairing_data().await?;
let data = match R::deserialize_bytes(pairing_data) {
Some(d) => d,
None => {
return Err(IdeviceError::UnexpectedResponse);
}
};
let data = tlv::deserialize_tlv8(&data)?;
if data
.iter()
.any(|x| x.tlv_type == tlv::PairingDataComponentType::ErrorResponse)
{
self.send_pair_verified_failed().await?;
return Err(IdeviceError::PairVerifyFailed);
}
let device_public_key = match data
.iter()
.find(|x| x.tlv_type == tlv::PairingDataComponentType::PublicKey)
{
Some(d) => d,
None => {
warn!("No public key in TLV data");
return Err(IdeviceError::UnexpectedResponse);
}
};
let peer_pub_bytes: [u8; 32] = match device_public_key.data.as_slice().try_into() {
Ok(d) => d,
Err(_) => {
warn!("Device public key isn't the expected size");
return Err(IdeviceError::NotEnoughBytes(
32,
device_public_key.data.len(),
));
}
};
let device_public_key = x25519_dalek::PublicKey::from(peer_pub_bytes);
let shared_secret = x_private_key.diffie_hellman(&device_public_key);
// Derive encryption key with HKDF-SHA512
let hk =
Hkdf::<sha2::Sha512>::new(Some(b"Pair-Verify-Encrypt-Salt"), shared_secret.as_bytes());
let mut okm = [0u8; 32];
hk.expand(b"Pair-Verify-Encrypt-Info", &mut okm).unwrap();
// ChaCha20Poly1305 AEAD cipher
let cipher = ChaCha20Poly1305::new(chacha20poly1305::Key::from_slice(&okm));
let ed25519_signing_key = &mut self.pairing_file.e_private_key;
let mut signbuf = Vec::with_capacity(32 + self.pairing_file.identifier.len() + 32);
signbuf.extend_from_slice(x_public_key.as_bytes()); // 32 bytes
signbuf.extend_from_slice(self.pairing_file.identifier.as_bytes()); // variable
signbuf.extend_from_slice(device_public_key.as_bytes()); // 32 bytes
let signature: Signature = ed25519_signing_key.sign(&signbuf);
let plaintext = vec![
tlv::TLV8Entry {
tlv_type: tlv::PairingDataComponentType::Identifier,
data: self.pairing_file.identifier.as_bytes().to_vec(),
},
tlv::TLV8Entry {
tlv_type: tlv::PairingDataComponentType::Signature,
data: signature.to_vec(),
},
];
let plaintext = tlv::serialize_tlv8(&plaintext);
let nonce = Nonce::from_slice(b"\x00\x00\x00\x00PV-Msg03"); // 12-byte nonce
let ciphertext = cipher
.encrypt(
nonce,
Payload {
msg: &plaintext,
aad: &[],
},
)
.expect("encryption should not fail");
let msg = vec![
tlv::TLV8Entry {
tlv_type: tlv::PairingDataComponentType::State,
data: [0x03].to_vec(),
},
tlv::TLV8Entry {
tlv_type: tlv::PairingDataComponentType::EncryptedData,
data: ciphertext,
},
];
debug!("Waiting for signbuf response");
self.send_pairing_data(plist! ({
"data": R::serialize_bytes(&tlv::serialize_tlv8(&msg)),
"kind": "verifyManualPairing",
"startNewSession": false
}))
.await?;
let res = self.receive_pairing_data().await?;
let data = match R::deserialize_bytes(res) {
Some(d) => d,
None => return Err(IdeviceError::UnexpectedResponse),
};
let data = tlv::deserialize_tlv8(&data)?;
debug!("Verify TLV: {data:#?}");
// Check if the device responded with an error (which is expected for a new pairing)
if data
.iter()
.any(|x| x.tlv_type == tlv::PairingDataComponentType::ErrorResponse)
{
debug!(
"Verification failed, device reported an error. This is expected for a new pairing."
);
self.send_pair_verified_failed().await?;
// Return a specific error to the caller.
return Err(IdeviceError::PairVerifyFailed);
}
Ok(())
}
pub async fn send_pair_verified_failed(&mut self) -> Result<(), IdeviceError> {
self.inner
.send_plain(
plist!({
"event": {
"_0": {
"pairVerifyFailed": {}
}
}
}),
self.sequence_number,
)
.await?;
self.sequence_number += 1;
Ok(())
}
pub async fn attempt_pair_verify(&mut self) -> Result<plist::Value, IdeviceError> {
debug!("Sending attemptPairVerify");
self.inner
.send_plain(
plist!({
"request": {
"_0": {
"handshake": {
"_0": {
"hostOptions": {
"attemptPairVerify": true
},
"wireProtocolVersion": plist::Value::Integer(WIRE_PROTOCOL_VERSION.into()),
}
}
}
}
}),
self.sequence_number,
)
.await?;
self.sequence_number += 1;
debug!("Waiting for attemptPairVerify response");
let response = self.inner.recv_plain().await?;
let response = response
.as_dictionary()
.and_then(|x| x.get("response"))
.and_then(|x| x.as_dictionary())
.and_then(|x| x.get("_1"))
.and_then(|x| x.as_dictionary())
.and_then(|x| x.get("handshake"))
.and_then(|x| x.as_dictionary())
.and_then(|x| x.get("_0"));
match response {
Some(v) => Ok(v.to_owned()),
None => Err(IdeviceError::UnexpectedResponse),
}
}
pub async fn pair<Fut, S>(
&mut self,
pin_callback: impl Fn(S) -> Fut,
state: S,
) -> Result<(), IdeviceError>
where
Fut: std::future::Future<Output = String>,
{
let (salt, public_key, pin) = self.request_pair_consent(pin_callback, state).await?;
let key = self.init_srp_context(&salt, &public_key, &pin).await?;
self.save_pair_record_on_peer(&key).await?;
Ok(())
}
/// Returns salt and public key and pin
async fn request_pair_consent<Fut, S>(
&mut self,
pin_callback: impl Fn(S) -> Fut,
state: S,
) -> Result<(Vec<u8>, Vec<u8>, String), IdeviceError>
where
Fut: std::future::Future<Output = String>,
{
let tlv = tlv::serialize_tlv8(&[
tlv::TLV8Entry {
tlv_type: tlv::PairingDataComponentType::Method,
data: vec![0x00],
},
tlv::TLV8Entry {
tlv_type: tlv::PairingDataComponentType::State,
data: vec![0x01],
},
]);
let tlv = R::serialize_bytes(&tlv);
self.send_pairing_data(plist!({
"data": tlv,
"kind": "setupManualPairing",
"sendingHost": &self.sending_host,
"startNewSession": true
}))
.await?;
let response = self.inner.recv_plain().await?;
let response = match response
.get_by("event")
.and_then(|x| x.get_by("_0"))
.and_then(|x| x.as_dictionary())
{
Some(r) => r,
None => {
return Err(IdeviceError::UnexpectedResponse);
}
};
let mut pin = None;
let pairing_data = match if let Some(err) = response.get("pairingRejectedWithError") {
let context = err
.get_by("wrappedError")
.and_then(|x| x.get_by("userInfo"))
.and_then(|x| x.get_by("NSLocalizedDescription"))
.and_then(|x| x.as_string())
.map(|x| x.to_string());
return Err(IdeviceError::PairingRejected(context.unwrap_or_default()));
} else if response.get("awaitingUserConsent").is_some() {
pin = Some("000000".to_string());
Some(self.receive_pairing_data().await?)
} else {
// On Apple TV, we can get the pin now
response
.get_by("pairingData")
.and_then(|x| x.get_by("_0"))
.and_then(|x| x.get_by("data"))
.map(|x| x.to_owned())
} {
Some(p) => p,
None => {
return Err(IdeviceError::UnexpectedResponse);
}
};
let tlv = tlv::deserialize_tlv8(&match R::deserialize_bytes(pairing_data) {
Some(t) => t,
None => return Err(IdeviceError::UnexpectedResponse),
})?;
debug!("Received pairingData response: {tlv:#?}");
let mut salt = Vec::new();
let mut public_key = Vec::new();
for t in tlv {
match t.tlv_type {
tlv::PairingDataComponentType::Salt => {
salt = t.data;
}
tlv::PairingDataComponentType::PublicKey => {
public_key.extend(t.data);
}
tlv::PairingDataComponentType::ErrorResponse => {
warn!("Pairing data contained error response");
return Err(IdeviceError::UnexpectedResponse);
}
_ => {
continue;
}
}
}
let pin = match pin {
Some(p) => p,
None => pin_callback(state).await,
};
if salt.is_empty() || public_key.is_empty() {
warn!("Pairing data did not contain salt or public key");
return Err(IdeviceError::UnexpectedResponse);
}
Ok((salt, public_key, pin))
}
/// Returns the encryption key
async fn init_srp_context(
&mut self,
salt: &[u8],
public_key: &[u8],
pin: &str,
) -> Result<Vec<u8>, IdeviceError> {
let client = SrpClient::<Sha512>::new(
&G_3072, // PRIME_3072 + generator
);
let mut a_private = [0u8; 32];
rand::rng().fill_bytes(&mut a_private);
let a_public = client.compute_public_ephemeral(&a_private);
let verifier = match client.process_reply(
&a_private,
"Pair-Setup".as_bytes(),
&pin.as_bytes()[..6],
salt,
public_key,
false,
) {
Ok(v) => v,
Err(e) => {
warn!("SRP verifier creation failed: {e:?}");
return Err(IdeviceError::SrpAuthFailed);
}
};
let client_proof = verifier.proof();
let tlv = tlv::serialize_tlv8(&[
tlv::TLV8Entry {
tlv_type: tlv::PairingDataComponentType::State,
data: vec![0x03],
},
tlv::TLV8Entry {
tlv_type: tlv::PairingDataComponentType::PublicKey,
data: a_public[..254].to_vec(),
},
tlv::TLV8Entry {
tlv_type: tlv::PairingDataComponentType::PublicKey,
data: a_public[254..].to_vec(),
},
tlv::TLV8Entry {
tlv_type: tlv::PairingDataComponentType::Proof,
data: client_proof.to_vec(),
},
]);
let tlv = R::serialize_bytes(&tlv);
self.send_pairing_data(plist!({
"data": tlv,
"kind": "setupManualPairing",
"sendingHost": &self.sending_host,
"startNewSession": false,
}))
.await?;
let response = self.receive_pairing_data().await?;
let response = tlv::deserialize_tlv8(&match R::deserialize_bytes(response.to_owned()) {
Some(r) => r,
None => return Err(IdeviceError::UnexpectedResponse),
})?;
debug!("Proof response: {response:#?}");
let proof = match response
.iter()
.find(|x| x.tlv_type == tlv::PairingDataComponentType::Proof)
{
Some(p) => &p.data,
None => {
warn!("Proof response did not contain server proof");
return Err(IdeviceError::UnexpectedResponse);
}
};
match verifier.verify_server(proof) {
Ok(_) => Ok(verifier.key().to_vec()),
Err(e) => {
warn!("Server auth failed: {e:?}");
Err(IdeviceError::SrpAuthFailed)
}
}
}
async fn save_pair_record_on_peer(
&mut self,
encryption_key: &[u8],
) -> Result<Vec<tlv::TLV8Entry>, IdeviceError> {
let salt = b"Pair-Setup-Encrypt-Salt";
let info = b"Pair-Setup-Encrypt-Info";
let hk = Hkdf::<Sha512>::new(Some(salt), encryption_key);
let mut setup_encryption_key = [0u8; 32];
hk.expand(info, &mut setup_encryption_key)
.expect("HKDF expand failed");
self.pairing_file.recreate_signing_keys();
{
// new scope, update our signing keys
let hk = Hkdf::<sha2::Sha512>::new(None, self.pairing_file.e_private_key.as_bytes());
let mut okm = [0u8; 32];
hk.expand(b"ClientEncrypt-main", &mut okm).unwrap();
self.client_cipher = ChaCha20Poly1305::new(chacha20poly1305::Key::from_slice(&okm));
let hk = Hkdf::<sha2::Sha512>::new(None, self.pairing_file.e_private_key.as_bytes());
let mut okm = [0u8; 32];
hk.expand(b"ServerEncrypt-main", &mut okm).unwrap();
self.server_cipher = ChaCha20Poly1305::new(chacha20poly1305::Key::from_slice(&okm));
}
let hk = Hkdf::<Sha512>::new(Some(b"Pair-Setup-Controller-Sign-Salt"), encryption_key);
let mut signbuf = Vec::with_capacity(32 + self.pairing_file.identifier.len() + 32);
let mut hkdf_out = [0u8; 32];
hk.expand(b"Pair-Setup-Controller-Sign-Info", &mut hkdf_out)
.expect("HKDF expand failed");
signbuf.extend_from_slice(&hkdf_out);
signbuf.extend_from_slice(self.pairing_file.identifier.as_bytes());
signbuf.extend_from_slice(self.pairing_file.e_public_key.as_bytes());
let signature = self.pairing_file.e_private_key.sign(&signbuf);
let device_info = crate::plist!({
"altIRK": b"\xe9\xe8-\xc0jIykVoT\x00\x19\xb1\xc7{".to_vec(),
"btAddr": "11:22:33:44:55:66",
"mac": b"\x11\x22\x33\x44\x55\x66".to_vec(),
"remotepairing_serial_number": "AAAAAAAAAAAA",
"accountID": self.pairing_file.identifier.as_str(),
"model": "computer-model",
"name": self.sending_host.as_str()
});
let device_info = opack::plist_to_opack(&device_info);
let tlv = tlv::serialize_tlv8(&[
tlv::TLV8Entry {
tlv_type: tlv::PairingDataComponentType::Identifier,
data: self.pairing_file.identifier.as_bytes().to_vec(),
},
tlv::TLV8Entry {
tlv_type: tlv::PairingDataComponentType::PublicKey,
data: self.pairing_file.e_public_key.to_bytes().to_vec(),
},
tlv::TLV8Entry {
tlv_type: tlv::PairingDataComponentType::Signature,
data: signature.to_vec(),
},
tlv::TLV8Entry {
tlv_type: tlv::PairingDataComponentType::Info,
data: device_info,
},
]);
let key = Key::from_slice(&setup_encryption_key); // 32 bytes
let cipher = ChaCha20Poly1305::new(key);
let nonce = Nonce::from_slice(b"\x00\x00\x00\x00PS-Msg05"); // 12 bytes
let plaintext = &tlv;
let ciphertext = match cipher.encrypt(
nonce,
Payload {
msg: plaintext,
aad: b"",
},
) {
Ok(c) => c,
Err(e) => {
warn!("Chacha encryption failed: {e:?}");
return Err(IdeviceError::ChachaEncryption(e));
}
};
debug!("ciphertext len: {}", ciphertext.len());
let tlv = tlv::serialize_tlv8(&[
tlv::TLV8Entry {
tlv_type: tlv::PairingDataComponentType::EncryptedData,
data: ciphertext[..254].to_vec(),
},
tlv::TLV8Entry {
tlv_type: tlv::PairingDataComponentType::EncryptedData,
data: ciphertext[254..].to_vec(),
},
tlv::TLV8Entry {
tlv_type: tlv::PairingDataComponentType::State,
data: vec![0x05],
},
]);
let tlv = R::serialize_bytes(&tlv);
debug!("Sending encrypted data");
self.send_pairing_data(plist!({
"data": tlv,
"kind": "setupManualPairing",
"sendingHost": &self.sending_host,
"startNewSession": false,
}))
.await?;
debug!("Waiting for encrypted data");
let response = match R::deserialize_bytes(self.receive_pairing_data().await?) {
Some(r) => r,
None => {
warn!("Pairing data response was not deserializable");
return Err(IdeviceError::UnexpectedResponse);
}
};
let tlv = tlv::deserialize_tlv8(&response)?;
let mut encrypted_data = Vec::new();
for t in tlv {
match t.tlv_type {
tlv::PairingDataComponentType::EncryptedData => encrypted_data.extend(t.data),
tlv::PairingDataComponentType::ErrorResponse => {
warn!("TLV contained error response");
return Err(IdeviceError::UnexpectedResponse);
}
_ => {}
}
}
let nonce = Nonce::from_slice(b"\x00\x00\x00\x00PS-Msg06");
let plaintext = cipher
.decrypt(
nonce,
Payload {
msg: &encrypted_data,
aad: b"",
},
)
.expect("decryption failure!");
let tlv = tlv::deserialize_tlv8(&plaintext)?;
debug!("Decrypted plaintext TLV: {tlv:?}");
Ok(tlv)
}
async fn send_pairing_data(
&mut self,
pairing_data: impl Serialize + PlistConvertible,
) -> Result<(), IdeviceError> {
self.inner
.send_plain(
plist!({
"event": {
"_0": {
"pairingData": {
"_0": pairing_data
}
}
}
}),
self.sequence_number,
)
.await?;
self.sequence_number += 1;
Ok(())
}
async fn receive_pairing_data(&mut self) -> Result<plist::Value, IdeviceError> {
let response = self.inner.recv_plain().await?;
let response = match response.get_by("event").and_then(|x| x.get_by("_0")) {
Some(r) => r,
None => return Err(IdeviceError::UnexpectedResponse),
};
if let Some(data) = response
.get_by("pairingData")
.and_then(|x| x.get_by("_0"))
.and_then(|x| x.get_by("data"))
{
Ok(data.to_owned())
} else if let Some(err) = response.get_by("pairingRejectedWithError") {
let context = err
.get_by("wrappedError")
.and_then(|x| x.get_by("userInfo"))
.and_then(|x| x.get_by("NSLocalizedDescription"))
.and_then(|x| x.as_string())
.map(|x| x.to_string());
Err(IdeviceError::PairingRejected(context.unwrap_or_default()))
} else {
Err(IdeviceError::UnexpectedResponse)
}
}
}
impl<R: RpPairingSocketProvider> std::fmt::Debug for RemotePairingClient<'_, R> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("RemotePairingClient")
.field("inner", &self.inner)
.field("sequence_number", &self.sequence_number)
.field("pairing_file", &self.pairing_file)
.field("sending_host", &self.sending_host)
.finish()
}
}

View File

@@ -1,165 +0,0 @@
// Jackson Coxson
use plist::Value;
pub fn plist_to_opack(value: &Value) -> Vec<u8> {
let mut buf = Vec::new();
plist_to_opack_inner(value, &mut buf);
buf
}
fn plist_to_opack_inner(node: &Value, buf: &mut Vec<u8>) {
match node {
Value::Dictionary(dict) => {
let count = dict.len() as u32;
let blen = if count < 15 {
(count as u8).wrapping_sub(32)
} else {
0xEF
};
buf.push(blen);
for (key, val) in dict {
plist_to_opack_inner(&Value::String(key.clone()), buf);
plist_to_opack_inner(val, buf);
}
if count > 14 {
buf.push(0x03);
}
}
Value::Array(array) => {
let count = array.len() as u32;
let blen = if count < 15 {
(count as u8).wrapping_sub(48)
} else {
0xDF
};
buf.push(blen);
for val in array {
plist_to_opack_inner(val, buf);
}
if count > 14 {
buf.push(0x03); // Terminator
}
}
Value::Boolean(b) => {
let bval = if *b { 1u8 } else { 2u8 };
buf.push(bval);
}
Value::Integer(integer) => {
let u64val = integer.as_unsigned().unwrap_or(0);
if u64val <= u8::MAX as u64 {
let u8val = u64val as u8;
if u8val > 0x27 {
buf.push(0x30);
buf.push(u8val);
} else {
buf.push(u8val + 8);
}
} else if u64val <= u32::MAX as u64 {
buf.push(0x32);
buf.extend_from_slice(&(u64val as u32).to_le_bytes());
} else {
buf.push(0x33);
buf.extend_from_slice(&u64val.to_le_bytes());
}
}
Value::Real(real) => {
let dval = *real;
let fval = dval as f32;
if fval as f64 == dval {
buf.push(0x35);
buf.extend_from_slice(&fval.to_bits().swap_bytes().to_ne_bytes());
} else {
buf.push(0x36);
buf.extend_from_slice(&dval.to_bits().swap_bytes().to_ne_bytes());
}
}
Value::String(s) => {
let bytes = s.as_bytes();
let len = bytes.len();
if len > 0x20 {
if len <= 0xFF {
buf.push(0x61);
buf.push(len as u8);
} else if len <= 0xFFFF {
buf.push(0x62);
buf.extend_from_slice(&(len as u16).to_le_bytes());
} else if len <= 0xFFFFFFFF {
buf.push(0x63);
buf.extend_from_slice(&(len as u32).to_le_bytes());
} else {
buf.push(0x64);
buf.extend_from_slice(&(len as u64).to_le_bytes());
}
} else {
buf.push(0x40 + len as u8);
}
buf.extend_from_slice(bytes);
}
Value::Data(data) => {
let len = data.len();
if len > 0x20 {
if len <= 0xFF {
buf.push(0x91);
buf.push(len as u8);
} else if len <= 0xFFFF {
buf.push(0x92);
buf.extend_from_slice(&(len as u16).to_le_bytes());
} else if len <= 0xFFFFFFFF {
buf.push(0x93);
buf.extend_from_slice(&(len as u32).to_le_bytes());
} else {
buf.push(0x94);
buf.extend_from_slice(&(len as u64).to_le_bytes());
}
} else {
buf.push(0x70 + len as u8);
}
buf.extend_from_slice(data);
}
_ => {}
}
}
#[cfg(test)]
mod tests {
#[test]
fn t1() {
let v = crate::plist!({
"altIRK": b"\xe9\xe8-\xc0jIykVoT\x00\x19\xb1\xc7{".to_vec(),
"btAddr": "11:22:33:44:55:66",
"mac": b"\x11\x22\x33\x44\x55\x66".to_vec(),
"remotepairing_serial_number": "AAAAAAAAAAAA",
"accountID": "lolsssss",
"model": "computer-model",
"name": "reeeee",
});
let res = super::plist_to_opack(&v);
let expected = [
0xe7, 0x46, 0x61, 0x6c, 0x74, 0x49, 0x52, 0x4b, 0x80, 0xe9, 0xe8, 0x2d, 0xc0, 0x6a,
0x49, 0x79, 0x6b, 0x56, 0x6f, 0x54, 0x00, 0x19, 0xb1, 0xc7, 0x7b, 0x46, 0x62, 0x74,
0x41, 0x64, 0x64, 0x72, 0x51, 0x31, 0x31, 0x3a, 0x32, 0x32, 0x3a, 0x33, 0x33, 0x3a,
0x34, 0x34, 0x3a, 0x35, 0x35, 0x3a, 0x36, 0x36, 0x43, 0x6d, 0x61, 0x63, 0x76, 0x11,
0x22, 0x33, 0x44, 0x55, 0x66, 0x5b, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x70, 0x61,
0x69, 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x5f, 0x6e,
0x75, 0x6d, 0x62, 0x65, 0x72, 0x4c, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
0x41, 0x41, 0x41, 0x41, 0x49, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x49, 0x44,
0x48, 0x6c, 0x6f, 0x6c, 0x73, 0x73, 0x73, 0x73, 0x73, 0x45, 0x6d, 0x6f, 0x64, 0x65,
0x6c, 0x4e, 0x63, 0x6f, 0x6d, 0x70, 0x75, 0x74, 0x65, 0x72, 0x2d, 0x6d, 0x6f, 0x64,
0x65, 0x6c, 0x44, 0x6e, 0x61, 0x6d, 0x65, 0x46, 0x72, 0x65, 0x65, 0x65, 0x65, 0x65,
];
println!("{res:02X?}");
assert_eq!(res, expected);
}
}

View File

@@ -1,114 +0,0 @@
// Jackson Coxson
use std::path::Path;
use ed25519_dalek::{SigningKey, VerifyingKey};
use plist::Dictionary;
use plist_macro::plist_to_xml_bytes;
use rsa::rand_core::OsRng;
use serde::de::Error;
use tracing::{debug, warn};
use crate::IdeviceError;
#[derive(Clone)]
pub struct RpPairingFile {
pub(crate) e_private_key: SigningKey,
pub(crate) e_public_key: VerifyingKey,
pub(crate) identifier: String,
}
impl RpPairingFile {
pub fn generate(sending_host: &str) -> Self {
// Ed25519 private key (persistent signing key)
let ed25519_private_key = SigningKey::generate(&mut OsRng);
let ed25519_public_key = VerifyingKey::from(&ed25519_private_key);
let identifier =
uuid::Uuid::new_v3(&uuid::Uuid::NAMESPACE_DNS, sending_host.as_bytes()).to_string();
Self {
e_private_key: ed25519_private_key,
e_public_key: ed25519_public_key,
identifier,
}
}
pub(crate) fn recreate_signing_keys(&mut self) {
let ed25519_private_key = SigningKey::generate(&mut OsRng);
let ed25519_public_key = VerifyingKey::from(&ed25519_private_key);
self.e_public_key = ed25519_public_key;
self.e_private_key = ed25519_private_key;
}
pub async fn write_to_file(&self, path: impl AsRef<Path>) -> Result<(), IdeviceError> {
let v = crate::plist!(dict {
"public_key": self.e_public_key.to_bytes().to_vec(),
"private_key": self.e_private_key.to_bytes().to_vec(),
"identifier": self.identifier.as_str()
});
tokio::fs::write(path, plist_to_xml_bytes(&v)).await?;
Ok(())
}
pub async fn read_from_file(path: impl AsRef<Path>) -> Result<Self, IdeviceError> {
let s = tokio::fs::read_to_string(path).await?;
let mut p: Dictionary = plist::from_bytes(s.as_bytes())?;
debug!("Read dictionary for rppairingfile: {p:#?}");
let public_key = match p
.remove("public_key")
.and_then(|x| x.into_data())
.filter(|x| x.len() == 32)
.and_then(|x| VerifyingKey::from_bytes(&x[..32].try_into().unwrap()).ok())
{
Some(p) => p,
None => {
warn!("plist did not contain valid public key bytes");
return Err(IdeviceError::Plist(plist::Error::missing_field(
"public_key",
)));
}
};
let private_key = match p
.remove("private_key")
.and_then(|x| x.into_data())
.filter(|x| x.len() == 32)
{
Some(p) => SigningKey::from_bytes(&p.try_into().unwrap()),
None => {
warn!("plist did not contain valid private key bytes");
return Err(IdeviceError::Plist(plist::Error::missing_field(
"private_key",
)));
}
};
let identifier = match p.remove("identifier").and_then(|x| x.into_string()) {
Some(i) => i,
None => {
warn!("plist did not contain identifier");
return Err(IdeviceError::Plist(plist::Error::missing_field(
"identifier",
)));
}
};
Ok(Self {
e_private_key: private_key,
e_public_key: public_key,
identifier,
})
}
}
impl std::fmt::Debug for RpPairingFile {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("RpPairingFile")
.field("e_public_key", &self.e_public_key)
.field("identifier", &self.identifier)
.finish()
}
}

View File

@@ -1,173 +0,0 @@
// Jackson Coxson
use base64::{Engine as _, engine::general_purpose::STANDARD as B64};
use plist_macro::{plist, pretty_print_plist};
use serde::Serialize;
use serde_json::json;
use std::{fmt::Debug, pin::Pin};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tracing::{debug, warn};
use crate::{
IdeviceError, ReadWrite, RemoteXpcClient, remote_pairing::RPPAIRING_MAGIC, xpc::XPCObject,
};
pub trait RpPairingSocketProvider: Debug {
fn send_plain(
&mut self,
value: impl Serialize,
seq: usize,
) -> Pin<Box<dyn Future<Output = Result<(), IdeviceError>> + Send + '_>>;
fn recv_plain<'a>(
&'a mut self,
) -> Pin<Box<dyn Future<Output = Result<plist::Value, IdeviceError>> + Send + 'a>>;
/// rppairing uses b64, while RemoteXPC uses raw bytes just fine
fn serialize_bytes(b: &[u8]) -> plist::Value;
fn deserialize_bytes(v: plist::Value) -> Option<Vec<u8>>;
}
#[derive(Debug)]
pub struct RpPairingSocket<R: ReadWrite> {
pub inner: R,
}
impl<R: ReadWrite> RpPairingSocket<R> {
pub fn new(socket: R) -> Self {
Self { inner: socket }
}
async fn send_rppairing(&mut self, value: impl Serialize) -> Result<(), IdeviceError> {
let value = serde_json::to_string(&value)?;
let x = value.as_bytes();
self.inner.write_all(RPPAIRING_MAGIC).await?;
self.inner
.write_all(&(x.len() as u16).to_be_bytes())
.await?;
self.inner.write_all(x).await?;
Ok(())
}
}
impl<R: ReadWrite> RpPairingSocketProvider for RpPairingSocket<R> {
fn send_plain(
&mut self,
value: impl Serialize,
seq: usize,
) -> Pin<Box<dyn Future<Output = Result<(), IdeviceError>> + Send + '_>> {
let v = json!({
"message": {"plain": {"_0": value}},
"originatedBy": "host",
"sequenceNumber": seq
});
Box::pin(async move {
self.send_rppairing(v).await?;
Ok(())
})
}
fn recv_plain<'a>(
&'a mut self,
) -> Pin<Box<dyn Future<Output = Result<plist::Value, IdeviceError>> + Send + 'a>> {
Box::pin(async move {
self.inner
.read_exact(&mut vec![0u8; RPPAIRING_MAGIC.len()])
.await?;
let mut packet_len_bytes = [0u8; 2];
self.inner.read_exact(&mut packet_len_bytes).await?;
let packet_len = u16::from_be_bytes(packet_len_bytes);
let mut value = vec![0u8; packet_len as usize];
self.inner.read_exact(&mut value).await?;
let value: serde_json::Value = serde_json::from_slice(&value)?;
let value = value
.get("message")
.and_then(|x| x.get("plain"))
.and_then(|x| x.get("_0"));
match value {
Some(v) => Ok(plist::to_value(v).unwrap()),
None => Err(IdeviceError::UnexpectedResponse),
}
})
}
fn serialize_bytes(b: &[u8]) -> plist::Value {
plist!(B64.encode(b))
}
fn deserialize_bytes(v: plist::Value) -> Option<Vec<u8>> {
if let plist::Value::String(v) = v {
B64.decode(v).ok()
} else {
None
}
}
}
impl<R: ReadWrite> RpPairingSocketProvider for RemoteXpcClient<R> {
fn send_plain(
&mut self,
value: impl Serialize,
seq: usize,
) -> Pin<Box<dyn Future<Output = Result<(), IdeviceError>> + Send + '_>> {
let value: plist::Value = plist::to_value(&value).expect("plist assert failed");
let value: XPCObject = value.into();
let v = crate::xpc!({
"mangledTypeName": "RemotePairing.ControlChannelMessageEnvelope",
"value": {
"message": {"plain": {"_0": value}},
"originatedBy": "host",
"sequenceNumber": seq as u64
}
});
debug!("Sending XPC: {v:#?}");
Box::pin(async move {
self.send_object(v, true).await?;
Ok(())
})
}
fn recv_plain<'a>(
&'a mut self,
) -> Pin<Box<dyn Future<Output = Result<plist::Value, IdeviceError>> + Send + 'a>> {
Box::pin(async move {
let msg = self.recv_root().await.unwrap();
debug!("Received RemoteXPC {}", pretty_print_plist(&msg));
let value = msg
.into_dictionary()
.and_then(|mut x| x.remove("value"))
.and_then(|x| x.into_dictionary())
.and_then(|mut x| x.remove("message"))
.and_then(|x| x.into_dictionary())
.and_then(|mut x| x.remove("plain"))
.and_then(|x| x.into_dictionary())
.and_then(|mut x| x.remove("_0"));
match value {
Some(v) => Ok(v),
None => Err(IdeviceError::UnexpectedResponse),
}
})
}
fn serialize_bytes(b: &[u8]) -> plist::Value {
plist::Value::Data(b.to_owned())
}
fn deserialize_bytes(v: plist::Value) -> Option<Vec<u8>> {
if let plist::Value::Data(v) = v {
Some(v)
} else {
warn!("Non-data passed to rppairingsocket::deserialize_bytes for RemoteXPC provider");
None
}
}
}

View File

@@ -1,160 +1,133 @@
// Jackson Coxson
use std::{io::SeekFrom, marker::PhantomPinned, pin::Pin};
use std::io::SeekFrom;
use tokio::io::{AsyncRead, AsyncSeek, AsyncWrite};
use crate::IdeviceError;
use crate::{
IdeviceError,
afc::{
AfcClient,
inner_file::{InnerFileDescriptor, OwnedInnerFileDescriptor},
},
use super::{
opcode::AfcOpcode,
packet::{AfcPacket, AfcPacketHeader},
};
#[derive(Debug)]
/// Maximum transfer size for file operations (64KB)
const MAX_TRANSFER: u64 = 64 * 1024; // this is what go-ios uses
/// Handle for an open file on the device.
/// Call close before dropping
pub struct FileDescriptor<'a> {
inner: Pin<Box<InnerFileDescriptor<'a>>>,
pub(crate) client: &'a mut super::AfcClient,
pub(crate) fd: u64,
pub(crate) path: String,
}
#[derive(Debug)]
pub struct OwnedFileDescriptor {
inner: Pin<Box<OwnedInnerFileDescriptor>>,
}
impl FileDescriptor<'_> {
/// Generic helper to send an AFC packet and read the response
async fn send_packet(
&mut self,
opcode: AfcOpcode,
header_payload: Vec<u8>,
payload: Vec<u8>,
) -> Result<AfcPacket, IdeviceError> {
let header_len = header_payload.len() as u64 + AfcPacketHeader::LEN;
let header = AfcPacketHeader {
magic: super::MAGIC,
entire_len: header_len + payload.len() as u64,
header_payload_len: header_len,
packet_num: self.client.package_number,
operation: opcode,
};
self.client.package_number += 1;
impl<'a> FileDescriptor<'a> {
/// create a new FileDescriptor from a raw fd
///
/// # Safety
/// make sure the fd is an opened file, and that you got it from a previous
/// FileDescriptor via `as_raw_fd()` method
pub unsafe fn new(client: &'a mut AfcClient, fd: u64, path: String) -> Self {
Self {
inner: Box::pin(InnerFileDescriptor {
client,
fd,
path,
pending_fut: None,
_m: PhantomPinned,
}),
}
}
let packet = AfcPacket {
header,
header_payload,
payload,
};
/// Closes the file descriptor
pub async fn close(self) -> Result<(), IdeviceError> {
self.inner.close().await
}
}
impl OwnedFileDescriptor {
/// create a new OwnedFileDescriptor from a raw fd
///
/// # Safety
/// make sure the fd is an opened file, and that you got it from a previous
/// OwnedFileDescriptor via `as_raw_fd()` method
pub unsafe fn new(client: AfcClient, fd: u64, path: String) -> Self {
Self {
inner: Box::pin(OwnedInnerFileDescriptor {
client,
fd,
path,
pending_fut: None,
_m: PhantomPinned,
}),
}
}
/// Closes the file descriptor
pub async fn close(self) -> Result<AfcClient, IdeviceError> {
self.inner.close().await
}
/// gets the owned afc
///
/// # Safety
/// this get's the afc out without closing, if you want to get the afc and close the file, use
/// `.close()`
pub unsafe fn get_inner_afc(self) -> AfcClient {
self.inner.get_inner_afc()
}
}
crate::impl_to_structs!(FileDescriptor<'_>, OwnedFileDescriptor; {
pub fn as_raw_fd(&self) -> u64 {
self.inner.fd
self.client.send(packet).await?;
self.client.read().await
}
/// Returns the current cursor position for the file
pub async fn seek_tell(&mut self) -> Result<u64, IdeviceError> {
self.inner.as_mut().seek_tell().await
let header_payload = self.fd.to_le_bytes().to_vec();
let res = self
.send_packet(AfcOpcode::FileTell, header_payload, Vec::new())
.await?;
let cur_pos = res
.header_payload
.get(..8)
.ok_or(IdeviceError::UnexpectedResponse)?
.try_into()
.map(u64::from_le_bytes)
.map_err(|_| IdeviceError::UnexpectedResponse)?;
Ok(cur_pos)
}
/// Moves the file cursor
pub async fn seek(&mut self, pos: SeekFrom) -> Result<(), IdeviceError> {
let (offset, whence) = match pos {
SeekFrom::Start(off) => (off as i64, 0),
SeekFrom::Current(off) => (off, 1),
SeekFrom::End(off) => (off, 2),
};
let mut header_payload = Vec::new();
header_payload.extend(self.fd.to_le_bytes());
header_payload.extend((whence as u64).to_le_bytes());
header_payload.extend(offset.to_le_bytes());
self.send_packet(AfcOpcode::FileSeek, header_payload, Vec::new())
.await?;
Ok(())
}
/// Closes the file descriptor
pub async fn close(mut self) -> Result<(), IdeviceError> {
let header_payload = self.fd.to_le_bytes().to_vec();
self.send_packet(AfcOpcode::FileClose, header_payload, Vec::new())
.await?;
Ok(())
}
/// Reads the entire contents of the file
///
/// # Returns
/// A vector containing the file's data
pub async fn read_entire(&mut self) -> Result<Vec<u8>, IdeviceError> {
self.inner.as_mut().read().await
pub async fn read(&mut self) -> Result<Vec<u8>, IdeviceError> {
let seek_pos = self.seek_tell().await? as usize;
let file_info = self.client.get_file_info(&self.path).await?;
let mut bytes_left = file_info.size.saturating_sub(seek_pos);
let mut collected_bytes = Vec::with_capacity(bytes_left);
while bytes_left > 0 {
let mut header_payload = self.fd.to_le_bytes().to_vec();
header_payload.extend_from_slice(&MAX_TRANSFER.to_le_bytes());
let res = self
.send_packet(AfcOpcode::Read, header_payload, Vec::new())
.await?;
bytes_left -= res.payload.len();
collected_bytes.extend(res.payload);
}
Ok(collected_bytes)
}
/// Writes data to the file
///
/// # Arguments
/// * `bytes` - Data to write to the file
pub async fn write_entire(&mut self, bytes: &[u8]) -> Result<(), IdeviceError> {
self.inner.as_mut().write(bytes).await
pub async fn write(&mut self, bytes: &[u8]) -> Result<(), IdeviceError> {
for chunk in bytes.chunks(MAX_TRANSFER as usize) {
let header_payload = self.fd.to_le_bytes().to_vec();
self.send_packet(AfcOpcode::Write, header_payload, chunk.to_vec())
.await?;
}
});
crate::impl_trait_to_structs!(AsyncRead for FileDescriptor<'_>, OwnedFileDescriptor; {
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<()>> {
let inner = self.inner.as_mut();
inner.poll_read(cx, buf)
Ok(())
}
});
crate::impl_trait_to_structs!(AsyncWrite for FileDescriptor<'_>, OwnedFileDescriptor; {
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>> {
let inner = self.inner.as_mut();
inner.poll_write(cx, buf)
}
fn poll_flush(
mut self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Result<(), std::io::Error>> {
let inner = self.inner.as_mut();
inner.poll_flush(cx)
}
fn poll_shutdown(
mut self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Result<(), std::io::Error>> {
let inner = self.inner.as_mut();
inner.poll_shutdown(cx)
}
});
crate::impl_trait_to_structs!(AsyncSeek for FileDescriptor<'_>, OwnedFileDescriptor; {
fn start_seek(mut self: Pin<&mut Self>, position: SeekFrom) -> std::io::Result<()> {
let this = self.inner.as_mut();
this.start_seek(position)
}
fn poll_complete(
mut self: Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<std::io::Result<u64>> {
let this = self.inner.as_mut();
this.poll_complete(cx)
}
});
}

View File

@@ -1,818 +0,0 @@
// Jackson Coxson
use std::{io::SeekFrom, pin::Pin};
use futures::{FutureExt, future::BoxFuture};
use tokio::io::{AsyncRead, AsyncSeek, AsyncWrite};
use crate::{
IdeviceError,
afc::{
AfcClient, MAGIC,
opcode::AfcOpcode,
packet::{AfcPacket, AfcPacketHeader},
},
};
/// Maximum transfer size for file operations (1MB)
const MAX_TRANSFER: u64 = 1024 * 1024; // this is what libimobiledevice uses in it's afcclient
fn chunk_number(n: usize, chunk_size: usize) -> impl Iterator<Item = usize> {
(0..n)
.step_by(chunk_size)
.map(move |i| (n - i).min(chunk_size))
}
// Used to descripe what the future returns
#[derive(Debug)]
pub(crate) enum PendingResult {
// writing
Empty,
// seeking
SeekPos(u64),
// reading
Bytes(Vec<u8>),
}
type OwnedBoxFuture = Pin<Box<dyn Future<Output = Result<PendingResult, IdeviceError>> + Send>>;
/// Handle for an open file on the device.
/// Call close before dropping
pub(crate) struct InnerFileDescriptor<'a> {
pub(crate) client: &'a mut AfcClient,
pub(crate) fd: u64,
pub(crate) path: String,
pub(crate) pending_fut: Option<BoxFuture<'a, Result<PendingResult, IdeviceError>>>,
pub(crate) _m: std::marker::PhantomPinned,
}
/// Handle for an owned open file on the device.
/// Call close before dropping
pub(crate) struct OwnedInnerFileDescriptor {
pub(crate) client: AfcClient,
pub(crate) fd: u64,
pub(crate) path: String,
pub(crate) pending_fut: Option<OwnedBoxFuture>,
pub(crate) _m: std::marker::PhantomPinned,
}
crate::impl_to_structs!(InnerFileDescriptor<'_>, OwnedInnerFileDescriptor; {
/// Generic helper to send an AFC packet and read the response
pub async fn send_packet(
self: Pin<&mut Self>,
opcode: AfcOpcode,
header_payload: Vec<u8>,
payload: Vec<u8>,
) -> Result<AfcPacket, IdeviceError> {
// SAFETY: we don't modify pinned fileds, it's ok
let this = unsafe { self.get_unchecked_mut() };
let header_len = header_payload.len() as u64 + AfcPacketHeader::LEN;
let header = AfcPacketHeader {
magic: MAGIC,
entire_len: header_len + payload.len() as u64,
header_payload_len: header_len,
packet_num: this.client.package_number,
operation: opcode,
};
this.client.package_number += 1;
let packet = AfcPacket {
header,
header_payload,
payload,
};
this.client.send(packet).await?;
this.client.read().await
}
/// Returns the current cursor position for the file
pub async fn seek_tell(self: Pin<&mut Self>) -> Result<u64, IdeviceError> {
let header_payload = self.fd.to_le_bytes().to_vec();
let res = self
.send_packet(AfcOpcode::FileTell, header_payload, Vec::new())
.await?;
let cur_pos = res
.header_payload
.get(..8)
.ok_or(IdeviceError::UnexpectedResponse)?
.try_into()
.map(u64::from_le_bytes)
.map_err(|_| IdeviceError::UnexpectedResponse)?;
Ok(cur_pos)
}
/// Moves the file cursor
async fn seek(mut self: Pin<&mut Self>, pos: SeekFrom) -> Result<u64, IdeviceError> {
let (offset, whence) = match pos {
SeekFrom::Start(off) => (off as i64, 0),
SeekFrom::Current(off) => (off, 1),
SeekFrom::End(off) => (off, 2),
};
let header_payload = [
self.fd.to_le_bytes(),
(whence as u64).to_le_bytes(),
offset.to_le_bytes(),
]
.concat();
self.as_mut()
.send_packet(AfcOpcode::FileSeek, header_payload, Vec::new())
.await?;
self.as_mut().seek_tell().await
}
/// Reads n size of contents from the file
///
/// # Arguments
/// * `n` - amount of bytes to read
/// # Returns
/// A vector containing the file's data
pub async fn read_n(mut self: Pin<&mut Self>, n: usize) -> Result<Vec<u8>, IdeviceError> {
let mut collected_bytes = Vec::with_capacity(n);
for chunk in chunk_number(n, MAX_TRANSFER as usize) {
let header_payload = [self.fd.to_le_bytes(), (chunk as u64).to_le_bytes()].concat();
let res = self
.as_mut()
.send_packet(AfcOpcode::Read, header_payload, Vec::new())
.await?;
collected_bytes.extend(res.payload);
}
Ok(collected_bytes)
}
/// Reads the entire contents of the file
///
/// # Returns
/// A vector containing the file's data
pub async fn read(mut self: Pin<&mut Self>) -> Result<Vec<u8>, IdeviceError> {
let seek_pos = self.as_mut().seek_tell().await? as usize;
let file_info = unsafe {
let this = self.as_mut().get_unchecked_mut();
this.client.get_file_info(&this.path).await?
};
let mut bytes_left = file_info.size.saturating_sub(seek_pos);
let mut collected_bytes = Vec::with_capacity(bytes_left);
while bytes_left > 0 {
let bytes = self.as_mut().read_n(MAX_TRANSFER as usize).await?;
bytes_left -= bytes.len();
collected_bytes.extend(bytes);
}
Ok(collected_bytes)
}
/// Writes data to the file
///
/// # Arguments
/// * `bytes` - Data to write to the file
pub async fn write(mut self: Pin<&mut Self>, bytes: &[u8]) -> Result<(), IdeviceError> {
for chunk in bytes.chunks(MAX_TRANSFER as usize) {
let header_payload = self.as_ref().fd.to_le_bytes().to_vec();
self.as_mut()
.send_packet(AfcOpcode::Write, header_payload, chunk.to_vec())
.await?;
}
Ok(())
}
fn store_pending_read(mut self: Pin<&mut Self>, buf_rem: usize) {
unsafe {
let this = self.as_mut().get_unchecked_mut() as *mut Self;
let fut = Some(
// SAFETY: we already know that self is pinned
Pin::new_unchecked(&mut *this)
.read_n(buf_rem)
.map(|r| r.map(PendingResult::Bytes))
.boxed(),
);
(&mut *this).pending_fut = fut;
}
}
fn store_pending_seek(mut self: Pin<&mut Self>, position: std::io::SeekFrom) {
unsafe {
let this = self.as_mut().get_unchecked_mut() as *mut Self;
let fut = Some(
Pin::new_unchecked(&mut *this)
.seek(position)
.map(|r| r.map(PendingResult::SeekPos))
.boxed(),
);
(&mut *this).pending_fut = fut;
}
}
fn store_pending_write(mut self: Pin<&mut Self>, buf: &'_ [u8]) {
unsafe {
let this = self.as_mut().get_unchecked_mut();
let this = this as *mut Self;
// move the entire buffer into the future so we don't have to store it somewhere
let pined_this = Pin::new_unchecked(&mut *this);
let buf = buf.to_vec();
let fut =
async move { pined_this.write(&buf).await.map(|_| PendingResult::Empty) }.boxed();
(&mut *this).pending_fut = Some(fut);
}
}
});
impl<'a> InnerFileDescriptor<'a> {
fn get_or_init_read_fut(
mut self: Pin<&mut Self>,
buf_rem: usize,
) -> &mut BoxFuture<'a, Result<PendingResult, IdeviceError>> {
if self.as_ref().pending_fut.is_none() {
self.as_mut().store_pending_read(buf_rem);
}
unsafe { self.get_unchecked_mut().pending_fut.as_mut().unwrap() }
}
fn get_or_init_write_fut(
mut self: Pin<&mut Self>,
buf: &'_ [u8],
) -> &mut BoxFuture<'a, Result<PendingResult, IdeviceError>> {
if self.as_ref().pending_fut.is_none() {
self.as_mut().store_pending_write(buf);
}
unsafe { self.get_unchecked_mut().pending_fut.as_mut().unwrap() }
}
fn get_seek_fut(
self: Pin<&mut Self>,
) -> Option<&mut BoxFuture<'a, Result<PendingResult, IdeviceError>>> {
unsafe { self.get_unchecked_mut().pending_fut.as_mut() }
}
fn remove_pending_fut(mut self: Pin<&mut Self>) {
unsafe {
self.as_mut().get_unchecked_mut().pending_fut.take();
}
}
/// Closes the file descriptor
pub async fn close(mut self: Pin<Box<Self>>) -> Result<(), IdeviceError> {
let header_payload = self.fd.to_le_bytes().to_vec();
self.as_mut()
.send_packet(AfcOpcode::FileClose, header_payload, Vec::new())
.await?;
Ok(())
}
}
impl OwnedInnerFileDescriptor {
fn get_or_init_read_fut(mut self: Pin<&mut Self>, buf_rem: usize) -> &mut OwnedBoxFuture {
if self.as_ref().pending_fut.is_none() {
self.as_mut().store_pending_read(buf_rem);
}
unsafe { self.get_unchecked_mut().pending_fut.as_mut().unwrap() }
}
fn get_or_init_write_fut(mut self: Pin<&mut Self>, buf: &'_ [u8]) -> &mut OwnedBoxFuture {
if self.as_ref().pending_fut.is_none() {
self.as_mut().store_pending_write(buf);
}
unsafe { self.get_unchecked_mut().pending_fut.as_mut().unwrap() }
}
fn get_seek_fut(self: Pin<&mut Self>) -> Option<&mut OwnedBoxFuture> {
unsafe { self.get_unchecked_mut().pending_fut.as_mut() }
}
fn remove_pending_fut(mut self: Pin<&mut Self>) {
unsafe {
self.as_mut().get_unchecked_mut().pending_fut.take();
}
}
/// Closes the file descriptor
pub async fn close(mut self: Pin<Box<Self>>) -> Result<AfcClient, IdeviceError> {
let header_payload = self.fd.to_le_bytes().to_vec();
self.as_mut()
.send_packet(AfcOpcode::FileClose, header_payload, Vec::new())
.await?;
// we don't need it to be pinned anymore
Ok(unsafe { Pin::into_inner_unchecked(self) }.client)
}
pub fn get_inner_afc(self: Pin<Box<Self>>) -> AfcClient {
unsafe { Pin::into_inner_unchecked(self).client }
}
}
crate::impl_trait_to_structs!(AsyncRead for InnerFileDescriptor<'_>, OwnedInnerFileDescriptor; {
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<()>> {
let contents = {
let read_func = self.as_mut().get_or_init_read_fut(buf.remaining());
match std::task::ready!(read_func.as_mut().poll(cx)) {
Ok(PendingResult::Bytes(c)) => {
self.as_mut().remove_pending_fut();
c
}
Err(e) => return std::task::Poll::Ready(Err(std::io::Error::other(e.to_string()))),
_ => unreachable!("a non read future was stored, this shouldn't happen"),
}
};
buf.put_slice(&contents);
std::task::Poll::Ready(Ok(()))
}
});
crate::impl_trait_to_structs!(AsyncWrite for InnerFileDescriptor<'_>, OwnedInnerFileDescriptor; {
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>> {
let write_func = self.as_mut().get_or_init_write_fut(buf);
match std::task::ready!(write_func.as_mut().poll(cx)) {
Ok(PendingResult::Empty) => self.as_mut().remove_pending_fut(),
Err(e) => {
println!("error: {e}");
return std::task::Poll::Ready(Err(std::io::Error::other(e.to_string())));
}
_ => unreachable!("a non write future was stored, this shouldn't happen"),
}
std::task::Poll::Ready(Ok(buf.len()))
}
fn poll_flush(
self: std::pin::Pin<&mut Self>,
_: &mut std::task::Context<'_>,
) -> std::task::Poll<Result<(), std::io::Error>> {
std::task::Poll::Ready(Ok(()))
}
fn poll_shutdown(
self: std::pin::Pin<&mut Self>,
_: &mut std::task::Context<'_>,
) -> std::task::Poll<Result<(), std::io::Error>> {
std::task::Poll::Ready(Ok(()))
}
});
crate::impl_trait_to_structs!(AsyncSeek for InnerFileDescriptor<'_>, OwnedInnerFileDescriptor; {
fn start_seek(self: Pin<&mut Self>, position: SeekFrom) -> std::io::Result<()> {
self.store_pending_seek(position);
Ok(())
}
fn poll_complete(
mut self: Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<std::io::Result<u64>> {
let Some(fut) = self.as_mut().get_seek_fut() else {
// tokio runs the `poll_complete` before the `start_seek` to ensure no previous seek is in progress
return std::task::Poll::Ready(Ok(0));
};
match std::task::ready!(fut.as_mut().poll(cx)) {
Ok(PendingResult::SeekPos(pos)) => {
self.as_mut().remove_pending_fut();
std::task::Poll::Ready(Ok(pos))
}
Err(e) => std::task::Poll::Ready(Err(std::io::Error::other(e.to_string()))),
_ => unreachable!("a non seek future was stored, this shouldn't happen"),
}
}
});
impl std::fmt::Debug for InnerFileDescriptor<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("InnerFileDescriptor")
.field("client", &self.client)
.field("fd", &self.fd)
.field("path", &self.path)
.finish()
}
}
impl std::fmt::Debug for OwnedInnerFileDescriptor {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("OwnedInnerFileDescriptor")
.field("client", &self.client)
.field("fd", &self.fd)
.field("path", &self.path)
.finish()
}
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use tokio::io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt};
use crate::{
IdeviceService as _,
afc::opcode::AfcFopenMode,
usbmuxd::{UsbmuxdAddr, UsbmuxdConnection},
};
use super::*;
async fn make_client() -> AfcClient {
let mut u = UsbmuxdConnection::default()
.await
.expect("failed to connect to usbmuxd");
let d = u
.get_devices()
.await
.expect("no devices")
.into_iter()
.next()
.expect("no devices connected")
.to_provider(UsbmuxdAddr::default(), "idevice_afc_file_inner_tests");
let mut ac = AfcClient::connect(&d)
.await
.expect("failed to connect to afc");
ac.mk_dir("/tmp").await.unwrap();
ac
}
#[tokio::test]
async fn write_and_read_large_file() {
let mut client = make_client().await;
let path = "/tmp/large_file.txt";
let data = vec![b'x'; 10_000_000]; // 10mb
{
let mut file = client.open(path, AfcFopenMode::WrOnly).await.unwrap();
file.write_all(&data).await.unwrap();
}
let mut file = client.open(path, AfcFopenMode::RdOnly).await.unwrap();
let mut buf = Vec::new();
file.read_to_end(&mut buf).await.unwrap();
assert_eq!(buf.len(), data.len());
drop(file);
client.remove(path).await.unwrap();
}
#[should_panic]
#[tokio::test]
async fn panic_safety() {
let mut client = make_client().await;
client.list_dir("/invalid").await.unwrap();
}
#[tokio::test]
async fn file_seek_and_append() {
let mut client = make_client().await;
let path = "/tmp/seek_append.txt";
let mut f = client.open(path, AfcFopenMode::WrOnly).await.unwrap();
f.write_all(b"start").await.unwrap();
f.seek(std::io::SeekFrom::Start(0)).await.unwrap();
f.write_all(b"over").await.unwrap();
drop(f);
let mut f = client.open(path, AfcFopenMode::RdOnly).await.unwrap();
let mut buf = Vec::new();
f.read_to_end(&mut buf).await.unwrap();
assert_eq!(&buf, b"overt"); // “over” overwrites start
drop(f);
client.remove(path).await.unwrap();
}
#[tokio::test]
async fn borrow_check_works() {
let mut client = make_client().await;
let fut = client.list_dir("/Downloads");
// // This line should fail to compile if uncommented:
// let fut2 = client.list_dir("/bar");
fut.await.unwrap();
}
#[tokio::test]
async fn not_send_across_threads() {
let mut client = make_client().await;
// // This should fail to compile if uncommented:
// tokio::spawn(async move { client.list_dir("/").await });
let _ = client.list_dir("/").await;
}
#[tokio::test]
async fn write_and_read_roundtrip() {
use tokio::io::{AsyncReadExt, AsyncWriteExt};
let mut client = make_client().await;
// Create a test file in /tmp (AFC should allow this)
let path = "/tmp/afc_test_file.txt";
let contents = b"hello async afc world";
{
let mut file = client.open(path, AfcFopenMode::WrOnly).await.unwrap();
file.write_all(contents).await.unwrap();
}
{
let mut file = client.open(path, AfcFopenMode::RdOnly).await.unwrap();
let mut buf = Vec::new();
file.read_to_end(&mut buf).await.unwrap();
assert_eq!(buf, contents);
}
client.remove(path).await.unwrap();
}
#[tokio::test]
async fn write_multiple_chunks() {
use tokio::io::AsyncWriteExt;
let mut client = make_client().await;
let path = "/tmp/afc_chunk_test.txt";
let mut file = client.open(path, AfcFopenMode::WrOnly).await.unwrap();
for i in 0..10 {
let data = format!("chunk{}\n", i);
file.write_all(data.as_bytes()).await.unwrap();
}
drop(file);
let mut file = client.open(path, AfcFopenMode::RdOnly).await.unwrap();
let mut buf = Vec::new();
tokio::io::AsyncReadExt::read_to_end(&mut file, &mut buf)
.await
.unwrap();
let s = String::from_utf8_lossy(&buf);
for i in 0..10 {
assert!(s.contains(&format!("chunk{}", i)));
}
drop(file);
client.remove(path).await.unwrap();
}
#[tokio::test]
async fn read_partial_and_resume() {
use tokio::io::AsyncReadExt;
let mut client = make_client().await;
let path = "/tmp/afc_partial_read.txt";
let contents = b"abcdefghijklmnopqrstuvwxyz";
{
let mut file = client.open(path, AfcFopenMode::WrOnly).await.unwrap();
file.write_all(contents).await.unwrap();
}
let mut file = client.open(path, AfcFopenMode::RdOnly).await.unwrap();
let mut buf = [0u8; 5];
let n = file.read(&mut buf).await.unwrap();
assert_eq!(&buf[..n], b"abcde");
let mut rest = Vec::new();
file.read_to_end(&mut rest).await.unwrap();
assert_eq!(rest, b"fghijklmnopqrstuvwxyz");
drop(file);
client.remove(path).await.unwrap();
}
#[tokio::test]
async fn zero_length_file() {
use tokio::io::AsyncReadExt;
let mut client = make_client().await;
let path = "/tmp/afc_empty.txt";
{
let _ = client.open(path, AfcFopenMode::WrOnly).await.unwrap();
}
let mut file = client.open(path, AfcFopenMode::RdOnly).await.unwrap();
let mut buf = Vec::new();
let n = file.read_to_end(&mut buf).await.unwrap();
assert_eq!(n, 0);
drop(file);
client.remove(path).await.unwrap();
}
#[tokio::test]
async fn write_then_append() {
use tokio::io::{AsyncReadExt, AsyncWriteExt};
let mut client = make_client().await;
let path = "/tmp/afc_append.txt";
{
let mut file = client.open(path, AfcFopenMode::WrOnly).await.unwrap();
file.write_all(b"first\n").await.unwrap();
file.flush().await.unwrap();
}
{
let mut file = client.open(path, AfcFopenMode::Append).await.unwrap();
file.write_all(b"second\n").await.unwrap();
file.flush().await.unwrap();
}
let mut file = client.open(path, AfcFopenMode::RdOnly).await.unwrap();
let mut buf = Vec::new();
file.read_to_end(&mut buf).await.unwrap();
assert_eq!(String::from_utf8_lossy(&buf), "first\nsecond\n");
drop(file);
client.remove(path).await.unwrap();
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn concurrent_file_access_should_not_ub() {
use std::sync::Arc;
use tokio::task;
let client = Arc::new(tokio::sync::Mutex::new(make_client().await));
let path = "/tmp/afc_threaded.txt";
let tasks: Vec<_> = (0..10)
.map(|i| {
let client = Arc::clone(&client);
task::spawn(async move {
let mut guard = client.lock().await;
let mut f = guard.open(path, AfcFopenMode::Append).await.unwrap();
f.write_all(format!("{}\n", i).as_bytes()).await.unwrap();
f.flush().await.unwrap();
})
})
.collect();
for t in tasks {
let _ = t.await;
}
let mut guard = client.lock().await;
let mut f = guard.open(path, AfcFopenMode::RdOnly).await.unwrap();
let mut buf = Vec::new();
tokio::io::AsyncReadExt::read_to_end(&mut f, &mut buf)
.await
.unwrap();
let s = String::from_utf8_lossy(&buf);
for i in 0..10 {
assert!(s.contains(&i.to_string()));
}
drop(f);
guard.remove(path).await.unwrap();
}
#[tokio::test]
async fn panic_during_write_does_not_leak() {
use std::sync::atomic::{AtomicUsize, Ordering};
static COUNT: AtomicUsize = AtomicUsize::new(0);
let mut client = make_client().await;
let path = "/tmp/afc_panic.txt";
let result = std::panic::AssertUnwindSafe(async {
let _f = client.open(path, AfcFopenMode::WrOnly).await.unwrap();
COUNT.fetch_add(1, Ordering::SeqCst);
panic!("simulate crash mid-write");
})
.catch_unwind()
.await;
assert!(result.is_err());
// Reopen to ensure no handles leaked
let _ = client.open(path, AfcFopenMode::WrOnly).await.unwrap();
assert_eq!(COUNT.load(Ordering::SeqCst), 1);
}
#[tokio::test]
async fn open_close_stress() {
let mut client = make_client().await;
let path = "/tmp/afc_stress.txt";
for _ in 0..100 {
let mut f = client.open(path, AfcFopenMode::WrOnly).await.unwrap();
f.write_all(b"hi").await.unwrap();
drop(f);
}
// Make sure handle cleanup didnt break internal state
client.remove(path).await.unwrap();
}
#[tokio::test]
async fn concurrent_access_stress() {
let client = Arc::new(tokio::sync::Mutex::new(make_client().await));
let mut handles = vec![];
for i in 0..10 {
let client = client.clone();
handles.push(tokio::spawn(async move {
let mut client = client.lock().await;
let path = format!("/tmp/testfile_{}", i);
client.mk_dir(&path).await.ok();
let _ = client.list_dir("/tmp").await;
client.remove(&path).await.ok();
}));
}
for h in handles {
let _ = h.await;
}
}
#[tokio::test]
async fn read_write_mode_works() {
let mut client = make_client().await;
// Clean up from previous runs
let _ = client.remove("/tmp/rw_test.txt").await;
// Open for read/write
let mut file = client
.open("/tmp/rw_test.txt", AfcFopenMode::Rw)
.await
.expect("failed to open file in rw mode");
// Write some data
let data = b"hello world";
file.write_all(data).await.expect("failed to write");
// Seek back to start
file.seek(std::io::SeekFrom::Start(0))
.await
.expect("seek failed");
// Read it back
let mut buf = vec![0u8; data.len()];
file.read_exact(&mut buf).await.expect("failed to read");
assert_eq!(&buf, data);
// Write again at end
file.seek(std::io::SeekFrom::End(0)).await.unwrap();
file.write_all(b"!").await.unwrap();
// Verify new content
file.seek(std::io::SeekFrom::Start(0)).await.unwrap();
let mut final_buf = Vec::new();
file.read_to_end(&mut final_buf).await.unwrap();
assert_eq!(&final_buf, b"hello world!");
file.close().await.expect("failed to close");
// Double check via list/read
let contents = client
.open("/tmp/rw_test.txt", AfcFopenMode::RdOnly)
.await
.unwrap()
.read_entire()
.await
.unwrap();
assert_eq!(contents, b"hello world!");
// Clean up
client.remove("/tmp/rw_test.txt").await.unwrap();
}
}

View File

@@ -1,23 +0,0 @@
#[macro_export]
macro_rules! impl_to_structs {
(
$( $name:ident $(<$li:lifetime>)? ),+;
$body:tt
) => {
$(
impl $name $(<$li>)? $body
)+
};
}
#[macro_export]
macro_rules! impl_trait_to_structs {
(
$trit:ident for $( $name:ident $(<$li:lifetime>)? ),+;
$body:tt
) => {
$(
impl $trit for $name $(<$li>)? $body
)+
};
}

View File

@@ -6,21 +6,15 @@
use std::collections::HashMap;
use errors::AfcError;
use file::FileDescriptor;
use log::warn;
use opcode::{AfcFopenMode, AfcOpcode};
use packet::{AfcPacket, AfcPacketHeader};
use tracing::warn;
use crate::{
Idevice, IdeviceError, IdeviceService,
afc::file::{FileDescriptor, OwnedFileDescriptor},
lockdown::LockdownClient,
obf,
};
use crate::{Idevice, IdeviceError, IdeviceService, obf};
pub mod errors;
pub mod file;
mod inner_file;
mod inner_file_impl_macro;
pub mod opcode;
pub mod packet;
@@ -28,7 +22,6 @@ pub mod packet;
pub const MAGIC: u64 = 0x4141504c36414643;
/// Client for interacting with the AFC service on iOS devices
#[derive(Debug)]
pub struct AfcClient {
/// The underlying iDevice connection
pub idevice: Idevice,
@@ -92,43 +85,6 @@ impl AfcClient {
}
}
/// Connects to afc2 from a provider
pub async fn new_afc2(
provider: &dyn crate::provider::IdeviceProvider,
) -> Result<Self, IdeviceError> {
let mut lockdown = LockdownClient::connect(provider).await?;
#[cfg(feature = "openssl")]
let legacy = lockdown
.get_value(Some("ProductVersion"), None)
.await
.ok()
.as_ref()
.and_then(|x| x.as_string())
.and_then(|x| x.split(".").next())
.and_then(|x| x.parse::<u8>().ok())
.map(|x| x < 5)
.unwrap_or(false);
#[cfg(not(feature = "openssl"))]
let legacy = false;
lockdown
.start_session(&provider.get_pairing_file().await?)
.await?;
let (port, ssl) = lockdown.start_service(obf!("com.apple.afc2")).await?;
let mut idevice = provider.connect(port).await?;
if ssl {
idevice
.start_session(&provider.get_pairing_file().await?, legacy)
.await?;
}
Self::from_stream(idevice).await
}
/// Lists the contents of a directory on the device
///
/// # Arguments
@@ -452,54 +408,11 @@ impl AfcClient {
return Err(IdeviceError::UnexpectedResponse);
}
let fd = u64::from_le_bytes(res.header_payload[..8].try_into().unwrap());
// we know it's a valid fd
Ok(unsafe { FileDescriptor::new(self, fd, path) })
}
/// Opens an owned file on the device
///
/// # Arguments
/// * `path` - Path to the file to open
/// * `mode` - Opening mode (read, write, etc.)
///
/// # Returns
/// A `OwnedFileDescriptor` struct for the opened file
pub async fn open_owned(
mut self,
path: impl Into<String>,
mode: AfcFopenMode,
) -> Result<OwnedFileDescriptor, IdeviceError> {
let path = path.into();
let mut header_payload = (mode as u64).to_le_bytes().to_vec();
header_payload.extend(path.as_bytes());
let header_len = header_payload.len() as u64 + AfcPacketHeader::LEN;
let header = AfcPacketHeader {
magic: MAGIC,
entire_len: header_len, // it's the same since the payload is empty for this
header_payload_len: header_len,
packet_num: self.package_number,
operation: AfcOpcode::FileOpen,
};
self.package_number += 1;
let packet = AfcPacket {
header,
header_payload,
payload: Vec::new(),
};
self.send(packet).await?;
let res = self.read().await?;
if res.header_payload.len() < 8 {
warn!("Header payload fd is less than 8 bytes");
return Err(IdeviceError::UnexpectedResponse);
}
let fd = u64::from_le_bytes(res.header_payload[..8].try_into().unwrap());
// we know it's a valid fd
Ok(unsafe { OwnedFileDescriptor::new(self, fd, path) })
Ok(FileDescriptor {
client: self,
fd,
path,
})
}
/// Creates a hard or symbolic link
@@ -595,7 +508,7 @@ impl AfcClient {
let res = AfcPacket::read(&mut self.idevice).await?;
if res.header.operation == AfcOpcode::Status {
if res.header_payload.len() < 8 {
tracing::error!("AFC returned error opcode, but not a code");
log::error!("AFC returned error opcode, but not a code");
return Err(IdeviceError::UnexpectedResponse);
}
let code = u64::from_le_bytes(res.header_payload[..8].try_into().unwrap());

View File

@@ -1,6 +1,6 @@
// Jackson Coxson
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[derive(Clone, Debug, PartialEq, Eq)]
#[repr(u64)]
pub enum AfcOpcode {
Status = 0x00000001,
@@ -36,7 +36,6 @@ pub enum AfcOpcode {
}
#[repr(u64)]
#[derive(Clone, Copy, Debug)]
pub enum AfcFopenMode {
RdOnly = 0x00000001, // r O_RDONLY
Rw = 0x00000002, // r+ O_RDWR | O_CREAT
@@ -47,7 +46,6 @@ pub enum AfcFopenMode {
}
#[repr(u64)]
#[derive(Clone, Copy, Debug)]
pub enum LinkType {
Hardlink = 0x00000001,
Symlink = 0x00000002,

View File

@@ -1,12 +1,12 @@
// Jackson Coxson
use tracing::debug;
use log::debug;
use crate::{Idevice, IdeviceError};
use super::opcode::AfcOpcode;
#[derive(Clone, Copy, Debug)]
#[derive(Clone, Debug)]
pub struct AfcPacketHeader {
pub magic: u64,
pub entire_len: u64,
@@ -32,7 +32,7 @@ impl AfcPacketHeader {
res.extend_from_slice(&self.entire_len.to_le_bytes());
res.extend_from_slice(&self.header_payload_len.to_le_bytes());
res.extend_from_slice(&self.packet_num.to_le_bytes());
res.extend_from_slice(&(self.operation as u64).to_le_bytes());
res.extend_from_slice(&(self.operation.clone() as u64).to_le_bytes());
res
}

View File

@@ -3,7 +3,6 @@
use crate::{Idevice, IdeviceError, IdeviceService, obf};
/// Client for interacting with the AMFI service on the device
#[derive(Debug)]
pub struct AmfiClient {
/// The underlying device connection with established amfi service
pub idevice: Idevice,

View File

@@ -5,7 +5,7 @@
use std::pin::Pin;
use futures::Stream;
use tracing::warn;
use log::{debug, warn};
use crate::{Idevice, IdeviceError, IdeviceService, obf};
@@ -13,7 +13,6 @@ use crate::{Idevice, IdeviceError, IdeviceService, obf};
/// You must have the Bluetooth profile installed, or you'll get no data.
///
/// ``https://developer.apple.com/bug-reporting/profiles-and-logs/?name=bluetooth``
#[derive(Debug)]
pub struct BtPacketLoggerClient {
/// The underlying device connection with established logger service
pub idevice: Idevice,
@@ -111,6 +110,16 @@ impl BtPacketLoggerClient {
let kind = BtPacketKind::from_byte(frame[off]);
let payload = &frame[off + 1..]; // whatever remains
// Optional soft check of advisory header.length
let advisory = hdr.length as usize;
let actual = 1 + payload.len(); // kind + payload
if advisory != actual {
debug!(
"BTPacketLogger advisory length {} != actual {}, proceeding",
advisory, actual
);
}
// Build H4 buffer (prepend type byte)
let mut h4 = Vec::with_capacity(1 + payload.len());
if let Some(t) = kind.h4_type() {
@@ -149,6 +158,13 @@ impl BtPacketLoggerClient {
let kind = BtPacketKind::from_byte(frame[off]);
let payload = &frame[off + 1..];
// soft advisory check
let advisory = hdr.length as usize;
let actual = 1 + payload.len();
if advisory != actual {
debug!("BTPacketLogger advisory length {} != actual {}", advisory, actual);
}
// make H4 buffer
let mut h4 = Vec::with_capacity(1 + payload.len());
if let Some(t) = kind.h4_type() {

View File

@@ -1,15 +1,13 @@
//! Companion Proxy is Apple's bridge to connect to the Apple Watch
use tracing::warn;
use log::warn;
use crate::{Idevice, IdeviceError, IdeviceService, RsdService, obf};
#[derive(Debug)]
pub struct CompanionProxy {
idevice: Idevice,
}
#[derive(Debug)]
pub struct CompanionProxyStream {
proxy: CompanionProxy,
}

View File

@@ -1,8 +1,7 @@
// Jackson Coxson
use plist_macro::plist_to_xml_bytes;
use log::warn;
use serde::Deserialize;
use tracing::warn;
use crate::{IdeviceError, ReadWrite, RsdService, obf, xpc::XPCObject};
@@ -20,7 +19,6 @@ impl RsdService for AppServiceClient<Box<dyn ReadWrite>> {
}
}
#[derive(Debug)]
pub struct AppServiceClient<R: ReadWrite> {
inner: CoreDeviceServiceClient<R>,
}
@@ -217,7 +215,7 @@ impl<R: ReadWrite> AppServiceClient<R> {
"user": {
"active": true,
},
"platformSpecificOptions": plist::Value::Data(plist_to_xml_bytes(&platform_options.unwrap_or_default())),
"platformSpecificOptions": plist::Value::Data(crate::util::plist_to_xml_bytes(&platform_options.unwrap_or_default())),
},
});

View File

@@ -3,7 +3,7 @@
use std::pin::Pin;
use futures::Stream;
use tracing::warn;
use log::warn;
use crate::{IdeviceError, ReadWrite, RsdService, obf};
@@ -19,7 +19,6 @@ impl RsdService for DiagnostisServiceClient<Box<dyn ReadWrite>> {
}
}
#[derive(Debug)]
pub struct DiagnostisServiceClient<R: ReadWrite> {
inner: super::CoreDeviceServiceClient<R>,
}
@@ -71,12 +70,3 @@ impl<R: ReadWrite> DiagnostisServiceClient<R> {
}
}
}
impl std::fmt::Debug for SysdiagnoseResponse<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SysdiagnoseResponse")
.field("preferred_filename", &self.preferred_filename)
.field("expected_length", &self.expected_length)
.finish()
}
}

View File

@@ -1,7 +1,7 @@
// Jackson Coxson
// Ported from pymobiledevice3
use tracing::warn;
use log::warn;
use crate::{
IdeviceError, ReadWrite, RemoteXpcClient,
@@ -17,7 +17,6 @@ pub use openstdiosocket::*;
const CORE_SERVICE_VERSION: &str = "443.18";
#[derive(Debug)]
pub struct CoreDeviceServiceClient<R: ReadWrite> {
inner: RemoteXpcClient<R>,
}

View File

@@ -16,7 +16,6 @@ impl RsdService for OpenStdioSocketClient {
/// Call ``read_uuid`` to get the UUID. Pass that to app service launch to connect to the stream of
/// the launched app. Inner is exposed to read and write to, using Tokio's AsyncReadExt/AsyncWriteExt
#[derive(Debug)]
pub struct OpenStdioSocketClient {
pub inner: Box<dyn ReadWrite>,
}

View File

@@ -17,7 +17,7 @@ use crate::{Idevice, IdeviceError, IdeviceService, obf};
use byteorder::{BigEndian, WriteBytesExt};
use serde::{Deserialize, Serialize};
use std::io::{self, IoSlice, Write};
use std::io::{self, Write};
/// A representation of a CDTunnel packet used in the CoreDeviceProxy protocol.
#[derive(Debug, PartialEq)]
@@ -82,7 +82,6 @@ impl CDTunnelPacket {
/// A high-level client for the `com.apple.internal.devicecompute.CoreDeviceProxy` service.
///
/// Handles session negotiation, handshake, and tunnel communication.
#[derive(Debug)]
pub struct CoreDeviceProxy {
/// The underlying idevice connection used for communication.
pub idevice: Idevice,
@@ -195,20 +194,6 @@ impl CoreDeviceProxy {
Ok(())
}
/// Sends a raw data packet through the tunnel using vectored I/O.
///
/// # Arguments
///
/// * `bufs` - The buffers to send.
///
/// # Returns
///
/// * `Ok(())` if the data is successfully sent.
/// * `Err(IdeviceError)` if sending fails.
pub async fn send_vectored(&mut self, bufs: &[IoSlice<'_>]) -> Result<(), IdeviceError> {
self.idevice.send_raw_vectored(bufs).await
}
/// Receives up to `mtu` bytes from the tunnel.
///
/// # Returns

View File

@@ -7,7 +7,7 @@
//! function to trigger a flush of crash logs from system storage into the
//! crash reports directory by connecting to the `com.apple.crashreportmover` service.
use tracing::{debug, warn};
use log::{debug, warn};
use crate::{Idevice, IdeviceError, IdeviceService, afc::AfcClient, lockdown::LockdownClient, obf};
@@ -15,7 +15,6 @@ use crate::{Idevice, IdeviceError, IdeviceService, afc::AfcClient, lockdown::Loc
///
/// This client wraps access to the `com.apple.crashreportcopymobile` service,
/// which exposes crash logs through the Apple File Conduit (AFC).
#[derive(Debug)]
pub struct CrashReportCopyMobileClient {
/// The underlying AFC client connected to the crash logs directory.
pub afc_client: AfcClient,
@@ -85,7 +84,7 @@ impl CrashReportCopyMobileClient {
.open(format!("/{log}"), crate::afc::opcode::AfcFopenMode::RdOnly)
.await?;
f.read_entire().await
f.read().await
}
/// Removes a specified crash log file from the device.
@@ -124,18 +123,6 @@ pub async fn flush_reports(
provider: &dyn crate::provider::IdeviceProvider,
) -> Result<(), IdeviceError> {
let mut lockdown = LockdownClient::connect(provider).await?;
let legacy = lockdown
.get_value(Some("ProductVersion"), None)
.await
.ok()
.as_ref()
.and_then(|x| x.as_string())
.and_then(|x| x.split(".").next())
.and_then(|x| x.parse::<u8>().ok())
.map(|x| x < 5)
.unwrap_or(false);
lockdown
.start_session(&provider.get_pairing_file().await?)
.await?;
@@ -147,7 +134,7 @@ pub async fn flush_reports(
let mut idevice = provider.connect(port).await?;
if ssl {
idevice
.start_session(&provider.get_pairing_file().await?, legacy)
.start_session(&provider.get_pairing_file().await?)
.await?;
}

View File

@@ -4,9 +4,9 @@
//! GDB Remote Serial Protocol as documented at:
//! https://sourceware.org/gdb/current/onlinedocs/gdb.html/Packets.html#Packets
use log::debug;
use std::fmt::Write;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tracing::debug;
use crate::{IdeviceError, ReadWrite, RsdService, obf};
@@ -27,7 +27,6 @@ impl RsdService for DebugProxyClient<Box<dyn ReadWrite>> {
///
/// Implements the GDB Remote Serial Protocol for communicating with debugserver
/// on iOS devices. Handles packet formatting, checksums, and acknowledgments.
#[derive(Debug)]
pub struct DebugProxyClient<R: ReadWrite> {
/// The underlying socket connection to debugproxy
pub socket: R,
@@ -39,7 +38,6 @@ pub struct DebugProxyClient<R: ReadWrite> {
///
/// Commands follow the GDB Remote Serial Protocol format:
/// $<command>[<hex-encoded args>]#<checksum>
#[derive(Debug)]
pub struct DebugserverCommand {
/// The command name (e.g. "qSupported", "vCont")
pub name: String,

View File

@@ -3,7 +3,6 @@
use crate::{Idevice, IdeviceError, IdeviceService, obf};
/// Client for interacting with the Diagnostics Relay
#[derive(Debug)]
pub struct DiagnosticsRelayClient {
/// The underlying device connection with established service
pub idevice: Idevice,

View File

@@ -46,7 +46,6 @@ use crate::{
};
/// A client for the location simulation service
#[derive(Debug)]
pub struct LocationSimulationClient<'a, R: ReadWrite> {
/// The underlying channel used for communication
channel: Channel<'a, R>,

View File

@@ -61,12 +61,12 @@
use plist::Value;
use tokio::io::{AsyncRead, AsyncReadExt};
use crate::{IdeviceError, pretty_print_plist};
use crate::IdeviceError;
/// Message header containing metadata about the message
///
/// 32-byte structure that appears at the start of every message
#[derive(Debug, Clone, Copy, PartialEq)]
#[derive(Debug, Clone, PartialEq)]
pub struct MessageHeader {
/// Magic number identifying the protocol (0x1F3D5B79)
magic: u32,
@@ -91,7 +91,7 @@ pub struct MessageHeader {
/// Payload header containing information about the message contents
///
/// 16-byte structure following the message header
#[derive(Debug, Default, Clone, Copy, PartialEq)]
#[derive(Debug, Default, Clone, PartialEq)]
pub struct PayloadHeader {
/// Flags controlling message processing
flags: u32,
@@ -104,7 +104,7 @@ pub struct PayloadHeader {
/// Header for auxiliary data section
///
/// 16-byte structure preceding auxiliary data
#[derive(Debug, Default, Clone, Copy, PartialEq)]
#[derive(Debug, Default, PartialEq)]
pub struct AuxHeader {
/// Buffer size hint (often 496)
buffer_size: u32,
@@ -141,7 +141,7 @@ pub enum AuxValue {
}
/// Complete protocol message
#[derive(PartialEq)]
#[derive(Debug, PartialEq)]
pub struct Message {
/// Message metadata header
pub message_header: MessageHeader,
@@ -525,14 +525,3 @@ impl std::fmt::Debug for AuxValue {
}
}
}
impl std::fmt::Debug for Message {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Message")
.field("message_header", &self.message_header)
.field("payload_header", &self.payload_header)
.field("aux", &self.aux)
.field("data", &self.data.as_ref().map(pretty_print_plist))
.finish()
}
}

View File

@@ -39,18 +39,6 @@ impl crate::IdeviceService for remote_server::RemoteServerClient<Box<dyn ReadWri
async fn connect(provider: &dyn IdeviceProvider) -> Result<Self, IdeviceError> {
// Establish Lockdown session
let mut lockdown = LockdownClient::connect(provider).await?;
let legacy = lockdown
.get_value(Some("ProductVersion"), None)
.await
.ok()
.as_ref()
.and_then(|x| x.as_string())
.and_then(|x| x.split(".").next())
.and_then(|x| x.parse::<u8>().ok())
.map(|x| x < 5)
.unwrap_or(false);
lockdown
.start_session(&provider.get_pairing_file().await?)
.await?;
@@ -68,7 +56,7 @@ impl crate::IdeviceService for remote_server::RemoteServerClient<Box<dyn ReadWri
let mut idevice = provider.connect(port).await?;
if ssl {
idevice
.start_session(&provider.get_pairing_file().await?, legacy)
.start_session(&provider.get_pairing_file().await?)
.await?;
}
// Convert to transport and build client

View File

@@ -10,8 +10,8 @@ use crate::{
},
obf,
};
use log::warn;
use plist::Value;
use tracing::warn;
#[derive(Debug)]
pub struct NotificationInfo {
@@ -23,7 +23,6 @@ pub struct NotificationInfo {
state_description: String,
}
#[derive(Debug)]
pub struct NotificationsClient<'a, R: ReadWrite> {
/// The underlying channel used for communication
pub channel: Channel<'a, R>,

View File

@@ -34,8 +34,8 @@
//! }
//! ```
use log::warn;
use plist::{Dictionary, Value};
use tracing::warn;
use crate::{IdeviceError, ReadWrite, dvt::message::AuxValue, obf};
@@ -45,7 +45,6 @@ use super::remote_server::{Channel, RemoteServerClient};
///
/// Provides methods for launching, killing, and managing processes through the
/// instruments protocol. Each instance maintains its own communication channel.
#[derive(Debug)]
pub struct ProcessControlClient<'a, R: ReadWrite> {
/// The underlying channel for communication
channel: Channel<'a, R>,

Some files were not shown because too many files have changed in this diff Show More