100 Commits

Author SHA1 Message Date
uncor3
c752ee92c5 Add lockdownd_pair to ffi (#75)
* Add lockdownd_pair to ffi

* fix missing param host_name
2026-02-22 19:47:02 -07:00
neo
76d847664b feat(crashreportcopymobile): ffi bindings (#71)
* feat(crashreportcopymobile): ffi bindings

* chore: update readme
2026-02-16 19:12:17 -07:00
Nicholas Sharp
1f7924b773 Add ApplicationVerificationFailed to list of known errors (#72)
* Add ApplicationVerificationFailed to list of known errors

* Add missing cfg directive
2026-02-16 19:11:54 -07:00
khcrysalis
b459eebe9d feat: support ios-arm64_x86_64-maccatalyst in xcframework (#69)
* feat: support maccatalyst in xcframework

* fix: missing ios-macabi targets in ci
2026-02-14 13:19:32 -07:00
neo
c246362f54 chore(readme): update (#68)
* feat(springboard): add get_icon subcommand

* chore: update readme
2026-02-14 13:18:37 -07:00
neo
bfe44e16e4 feat: notification proxy (#70)
* init

* chore: clippy and fmt

* feat: ffi wrapper

* feat: multi-observe and timeout to notification proxy

* fix: nitpicks

1. proxy death its onw error in emun #69
2. make returned stream actual stream, copied from 54439b85dd/idevice/src/services/bt_packet_logger.rs (L126-L138)
2026-02-14 13:16:26 -07:00
neo
54439b85dd feat(springboard): get homescreen icon metrics (#67)
* feat(springboard): get homescreen icon metrics

* chore: clippy and fmt
2026-02-13 13:00:47 -07:00
Jackson Coxson
a523f0cb9c Bump version 2026-02-13 12:10:20 -07:00
neo
cb375f88a1 feat(springboard): get device orientation (#65) 2026-02-13 07:54:00 -07:00
neo
c5aa731ee5 feat(springboard): add wallpaper preview command support (#64)
* feat(springboard): add wallpaper preview command support

* Use subargs to switch between preview type in sb cli

---------

Co-authored-by: Jackson Coxson <jkcoxson@gmail.com>
2026-02-09 19:14:16 -07:00
Jackson Coxson
38a3a558b5 (BREAKING) implement host name parameter for lockdown pair 2026-02-06 16:45:27 -07:00
Jackson Coxson
496e099187 Make UUID an argument of trust subcommand in amfi 2026-01-30 08:56:07 -07:00
Jackson Coxson
77ea34f820 Bump version 2026-01-22 15:34:15 -07:00
fulln
9a71279fe9 feat(springboard): add get_icon_state and set_icon_state methods (#63)
* feat(springboard): add get_icon_state method for reading home screen layout

Add get_icon_state() method to SpringBoardServicesClient that retrieves
the current home screen icon layout from iOS devices.

Features:
- Read complete home screen layout including icon positions and folders
- Support for optional formatVersion parameter
- Works on all iOS versions (tested on iOS 18.7.3)
- Comprehensive documentation with usage examples

Note: This PR intentionally does NOT include set_icon_state() as that
functionality is non-operational on iOS 18+ (see issue #62 for details).

Tested on:
- Device: iPhone 16,2 (iPhone 15 Pro)
- iOS: 18.7.3 (Build 22H217)

* feat(springboard): add set_icon_state method with date precision fix

- Implement set_icon_state() to modify home screen layout
- Implement set_icon_state_with_version() with format_version parameter
- Add truncate_dates_to_seconds() to convert nanosecond precision dates to second precision
- Fix iOS compatibility issue where high-precision dates were rejected
- Successfully tested on iOS 18.7.3 (previously believed to be restricted)
- Follows pymobiledevice3 implementation pattern

* refactor(utils): extract truncate_dates_to_seconds to utils::plist module

- Move date truncation logic from springboardservices to reusable utils::plist module
- Add comprehensive unit tests for date truncation functionality
- Add public API documentation for the utility function
- This makes the date normalization logic available for other services that may need it

* perf(springboard): normalize dates on read instead of write

- Move date truncation from set_icon_state to get_icon_state
- Eliminates unnecessary clone() operation in set_icon_state
- Better performance when setting icon state multiple times
- Cleaner API: data from get_icon_state is directly usable in set_icon_state
- Users don't need to worry about date precision issues

* refactor(springboard): address PR feedback - use Option<&str> and add error validation

- Change format_version parameter from Option<String> to Option<&str> for consistency
- Remove outdated iOS 18+ restriction comments since setIconState works on iOS 18+
- Add error validation to get_icon_state method similar to get_icon_pngdata
- Update documentation to reflect accurate iOS compatibility

* Fix cargo clippy warnings

* Fix clippy warnings in plist.rs

* Add springboard CLI commands

---------

Co-authored-by: Jackson Coxson <jkcoxson@gmail.com>
2026-01-22 15:32:01 -07:00
Jackson Coxson
142708c289 Add a no-session flag to lockdown CLI 2026-01-19 16:02:17 -07:00
uncor3
5bb9330cf6 add diag relay c++ bindings, screenshotr ffi (#58) 2026-01-14 14:46:47 -07:00
Jackson Coxson
ead5fbf3d3 Add house arrest bindings 2026-01-14 13:39:10 -07:00
SAMSAM
f44a5c0779 fix: fixes misagent cmd + adds new install subcmd (#59)
Calling init twice is a bad idea and causes the program to crash, also adds an install command for provisioning profile paths
2026-01-14 13:01:04 -07:00
Jackson Coxson
1db78e6a8d Re-add iOS checks to CI 2026-01-14 08:14:09 -07:00
Jackson Coxson
bb64dc0b1c Implement lockdown enter recovery 2026-01-05 12:00:11 -07:00
Jackson Coxson
a4e17ea076 Poll correct sub argument tree for domain flag in lockdown CLI 2026-01-05 08:08:11 -07:00
Jackson Coxson
6dcfd4bc4c Move domain to flag of lockdown CLI 2026-01-05 07:57:46 -07:00
Jackson Coxson
602e1ba855 Replace off_t with Windows-allowed value in AFC FFI 2026-01-05 07:23:55 -07:00
Jackson Coxson
ae39fcb7df Add afc2 abstractions (#55) 2026-01-05 07:08:40 -07:00
Jackson Coxson
13be1ae377 Add read_entire to FFI
a
2026-01-05 06:56:11 -07:00
Jackson Coxson
96b380ebc9 Correctly parse DDI image lookup result 2026-01-05 06:42:56 -07:00
Jackson Coxson
189dd5caf2 Refactor idevice tools into single binary 2026-01-03 16:58:36 -07:00
Jackson Coxson
2eebbff172 Bump reqwest 2026-01-03 16:58:36 -07:00
Jackson Coxson
166c497878 Implement seek and tell for AFC (#42) 2026-01-03 16:37:55 -07:00
Jackson Coxson
6d9f0987c1 Migrate to plist_macro crate for utils 2025-12-31 21:21:37 -07:00
Jackson Coxson
081cb2f8d8 Bump version 2025-12-28 19:33:59 -07:00
Jackson Coxson
35c3d61355 Make escrow bag an optional pairing file field 2025-12-23 08:18:41 -07:00
Jackson Coxson
328224d46c cargo fmt 2025-12-22 12:26:43 -07:00
alexytomi
d8bff83753 Fix failure to compile to 32-bit (#49) 2025-12-21 09:20:24 -07:00
se2crid
ae5071a309 Include ErrorDescription in unknown device errors (#46)
* Include ErrorDescription in unknown device errors

* Remove unused error import from tracing
2025-12-20 13:31:30 -07:00
Abdullah Al-Banna
83e43aa3d6 add the futures dep for the afc feature (#48) 2025-12-20 13:30:26 -07:00
uncor3
2a6631f3da fix offline build (#47) 2025-12-19 21:10:42 -07:00
Jackson Coxson
e3c12ddf98 Remove provider free from mounter FFI example 2025-12-13 13:08:42 -07:00
Jackson Coxson
44e8b83698 Free data with underlying vec with vec recreation 2025-12-13 13:08:33 -07:00
Jackson Coxson
9776516544 Free the outer slice in C++ usbmuxd::get_devices 2025-12-13 12:34:41 -07:00
Jackson Coxson
080fea02eb Take ownership of socket in rsd_handshake_new FFI 2025-12-13 11:41:43 -07:00
Jackson Coxson
39d454d77d Add function to free arbitrary stream handle 2025-12-13 11:41:33 -07:00
Jackson Coxson
a3dcac93b2 Add plist_array_free helper function to FFI
a

b
2025-12-13 11:09:41 -07:00
Jackson Coxson
d2375e8f5c Convert plist array into boxed slice for installation_proxy get_apps FFI 2025-12-13 10:32:51 -07:00
Abdullah Al-Banna
9e8abb7d37 open an owned file in AFC (#44)
* ability to open an owned file

Signed-off-by: Abdullah Al-Banna <abdu.albanna@proton.me>

* get the inner afc of an owned file without closing the file

Signed-off-by: Abdullah Al-Banna <abdu.albanna@proton.me>

---------

Signed-off-by: Abdullah Al-Banna <abdu.albanna@proton.me>
2025-12-09 12:58:09 -07:00
Jackson Coxson
c60f0d102b Replace std fs dl callbacks with async tokio 2025-12-01 12:51:03 -07:00
Abdullah Al-Banna
5f1e03911f get and create a FileDescriptor from the fd (#43) 2025-11-30 09:43:28 -07:00
Jackson Coxson
c1b7009a7b Create diagnostics_relay bindings 2025-11-28 22:46:11 -07:00
Jackson Coxson
a708db6307 Bump version 2025-11-27 11:12:39 -07:00
Ylarod
c432627659 tools: add iproxy (#37)
* tools: add iproxy

* cargofmt and clippy cleanup

---------

Co-authored-by: Jackson Coxson <jkcoxson@gmail.com>
2025-11-27 11:11:06 -07:00
Lorenzo Rizzotti
c0ec9b4bcd Add vectored IO to avoid userspace vec copy (#38) 2025-11-27 11:06:08 -07:00
Abdullah Al-Banna
c1bc887fd4 Raise the AFC max transfer to 1MB (#39)
Signed-off-by: Abdullah Al-Banna <abdu.albanna@proton.me>
2025-11-27 10:49:26 -07:00
Jackson Coxson
761cb06418 Bump version 2025-11-17 12:55:30 -07:00
Jackson Coxson
db4547e0da Warn if legacy compiled with rustls 2025-11-17 12:44:32 -07:00
Jackson Coxson
59c3c3a12c Add legacy argument to cpp start_session 2025-11-17 12:42:29 -07:00
Jackson Coxson
f11a1bafff Enable SSL on iOS < 5 2025-11-17 12:27:45 -07:00
Jackson Coxson
c9ca113239 Merge branch 'master' into openssl 2025-11-17 11:01:54 -07:00
Jackson Coxson
08d6b41536 Add debug derives for all possible structures 2025-11-15 12:01:10 -07:00
Jackson Coxson
db894120da Add OpenSSL dependency 2025-11-15 11:45:34 -07:00
Jackson Coxson
13c5b48b1c Add ns keyed archive dep to core_device services 2025-11-10 13:33:54 -07:00
Jackson Coxson
e31f39eac0 Clean Rust 1.90 clippy warnings 2025-11-10 13:32:49 -07:00
Jackson Coxson
fbdc290d88 Remove device_id argument for usbmuxd save pair record 2025-11-10 11:16:40 -07:00
Jackson Coxson
247acb192d Bump version 2025-11-07 16:12:47 -07:00
Jackson Coxson
d15c255524 Add futures dependency for usbmuxd 2025-11-07 16:12:28 -07:00
Jackson Coxson
c8e5f52ccd Bump version 2025-11-07 12:13:30 -07:00
Jackson Coxson
6d4bd7e853 Add rlib to ffi build kind 2025-11-06 14:44:41 -07:00
Jackson Coxson
4fa46e4c54 chore: Changed the macOS deployment target to 10.12 (#34)
Co-authored-by: macmade <macmade@xs-labs.com>
2025-11-05 07:28:18 -07:00
Abdullah Al-Banna
b26dd17b13 impl tokio's AsyncRead/Write/Seek for AFC FileDescriptor (#33)
* AsyncWrite/Read/Seek

* clean up

* use only one field to store the future

This struct should not be shared across threads because simultaneous
operations
like reading, writing, or seeking could lead to data races or
inconsistent state, because the cursor moves.

Only one operation will ever run at a time, which allows us to safely
store
different types of pending operations (read, write, or seek) in the same
field.

* consume self without mut when closing

* clippy

* Add inner_file safety tests

* more tests

---------

Co-authored-by: Jackson Coxson <jkcoxson@gmail.com>
2025-10-28 07:57:35 -06:00
Jackson Coxson
105a9b2837 Fix VS solution for idevice++ 2025-10-23 13:10:51 -06:00
Jackson Coxson
7da735f141 Rewrite async runtime handling and port to tracing 2025-10-23 12:29:59 -06:00
Jackson Coxson
7527cdff7b Log in the screenshot cpp example 2025-10-23 12:29:36 -06:00
Jackson Coxson
ec4663e93d Qualify unix from_raw_fd call in FFI 2025-10-23 10:10:07 -06:00
Jackson Coxson
20f00e38dd Idevice::from_fd only on unix 2025-10-23 09:52:51 -06:00
Jackson Coxson
a297eed156 Replace log crate with tracing 2025-10-23 09:49:38 -06:00
Jackson Coxson
18b8b7295c Set FFI error message to formatted error string 2025-10-22 10:27:13 -06:00
Jackson Coxson
0ccec70ed8 Implement creating an Idevice with a file descriptor 2025-10-22 10:26:57 -06:00
Jackson Coxson
7805f943af Update the C++ idevice.h header on build 2025-10-22 10:26:25 -06:00
Jackson Coxson
5ed2144d9e Add adapter_close to stop TCP stack 2025-10-22 10:26:13 -06:00
Jackson Coxson
dd2db92967 Update Xcode solution 2025-10-21 11:12:13 -06:00
Jackson Coxson
94624f07af Rename screenshot_clear to screenshot_take_screenshot in FFI 2025-10-21 09:16:47 -06:00
Jackson Coxson
a7daac3a46 Add DVT screenshot bindings 2025-10-21 08:47:07 -06:00
Jackson Coxson
779db7ecec Print which screenshot protocol is being used in tool 2025-10-20 10:51:52 -06:00
Jackson Coxson
c10f4da9f1 Add missing usbmuxd FFI methods 2025-10-18 22:10:14 -06:00
Jackson Coxson
ea61459df2 Support usbmuxd's 1C network type 2025-10-18 21:26:55 -06:00
Jackson Coxson
cf7c92f8ad Fix usbmuxd windows build 2025-10-18 18:42:36 -06:00
Jackson Coxson
2ffeb0f25c Add listen command to usbmuxd connection 2025-10-18 18:10:53 -06:00
Jackson Coxson
aff3ef589f Bump version
a
2025-10-18 11:49:25 -06:00
Jackson Coxson
11c8f98b4f Add read_with_callback to AFC 2025-10-18 11:16:41 -06:00
Jackson Coxson
ef14b7669d Bump version 2025-10-18 00:15:51 -06:00
Jackson Coxson
0490c246be Add AFC write_with_callback 2025-10-18 00:15:32 -06:00
Jackson Coxson
9a656a2a0e Bump dependencies 2025-10-17 16:06:11 -06:00
Jackson Coxson
87ad894875 Remove unneeded lockdown structure 2025-10-17 15:50:51 -06:00
Jackson Coxson
7853f2d6d0 Initial implementation of installcoordination_proxy 2025-10-16 12:10:14 -06:00
Jackson Coxson
526773d8ca Implemement missing null type and reply flag for XPC 2025-10-16 11:57:16 -06:00
Jackson Coxson
eb37facd04 Implement XPC macro 2025-10-16 11:00:12 -06:00
Jackson Coxson
b897c3a126 Remove MacOS only headers from idevice++ src 2025-10-15 12:22:32 -06:00
Jackson Coxson
fb3043b3e0 Update FFI examples to current idevice 2025-10-15 12:15:31 -06:00
Jackson Coxson
b0f871af99 Log if h4 type is unknown 2025-10-14 10:53:05 -06:00
Jackson Coxson
7507b9609c Remove bt_packet_logger advisory check 2025-10-14 10:50:32 -06:00
Jackson Coxson
4710ffce34 Make plist macro public 2025-10-04 16:02:51 -06:00
182 changed files with 10868 additions and 5981 deletions

View File

@@ -30,8 +30,14 @@ jobs:
- name: Install rustup targets - name: Install rustup targets
run: | 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 aarch64-apple-darwin && \
rustup target add x86_64-apple-darwin && cargo install --force --locked bindgen-cli 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
- name: Build all Apple targets and examples/tools - name: Build all Apple targets and examples/tools
run: | run: |
@@ -44,6 +50,12 @@ jobs:
path: | path: |
target/*apple*/release/libidevice_ffi.a 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 - name: Upload C examples/tools
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:

1557
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -47,6 +47,8 @@ To keep dependency bloat and compile time down, everything is contained in featu
|------------------------|-----------------------------------------------------------------------------| |------------------------|-----------------------------------------------------------------------------|
| `afc` | Apple File Conduit for file system access.| | `afc` | Apple File Conduit for file system access.|
| `amfi` | Apple mobile file integrity service | | `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. | | `core_device_proxy` | Start a secure tunnel to access protected services. |
| `crashreportcopymobile`| Copy crash reports.| | `crashreportcopymobile`| Copy crash reports.|
| `debug_proxy` | Send GDB commands to the device.| | `debug_proxy` | Send GDB commands to the device.|
@@ -55,34 +57,36 @@ To keep dependency bloat and compile time down, everything is contained in featu
| `heartbeat` | Maintain a heartbeat connection.| | `heartbeat` | Maintain a heartbeat connection.|
| `house_arrest` | Manage files in app containers | | `house_arrest` | Manage files in app containers |
| `installation_proxy` | Manage app installation and uninstallation.| | `installation_proxy` | Manage app installation and uninstallation.|
| `springboardservices` | Control SpringBoard (e.g. UI interactions). Partial support.| | `installcoordination_proxy` | Manage app installation coordination.|
| `misagent` | Manage provisioning profiles on the device.|
| `mobilebackup2` | Manage backups.|
| `mobile_image_mounter` | Manage DDI images.|
| `location_simulation` | Simulate GPS locations on the device.| | `location_simulation` | Simulate GPS locations on the device.|
| `misagent` | Manage provisioning profiles on the device.|
| `mobile_image_mounter` | Manage DDI images.|
| `mobileactivationd` | Activate/Deactivate device.|
| `mobilebackup2` | Manage backups.|
| `pair` | Pair the device.| | `pair` | Pair the device.|
| `syslog_relay` | Relay system logs from 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. |
| `tcp` | Connect to devices over TCP.| | `tcp` | Connect to devices over TCP.|
| `tunnel_tcp_stack` | Naive in-process TCP stack for `core_device_proxy`.| | `tunnel_tcp_stack` | Naive in-process TCP stack for `core_device_proxy`.|
| `tss` | Make requests to Apple's TSS servers. Partial support.| | `tss` | Make requests to Apple's TSS servers. Partial support.|
| `tunneld` | Interface with [pymobiledevice3](https://github.com/doronz88/pymobiledevice3)'s tunneld. | | `tunneld` | Interface with [pymobiledevice3](https://github.com/doronz88/pymobiledevice3)'s tunneld. |
| `usbmuxd` | Connect using the usbmuxd daemon.| | `usbmuxd` | Connect using the usbmuxd daemon.|
| `xpc` | Access protected services via XPC over RSD. | | `xpc` | Access protected services via XPC over RSD. |
| `notification_proxy` | Post and observe iOS notifications. |
### Planned/TODO ### Planned/TODO
Finish the following: Finish the following:
- springboard - webinspector
Implement the following: Implement the following:
- companion_proxy - file_relay
- diagnostics
- mobilebackup2
- notification_proxy
- screenshot
- webinspector
As this project is done in my free time within my busy schedule, there 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! 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 ----------------------------------------- # ---- Build the C++ wrapper library -----------------------------------------
# Collect your .cpps # Collect your .cpps
file(GLOB IDEVICE_CPP_SOURCES file(GLOB_RECURSE IDEVICE_CPP_SOURCES
${IDEVICE_CPP_SRC_DIR}/*.cpp "${IDEVICE_CPP_SRC_DIR}/*.cpp"
) )
file(GLOB PLIST_CPP_SOURCES file(GLOB PLIST_CPP_SOURCES

View File

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

108
cpp/examples/screenshot.cpp Normal file
View File

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

View File

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

View File

@@ -0,0 +1,50 @@
// Jackson Coxson
#pragma once
#include <idevice++/bindings.hpp>
#include <idevice++/ffi.hpp>
#include <idevice++/provider.hpp>
#include <memory>
#include <string>
#include <vector>
namespace IdeviceFFI {
using CrashReportCopyMobilePtr =
std::unique_ptr<CrashReportCopyMobileHandle,
FnDeleter<CrashReportCopyMobileHandle, crash_report_client_free>>;
class CrashReportCopyMobile {
public:
// Factory: connect via Provider
static Result<CrashReportCopyMobile, FfiError> connect(Provider& provider);
// Factory: wrap an existing Idevice socket (consumes it on success)
static Result<CrashReportCopyMobile, FfiError> from_socket(Idevice&& socket);
// Static: flush crash reports from system storage
static Result<void, FfiError> flush(Provider& provider);
// Ops
Result<std::vector<std::string>, FfiError> ls(const char* dir_path = nullptr);
Result<std::vector<char>, FfiError> pull(const std::string& log_name);
Result<void, FfiError> remove(const std::string& log_name);
// RAII / moves
~CrashReportCopyMobile() noexcept = default;
CrashReportCopyMobile(CrashReportCopyMobile&&) noexcept = default;
CrashReportCopyMobile& operator=(CrashReportCopyMobile&&) noexcept = default;
CrashReportCopyMobile(const CrashReportCopyMobile&) = delete;
CrashReportCopyMobile& operator=(const CrashReportCopyMobile&) = delete;
CrashReportCopyMobileHandle* raw() const noexcept { return handle_.get(); }
static CrashReportCopyMobile adopt(CrashReportCopyMobileHandle* h) noexcept {
return CrashReportCopyMobile(h);
}
private:
explicit CrashReportCopyMobile(CrashReportCopyMobileHandle* h) noexcept : handle_(h) {}
CrashReportCopyMobilePtr handle_{};
};
} // namespace IdeviceFFI

View File

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

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

View File

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

View File

@@ -0,0 +1,49 @@
// 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,7 +3,6 @@
#include <idevice++/ffi.hpp> #include <idevice++/ffi.hpp>
#include <idevice++/provider.hpp> #include <idevice++/provider.hpp>
#include <memory> #include <memory>
#include <sys/_types/_u_int64_t.h>
namespace IdeviceFFI { namespace IdeviceFFI {

View File

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

View File

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

View File

@@ -0,0 +1,46 @@
#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

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

View File

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

View File

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

View File

@@ -0,0 +1,94 @@
// Jackson Coxson
#include <cstring>
#include <idevice++/bindings.hpp>
#include <idevice++/crashreportcopymobile.hpp>
#include <idevice++/ffi.hpp>
#include <idevice++/provider.hpp>
namespace IdeviceFFI {
// -------- Factory Methods --------
Result<CrashReportCopyMobile, FfiError> CrashReportCopyMobile::connect(Provider& provider) {
CrashReportCopyMobileHandle* out = nullptr;
FfiError e(::crash_report_client_connect(provider.raw(), &out));
if (e) {
return Err(e);
}
return Ok(CrashReportCopyMobile::adopt(out));
}
Result<CrashReportCopyMobile, FfiError> CrashReportCopyMobile::from_socket(Idevice&& socket) {
CrashReportCopyMobileHandle* out = nullptr;
FfiError e(::crash_report_client_new(socket.raw(), &out));
if (e) {
return Err(e);
}
socket.release();
return Ok(CrashReportCopyMobile::adopt(out));
}
Result<void, FfiError> CrashReportCopyMobile::flush(Provider& provider) {
FfiError e(::crash_report_flush(provider.raw()));
if (e) {
return Err(e);
}
return Ok();
}
// -------- Ops --------
Result<std::vector<std::string>, FfiError>
CrashReportCopyMobile::ls(const char* dir_path) {
char** entries_raw = nullptr;
size_t count = 0;
FfiError e(::crash_report_client_ls(handle_.get(), dir_path, &entries_raw, &count));
if (e) {
return Err(e);
}
std::vector<std::string> result;
if (entries_raw) {
result.reserve(count);
for (size_t i = 0; i < count; ++i) {
if (entries_raw[i]) {
result.emplace_back(entries_raw[i]);
::idevice_string_free(entries_raw[i]);
}
}
std::free(entries_raw);
}
return Ok(std::move(result));
}
Result<std::vector<char>, FfiError>
CrashReportCopyMobile::pull(const std::string& log_name) {
uint8_t* data = nullptr;
size_t length = 0;
FfiError e(::crash_report_client_pull(handle_.get(), log_name.c_str(), &data, &length));
if (e) {
return Err(e);
}
std::vector<char> result;
if (data && length > 0) {
result.assign(reinterpret_cast<char*>(data), reinterpret_cast<char*>(data) + length);
::idevice_data_free(data, length);
}
return Ok(std::move(result));
}
Result<void, FfiError> CrashReportCopyMobile::remove(const std::string& log_name) {
FfiError e(::crash_report_client_remove(handle_.get(), log_name.c_str()));
if (e) {
return Err(e);
}
return Ok();
}
} // namespace IdeviceFFI

View File

@@ -0,0 +1,159 @@
// 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,6 +1,6 @@
// Jackson Coxson // Jackson Coxson
#include <idevice++/location_simulation.hpp> #include <idevice++/dvt/location_simulation.hpp>
namespace IdeviceFFI { namespace IdeviceFFI {

View File

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

View File

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

View File

@@ -0,0 +1,37 @@
// 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,6 +13,17 @@ Result<Idevice, FfiError> Idevice::create(IdeviceSocketHandle* socket, const std
return Ok(Idevice(h)); 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> Result<Idevice, FfiError>
Idevice::create_tcp(const sockaddr* addr, socklen_t addr_len, const std::string& label) { Idevice::create_tcp(const sockaddr* addr, socklen_t addr_len, const std::string& label) {
IdeviceHandle* h = nullptr; IdeviceHandle* h = nullptr;
@@ -42,8 +53,8 @@ Result<void, FfiError> Idevice::rsd_checkin() {
return Ok(); return Ok();
} }
Result<void, FfiError> Idevice::start_session(const PairingFile& pairing_file) { Result<void, FfiError> Idevice::start_session(const PairingFile& pairing_file, bool legacy) {
FfiError e(idevice_start_session(handle_.get(), pairing_file.raw())); FfiError e(idevice_start_session(handle_.get(), pairing_file.raw(), legacy));
if (e) { if (e) {
return Err(e); return Err(e);
} }

View File

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

View File

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

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

View File

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

View File

@@ -35,16 +35,25 @@ fn main() {
.expect("Unable to generate bindings") .expect("Unable to generate bindings")
.write_to_file("idevice.h"); .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 // download plist.h
let h = ureq::get("https://raw.githubusercontent.com/libimobiledevice/libplist/refs/heads/master/include/plist/plist.h") let h = ureq::get("https://raw.githubusercontent.com/libimobiledevice/libplist/refs/heads/master/include/plist/plist.h")
.call() .call()
.expect("failed to download plist.h"); .expect("failed to download plist.h");
let h = h h.into_body()
.into_body()
.read_to_string() .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(); let mut f = OpenOptions::new().append(true).open("idevice.h").unwrap();
f.write_all(b"\n\n\n").unwrap(); f.write_all(b"\n\n\n").unwrap();
f.write_all(&h.into_bytes()) f.write_all(&h.into_bytes())
.expect("failed to append plist.h"); .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 { } else {
uint8_t *data = NULL; uint8_t *data = NULL;
size_t length = 0; size_t length = 0;
err = afc_file_read(file, &data, &length); err = afc_file_read_entire(file, &data, &length);
if (err == NULL) { if (err == NULL) {
if (write_file(dest_path, data, length)) { if (write_file(dest_path, data, length)) {
printf("File downloaded successfully\n"); printf("File downloaded successfully\n");

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,14 +1,18 @@
// Jackson Coxson // Jackson Coxson
use std::ptr::null_mut; use std::{io::SeekFrom, ptr::null_mut};
use idevice::{ use idevice::{
IdeviceError, IdeviceService, IdeviceError, IdeviceService,
afc::{AfcClient, DeviceInfo, FileInfo}, afc::{AfcClient, DeviceInfo, FileInfo, file::FileDescriptor},
provider::IdeviceProvider, provider::IdeviceProvider,
}; };
use tokio::io::{AsyncReadExt, AsyncSeekExt};
use crate::{IdeviceFfiError, IdeviceHandle, RUNTIME, ffi_err, provider::IdeviceProviderHandle}; use crate::{
IdeviceFfiError, IdeviceHandle, LOCAL_RUNTIME, ffi_err, provider::IdeviceProviderHandle,
run_sync, run_sync_local,
};
pub struct AfcClientHandle(pub AfcClient); pub struct AfcClientHandle(pub AfcClient);
@@ -30,11 +34,11 @@ pub unsafe extern "C" fn afc_client_connect(
client: *mut *mut AfcClientHandle, client: *mut *mut AfcClientHandle,
) -> *mut IdeviceFfiError { ) -> *mut IdeviceFfiError {
if provider.is_null() || client.is_null() { if provider.is_null() || client.is_null() {
log::error!("Null pointer provided"); tracing::error!("Null pointer provided");
return ffi_err!(IdeviceError::FfiInvalidArg); return ffi_err!(IdeviceError::FfiInvalidArg);
} }
let res = RUNTIME.block_on(async { let res = run_sync_local(async {
let provider_ref: &dyn IdeviceProvider = unsafe { &*(*provider).0 }; let provider_ref: &dyn IdeviceProvider = unsafe { &*(*provider).0 };
AfcClient::connect(provider_ref).await AfcClient::connect(provider_ref).await
@@ -50,6 +54,44 @@ 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 /// Creates a new AfcClient from an existing Idevice connection
/// ///
/// # Arguments /// # Arguments
@@ -88,7 +130,7 @@ pub unsafe extern "C" fn afc_client_new(
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
pub unsafe extern "C" fn afc_client_free(handle: *mut AfcClientHandle) { pub unsafe extern "C" fn afc_client_free(handle: *mut AfcClientHandle) {
if !handle.is_null() { if !handle.is_null() {
log::debug!("Freeing afc_client"); tracing::debug!("Freeing afc_client");
let _ = unsafe { Box::from_raw(handle) }; let _ = unsafe { Box::from_raw(handle) };
} }
} }
@@ -122,7 +164,7 @@ pub unsafe extern "C" fn afc_list_directory(
// Use to_string_lossy to handle non-UTF8 paths // Use to_string_lossy to handle non-UTF8 paths
let path = path_cstr.to_string_lossy(); let path = path_cstr.to_string_lossy();
let res: Result<Vec<String>, IdeviceError> = RUNTIME.block_on(async move { let res: Result<Vec<String>, IdeviceError> = run_sync_local(async move {
// SAFETY: We're assuming client is a valid pointer here // SAFETY: We're assuming client is a valid pointer here
let client_ref = unsafe { &mut (*client).0 }; let client_ref = unsafe { &mut (*client).0 };
client_ref.list_dir(&path.to_string()).await client_ref.list_dir(&path.to_string()).await
@@ -194,7 +236,7 @@ pub unsafe extern "C" fn afc_make_directory(
Err(_) => return ffi_err!(IdeviceError::FfiInvalidArg), Err(_) => return ffi_err!(IdeviceError::FfiInvalidArg),
}; };
let res: Result<(), IdeviceError> = RUNTIME.block_on(async move { let res: Result<(), IdeviceError> = run_sync_local(async move {
let client_ref = unsafe { &mut (*client).0 }; let client_ref = unsafe { &mut (*client).0 };
client_ref.mk_dir(path).await client_ref.mk_dir(path).await
}); });
@@ -246,7 +288,7 @@ pub unsafe extern "C" fn afc_get_file_info(
Err(_) => return ffi_err!(IdeviceError::FfiInvalidArg), Err(_) => return ffi_err!(IdeviceError::FfiInvalidArg),
}; };
let res: Result<FileInfo, IdeviceError> = RUNTIME.block_on(async move { let res: Result<FileInfo, IdeviceError> = run_sync_local(async move {
let client_ref = unsafe { &mut (*client).0 }; let client_ref = unsafe { &mut (*client).0 };
client_ref.get_file_info(path).await client_ref.get_file_info(path).await
}); });
@@ -331,7 +373,7 @@ pub unsafe extern "C" fn afc_get_device_info(
return ffi_err!(IdeviceError::FfiInvalidArg); return ffi_err!(IdeviceError::FfiInvalidArg);
} }
let res: Result<DeviceInfo, IdeviceError> = RUNTIME.block_on(async move { let res: Result<DeviceInfo, IdeviceError> = run_sync_local(async move {
let client_ref = unsafe { &mut (*client).0 }; let client_ref = unsafe { &mut (*client).0 };
client_ref.get_device_info().await client_ref.get_device_info().await
}); });
@@ -395,7 +437,7 @@ pub unsafe extern "C" fn afc_remove_path(
Err(_) => return ffi_err!(IdeviceError::FfiInvalidArg), Err(_) => return ffi_err!(IdeviceError::FfiInvalidArg),
}; };
let res: Result<(), IdeviceError> = RUNTIME.block_on(async move { let res: Result<(), IdeviceError> = run_sync_local(async move {
let client_ref = unsafe { &mut (*client).0 }; let client_ref = unsafe { &mut (*client).0 };
client_ref.remove(path).await client_ref.remove(path).await
}); });
@@ -433,7 +475,7 @@ pub unsafe extern "C" fn afc_remove_path_and_contents(
Err(_) => return ffi_err!(IdeviceError::FfiInvalidArg), Err(_) => return ffi_err!(IdeviceError::FfiInvalidArg),
}; };
let res: Result<(), IdeviceError> = RUNTIME.block_on(async move { let res: Result<(), IdeviceError> = run_sync_local(async move {
let client_ref = unsafe { &mut (*client).0 }; let client_ref = unsafe { &mut (*client).0 };
client_ref.remove_all(path).await client_ref.remove_all(path).await
}); });
@@ -506,7 +548,7 @@ pub unsafe extern "C" fn afc_file_open(
let mode = mode.into(); let mode = mode.into();
let res: Result<*mut AfcFileHandle, IdeviceError> = RUNTIME.block_on(async move { let res: Result<*mut AfcFileHandle, IdeviceError> = LOCAL_RUNTIME.block_on(async move {
let client_ref = unsafe { &mut (*client).0 }; let client_ref = unsafe { &mut (*client).0 };
let result = client_ref.open(path, mode).await; let result = client_ref.open(path, mode).await;
match result { match result {
@@ -544,7 +586,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 fd = unsafe { Box::from_raw(handle as *mut idevice::afc::file::FileDescriptor) };
let res: Result<(), IdeviceError> = RUNTIME.block_on(async move { fd.close().await }); let res: Result<(), IdeviceError> = run_sync(async move { fd.close().await });
match res { match res {
Ok(_) => null_mut(), Ok(_) => null_mut(),
@@ -552,12 +594,13 @@ pub unsafe extern "C" fn afc_file_close(handle: *mut AfcFileHandle) -> *mut Idev
} }
} }
/// Reads data from an open file /// Reads data from an open file. This advances the cursor of the file.
/// ///
/// # Arguments /// # Arguments
/// * [`handle`] - File handle to read from /// * [`handle`] - File handle to read from
/// * [`data`] - Will be set to point to the read data /// * [`data`] - Will be set to point to the read data
/// * [`length`] - Will be set to the length of the read data /// * [`len`] - Number of bytes to read from the file
/// * [`bytes_read`] - The number of bytes read from the file
/// ///
/// # Returns /// # Returns
/// An IdeviceFfiError on error, null on success /// An IdeviceFfiError on error, null on success
@@ -566,6 +609,53 @@ pub unsafe extern "C" fn afc_file_close(handle: *mut AfcFileHandle) -> *mut Idev
/// All pointers must be valid and non-null /// All pointers must be valid and non-null
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
pub unsafe extern "C" fn afc_file_read( 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, handle: *mut AfcFileHandle,
data: *mut *mut u8, data: *mut *mut u8,
length: *mut libc::size_t, length: *mut libc::size_t,
@@ -575,7 +665,7 @@ pub unsafe extern "C" fn afc_file_read(
} }
let fd = unsafe { &mut *(handle as *mut idevice::afc::file::FileDescriptor) }; let fd = unsafe { &mut *(handle as *mut idevice::afc::file::FileDescriptor) };
let res: Result<Vec<u8>, IdeviceError> = RUNTIME.block_on(async move { fd.read().await }); let res: Result<Vec<u8>, IdeviceError> = run_sync(async move { fd.read_entire().await });
match res { match res {
Ok(bytes) => { Ok(bytes) => {
@@ -591,6 +681,100 @@ pub unsafe extern "C" fn afc_file_read(
} }
} }
/// 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 /// Writes data to an open file
/// ///
/// # Arguments /// # Arguments
@@ -617,7 +801,7 @@ pub unsafe extern "C" fn afc_file_write(
let fd = unsafe { &mut *(handle as *mut idevice::afc::file::FileDescriptor) }; let fd = unsafe { &mut *(handle as *mut idevice::afc::file::FileDescriptor) };
let data_slice = unsafe { std::slice::from_raw_parts(data, length) }; let data_slice = unsafe { std::slice::from_raw_parts(data, length) };
let res: Result<(), IdeviceError> = RUNTIME.block_on(async move { fd.write(data_slice).await }); let res: Result<(), IdeviceError> = run_sync(async move { fd.write_entire(data_slice).await });
match res { match res {
Ok(_) => null_mut(), Ok(_) => null_mut(),
@@ -674,7 +858,7 @@ pub unsafe extern "C" fn afc_make_link(
AfcLinkType::Symbolic => idevice::afc::opcode::LinkType::Symlink, AfcLinkType::Symbolic => idevice::afc::opcode::LinkType::Symlink,
}; };
let res: Result<(), IdeviceError> = RUNTIME.block_on(async move { let res: Result<(), IdeviceError> = run_sync_local(async move {
let client_ref = unsafe { &mut (*client).0 }; let client_ref = unsafe { &mut (*client).0 };
client_ref.link(target, source, link_type).await client_ref.link(target, source, link_type).await
}); });
@@ -720,7 +904,7 @@ pub unsafe extern "C" fn afc_rename_path(
Err(_) => return ffi_err!(IdeviceError::FfiInvalidArg), Err(_) => return ffi_err!(IdeviceError::FfiInvalidArg),
}; };
let res: Result<(), IdeviceError> = RUNTIME.block_on(async move { let res: Result<(), IdeviceError> = run_sync_local(async move {
let client_ref = unsafe { &mut (*client).0 }; let client_ref = unsafe { &mut (*client).0 };
client_ref.rename(source, target).await client_ref.rename(source, target).await
}); });
@@ -741,7 +925,7 @@ pub unsafe extern "C" fn afc_rename_path(
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
pub unsafe extern "C" fn afc_file_read_data_free(data: *mut u8, length: libc::size_t) { pub unsafe extern "C" fn afc_file_read_data_free(data: *mut u8, length: libc::size_t) {
if !data.is_null() { if !data.is_null() {
let boxed = unsafe { Box::from_raw(std::slice::from_raw_parts_mut(data, length)) }; let boxed = unsafe { Box::from_raw(std::ptr::slice_from_raw_parts_mut(data, length)) };
drop(boxed); drop(boxed);
} }
} }

View File

@@ -4,7 +4,9 @@ use std::ptr::null_mut;
use idevice::{IdeviceError, IdeviceService, amfi::AmfiClient, provider::IdeviceProvider}; use idevice::{IdeviceError, IdeviceService, amfi::AmfiClient, provider::IdeviceProvider};
use crate::{IdeviceFfiError, IdeviceHandle, RUNTIME, ffi_err, provider::IdeviceProviderHandle}; use crate::{
IdeviceFfiError, IdeviceHandle, ffi_err, provider::IdeviceProviderHandle, run_sync_local,
};
pub struct AmfiClientHandle(pub AmfiClient); pub struct AmfiClientHandle(pub AmfiClient);
@@ -26,11 +28,11 @@ pub unsafe extern "C" fn amfi_connect(
client: *mut *mut AmfiClientHandle, client: *mut *mut AmfiClientHandle,
) -> *mut IdeviceFfiError { ) -> *mut IdeviceFfiError {
if provider.is_null() || client.is_null() { if provider.is_null() || client.is_null() {
log::error!("Null pointer provided"); tracing::error!("Null pointer provided");
return ffi_err!(IdeviceError::FfiInvalidArg); return ffi_err!(IdeviceError::FfiInvalidArg);
} }
let res: Result<AmfiClient, IdeviceError> = RUNTIME.block_on(async move { let res: Result<AmfiClient, IdeviceError> = run_sync_local(async move {
let provider_ref: &dyn IdeviceProvider = unsafe { &*(*provider).0 }; let provider_ref: &dyn IdeviceProvider = unsafe { &*(*provider).0 };
// Connect using the reference // Connect using the reference
@@ -94,7 +96,7 @@ pub unsafe extern "C" fn amfi_reveal_developer_mode_option_in_ui(
return ffi_err!(IdeviceError::FfiInvalidArg); return ffi_err!(IdeviceError::FfiInvalidArg);
} }
let res: Result<(), IdeviceError> = RUNTIME.block_on(async move { let res: Result<(), IdeviceError> = run_sync_local(async move {
let client_ref = unsafe { &mut (*client).0 }; let client_ref = unsafe { &mut (*client).0 };
client_ref.reveal_developer_mode_option_in_ui().await client_ref.reveal_developer_mode_option_in_ui().await
}); });
@@ -122,7 +124,7 @@ pub unsafe extern "C" fn amfi_enable_developer_mode(
return ffi_err!(IdeviceError::FfiInvalidArg); return ffi_err!(IdeviceError::FfiInvalidArg);
} }
let res: Result<(), IdeviceError> = RUNTIME.block_on(async move { let res: Result<(), IdeviceError> = run_sync_local(async move {
let client_ref = unsafe { &mut (*client).0 }; let client_ref = unsafe { &mut (*client).0 };
client_ref.enable_developer_mode().await client_ref.enable_developer_mode().await
}); });
@@ -150,7 +152,7 @@ pub unsafe extern "C" fn amfi_accept_developer_mode(
return ffi_err!(IdeviceError::FfiInvalidArg); return ffi_err!(IdeviceError::FfiInvalidArg);
} }
let res: Result<(), IdeviceError> = RUNTIME.block_on(async move { let res: Result<(), IdeviceError> = run_sync_local(async move {
let client_ref = unsafe { &mut (*client).0 }; let client_ref = unsafe { &mut (*client).0 };
client_ref.accept_developer_mode().await client_ref.accept_developer_mode().await
}); });
@@ -171,7 +173,7 @@ pub unsafe extern "C" fn amfi_accept_developer_mode(
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
pub unsafe extern "C" fn amfi_client_free(handle: *mut AmfiClientHandle) { pub unsafe extern "C" fn amfi_client_free(handle: *mut AmfiClientHandle) {
if !handle.is_null() { if !handle.is_null() {
log::debug!("Freeing AmfiClient handle"); tracing::debug!("Freeing AmfiClient handle");
let _ = unsafe { Box::from_raw(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::core_device_proxy::AdapterHandle;
use crate::rsd::RsdHandshakeHandle; use crate::rsd::RsdHandshakeHandle;
use crate::{IdeviceFfiError, RUNTIME, ReadWriteOpaque, ffi_err}; use crate::{IdeviceFfiError, ReadWriteOpaque, ffi_err, run_sync, run_sync_local};
/// Opaque handle to an AppServiceClient /// Opaque handle to an AppServiceClient
pub struct AppServiceHandle(pub AppServiceClient<Box<dyn ReadWrite>>); 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> = let res: Result<AppServiceClient<Box<dyn ReadWrite>>, IdeviceError> =
RUNTIME.block_on(async move { run_sync_local(async move {
let provider_ref = unsafe { &mut (*provider).0 }; let provider_ref = unsafe { &mut (*provider).0 };
let handshake_ref = unsafe { &mut (*handshake).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 socket = unsafe { Box::from_raw(socket) };
let res = RUNTIME.block_on(async move { AppServiceClient::new(socket.inner.unwrap()).await }); let res = run_sync(async move { AppServiceClient::new(socket.inner.unwrap()).await });
match res { match res {
Ok(client) => { Ok(client) => {
@@ -186,7 +186,7 @@ pub unsafe extern "C" fn app_service_list_apps(
} }
let client = unsafe { &mut (*handle).0 }; let client = unsafe { &mut (*handle).0 };
let res = RUNTIME.block_on(async move { let res = run_sync(async move {
client client
.list_apps( .list_apps(
app_clips != 0, app_clips != 0,
@@ -347,7 +347,7 @@ pub unsafe extern "C" fn app_service_launch_app(
}; };
let client = unsafe { &mut (*handle).0 }; let client = unsafe { &mut (*handle).0 };
let res = RUNTIME.block_on(async move { let res = run_sync(async move {
client client
.launch_application( .launch_application(
bundle_id_str, bundle_id_str,
@@ -430,7 +430,7 @@ pub unsafe extern "C" fn app_service_list_processes(
} }
let client = unsafe { &mut (*handle).0 }; let client = unsafe { &mut (*handle).0 };
let res = RUNTIME.block_on(async move { client.list_processes().await }); let res = run_sync(async move { client.list_processes().await });
match res { match res {
Ok(process_list) => { Ok(process_list) => {
@@ -477,7 +477,7 @@ pub unsafe extern "C" fn app_service_free_process_list(
count: usize, count: usize,
) { ) {
if !processes.is_null() && count > 0 { if !processes.is_null() && count > 0 {
let processes_slice = unsafe { std::slice::from_raw_parts_mut(processes, count) }; let processes_slice = unsafe { Vec::from_raw_parts(processes, count, count) };
for process in processes_slice { for process in processes_slice {
if !process.executable_url.is_null() { if !process.executable_url.is_null() {
let _ = unsafe { CString::from_raw(process.executable_url) }; 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 client = unsafe { &mut (*handle).0 };
let res = RUNTIME.block_on(async move { client.uninstall_app(bundle_id_str).await }); let res = run_sync(async move { client.uninstall_app(bundle_id_str).await });
match res { match res {
Ok(_) => null_mut(), Ok(_) => null_mut(),
@@ -546,7 +546,7 @@ pub unsafe extern "C" fn app_service_send_signal(
} }
let client = unsafe { &mut (*handle).0 }; let client = unsafe { &mut (*handle).0 };
let res = RUNTIME.block_on(async move { client.send_signal(pid, signal).await }); let res = run_sync(async move { client.send_signal(pid, signal).await });
match res { match res {
Ok(signal_response) => { Ok(signal_response) => {
@@ -628,7 +628,7 @@ pub unsafe extern "C" fn app_service_fetch_app_icon(
}; };
let client = unsafe { &mut (*handle).0 }; let client = unsafe { &mut (*handle).0 };
let res = RUNTIME.block_on(async move { let res = run_sync(async move {
client client
.fetch_app_icon(bundle_id_str, width, height, scale, allow_placeholder != 0) .fetch_app_icon(bundle_id_str, width, height, scale, allow_placeholder != 0)
.await .await

View File

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

View File

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

View File

@@ -0,0 +1,325 @@
// Jackson Coxson
use std::{
ffi::{CStr, c_char},
ptr::null_mut,
};
use idevice::{
IdeviceError, IdeviceService,
provider::IdeviceProvider,
services::crashreportcopymobile::{CrashReportCopyMobileClient, flush_reports},
};
use crate::{
IdeviceFfiError, IdeviceHandle, afc::AfcClientHandle, ffi_err, provider::IdeviceProviderHandle,
run_sync_local,
};
pub struct CrashReportCopyMobileHandle(pub CrashReportCopyMobileClient);
/// Automatically creates and connects to the crash report copy mobile service,
/// returning a client handle
///
/// # Arguments
/// * [`provider`] - An IdeviceProvider
/// * [`client`] - On success, will be set to point to a newly allocated 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 crash_report_client_connect(
provider: *mut IdeviceProviderHandle,
client: *mut *mut CrashReportCopyMobileHandle,
) -> *mut IdeviceFfiError {
if provider.is_null() || client.is_null() {
tracing::error!("Null pointer provided");
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let res: Result<CrashReportCopyMobileClient, IdeviceError> = run_sync_local(async move {
let provider_ref: &dyn IdeviceProvider = unsafe { &*(*provider).0 };
CrashReportCopyMobileClient::connect(provider_ref).await
});
match res {
Ok(r) => {
let boxed = Box::new(CrashReportCopyMobileHandle(r));
unsafe { *client = Box::into_raw(boxed) };
null_mut()
}
Err(e) => ffi_err!(e),
}
}
/// Creates a new CrashReportCopyMobile client from an existing Idevice connection
///
/// # Arguments
/// * [`socket`] - An IdeviceSocket handle
/// * [`client`] - On success, will be set to point to a newly allocated 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 crash_report_client_new(
socket: *mut IdeviceHandle,
client: *mut *mut CrashReportCopyMobileHandle,
) -> *mut IdeviceFfiError {
if socket.is_null() || client.is_null() {
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let socket = unsafe { Box::from_raw(socket) }.0;
let r = CrashReportCopyMobileClient::new(socket);
let boxed = Box::new(CrashReportCopyMobileHandle(r));
unsafe { *client = Box::into_raw(boxed) };
null_mut()
}
/// Lists crash report files in the specified directory
///
/// # Arguments
/// * [`client`] - A valid CrashReportCopyMobile handle
/// * [`dir_path`] - Optional directory path (NULL for root "/")
/// * [`entries`] - Will be set to point to an array of C strings
/// * [`count`] - Will be set to the number of entries
///
/// # Returns
/// An IdeviceFfiError on error, null on success
///
/// # Safety
/// All pointers must be valid and non-null
/// `dir_path` may be NULL (defaults to root)
/// Caller must free the returned array with `afc_free_directory_entries`
#[unsafe(no_mangle)]
pub unsafe extern "C" fn crash_report_client_ls(
client: *mut CrashReportCopyMobileHandle,
dir_path: *const c_char,
entries: *mut *mut *mut c_char,
count: *mut libc::size_t,
) -> *mut IdeviceFfiError {
if client.is_null() || entries.is_null() || count.is_null() {
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let path = if dir_path.is_null() {
None
} else {
match unsafe { CStr::from_ptr(dir_path) }.to_str() {
Ok(s) => Some(s),
Err(_) => return ffi_err!(IdeviceError::FfiInvalidString),
}
};
let res: Result<Vec<String>, IdeviceError> = run_sync_local(async {
let client_ref = unsafe { &mut (*client).0 };
client_ref.ls(path).await
});
match res {
Ok(items) => {
let c_strings = items
.into_iter()
.filter_map(|s| std::ffi::CString::new(s).ok())
.collect::<Vec<_>>();
let string_count = c_strings.len();
// Allocate array for char pointers (with NULL terminator)
let layout = std::alloc::Layout::array::<*mut c_char>(string_count + 1).unwrap();
let ptr = unsafe { std::alloc::alloc(layout) as *mut *mut c_char };
if ptr.is_null() {
return ffi_err!(IdeviceError::FfiInvalidArg);
}
for (i, cstring) in c_strings.into_iter().enumerate() {
let string_ptr = cstring.into_raw();
unsafe { *ptr.add(i) = string_ptr };
}
// NULL terminator
unsafe { *ptr.add(string_count) = std::ptr::null_mut() };
unsafe {
*entries = ptr;
*count = string_count;
}
null_mut()
}
Err(e) => ffi_err!(e),
}
}
/// Downloads a crash report file from the device
///
/// # Arguments
/// * [`client`] - A valid CrashReportCopyMobile handle
/// * [`log_name`] - Name of the log file to download (C string)
/// * [`data`] - Will be set to point to the file contents
/// * [`length`] - Will be set to the size of the data
///
/// # Returns
/// An IdeviceFfiError on error, null on success
///
/// # Safety
/// All pointers must be valid and non-null
/// `log_name` must be a valid C string
/// Caller must free the returned data with `idevice_data_free`
#[unsafe(no_mangle)]
pub unsafe extern "C" fn crash_report_client_pull(
client: *mut CrashReportCopyMobileHandle,
log_name: *const c_char,
data: *mut *mut u8,
length: *mut libc::size_t,
) -> *mut IdeviceFfiError {
if client.is_null() || log_name.is_null() || data.is_null() || length.is_null() {
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let name = match unsafe { CStr::from_ptr(log_name) }.to_str() {
Ok(s) => s.to_string(),
Err(_) => return ffi_err!(IdeviceError::FfiInvalidString),
};
let res: Result<Vec<u8>, IdeviceError> = run_sync_local(async {
let client_ref = unsafe { &mut (*client).0 };
client_ref.pull(name).await
});
match res {
Ok(file_data) => {
let len = file_data.len();
let mut boxed = file_data.into_boxed_slice();
unsafe {
*data = boxed.as_mut_ptr();
*length = len;
}
std::mem::forget(boxed);
null_mut()
}
Err(e) => ffi_err!(e),
}
}
/// Removes a crash report file from the device
///
/// # Arguments
/// * [`client`] - A valid CrashReportCopyMobile handle
/// * [`log_name`] - Name of the log file to remove (C string)
///
/// # Returns
/// An IdeviceFfiError on error, null on success
///
/// # Safety
/// `client` must be a valid pointer to a handle allocated by this library
/// `log_name` must be a valid C string
#[unsafe(no_mangle)]
pub unsafe extern "C" fn crash_report_client_remove(
client: *mut CrashReportCopyMobileHandle,
log_name: *const c_char,
) -> *mut IdeviceFfiError {
if client.is_null() || log_name.is_null() {
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let name = match unsafe { CStr::from_ptr(log_name) }.to_str() {
Ok(s) => s.to_string(),
Err(_) => return ffi_err!(IdeviceError::FfiInvalidString),
};
let res = run_sync_local(async {
let client_ref = unsafe { &mut (*client).0 };
client_ref.remove(name).await
});
match res {
Ok(_) => null_mut(),
Err(e) => ffi_err!(e),
}
}
/// Converts this client to an AFC client for advanced file operations
///
/// # Arguments
/// * [`client`] - A valid CrashReportCopyMobile handle (will be consumed)
/// * [`afc_client`] - On success, will be set to an AFC client handle
///
/// # Returns
/// An IdeviceFfiError on error, null on success
///
/// # Safety
/// `client` must be a valid pointer (will be freed after this call)
/// `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 crash_report_client_to_afc(
client: *mut CrashReportCopyMobileHandle,
afc_client: *mut *mut AfcClientHandle,
) -> *mut IdeviceFfiError {
if client.is_null() || afc_client.is_null() {
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let crash_client = unsafe { Box::from_raw(client) }.0;
let afc = crash_client.to_afc_client();
let a = Box::into_raw(Box::new(AfcClientHandle(afc)));
unsafe { *afc_client = a };
null_mut()
}
/// Triggers a flush of crash logs from system storage
///
/// This connects to the crashreportmover service to move crash logs
/// into the AFC-accessible directory. Should be called before listing logs.
///
/// # Arguments
/// * [`provider`] - An IdeviceProvider
///
/// # Returns
/// An IdeviceFfiError on error, null on success
///
/// # Safety
/// `provider` must be a valid pointer to a handle allocated by this library
#[unsafe(no_mangle)]
pub unsafe extern "C" fn crash_report_flush(
provider: *mut IdeviceProviderHandle,
) -> *mut IdeviceFfiError {
if provider.is_null() {
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let res = run_sync_local(async {
let provider_ref: &dyn IdeviceProvider = unsafe { &*(*provider).0 };
flush_reports(provider_ref).await
});
match res {
Ok(_) => null_mut(),
Err(e) => ffi_err!(e),
}
}
/// Frees a CrashReportCopyMobile client 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 crash_report_client_free(handle: *mut CrashReportCopyMobileHandle) {
if !handle.is_null() {
tracing::debug!("Freeing crash_report_client");
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::core_device_proxy::AdapterHandle;
use crate::rsd::RsdHandshakeHandle; use crate::rsd::RsdHandshakeHandle;
use crate::{IdeviceFfiError, RUNTIME, ReadWriteOpaque, ffi_err}; use crate::{IdeviceFfiError, ReadWriteOpaque, ffi_err, run_sync, run_sync_local};
/// Opaque handle to a DebugProxyClient /// Opaque handle to a DebugProxyClient
pub struct DebugProxyHandle(pub DebugProxyClient<Box<dyn ReadWrite>>); 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); return ffi_err!(IdeviceError::FfiInvalidArg);
} }
let res: Result<DebugProxyClient<Box<dyn ReadWrite>>, IdeviceError> = let res: Result<DebugProxyClient<Box<dyn ReadWrite>>, IdeviceError> =
RUNTIME.block_on(async move { run_sync_local(async move {
let provider_ref = unsafe { &mut (*provider).0 }; let provider_ref = unsafe { &mut (*provider).0 };
let handshake_ref = unsafe { &mut (*handshake).0 }; let handshake_ref = unsafe { &mut (*handshake).0 };
@@ -241,7 +241,7 @@ pub unsafe extern "C" fn debug_proxy_send_command(
}, },
}; };
let res = RUNTIME.block_on(async move { client.send_command(cmd).await }); let res = run_sync(async move { client.send_command(cmd).await });
match res { match res {
Ok(Some(r)) => { Ok(Some(r)) => {
@@ -282,7 +282,7 @@ pub unsafe extern "C" fn debug_proxy_read_response(
} }
let client = unsafe { &mut (*handle).0 }; let client = unsafe { &mut (*handle).0 };
let res = RUNTIME.block_on(async move { client.read_response().await }); let res = run_sync(async move { client.read_response().await });
match res { match res {
Ok(Some(r)) => { Ok(Some(r)) => {
@@ -326,7 +326,7 @@ pub unsafe extern "C" fn debug_proxy_send_raw(
let client = unsafe { &mut (*handle).0 }; let client = unsafe { &mut (*handle).0 };
let data_slice = unsafe { std::slice::from_raw_parts(data, len) }; let data_slice = unsafe { std::slice::from_raw_parts(data, len) };
let res = RUNTIME.block_on(async move { client.send_raw(data_slice).await }); let res = run_sync(async move { client.send_raw(data_slice).await });
match res { match res {
Ok(_) => null_mut(), Ok(_) => null_mut(),
@@ -358,7 +358,7 @@ pub unsafe extern "C" fn debug_proxy_read(
} }
let client = unsafe { &mut (*handle).0 }; let client = unsafe { &mut (*handle).0 };
let res = RUNTIME.block_on(async move { client.read(len).await }); let res = run_sync(async move { client.read(len).await });
match res { match res {
Ok(r) => { Ok(r) => {
@@ -416,7 +416,7 @@ pub unsafe extern "C" fn debug_proxy_set_argv(
.collect() .collect()
}; };
let res = RUNTIME.block_on(async move { client.set_argv(argv_vec).await }); let res = run_sync(async move { client.set_argv(argv_vec).await });
match res { match res {
Ok(r) => { Ok(r) => {
@@ -450,7 +450,7 @@ pub unsafe extern "C" fn debug_proxy_send_ack(
} }
let client = unsafe { &mut (*handle).0 }; let client = unsafe { &mut (*handle).0 };
let res = RUNTIME.block_on(async move { client.send_ack().await }); let res = run_sync(async move { client.send_ack().await });
match res { match res {
Ok(_) => null_mut(), Ok(_) => null_mut(),
@@ -477,7 +477,7 @@ pub unsafe extern "C" fn debug_proxy_send_nack(
} }
let client = unsafe { &mut (*handle).0 }; let client = unsafe { &mut (*handle).0 };
let res = RUNTIME.block_on(async move { client.send_noack().await }); let res = run_sync(async move { client.send_noack().await });
match res { match res {
Ok(_) => null_mut(), Ok(_) => null_mut(),

View File

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

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

8
ffi/src/dvt/mod.rs Normal file
View File

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

View File

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

View File

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

113
ffi/src/dvt/screenshot.rs Normal file
View File

@@ -0,0 +1,113 @@
// 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 err: IdeviceError = $err.into();
let code = err.code(); let code = err.code();
let msg = CString::new(err.to_string()) let msg = CString::new(format!("{:?}", err))
.unwrap_or_else(|_| CString::new("invalid error").unwrap()); .unwrap_or_else(|_| CString::new("invalid error").unwrap());
let raw_msg = msg.into_raw(); let raw_msg = msg.into_raw();

View File

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

183
ffi/src/house_arrest.rs Normal file
View File

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

View File

@@ -10,31 +10,37 @@ pub mod amfi;
pub mod core_device; pub mod core_device;
#[cfg(feature = "core_device_proxy")] #[cfg(feature = "core_device_proxy")]
pub mod core_device_proxy; pub mod core_device_proxy;
#[cfg(feature = "crashreportcopymobile")]
pub mod crashreportcopymobile;
#[cfg(feature = "debug_proxy")] #[cfg(feature = "debug_proxy")]
pub mod debug_proxy; pub mod debug_proxy;
#[cfg(feature = "diagnostics_relay")]
pub mod diagnostics_relay;
#[cfg(feature = "dvt")]
pub mod dvt;
mod errors; mod errors;
#[cfg(feature = "heartbeat")] #[cfg(feature = "heartbeat")]
pub mod heartbeat; pub mod heartbeat;
#[cfg(feature = "house_arrest")]
pub mod house_arrest;
#[cfg(feature = "installation_proxy")] #[cfg(feature = "installation_proxy")]
pub mod installation_proxy; pub mod installation_proxy;
#[cfg(feature = "location_simulation")]
pub mod location_simulation;
pub mod lockdown; pub mod lockdown;
pub mod logging; pub mod logging;
#[cfg(feature = "misagent")] #[cfg(feature = "misagent")]
pub mod misagent; pub mod misagent;
#[cfg(feature = "mobile_image_mounter")] #[cfg(feature = "mobile_image_mounter")]
pub mod mobile_image_mounter; pub mod mobile_image_mounter;
#[cfg(feature = "notification_proxy")]
pub mod notification_proxy;
#[cfg(feature = "syslog_relay")] #[cfg(feature = "syslog_relay")]
pub mod os_trace_relay; pub mod os_trace_relay;
mod pairing_file; mod pairing_file;
#[cfg(feature = "dvt")]
pub mod process_control;
pub mod provider; pub mod provider;
#[cfg(feature = "dvt")]
pub mod remote_server;
#[cfg(feature = "xpc")] #[cfg(feature = "xpc")]
pub mod rsd; pub mod rsd;
#[cfg(feature = "screenshotr")]
pub mod screenshotr;
#[cfg(feature = "springboardservices")] #[cfg(feature = "springboardservices")]
pub mod springboardservices; pub mod springboardservices;
#[cfg(feature = "syslog_relay")] #[cfg(feature = "syslog_relay")]
@@ -50,8 +56,9 @@ pub use pairing_file::*;
use idevice::{Idevice, IdeviceSocket, ReadWrite}; use idevice::{Idevice, IdeviceSocket, ReadWrite};
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use plist_ffi::PlistWrapper;
use std::{ use std::{
ffi::{CStr, CString, c_char}, ffi::{CStr, CString, c_char, c_void},
ptr::null_mut, ptr::null_mut,
}; };
use tokio::runtime::{self, Runtime}; use tokio::runtime::{self, Runtime};
@@ -59,7 +66,7 @@ use tokio::runtime::{self, Runtime};
#[cfg(unix)] #[cfg(unix)]
use crate::util::{idevice_sockaddr, idevice_socklen_t}; use crate::util::{idevice_sockaddr, idevice_socklen_t};
static RUNTIME: Lazy<Runtime> = Lazy::new(|| { static GLOBAL_RUNTIME: Lazy<Runtime> = Lazy::new(|| {
runtime::Builder::new_multi_thread() runtime::Builder::new_multi_thread()
.enable_io() .enable_io()
.enable_time() .enable_time()
@@ -67,6 +74,39 @@ static RUNTIME: Lazy<Runtime> = Lazy::new(|| {
.unwrap() .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; pub const LOCKDOWN_PORT: u16 = 62078;
#[repr(C)] #[repr(C)]
@@ -126,6 +166,47 @@ pub unsafe extern "C" fn idevice_new(
null_mut() 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 /// Creates a new Idevice connection
/// ///
/// # Arguments /// # Arguments
@@ -152,7 +233,7 @@ pub unsafe extern "C" fn idevice_new_tcp_socket(
use crate::util::SockAddr; use crate::util::SockAddr;
if addr.is_null() || label.is_null() || idevice.is_null() { if addr.is_null() || label.is_null() || idevice.is_null() {
log::error!("null pointer(s) to idevice_new_tcp_socket"); tracing::error!("null pointer(s) to idevice_new_tcp_socket");
return ffi_err!(IdeviceError::FfiInvalidArg); return ffi_err!(IdeviceError::FfiInvalidArg);
} }
let addr = addr as *const SockAddr; let addr = addr as *const SockAddr;
@@ -167,7 +248,7 @@ pub unsafe extern "C" fn idevice_new_tcp_socket(
Err(e) => return ffi_err!(e), Err(e) => return ffi_err!(e),
}; };
let device = RUNTIME.block_on(async move { let device = run_sync(async move {
let stream = tokio::net::TcpStream::connect(addr).await?; let stream = tokio::net::TcpStream::connect(addr).await?;
Ok::<idevice::Idevice, idevice::IdeviceError>(idevice::Idevice::new( Ok::<idevice::Idevice, idevice::IdeviceError>(idevice::Idevice::new(
Box::new(stream), Box::new(stream),
@@ -210,7 +291,7 @@ pub unsafe extern "C" fn idevice_get_type(
let dev = unsafe { &mut (*idevice).0 }; let dev = unsafe { &mut (*idevice).0 };
// Run the get_type method in the runtime // Run the get_type method in the runtime
let result = RUNTIME.block_on(async { dev.get_type().await }); let result = run_sync(async { dev.get_type().await });
match result { match result {
Ok(type_str) => match CString::new(type_str) { Ok(type_str) => match CString::new(type_str) {
@@ -244,7 +325,7 @@ pub unsafe extern "C" fn idevice_rsd_checkin(idevice: *mut IdeviceHandle) -> *mu
let dev = unsafe { &mut (*idevice).0 }; let dev = unsafe { &mut (*idevice).0 };
// Run the rsd_checkin method in the runtime // Run the rsd_checkin method in the runtime
let result = RUNTIME.block_on(async { dev.rsd_checkin().await }); let result = run_sync(async { dev.rsd_checkin().await });
match result { match result {
Ok(_) => null_mut(), Ok(_) => null_mut(),
@@ -268,6 +349,7 @@ pub unsafe extern "C" fn idevice_rsd_checkin(idevice: *mut IdeviceHandle) -> *mu
pub unsafe extern "C" fn idevice_start_session( pub unsafe extern "C" fn idevice_start_session(
idevice: *mut IdeviceHandle, idevice: *mut IdeviceHandle,
pairing_file: *const IdevicePairingFile, pairing_file: *const IdevicePairingFile,
legacy: bool,
) -> *mut IdeviceFfiError { ) -> *mut IdeviceFfiError {
if idevice.is_null() || pairing_file.is_null() { if idevice.is_null() || pairing_file.is_null() {
return ffi_err!(IdeviceError::FfiInvalidArg); return ffi_err!(IdeviceError::FfiInvalidArg);
@@ -280,7 +362,7 @@ pub unsafe extern "C" fn idevice_start_session(
let pf = unsafe { &(*pairing_file).0 }; let pf = unsafe { &(*pairing_file).0 };
// Run the start_session method in the runtime // Run the start_session method in the runtime
let result = RUNTIME.block_on(async { dev.start_session(pf).await }); let result = run_sync(async move { dev.start_session(pf, legacy).await });
match result { match result {
Ok(_) => null_mut(), Ok(_) => null_mut(),
@@ -303,6 +385,17 @@ 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 /// Frees a string allocated by this library
/// ///
/// # Arguments /// # Arguments
@@ -329,6 +422,36 @@ pub unsafe extern "C" fn idevice_string_free(string: *mut c_char) {
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
pub unsafe extern "C" fn idevice_data_free(data: *mut u8, len: usize) { pub unsafe extern "C" fn idevice_data_free(data: *mut u8, len: usize) {
if !data.is_null() { if !data.is_null() {
let _ = unsafe { std::slice::from_raw_parts(data, len) }; 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) };
} }
} }

View File

@@ -6,8 +6,8 @@ use idevice::{IdeviceError, IdeviceService, lockdown::LockdownClient, provider::
use plist_ffi::plist_t; use plist_ffi::plist_t;
use crate::{ use crate::{
IdeviceFfiError, IdeviceHandle, IdevicePairingFile, RUNTIME, ffi_err, IdeviceFfiError, IdeviceHandle, IdevicePairingFile, ffi_err, provider::IdeviceProviderHandle,
provider::IdeviceProviderHandle, run_sync_local,
}; };
pub struct LockdowndClientHandle(pub LockdownClient); pub struct LockdowndClientHandle(pub LockdownClient);
@@ -30,11 +30,11 @@ pub unsafe extern "C" fn lockdownd_connect(
client: *mut *mut LockdowndClientHandle, client: *mut *mut LockdowndClientHandle,
) -> *mut IdeviceFfiError { ) -> *mut IdeviceFfiError {
if provider.is_null() || client.is_null() { if provider.is_null() || client.is_null() {
log::error!("Null pointer provided"); tracing::error!("Null pointer provided");
return ffi_err!(IdeviceError::FfiInvalidArg); return ffi_err!(IdeviceError::FfiInvalidArg);
} }
let res: Result<LockdownClient, IdeviceError> = RUNTIME.block_on(async move { let res: Result<LockdownClient, IdeviceError> = run_sync_local(async move {
let provider_ref: &dyn IdeviceProvider = unsafe { &*(*provider).0 }; let provider_ref: &dyn IdeviceProvider = unsafe { &*(*provider).0 };
LockdownClient::connect(provider_ref).await LockdownClient::connect(provider_ref).await
}); });
@@ -97,7 +97,7 @@ pub unsafe extern "C" fn lockdownd_start_session(
client: *mut LockdowndClientHandle, client: *mut LockdowndClientHandle,
pairing_file: *mut IdevicePairingFile, pairing_file: *mut IdevicePairingFile,
) -> *mut IdeviceFfiError { ) -> *mut IdeviceFfiError {
let res: Result<(), IdeviceError> = RUNTIME.block_on(async move { let res: Result<(), IdeviceError> = run_sync_local(async move {
let client_ref = unsafe { &mut (*client).0 }; let client_ref = unsafe { &mut (*client).0 };
let pairing_file_ref = unsafe { &(*pairing_file).0 }; let pairing_file_ref = unsafe { &(*pairing_file).0 };
@@ -140,7 +140,7 @@ pub unsafe extern "C" fn lockdownd_start_service(
.to_string_lossy() .to_string_lossy()
.into_owned(); .into_owned();
let res: Result<(u16, bool), IdeviceError> = RUNTIME.block_on(async move { let res: Result<(u16, bool), IdeviceError> = run_sync_local(async move {
let client_ref = unsafe { &mut (*client).0 }; let client_ref = unsafe { &mut (*client).0 };
client_ref.start_service(identifier).await client_ref.start_service(identifier).await
}); });
@@ -157,6 +157,74 @@ pub unsafe extern "C" fn lockdownd_start_service(
} }
} }
/// Pairs with the device using lockdownd
///
/// # Arguments
/// * `client` - A valid LockdowndClient handle
/// * `host_id` - The host ID (null-terminated string)
/// * `system_buid` - The system BUID (null-terminated string)
/// * `pairing_file` - On success, will be set to point to a newly allocated IdevicePairingFile handle
///
/// # Returns
/// An IdeviceFfiError on error, null on success
///
/// # Safety
/// `client` must be a valid pointer to a handle allocated by this library
/// `host_id` must be a valid null-terminated string
/// `system_buid` must be a valid null-terminated string
/// `pairing_file` must be a valid, non-null pointer to a location where the handle will be stored
#[unsafe(no_mangle)]
pub unsafe extern "C" fn lockdownd_pair(
client: *mut LockdowndClientHandle,
host_id: *const libc::c_char,
system_buid: *const libc::c_char,
host_name: *const libc::c_char,
pairing_file: *mut *mut IdevicePairingFile,
) -> *mut IdeviceFfiError {
if client.is_null() || host_id.is_null() || system_buid.is_null() {
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let host_id = unsafe {
std::ffi::CStr::from_ptr(host_id)
.to_string_lossy()
.into_owned()
};
let system_buid = unsafe {
std::ffi::CStr::from_ptr(system_buid)
.to_string_lossy()
.into_owned()
};
let host_name = if host_name.is_null() {
None
} else {
Some(
match unsafe { std::ffi::CStr::from_ptr(host_name) }.to_str() {
Ok(v) => v,
Err(_) => {
return ffi_err!(IdeviceError::InvalidCString);
}
},
)
};
let res = run_sync_local(async move {
let client_ref = unsafe { &mut (*client).0 };
client_ref.pair(host_id, system_buid, host_name).await
});
match res {
Ok(pairing_file_res) => {
let boxed_pairing_file = Box::new(IdevicePairingFile(pairing_file_res));
unsafe { *pairing_file = Box::into_raw(boxed_pairing_file) };
null_mut()
}
Err(e) => ffi_err!(e),
}
}
/// Gets a value from lockdownd /// Gets a value from lockdownd
/// ///
/// # Arguments /// # Arguments
@@ -179,7 +247,7 @@ pub unsafe extern "C" fn lockdownd_get_value(
domain: *const libc::c_char, domain: *const libc::c_char,
out_plist: *mut plist_t, out_plist: *mut plist_t,
) -> *mut IdeviceFfiError { ) -> *mut IdeviceFfiError {
if out_plist.is_null() { if client.is_null() || out_plist.is_null() {
return ffi_err!(IdeviceError::FfiInvalidArg); return ffi_err!(IdeviceError::FfiInvalidArg);
} }
@@ -205,7 +273,7 @@ pub unsafe extern "C" fn lockdownd_get_value(
}) })
}; };
let res: Result<plist::Value, IdeviceError> = RUNTIME.block_on(async move { let res: Result<plist::Value, IdeviceError> = run_sync_local(async move {
let client_ref = unsafe { &mut (*client).0 }; let client_ref = unsafe { &mut (*client).0 };
client_ref.get_value(value, domain).await client_ref.get_value(value, domain).await
}); });
@@ -221,6 +289,89 @@ 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 /// Frees a LockdowndClient handle
/// ///
/// # Arguments /// # Arguments
@@ -232,7 +383,7 @@ pub unsafe extern "C" fn lockdownd_get_value(
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
pub unsafe extern "C" fn lockdownd_client_free(handle: *mut LockdowndClientHandle) { pub unsafe extern "C" fn lockdownd_client_free(handle: *mut LockdowndClientHandle) {
if !handle.is_null() { if !handle.is_null() {
log::debug!("Freeing lockdownd_client"); tracing::debug!("Freeing lockdownd_client");
let _ = unsafe { Box::from_raw(handle) }; let _ = unsafe { Box::from_raw(handle) };
} }
} }

View File

@@ -3,75 +3,13 @@
use std::{ use std::{
ffi::{CStr, c_char}, ffi::{CStr, c_char},
fs::File, fs::File,
sync::Once,
}; };
use log::LevelFilter; use tracing::Level;
use simplelog::{ use tracing_appender::non_blocking;
ColorChoice, CombinedLogger, Config, SharedLogger, TermLogger, TerminalMode, WriteLogger, use tracing_subscriber::{EnvFilter, Layer};
}; use tracing_subscriber::{Registry, fmt, layer::SubscriberExt, util::SubscriberInitExt};
/// 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)] #[repr(C)]
pub enum IdeviceLoggerError { pub enum IdeviceLoggerError {
@@ -92,33 +30,113 @@ pub enum IdeviceLogLevel {
Trace = 5, Trace = 5,
} }
impl TryFrom<u8> for IdeviceLogLevel { impl From<IdeviceLogLevel> for Level {
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 { fn from(value: IdeviceLogLevel) -> Self {
match value { match value {
IdeviceLogLevel::Disabled => LevelFilter::Off, IdeviceLogLevel::Disabled => Level::ERROR, // won't matter, filter will disable
IdeviceLogLevel::ErrorLevel => LevelFilter::Error, IdeviceLogLevel::ErrorLevel => Level::ERROR,
IdeviceLogLevel::Warn => LevelFilter::Warn, IdeviceLogLevel::Warn => Level::WARN,
IdeviceLogLevel::Info => LevelFilter::Info, IdeviceLogLevel::Info => Level::INFO,
IdeviceLogLevel::Debug => LevelFilter::Debug, IdeviceLogLevel::Debug => Level::DEBUG,
IdeviceLogLevel::Trace => LevelFilter::Trace, IdeviceLogLevel::Trace => Level::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 idevice::{IdeviceError, IdeviceService, misagent::MisagentClient, provider::IdeviceProvider};
use crate::{IdeviceFfiError, RUNTIME, ffi_err, provider::IdeviceProviderHandle}; use crate::{IdeviceFfiError, ffi_err, provider::IdeviceProviderHandle, run_sync_local};
pub struct MisagentClientHandle(pub MisagentClient); pub struct MisagentClientHandle(pub MisagentClient);
@@ -28,11 +28,11 @@ pub unsafe extern "C" fn misagent_connect(
client: *mut *mut MisagentClientHandle, client: *mut *mut MisagentClientHandle,
) -> *mut IdeviceFfiError { ) -> *mut IdeviceFfiError {
if provider.is_null() || client.is_null() { if provider.is_null() || client.is_null() {
log::error!("Null pointer provided"); tracing::error!("Null pointer provided");
return ffi_err!(IdeviceError::FfiInvalidArg); return ffi_err!(IdeviceError::FfiInvalidArg);
} }
let res: Result<MisagentClient, IdeviceError> = RUNTIME.block_on(async move { let res: Result<MisagentClient, IdeviceError> = run_sync_local(async move {
let provider_ref: &dyn IdeviceProvider = unsafe { &*(*provider).0 }; let provider_ref: &dyn IdeviceProvider = unsafe { &*(*provider).0 };
MisagentClient::connect(provider_ref).await 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 profile = unsafe { std::slice::from_raw_parts(profile_data, profile_len) }.to_vec();
let res = RUNTIME.block_on(async { unsafe { &mut *client }.0.install(profile).await }); let res = run_sync_local(async { unsafe { &mut *client }.0.install(profile).await });
match res { match res {
Ok(_) => null_mut(), Ok(_) => null_mut(),
@@ -105,7 +105,7 @@ pub unsafe extern "C" fn misagent_remove(
.to_string_lossy() .to_string_lossy()
.into_owned(); .into_owned();
let res = RUNTIME.block_on(async { unsafe { &mut *client }.0.remove(&id).await }); let res = run_sync_local(async { unsafe { &mut *client }.0.remove(&id).await });
match res { match res {
Ok(_) => null_mut(), Ok(_) => null_mut(),
@@ -143,7 +143,7 @@ pub unsafe extern "C" fn misagent_copy_all(
} }
let res: Result<Vec<Vec<u8>>, IdeviceError> = let res: Result<Vec<Vec<u8>>, IdeviceError> =
RUNTIME.block_on(async { unsafe { &mut *client }.0.copy_all().await }); run_sync_local(async { unsafe { &mut *client }.0.copy_all().await });
match res { match res {
Ok(profiles) => { Ok(profiles) => {
@@ -199,7 +199,7 @@ pub unsafe extern "C" fn misagent_free_profiles(
for (ptr, len) in profiles.iter_mut().zip(lens.iter()) { for (ptr, len) in profiles.iter_mut().zip(lens.iter()) {
if !ptr.is_null() { if !ptr.is_null() {
let _ = unsafe { Box::from_raw(std::slice::from_raw_parts_mut(*ptr, *len)) }; let _ = unsafe { Box::from_raw(std::ptr::slice_from_raw_parts_mut(*ptr, *len)) };
} }
} }
@@ -218,7 +218,7 @@ pub unsafe extern "C" fn misagent_free_profiles(
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
pub unsafe extern "C" fn misagent_client_free(handle: *mut MisagentClientHandle) { pub unsafe extern "C" fn misagent_client_free(handle: *mut MisagentClientHandle) {
if !handle.is_null() { if !handle.is_null() {
log::debug!("Freeing misagent_client"); tracing::debug!("Freeing misagent_client");
let _ = unsafe { Box::from_raw(handle) }; let _ = unsafe { Box::from_raw(handle) };
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

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

132
ffi/src/screenshotr.rs Normal file
View File

@@ -0,0 +1,132 @@
// 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,8 +7,12 @@ use idevice::{
IdeviceError, IdeviceService, provider::IdeviceProvider, IdeviceError, IdeviceService, provider::IdeviceProvider,
springboardservices::SpringBoardServicesClient, springboardservices::SpringBoardServicesClient,
}; };
use plist_ffi::plist_t;
use crate::{IdeviceFfiError, IdeviceHandle, RUNTIME, ffi_err, provider::IdeviceProviderHandle}; use crate::{
IdeviceFfiError, IdeviceHandle, ffi_err, provider::IdeviceProviderHandle, run_sync,
run_sync_local,
};
pub struct SpringBoardServicesClientHandle(pub SpringBoardServicesClient); pub struct SpringBoardServicesClientHandle(pub SpringBoardServicesClient);
@@ -30,11 +34,11 @@ pub unsafe extern "C" fn springboard_services_connect(
client: *mut *mut SpringBoardServicesClientHandle, client: *mut *mut SpringBoardServicesClientHandle,
) -> *mut IdeviceFfiError { ) -> *mut IdeviceFfiError {
if provider.is_null() || client.is_null() { if provider.is_null() || client.is_null() {
log::error!("Null pointer provided"); tracing::error!("Null pointer provided");
return ffi_err!(IdeviceError::FfiInvalidArg); return ffi_err!(IdeviceError::FfiInvalidArg);
} }
let res: Result<SpringBoardServicesClient, IdeviceError> = RUNTIME.block_on(async move { let res: Result<SpringBoardServicesClient, IdeviceError> = run_sync_local(async move {
let provider_ref: &dyn IdeviceProvider = unsafe { &*(*provider).0 }; let provider_ref: &dyn IdeviceProvider = unsafe { &*(*provider).0 };
SpringBoardServicesClient::connect(provider_ref).await SpringBoardServicesClient::connect(provider_ref).await
}); });
@@ -103,7 +107,7 @@ pub unsafe extern "C" fn springboard_services_get_icon(
out_result_len: *mut libc::size_t, out_result_len: *mut libc::size_t,
) -> *mut IdeviceFfiError { ) -> *mut IdeviceFfiError {
if client.is_null() || out_result.is_null() || out_result_len.is_null() { if client.is_null() || out_result.is_null() || out_result_len.is_null() {
log::error!("Invalid arguments: {client:?}, {out_result:?}"); tracing::error!("Invalid arguments: {client:?}, {out_result:?}");
return ffi_err!(IdeviceError::FfiInvalidArg); return ffi_err!(IdeviceError::FfiInvalidArg);
} }
let client = unsafe { &mut *client }; let client = unsafe { &mut *client };
@@ -115,7 +119,7 @@ pub unsafe extern "C" fn springboard_services_get_icon(
}; };
let res: Result<Vec<u8>, IdeviceError> = let res: Result<Vec<u8>, IdeviceError> =
RUNTIME.block_on(async { client.0.get_icon_pngdata(bundle_id).await }); run_sync(async { client.0.get_icon_pngdata(bundle_id).await });
match res { match res {
Ok(r) => { Ok(r) => {
@@ -134,6 +138,169 @@ 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 /// Frees an SpringBoardServicesClient handle
/// ///
/// # Arguments /// # Arguments
@@ -145,7 +312,7 @@ pub unsafe extern "C" fn springboard_services_get_icon(
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
pub unsafe extern "C" fn springboard_services_free(handle: *mut SpringBoardServicesClientHandle) { pub unsafe extern "C" fn springboard_services_free(handle: *mut SpringBoardServicesClientHandle) {
if !handle.is_null() { if !handle.is_null() {
log::debug!("Freeing springboard_services_client"); tracing::debug!("Freeing springboard_services_client");
let _ = unsafe { Box::from_raw(handle) }; let _ = unsafe { Box::from_raw(handle) };
} }
} }

View File

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

View File

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

View File

@@ -2,22 +2,27 @@
use std::{ use std::{
ffi::{CStr, CString, c_char}, ffi::{CStr, CString, c_char},
pin::Pin,
ptr::null_mut, ptr::null_mut,
}; };
use crate::{ use crate::{
IdeviceFfiError, IdeviceHandle, IdevicePairingFile, RUNTIME, ffi_err, IdeviceFfiError, IdeviceHandle, IdevicePairingFile, ffi_err, run_sync, run_sync_local,
util::{SockAddr, c_socket_to_rust, idevice_sockaddr, idevice_socklen_t}, util::{SockAddr, c_socket_to_rust, idevice_sockaddr, idevice_socklen_t},
}; };
use futures::{Stream, StreamExt};
use idevice::{ use idevice::{
IdeviceError, IdeviceError,
usbmuxd::{UsbmuxdAddr, UsbmuxdConnection, UsbmuxdDevice}, usbmuxd::{UsbmuxdAddr, UsbmuxdConnection, UsbmuxdDevice, UsbmuxdListenEvent},
}; };
use log::error; use tracing::error;
pub struct UsbmuxdConnectionHandle(pub UsbmuxdConnection); pub struct UsbmuxdConnectionHandle(pub UsbmuxdConnection);
pub struct UsbmuxdAddrHandle(pub UsbmuxdAddr); pub struct UsbmuxdAddrHandle(pub UsbmuxdAddr);
pub struct UsbmuxdDeviceHandle(pub UsbmuxdDevice); 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 /// Connects to a usbmuxd instance over TCP
/// ///
@@ -52,7 +57,7 @@ pub unsafe extern "C" fn idevice_usbmuxd_new_tcp_connection(
Err(e) => return ffi_err!(e), Err(e) => return ffi_err!(e),
}; };
let res = RUNTIME.block_on(async move { let res = run_sync(async move {
let stream = tokio::net::TcpStream::connect(addr).await?; let stream = tokio::net::TcpStream::connect(addr).await?;
Ok::<_, IdeviceError>(UsbmuxdConnection::new(Box::new(stream), tag)) Ok::<_, IdeviceError>(UsbmuxdConnection::new(Box::new(stream), tag))
}); });
@@ -91,7 +96,7 @@ pub unsafe extern "C" fn idevice_usbmuxd_new_unix_socket_connection(
Err(_) => return ffi_err!(IdeviceError::FfiInvalidArg), Err(_) => return ffi_err!(IdeviceError::FfiInvalidArg),
}; };
let res: Result<UsbmuxdConnection, IdeviceError> = RUNTIME.block_on(async move { let res: Result<UsbmuxdConnection, IdeviceError> = run_sync(async move {
let stream = tokio::net::UnixStream::connect(addr).await?; let stream = tokio::net::UnixStream::connect(addr).await?;
Ok(UsbmuxdConnection::new(Box::new(stream), tag)) Ok(UsbmuxdConnection::new(Box::new(stream), tag))
}); });
@@ -127,13 +132,13 @@ pub unsafe extern "C" fn idevice_usbmuxd_new_default_connection(
let addr = match UsbmuxdAddr::from_env_var() { let addr = match UsbmuxdAddr::from_env_var() {
Ok(a) => a, Ok(a) => a,
Err(e) => { Err(e) => {
log::error!("Invalid address set: {e:?}"); tracing::error!("Invalid address set: {e:?}");
return ffi_err!(IdeviceError::FfiInvalidArg); return ffi_err!(IdeviceError::FfiInvalidArg);
} }
}; };
let res: Result<UsbmuxdConnection, IdeviceError> = let res: Result<UsbmuxdConnection, IdeviceError> =
RUNTIME.block_on(async move { addr.connect(tag).await }); run_sync(async move { addr.connect(tag).await });
match res { match res {
Ok(r) => { Ok(r) => {
@@ -171,7 +176,7 @@ pub unsafe extern "C" fn idevice_usbmuxd_get_devices(
} }
let conn = unsafe { &mut (*usbmuxd_conn).0 }; let conn = unsafe { &mut (*usbmuxd_conn).0 };
let res = RUNTIME.block_on(async { conn.get_devices().await }); let res = run_sync(async { conn.get_devices().await });
match res { match res {
Ok(device_vec) => { Ok(device_vec) => {
@@ -239,7 +244,7 @@ pub unsafe extern "C" fn idevice_usbmuxd_connect_to_device(
} }
}; };
let res = RUNTIME.block_on(async move { conn.connect_to_device(device_id, port, label).await }); let res = run_sync(async move { conn.connect_to_device(device_id, port, label).await });
match res { match res {
Ok(device_conn) => { Ok(device_conn) => {
@@ -287,7 +292,7 @@ pub unsafe extern "C" fn idevice_usbmuxd_get_pair_record(
} }
}; };
let res = RUNTIME.block_on(async { conn.get_pair_record(udid_str).await }); let res = run_sync(async { conn.get_pair_record(udid_str).await });
match res { match res {
Ok(pf) => { Ok(pf) => {
@@ -301,6 +306,149 @@ 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. /// Reads the BUID (Boot-Unique ID) from usbmuxd.
/// ///
/// The returned string must be freed with `idevice_string_free`. /// The returned string must be freed with `idevice_string_free`.
@@ -325,7 +473,7 @@ pub unsafe extern "C" fn idevice_usbmuxd_get_buid(
} }
let conn = unsafe { &mut (*usbmuxd_conn).0 }; let conn = unsafe { &mut (*usbmuxd_conn).0 };
let res = RUNTIME.block_on(async { conn.get_buid().await }); let res = run_sync(async { conn.get_buid().await });
match res { match res {
Ok(buid_str) => match CString::new(buid_str) { Ok(buid_str) => match CString::new(buid_str) {

View File

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

View File

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

334
idevice/src/cursor.rs Normal file
View File

@@ -0,0 +1,334 @@
// 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,11 +1,15 @@
#![doc = include_str!("../README.md")] #![doc = include_str!("../README.md")]
#![warn(missing_debug_implementations)]
#![warn(missing_copy_implementations)]
// Jackson Coxson // Jackson Coxson
#[cfg(feature = "pair")] #[cfg(all(feature = "pair", feature = "rustls"))]
mod ca; mod ca;
pub mod cursor;
mod obfuscation;
pub mod pairing_file; pub mod pairing_file;
mod plist_macro;
pub mod provider; pub mod provider;
#[cfg(feature = "rustls")]
mod sni; mod sni;
#[cfg(feature = "tunnel_tcp_stack")] #[cfg(feature = "tunnel_tcp_stack")]
pub mod tcp; pub mod tcp;
@@ -15,7 +19,6 @@ pub mod tss;
pub mod tunneld; pub mod tunneld;
#[cfg(feature = "usbmuxd")] #[cfg(feature = "usbmuxd")]
pub mod usbmuxd; pub mod usbmuxd;
mod util;
pub mod utils; pub mod utils;
#[cfg(feature = "xpc")] #[cfg(feature = "xpc")]
pub mod xpc; pub mod xpc;
@@ -26,8 +29,9 @@ pub use services::*;
#[cfg(feature = "xpc")] #[cfg(feature = "xpc")]
pub use xpc::RemoteXpcClient; pub use xpc::RemoteXpcClient;
use log::{debug, error, trace}; use plist_macro::{plist, pretty_print_dictionary, pretty_print_plist};
use provider::{IdeviceProvider, RsdProvider}; use provider::{IdeviceProvider, RsdProvider};
#[cfg(feature = "rustls")]
use rustls::{crypto::CryptoProvider, pki_types::ServerName}; use rustls::{crypto::CryptoProvider, pki_types::ServerName};
use std::{ use std::{
io::{self, BufWriter}, io::{self, BufWriter},
@@ -35,8 +39,7 @@ use std::{
}; };
use thiserror::Error; use thiserror::Error;
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; 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; use crate::services::lockdown::LockdownClient;
@@ -72,6 +75,22 @@ pub trait IdeviceService: Sized {
#[allow(async_fn_in_trait)] #[allow(async_fn_in_trait)]
async fn connect(provider: &dyn IdeviceProvider) -> Result<Self, IdeviceError> { async fn connect(provider: &dyn IdeviceProvider) -> Result<Self, IdeviceError> {
let mut lockdown = LockdownClient::connect(provider).await?; 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 lockdown
.start_session(&provider.get_pairing_file().await?) .start_session(&provider.get_pairing_file().await?)
.await?; .await?;
@@ -86,7 +105,7 @@ pub trait IdeviceService: Sized {
let mut idevice = provider.connect(port).await?; let mut idevice = provider.connect(port).await?;
if ssl { if ssl {
idevice idevice
.start_session(&provider.get_pairing_file().await?) .start_session(&provider.get_pairing_file().await?, legacy)
.await?; .await?;
} }
@@ -128,6 +147,7 @@ pub type IdeviceSocket = Box<dyn ReadWrite>;
/// ///
/// Manages the connection socket and provides methods for common device operations /// Manages the connection socket and provides methods for common device operations
/// and message exchange. /// and message exchange.
#[derive(Debug)]
pub struct Idevice { pub struct Idevice {
/// The underlying connection socket, boxed for dynamic dispatch /// The underlying connection socket, boxed for dynamic dispatch
socket: Option<Box<dyn ReadWrite>>, socket: Option<Box<dyn ReadWrite>>,
@@ -175,7 +195,7 @@ impl Idevice {
/// # Errors /// # Errors
/// Returns `IdeviceError` if communication fails or response is invalid /// Returns `IdeviceError` if communication fails or response is invalid
pub async fn get_type(&mut self) -> Result<String, IdeviceError> { pub async fn get_type(&mut self) -> Result<String, IdeviceError> {
let req = crate::plist!({ let req = plist!({
"Label": self.label.clone(), "Label": self.label.clone(),
"Request": "QueryType", "Request": "QueryType",
}); });
@@ -195,7 +215,7 @@ impl Idevice {
/// # Errors /// # Errors
/// Returns `IdeviceError` if the protocol sequence isn't followed correctly /// Returns `IdeviceError` if the protocol sequence isn't followed correctly
pub async fn rsd_checkin(&mut self) -> Result<(), IdeviceError> { pub async fn rsd_checkin(&mut self) -> Result<(), IdeviceError> {
let req = crate::plist!({ let req = plist!({
"Label": self.label.clone(), "Label": self.label.clone(),
"ProtocolVersion": "2", "ProtocolVersion": "2",
"Request": "RSDCheckin", "Request": "RSDCheckin",
@@ -287,6 +307,75 @@ impl Idevice {
self.send_raw_with_progress(message, |_| async {}, ()).await 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 /// Sends raw binary data with progress callbacks
/// ///
/// # Arguments /// # Arguments
@@ -389,14 +478,20 @@ impl Idevice {
} }
} }
_ => { _ => {
log::error!("Error is not a string or integer from read_plist: {e:?}"); tracing::error!("Error is not a string or integer from read_plist: {e:?}");
return Err(IdeviceError::UnexpectedResponse); return Err(IdeviceError::UnexpectedResponse);
} }
}; };
if let Some(e) = IdeviceError::from_device_error_type(e.as_str(), &res) { if let Some(e) = IdeviceError::from_device_error_type(e.as_str(), &res) {
return Err(e); return Err(e);
} else { } else {
return Err(IdeviceError::UnknownErrorType(e)); 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));
} }
} }
Ok(res) Ok(res)
@@ -459,7 +554,16 @@ impl Idevice {
pub async fn start_session( pub async fn start_session(
&mut self, &mut self,
pairing_file: &pairing_file::PairingFile, pairing_file: &pairing_file::PairingFile,
legacy: bool,
) -> Result<(), IdeviceError> { ) -> 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() { if CryptoProvider::get_default().is_none() {
// rust-analyzer will choke on this block, don't worry about it // rust-analyzer will choke on this block, don't worry about it
let crypto_provider: CryptoProvider = { let crypto_provider: CryptoProvider = {
@@ -488,7 +592,9 @@ impl Idevice {
// My sanity while debugging the workspace crates are more important. // My sanity while debugging the workspace crates are more important.
debug!("Using ring crypto backend, because both were passed"); debug!("Using ring crypto backend, because both were passed");
log::warn!("Both ring && aws-lc are selected as idevice crypto backends!"); tracing::warn!(
"Both ring && aws-lc are selected as idevice crypto backends!"
);
rustls::crypto::ring::default_provider() rustls::crypto::ring::default_provider()
} }
}; };
@@ -497,7 +603,7 @@ impl Idevice {
// For whatever reason, getting the default provider will return None on iOS at // 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 // 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. // we will log it but not propogate it. An issue should be opened with rustls.
log::error!("Failed to set crypto provider: {e:?}"); tracing::error!("Failed to set crypto provider: {e:?}");
} }
} }
let config = sni::create_client_config(pairing_file)?; let config = sni::create_client_config(pairing_file)?;
@@ -512,6 +618,30 @@ impl Idevice {
Ok(()) 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 /// Comprehensive error type for all device communication failures
@@ -521,12 +651,24 @@ impl Idevice {
pub enum IdeviceError { pub enum IdeviceError {
#[error("device socket io failed")] #[error("device socket io failed")]
Socket(#[from] io::Error) = -1, Socket(#[from] io::Error) = -1,
#[cfg(feature = "rustls")]
#[error("PEM parse failed")] #[error("PEM parse failed")]
PemParseFailed(#[from] rustls::pki_types::pem::Error) = -2, PemParseFailed(#[from] rustls::pki_types::pem::Error) = -2,
#[cfg(feature = "rustls")]
#[error("TLS error")] #[error("TLS error")]
Rustls(#[from] rustls::Error) = -3, 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")] #[error("TLS verifiction build failed")]
TlsBuilderFailed(#[from] rustls::server::VerifierBuilderError) = -4, 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")] #[error("io on plist")]
Plist(#[from] plist::Error) = -5, Plist(#[from] plist::Error) = -5,
#[error("can't convert bytes to utf8")] #[error("can't convert bytes to utf8")]
@@ -724,24 +866,13 @@ pub enum IdeviceError {
#[error("Developer mode is not enabled")] #[error("Developer mode is not enabled")]
DeveloperModeNotEnabled = -68, DeveloperModeNotEnabled = -68,
#[cfg(feature = "remote_pairing")] #[cfg(feature = "notification_proxy")]
#[error("could not parse as JSON")] #[error("notification proxy died")]
JsonParseFailed(#[from] json::Error) = -69, NotificationProxyDeath = -69,
#[cfg(feature = "remote_pairing")] #[cfg(feature = "installation_proxy")]
#[error("unknown TLV type: {0}")] #[error("Application verification failed: {0}")]
UnknownTlv(u8) = -70, ApplicationVerificationFailed(String) = -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,
} }
impl IdeviceError { impl IdeviceError {
@@ -785,6 +916,15 @@ impl IdeviceError {
Some(Self::InternalError(detailed_error)) Some(Self::InternalError(detailed_error))
} }
} }
#[cfg(feature = "installation_proxy")]
"ApplicationVerificationFailed" => {
let msg = context
.get("ErrorDescription")
.and_then(|x| x.as_string())
.unwrap_or("No context")
.to_string();
Some(Self::ApplicationVerificationFailed(msg))
}
_ => None, _ => None,
} }
} }
@@ -792,6 +932,7 @@ impl IdeviceError {
pub fn code(&self) -> i32 { pub fn code(&self) -> i32 {
match self { match self {
IdeviceError::Socket(_) => -1, IdeviceError::Socket(_) => -1,
#[cfg(feature = "rustls")]
IdeviceError::PemParseFailed(_) => -2, IdeviceError::PemParseFailed(_) => -2,
IdeviceError::Rustls(_) => -3, IdeviceError::Rustls(_) => -3,
IdeviceError::TlsBuilderFailed(_) => -4, IdeviceError::TlsBuilderFailed(_) => -4,
@@ -906,15 +1047,12 @@ impl IdeviceError {
#[cfg(feature = "installation_proxy")] #[cfg(feature = "installation_proxy")]
IdeviceError::MalformedPackageArchive(_) => -67, IdeviceError::MalformedPackageArchive(_) => -67,
IdeviceError::DeveloperModeNotEnabled => -68, IdeviceError::DeveloperModeNotEnabled => -68,
#[cfg(feature = "remote_pairing")]
IdeviceError::JsonParseFailed(_) => -69, #[cfg(feature = "notification_proxy")]
#[cfg(feature = "remote_pairing")] IdeviceError::NotificationProxyDeath => -69,
IdeviceError::UnknownTlv(_) => -70,
#[cfg(feature = "remote_pairing")] #[cfg(feature = "installation_proxy")]
IdeviceError::MalformedTlv => -71, IdeviceError::ApplicationVerificationFailed(_) => -70,
IdeviceError::Base64Decode(_) => -72,
#[cfg(feature = "remote_pairing")]
IdeviceError::PairVerifyFailed => -73,
} }
} }
} }

View File

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

View File

@@ -1,714 +0,0 @@
// 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,133 +1,160 @@
// Jackson Coxson // Jackson Coxson
use std::io::SeekFrom; use std::{io::SeekFrom, marker::PhantomPinned, pin::Pin};
use crate::IdeviceError; use tokio::io::{AsyncRead, AsyncSeek, AsyncWrite};
use super::{ use crate::{
opcode::AfcOpcode, IdeviceError,
packet::{AfcPacket, AfcPacketHeader}, afc::{
AfcClient,
inner_file::{InnerFileDescriptor, OwnedInnerFileDescriptor},
},
}; };
/// Maximum transfer size for file operations (64KB) #[derive(Debug)]
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> { pub struct FileDescriptor<'a> {
pub(crate) client: &'a mut super::AfcClient, inner: Pin<Box<InnerFileDescriptor<'a>>>,
pub(crate) fd: u64,
pub(crate) path: String,
} }
impl FileDescriptor<'_> { #[derive(Debug)]
/// Generic helper to send an AFC packet and read the response pub struct OwnedFileDescriptor {
async fn send_packet( inner: Pin<Box<OwnedInnerFileDescriptor>>,
&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;
let packet = AfcPacket { impl<'a> FileDescriptor<'a> {
header, /// create a new FileDescriptor from a raw fd
header_payload, ///
payload, /// # 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,
}),
}
}
self.client.send(packet).await?; /// Closes the file descriptor
self.client.read().await 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
} }
/// Returns the current cursor position for the file /// Returns the current cursor position for the file
pub async fn seek_tell(&mut self) -> Result<u64, IdeviceError> { pub async fn seek_tell(&mut self) -> Result<u64, IdeviceError> {
let header_payload = self.fd.to_le_bytes().to_vec(); self.inner.as_mut().seek_tell().await
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 /// Reads the entire contents of the file
/// ///
/// # Returns /// # Returns
/// A vector containing the file's data /// A vector containing the file's data
pub async fn read(&mut self) -> Result<Vec<u8>, IdeviceError> { pub async fn read_entire(&mut self) -> Result<Vec<u8>, IdeviceError> {
let seek_pos = self.seek_tell().await? as usize; self.inner.as_mut().read().await
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 /// Writes data to the file
/// ///
/// # Arguments /// # Arguments
/// * `bytes` - Data to write to the file /// * `bytes` - Data to write to the file
pub async fn write(&mut self, bytes: &[u8]) -> Result<(), IdeviceError> { pub async fn write_entire(&mut self, bytes: &[u8]) -> Result<(), IdeviceError> {
for chunk in bytes.chunks(MAX_TRANSFER as usize) { self.inner.as_mut().write(bytes).await
let header_payload = self.fd.to_le_bytes().to_vec();
self.send_packet(AfcOpcode::Write, header_payload, chunk.to_vec())
.await?;
} }
Ok(()) });
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)
} }
});
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

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

@@ -0,0 +1,23 @@
#[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,15 +6,21 @@
use std::collections::HashMap; use std::collections::HashMap;
use errors::AfcError; use errors::AfcError;
use file::FileDescriptor;
use log::warn;
use opcode::{AfcFopenMode, AfcOpcode}; use opcode::{AfcFopenMode, AfcOpcode};
use packet::{AfcPacket, AfcPacketHeader}; use packet::{AfcPacket, AfcPacketHeader};
use tracing::warn;
use crate::{Idevice, IdeviceError, IdeviceService, obf}; use crate::{
Idevice, IdeviceError, IdeviceService,
afc::file::{FileDescriptor, OwnedFileDescriptor},
lockdown::LockdownClient,
obf,
};
pub mod errors; pub mod errors;
pub mod file; pub mod file;
mod inner_file;
mod inner_file_impl_macro;
pub mod opcode; pub mod opcode;
pub mod packet; pub mod packet;
@@ -22,6 +28,7 @@ pub mod packet;
pub const MAGIC: u64 = 0x4141504c36414643; pub const MAGIC: u64 = 0x4141504c36414643;
/// Client for interacting with the AFC service on iOS devices /// Client for interacting with the AFC service on iOS devices
#[derive(Debug)]
pub struct AfcClient { pub struct AfcClient {
/// The underlying iDevice connection /// The underlying iDevice connection
pub idevice: Idevice, pub idevice: Idevice,
@@ -85,6 +92,43 @@ 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 /// Lists the contents of a directory on the device
/// ///
/// # Arguments /// # Arguments
@@ -408,11 +452,54 @@ impl AfcClient {
return Err(IdeviceError::UnexpectedResponse); return Err(IdeviceError::UnexpectedResponse);
} }
let fd = u64::from_le_bytes(res.header_payload[..8].try_into().unwrap()); let fd = u64::from_le_bytes(res.header_payload[..8].try_into().unwrap());
Ok(FileDescriptor {
client: self, // we know it's a valid fd
fd, Ok(unsafe { FileDescriptor::new(self, fd, path) })
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) })
} }
/// Creates a hard or symbolic link /// Creates a hard or symbolic link
@@ -508,7 +595,7 @@ impl AfcClient {
let res = AfcPacket::read(&mut self.idevice).await?; let res = AfcPacket::read(&mut self.idevice).await?;
if res.header.operation == AfcOpcode::Status { if res.header.operation == AfcOpcode::Status {
if res.header_payload.len() < 8 { if res.header_payload.len() < 8 {
log::error!("AFC returned error opcode, but not a code"); tracing::error!("AFC returned error opcode, but not a code");
return Err(IdeviceError::UnexpectedResponse); return Err(IdeviceError::UnexpectedResponse);
} }
let code = u64::from_le_bytes(res.header_payload[..8].try_into().unwrap()); let code = u64::from_le_bytes(res.header_payload[..8].try_into().unwrap());

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -3,7 +3,7 @@
use std::pin::Pin; use std::pin::Pin;
use futures::Stream; use futures::Stream;
use log::warn; use tracing::warn;
use crate::{IdeviceError, ReadWrite, RsdService, obf}; use crate::{IdeviceError, ReadWrite, RsdService, obf};
@@ -19,6 +19,7 @@ impl RsdService for DiagnostisServiceClient<Box<dyn ReadWrite>> {
} }
} }
#[derive(Debug)]
pub struct DiagnostisServiceClient<R: ReadWrite> { pub struct DiagnostisServiceClient<R: ReadWrite> {
inner: super::CoreDeviceServiceClient<R>, inner: super::CoreDeviceServiceClient<R>,
} }
@@ -70,3 +71,12 @@ 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 // Jackson Coxson
// Ported from pymobiledevice3 // Ported from pymobiledevice3
use log::warn; use tracing::warn;
use crate::{ use crate::{
IdeviceError, ReadWrite, RemoteXpcClient, IdeviceError, ReadWrite, RemoteXpcClient,
@@ -17,6 +17,7 @@ pub use openstdiosocket::*;
const CORE_SERVICE_VERSION: &str = "443.18"; const CORE_SERVICE_VERSION: &str = "443.18";
#[derive(Debug)]
pub struct CoreDeviceServiceClient<R: ReadWrite> { pub struct CoreDeviceServiceClient<R: ReadWrite> {
inner: RemoteXpcClient<R>, inner: RemoteXpcClient<R>,
} }

View File

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

View File

@@ -17,7 +17,7 @@ use crate::{Idevice, IdeviceError, IdeviceService, obf};
use byteorder::{BigEndian, WriteBytesExt}; use byteorder::{BigEndian, WriteBytesExt};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::io::{self, Write}; use std::io::{self, IoSlice, Write};
/// A representation of a CDTunnel packet used in the CoreDeviceProxy protocol. /// A representation of a CDTunnel packet used in the CoreDeviceProxy protocol.
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
@@ -82,6 +82,7 @@ impl CDTunnelPacket {
/// A high-level client for the `com.apple.internal.devicecompute.CoreDeviceProxy` service. /// A high-level client for the `com.apple.internal.devicecompute.CoreDeviceProxy` service.
/// ///
/// Handles session negotiation, handshake, and tunnel communication. /// Handles session negotiation, handshake, and tunnel communication.
#[derive(Debug)]
pub struct CoreDeviceProxy { pub struct CoreDeviceProxy {
/// The underlying idevice connection used for communication. /// The underlying idevice connection used for communication.
pub idevice: Idevice, pub idevice: Idevice,
@@ -194,6 +195,20 @@ impl CoreDeviceProxy {
Ok(()) 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. /// Receives up to `mtu` bytes from the tunnel.
/// ///
/// # Returns /// # Returns

View File

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

View File

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

View File

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

View File

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

View File

@@ -61,12 +61,12 @@
use plist::Value; use plist::Value;
use tokio::io::{AsyncRead, AsyncReadExt}; use tokio::io::{AsyncRead, AsyncReadExt};
use crate::IdeviceError; use crate::{IdeviceError, pretty_print_plist};
/// Message header containing metadata about the message /// Message header containing metadata about the message
/// ///
/// 32-byte structure that appears at the start of every message /// 32-byte structure that appears at the start of every message
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, Copy, PartialEq)]
pub struct MessageHeader { pub struct MessageHeader {
/// Magic number identifying the protocol (0x1F3D5B79) /// Magic number identifying the protocol (0x1F3D5B79)
magic: u32, magic: u32,
@@ -91,7 +91,7 @@ pub struct MessageHeader {
/// Payload header containing information about the message contents /// Payload header containing information about the message contents
/// ///
/// 16-byte structure following the message header /// 16-byte structure following the message header
#[derive(Debug, Default, Clone, PartialEq)] #[derive(Debug, Default, Clone, Copy, PartialEq)]
pub struct PayloadHeader { pub struct PayloadHeader {
/// Flags controlling message processing /// Flags controlling message processing
flags: u32, flags: u32,
@@ -104,7 +104,7 @@ pub struct PayloadHeader {
/// Header for auxiliary data section /// Header for auxiliary data section
/// ///
/// 16-byte structure preceding auxiliary data /// 16-byte structure preceding auxiliary data
#[derive(Debug, Default, PartialEq)] #[derive(Debug, Default, Clone, Copy, PartialEq)]
pub struct AuxHeader { pub struct AuxHeader {
/// Buffer size hint (often 496) /// Buffer size hint (often 496)
buffer_size: u32, buffer_size: u32,
@@ -141,7 +141,7 @@ pub enum AuxValue {
} }
/// Complete protocol message /// Complete protocol message
#[derive(Debug, PartialEq)] #[derive(PartialEq)]
pub struct Message { pub struct Message {
/// Message metadata header /// Message metadata header
pub message_header: MessageHeader, pub message_header: MessageHeader,
@@ -525,3 +525,14 @@ 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,6 +39,18 @@ impl crate::IdeviceService for remote_server::RemoteServerClient<Box<dyn ReadWri
async fn connect(provider: &dyn IdeviceProvider) -> Result<Self, IdeviceError> { async fn connect(provider: &dyn IdeviceProvider) -> Result<Self, IdeviceError> {
// Establish Lockdown session // Establish Lockdown session
let mut lockdown = LockdownClient::connect(provider).await?; 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 lockdown
.start_session(&provider.get_pairing_file().await?) .start_session(&provider.get_pairing_file().await?)
.await?; .await?;
@@ -56,7 +68,7 @@ impl crate::IdeviceService for remote_server::RemoteServerClient<Box<dyn ReadWri
let mut idevice = provider.connect(port).await?; let mut idevice = provider.connect(port).await?;
if ssl { if ssl {
idevice idevice
.start_session(&provider.get_pairing_file().await?) .start_session(&provider.get_pairing_file().await?, legacy)
.await?; .await?;
} }
// Convert to transport and build client // Convert to transport and build client

View File

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

View File

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

View File

@@ -51,8 +51,8 @@
use std::collections::{HashMap, VecDeque}; use std::collections::{HashMap, VecDeque};
use log::{debug, warn};
use tokio::io::AsyncWriteExt; use tokio::io::AsyncWriteExt;
use tracing::{debug, warn};
use crate::{ use crate::{
IdeviceError, ReadWrite, IdeviceError, ReadWrite,
@@ -66,6 +66,7 @@ pub const INSTRUMENTS_MESSAGE_TYPE: u32 = 2;
/// ///
/// Manages multiple communication channels and handles message serialization/deserialization. /// Manages multiple communication channels and handles message serialization/deserialization.
/// Each channel operates independently and maintains its own message queue. /// Each channel operates independently and maintains its own message queue.
#[derive(Debug)]
pub struct RemoteServerClient<R: ReadWrite> { pub struct RemoteServerClient<R: ReadWrite> {
/// The underlying device connection /// The underlying device connection
idevice: R, idevice: R,
@@ -80,6 +81,7 @@ pub struct RemoteServerClient<R: ReadWrite> {
/// Handle to a specific communication channel /// Handle to a specific communication channel
/// ///
/// Provides channel-specific operations for use on the remote server client. /// Provides channel-specific operations for use on the remote server client.
#[derive(Debug)]
pub struct Channel<'a, R: ReadWrite> { pub struct Channel<'a, R: ReadWrite> {
/// Reference to parent client /// Reference to parent client
client: &'a mut RemoteServerClient<R>, client: &'a mut RemoteServerClient<R>,

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