93 Commits

Author SHA1 Message Date
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
169 changed files with 9559 additions and 5302 deletions

View File

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

1593
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

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

View File

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

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

View File

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

View File

@@ -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
#include <idevice++/bindings.hpp>
#include <idevice++/remote_server.hpp>
#include <idevice++/dvt/remote_server.hpp>
#include <idevice++/result.hpp>
#include <memory>

View File

@@ -2,7 +2,7 @@
#pragma once
#include <idevice++/bindings.hpp>
#include <idevice++/remote_server.hpp>
#include <idevice++/dvt/remote_server.hpp>
#include <idevice++/result.hpp>
#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++/provider.hpp>
#include <memory>
#include <sys/_types/_u_int64_t.h>
namespace IdeviceFFI {

View File

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

View File

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

View File

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

View File

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

View File

@@ -6,10 +6,11 @@
namespace IdeviceFFI {
Result<void, FfiError> AdapterStream::close() {
if (!h_)
if (!h_) {
return Ok();
}
FfiError e(::adapter_close(h_));
FfiError e(::adapter_stream_close(h_));
if (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) {
if (!h_)
if (!h_) {
return Err(FfiError::NotConnected());
}
FfiError e(::adapter_send(h_, data, len));
if (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) {
if (!h_)
if (!h_) {
return Err(FfiError::NotConnected());
}
if (max_hint == 0)
if (max_hint == 0) {
max_hint = 2048;
}
std::vector<uint8_t> out(max_hint);
size_t actual = 0;

View File

@@ -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
#include <idevice++/location_simulation.hpp>
#include <idevice++/dvt/location_simulation.hpp>
namespace IdeviceFFI {

View File

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

View File

@@ -1,6 +1,6 @@
// Jackson Coxson
#include <idevice++/remote_server.hpp>
#include <idevice++/dvt/remote_server.hpp>
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));
}
#if defined(__unix__) || defined(__APPLE__)
Result<Idevice, FfiError> Idevice::from_fd(int fd, const std::string& label) {
IdeviceHandle* h = nullptr;
FfiError e(idevice_from_fd(fd, label.c_str(), &h));
if (e) {
return Err(e);
}
return Ok(Idevice(h));
}
#endif
Result<Idevice, FfiError>
Idevice::create_tcp(const sockaddr* addr, socklen_t addr_len, const std::string& label) {
IdeviceHandle* h = nullptr;
@@ -42,8 +53,8 @@ Result<void, FfiError> Idevice::rsd_checkin() {
return Ok();
}
Result<void, FfiError> Idevice::start_session(const PairingFile& pairing_file) {
FfiError e(idevice_start_session(handle_.get(), pairing_file.raw()));
Result<void, FfiError> Idevice::start_session(const PairingFile& pairing_file, bool legacy) {
FfiError e(idevice_start_session(handle_.get(), pairing_file.raw(), legacy));
if (e) {
return Err(e);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -42,7 +42,7 @@ int main() {
// Connect to installation proxy
InstallationProxyClientHandle *client = NULL;
err = installation_proxy_connect_tcp(provider, &client);
err = installation_proxy_connect(provider, &client);
if (err != NULL) {
fprintf(stderr, "Failed to connect to installation proxy: [%d] %s",
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,
err->message);
idevice_error_free(err);
idevice_provider_free(provider);
return 1;
}
idevice_provider_free(provider);

View File

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

View File

@@ -1,14 +1,18 @@
// Jackson Coxson
use std::ptr::null_mut;
use std::{io::SeekFrom, ptr::null_mut};
use idevice::{
IdeviceError, IdeviceService,
afc::{AfcClient, DeviceInfo, FileInfo},
afc::{AfcClient, DeviceInfo, FileInfo, file::FileDescriptor},
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);
@@ -30,11 +34,11 @@ pub unsafe extern "C" fn afc_client_connect(
client: *mut *mut AfcClientHandle,
) -> *mut IdeviceFfiError {
if provider.is_null() || client.is_null() {
log::error!("Null pointer provided");
tracing::error!("Null pointer provided");
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 };
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
///
/// # Arguments
@@ -88,7 +130,7 @@ pub unsafe extern "C" fn afc_client_new(
#[unsafe(no_mangle)]
pub unsafe extern "C" fn afc_client_free(handle: *mut AfcClientHandle) {
if !handle.is_null() {
log::debug!("Freeing afc_client");
tracing::debug!("Freeing afc_client");
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
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
let client_ref = unsafe { &mut (*client).0 };
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),
};
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 };
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),
};
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 };
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);
}
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 };
client_ref.get_device_info().await
});
@@ -395,7 +437,7 @@ pub unsafe extern "C" fn afc_remove_path(
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 };
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),
};
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 };
client_ref.remove_all(path).await
});
@@ -506,7 +548,7 @@ pub unsafe extern "C" fn afc_file_open(
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 result = client_ref.open(path, mode).await;
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 res: Result<(), IdeviceError> = RUNTIME.block_on(async move { fd.close().await });
let res: Result<(), IdeviceError> = run_sync(async move { fd.close().await });
match res {
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
/// * [`handle`] - File handle to read from
/// * [`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
/// 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
#[unsafe(no_mangle)]
pub unsafe extern "C" fn afc_file_read(
handle: *mut AfcFileHandle,
data: *mut *mut u8,
len: usize,
bytes_read: *mut libc::size_t,
) -> *mut IdeviceFfiError {
if handle.is_null() || data.is_null() || bytes_read.is_null() {
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let fd = unsafe { &mut *(handle as *mut FileDescriptor) };
let res: Result<Vec<u8>, IdeviceError> = run_sync({
let mut buf = vec![0u8; len];
async move {
let r = fd.read(&mut buf).await?;
buf.truncate(r);
Ok(buf)
}
});
match res {
Ok(bytes) => {
let mut boxed = bytes.into_boxed_slice();
unsafe {
*data = boxed.as_mut_ptr();
*bytes_read = boxed.len();
}
std::mem::forget(boxed);
null_mut()
}
Err(e) => ffi_err!(e),
}
}
/// Reads all data from an open file.
///
/// # Arguments
/// * [`handle`] - File handle to read from
/// * [`data`] - Will be set to point to the read data
/// * [`length`] - The number of bytes read from the file
///
/// # Returns
/// An IdeviceFfiError on error, null on success
///
/// # Safety
/// All pointers must be valid and non-null
#[unsafe(no_mangle)]
pub unsafe extern "C" fn afc_file_read_entire(
handle: *mut AfcFileHandle,
data: *mut *mut u8,
length: *mut libc::size_t,
@@ -575,7 +665,7 @@ pub unsafe extern "C" fn afc_file_read(
}
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 {
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
///
/// # 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 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 {
Ok(_) => null_mut(),
@@ -674,7 +858,7 @@ pub unsafe extern "C" fn afc_make_link(
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 };
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),
};
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 };
client_ref.rename(source, target).await
});
@@ -741,7 +925,7 @@ pub unsafe extern "C" fn afc_rename_path(
#[unsafe(no_mangle)]
pub unsafe extern "C" fn afc_file_read_data_free(data: *mut u8, length: libc::size_t) {
if !data.is_null() {
let boxed = unsafe { Box::from_raw(std::slice::from_raw_parts_mut(data, length)) };
let boxed = unsafe { Box::from_raw(std::ptr::slice_from_raw_parts_mut(data, length)) };
drop(boxed);
}
}

View File

@@ -4,7 +4,9 @@ use std::ptr::null_mut;
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);
@@ -26,11 +28,11 @@ pub unsafe extern "C" fn amfi_connect(
client: *mut *mut AmfiClientHandle,
) -> *mut IdeviceFfiError {
if provider.is_null() || client.is_null() {
log::error!("Null pointer provided");
tracing::error!("Null pointer provided");
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 };
// 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);
}
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 };
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);
}
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 };
client_ref.enable_developer_mode().await
});
@@ -150,7 +152,7 @@ pub unsafe extern "C" fn amfi_accept_developer_mode(
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 };
client_ref.accept_developer_mode().await
});
@@ -171,7 +173,7 @@ pub unsafe extern "C" fn amfi_accept_developer_mode(
#[unsafe(no_mangle)]
pub unsafe extern "C" fn amfi_client_free(handle: *mut AmfiClientHandle) {
if !handle.is_null() {
log::debug!("Freeing AmfiClient handle");
tracing::debug!("Freeing AmfiClient handle");
let _ = unsafe { Box::from_raw(handle) };
}
}

View File

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

View File

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

View File

@@ -9,7 +9,10 @@ use idevice::{
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 AdapterHandle(pub idevice::tcp::handle::AdapterHandle);
@@ -32,11 +35,11 @@ pub unsafe extern "C" fn core_device_proxy_connect(
client: *mut *mut CoreDeviceProxyHandle,
) -> *mut IdeviceFfiError {
if provider.is_null() || client.is_null() {
log::error!("Null pointer provided");
tracing::error!("Null pointer provided");
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 };
// 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 r: Result<CoreDeviceProxy, IdeviceError> =
RUNTIME.block_on(async move { CoreDeviceProxy::new(socket).await });
run_sync(async move { CoreDeviceProxy::new(socket).await });
match r {
Ok(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 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 {
Ok(_) => null_mut(),
@@ -149,7 +152,7 @@ pub unsafe extern "C" fn core_device_proxy_recv(
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 {
Ok(received_data) => {
@@ -192,7 +195,7 @@ pub unsafe extern "C" fn core_device_proxy_get_client_parameters(
netmask: *mut *mut c_char,
) -> *mut IdeviceFfiError {
if handle.is_null() {
log::error!("Passed null handle");
tracing::error!("Passed null handle");
return ffi_err!(IdeviceError::FfiInvalidArg);
}
@@ -312,7 +315,7 @@ pub unsafe extern "C" fn core_device_proxy_create_tcp_adapter(
match result {
Ok(adapter_obj) => {
// We have to run this in the RUNTIME since we're spawning a new thread
let adapter_handle = 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));
unsafe { *adapter = Box::into_raw(boxed) };
@@ -333,7 +336,7 @@ pub unsafe extern "C" fn core_device_proxy_create_tcp_adapter(
#[unsafe(no_mangle)]
pub unsafe extern "C" fn core_device_proxy_free(handle: *mut CoreDeviceProxyHandle) {
if !handle.is_null() {
log::debug!("Freeing core_device_proxy");
tracing::debug!("Freeing core_device_proxy");
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)]
pub unsafe extern "C" fn adapter_free(handle: *mut AdapterHandle) {
if !handle.is_null() {
log::debug!("Freeing adapter");
tracing::debug!("Freeing adapter");
let _ = unsafe { Box::from_raw(handle) };
}
}

View File

@@ -9,7 +9,7 @@ use idevice::{IdeviceError, ReadWrite, RsdService};
use crate::core_device_proxy::AdapterHandle;
use crate::rsd::RsdHandshakeHandle;
use crate::{IdeviceFfiError, RUNTIME, ReadWriteOpaque, ffi_err};
use crate::{IdeviceFfiError, ReadWriteOpaque, ffi_err, run_sync, run_sync_local};
/// Opaque handle to a DebugProxyClient
pub struct DebugProxyHandle(pub DebugProxyClient<Box<dyn ReadWrite>>);
@@ -136,7 +136,7 @@ pub unsafe extern "C" fn debug_proxy_connect_rsd(
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let res: Result<DebugProxyClient<Box<dyn ReadWrite>>, IdeviceError> =
RUNTIME.block_on(async move {
run_sync_local(async move {
let provider_ref = unsafe { &mut (*provider).0 };
let handshake_ref = unsafe { &mut (*handshake).0 };
@@ -241,7 +241,7 @@ pub unsafe extern "C" fn debug_proxy_send_command(
},
};
let res = RUNTIME.block_on(async move { client.send_command(cmd).await });
let res = run_sync(async move { client.send_command(cmd).await });
match res {
Ok(Some(r)) => {
@@ -282,7 +282,7 @@ pub unsafe extern "C" fn debug_proxy_read_response(
}
let client = unsafe { &mut (*handle).0 };
let res = RUNTIME.block_on(async move { client.read_response().await });
let res = run_sync(async move { client.read_response().await });
match res {
Ok(Some(r)) => {
@@ -326,7 +326,7 @@ pub unsafe extern "C" fn debug_proxy_send_raw(
let client = unsafe { &mut (*handle).0 };
let data_slice = unsafe { std::slice::from_raw_parts(data, len) };
let res = 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 {
Ok(_) => null_mut(),
@@ -358,7 +358,7 @@ pub unsafe extern "C" fn debug_proxy_read(
}
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 {
Ok(r) => {
@@ -416,7 +416,7 @@ pub unsafe extern "C" fn debug_proxy_set_argv(
.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 {
Ok(r) => {
@@ -450,7 +450,7 @@ pub unsafe extern "C" fn debug_proxy_send_ack(
}
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 {
Ok(_) => null_mut(),
@@ -477,7 +477,7 @@ pub unsafe extern "C" fn debug_proxy_send_nack(
}
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 {
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 crate::{IdeviceFfiError, RUNTIME, ffi_err, remote_server::RemoteServerHandle};
use crate::{IdeviceFfiError, dvt::remote_server::RemoteServerHandle, ffi_err, run_sync};
/// Opaque handle to a ProcessControlClient
pub struct LocationSimulationHandle<'a>(pub LocationSimulationClient<'a, Box<dyn ReadWrite>>);
@@ -31,7 +31,7 @@ pub unsafe extern "C" fn location_simulation_new(
}
let server = unsafe { &mut (*server).0 };
let res = RUNTIME.block_on(async move { LocationSimulationClient::new(server).await });
let res = run_sync(async move { LocationSimulationClient::new(server).await });
match res {
Ok(client) => {
@@ -76,7 +76,7 @@ pub unsafe extern "C" fn location_simulation_clear(
}
let client = unsafe { &mut (*handle).0 };
let res = RUNTIME.block_on(async move { client.clear().await });
let res = run_sync(async move { client.clear().await });
match res {
Ok(_) => null_mut(),
@@ -107,7 +107,7 @@ pub unsafe extern "C" fn location_simulation_set(
}
let client = unsafe { &mut (*handle).0 };
let res = RUNTIME.block_on(async move { client.set(latitude, longitude).await });
let res = run_sync(async move { client.set(latitude, longitude).await });
match res {
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 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
pub struct ProcessControlHandle<'a>(pub ProcessControlClient<'a, Box<dyn ReadWrite>>);
@@ -35,7 +35,7 @@ pub unsafe extern "C" fn process_control_new(
}
let server = unsafe { &mut (*server).0 };
let res = RUNTIME.block_on(async move { ProcessControlClient::new(server).await });
let res = run_sync(async move { ProcessControlClient::new(server).await });
match res {
Ok(client) => {
@@ -128,7 +128,7 @@ pub unsafe extern "C" fn process_control_launch_app(
}
let client = unsafe { &mut (*handle).0 };
let res = RUNTIME.block_on(async move {
let res = run_sync(async move {
client
.launch_app(
bundle_id,
@@ -170,7 +170,7 @@ pub unsafe extern "C" fn process_control_kill_app(
}
let client = unsafe { &mut (*handle).0 };
let res = RUNTIME.block_on(async move { client.kill_app(pid).await });
let res = run_sync(async move { client.kill_app(pid).await });
match res {
Ok(_) => null_mut(),
@@ -199,7 +199,7 @@ pub unsafe extern "C" fn process_control_disable_memory_limit(
}
let client = unsafe { &mut (*handle).0 };
let res = 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 {
Ok(_) => null_mut(),

View File

@@ -4,7 +4,7 @@ use std::ptr::null_mut;
use crate::core_device_proxy::AdapterHandle;
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::{IdeviceError, ReadWrite, RsdService};
@@ -37,7 +37,7 @@ pub unsafe extern "C" fn remote_server_new(
let res: Result<RemoteServerClient<Box<dyn ReadWrite>>, IdeviceError> =
match wrapper.inner.take() {
Some(stream) => RUNTIME.block_on(async move {
Some(stream) => run_sync(async move {
let mut client = RemoteServerClient::new(stream);
client.read_message(0).await?;
Ok(client)
@@ -77,7 +77,7 @@ pub unsafe extern "C" fn remote_server_connect_rsd(
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let res: Result<RemoteServerClient<Box<dyn ReadWrite>>, IdeviceError> =
RUNTIME.block_on(async move {
run_sync_local(async move {
let provider_ref = unsafe { &mut (*provider).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 code = err.code();
let msg = CString::new(err.to_string())
let msg = CString::new(format!("{:?}", err))
.unwrap_or_else(|_| CString::new("invalid error").unwrap());
let raw_msg = msg.into_raw();

View File

@@ -6,7 +6,9 @@ use idevice::{
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);
@@ -28,11 +30,11 @@ pub unsafe extern "C" fn heartbeat_connect(
client: *mut *mut HeartbeatClientHandle,
) -> *mut IdeviceFfiError {
if provider.is_null() || client.is_null() {
log::error!("Null pointer provided");
tracing::error!("Null pointer provided");
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 };
// Connect using the reference
HeartbeatClient::connect(provider_ref).await
@@ -95,7 +97,7 @@ pub unsafe extern "C" fn heartbeat_send_polo(
if client.is_null() {
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 };
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() {
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 };
client_ref.get_marco(interval).await
});
@@ -150,7 +152,7 @@ pub unsafe extern "C" fn heartbeat_get_marco(
#[unsafe(no_mangle)]
pub unsafe extern "C" fn heartbeat_client_free(handle: *mut HeartbeatClientHandle) {
if !handle.is_null() {
log::debug!("Freeing installation_proxy_client");
tracing::debug!("Freeing installation_proxy_client");
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 crate::{IdeviceFfiError, IdeviceHandle, RUNTIME, ffi_err, provider::IdeviceProviderHandle};
use crate::{
IdeviceFfiError, IdeviceHandle, ffi_err, provider::IdeviceProviderHandle, run_sync_local,
};
pub struct InstallationProxyClientHandle(pub InstallationProxyClient);
@@ -30,11 +32,11 @@ pub unsafe extern "C" fn installation_proxy_connect(
client: *mut *mut InstallationProxyClientHandle,
) -> *mut IdeviceFfiError {
if provider.is_null() || client.is_null() {
log::error!("Null pointer provided");
tracing::error!("Null pointer provided");
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 };
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,
) -> *mut IdeviceFfiError {
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);
}
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| {
apps.into_values()
.map(|v| PlistWrapper::new_node(v).into_ptr())
@@ -143,7 +145,8 @@ pub unsafe extern "C" fn installation_proxy_get_apps(
});
match res {
Ok(mut r) => {
Ok(r) => {
let mut r = r.into_boxed_slice();
let ptr = r.as_mut_ptr();
let len = r.len();
std::mem::forget(r);
@@ -171,7 +174,7 @@ pub unsafe extern "C" fn installation_proxy_client_free(
handle: *mut InstallationProxyClientHandle,
) {
if !handle.is_null() {
log::debug!("Freeing installation_proxy_client");
tracing::debug!("Freeing installation_proxy_client");
let _ = unsafe { Box::from_raw(handle) };
}
}
@@ -210,7 +213,7 @@ pub unsafe extern "C" fn installation_proxy_install(
}
.map(|x| x.borrow_self().clone());
let res = RUNTIME.block_on(async {
let res = run_sync_local(async {
unsafe { &mut *client }
.0
.install(package_path, options)
@@ -261,7 +264,7 @@ pub unsafe extern "C" fn installation_proxy_install_with_callback(
}
.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 {
callback(progress, context);
};
@@ -312,7 +315,7 @@ pub unsafe extern "C" fn installation_proxy_upgrade(
}
.map(|x| x.borrow_self().clone());
let res = RUNTIME.block_on(async {
let res = run_sync_local(async {
unsafe { &mut *client }
.0
.upgrade(package_path, options)
@@ -363,7 +366,7 @@ pub unsafe extern "C" fn installation_proxy_upgrade_with_callback(
}
.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 {
callback(progress, context);
};
@@ -414,7 +417,7 @@ pub unsafe extern "C" fn installation_proxy_uninstall(
}
.map(|x| x.borrow_self().clone());
let res = RUNTIME.block_on(async {
let res = run_sync_local(async {
unsafe { &mut *client }
.0
.uninstall(bundle_id, options)
@@ -465,7 +468,7 @@ pub unsafe extern "C" fn installation_proxy_uninstall_with_callback(
}
.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 {
callback(progress, context);
};
@@ -527,7 +530,7 @@ pub unsafe extern "C" fn installation_proxy_check_capabilities_match(
}
.map(|x| x.borrow_self().clone());
let res = RUNTIME.block_on(async {
let res = run_sync_local(async {
unsafe { &mut *client }
.0
.check_capabilities_match(capabilities, options)
@@ -577,7 +580,7 @@ pub unsafe extern "C" fn installation_proxy_browse(
}
.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| {
apps.into_iter()
.map(|v| PlistWrapper::new_node(v).into_ptr())

View File

@@ -12,13 +12,17 @@ pub mod core_device;
pub mod core_device_proxy;
#[cfg(feature = "debug_proxy")]
pub mod debug_proxy;
#[cfg(feature = "diagnostics_relay")]
pub mod diagnostics_relay;
#[cfg(feature = "dvt")]
pub mod dvt;
mod errors;
#[cfg(feature = "heartbeat")]
pub mod heartbeat;
#[cfg(feature = "house_arrest")]
pub mod house_arrest;
#[cfg(feature = "installation_proxy")]
pub mod installation_proxy;
#[cfg(feature = "location_simulation")]
pub mod location_simulation;
pub mod lockdown;
pub mod logging;
#[cfg(feature = "misagent")]
@@ -28,13 +32,11 @@ pub mod mobile_image_mounter;
#[cfg(feature = "syslog_relay")]
pub mod os_trace_relay;
mod pairing_file;
#[cfg(feature = "dvt")]
pub mod process_control;
pub mod provider;
#[cfg(feature = "dvt")]
pub mod remote_server;
#[cfg(feature = "xpc")]
pub mod rsd;
#[cfg(feature = "screenshotr")]
pub mod screenshotr;
#[cfg(feature = "springboardservices")]
pub mod springboardservices;
#[cfg(feature = "syslog_relay")]
@@ -50,8 +52,9 @@ pub use pairing_file::*;
use idevice::{Idevice, IdeviceSocket, ReadWrite};
use once_cell::sync::Lazy;
use plist_ffi::PlistWrapper;
use std::{
ffi::{CStr, CString, c_char},
ffi::{CStr, CString, c_char, c_void},
ptr::null_mut,
};
use tokio::runtime::{self, Runtime};
@@ -59,7 +62,7 @@ use tokio::runtime::{self, Runtime};
#[cfg(unix)]
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()
.enable_io()
.enable_time()
@@ -67,6 +70,39 @@ static RUNTIME: Lazy<Runtime> = Lazy::new(|| {
.unwrap()
});
static LOCAL_RUNTIME: Lazy<Runtime> = Lazy::new(|| {
runtime::Builder::new_multi_thread()
.enable_all()
.build()
.unwrap()
});
/// Spawn the future on the global runtime and block current (FFI) thread until result.
/// F and R must be Send + 'static.
pub fn run_sync<F, R>(fut: F) -> R
where
F: std::future::Future<Output = R> + Send + 'static,
R: Send + 'static,
{
let (tx, rx) = std::sync::mpsc::sync_channel(1);
GLOBAL_RUNTIME.handle().spawn(async move {
let res = fut.await;
// best-effort send; ignore if receiver dropped
let _ = tx.send(res);
});
rx.recv().expect("runtime worker panicked")
}
pub fn run_sync_local<F, R>(fut: F) -> R
where
F: std::future::Future<Output = R>,
R: 'static,
{
LOCAL_RUNTIME.block_on(fut)
}
pub const LOCKDOWN_PORT: u16 = 62078;
#[repr(C)]
@@ -126,6 +162,47 @@ pub unsafe extern "C" fn idevice_new(
null_mut()
}
/// Creates an Idevice object from a socket file descriptor
///
/// # Safety
/// The socket FD must be valid.
/// The pointers must be valid and non-null.
#[cfg(unix)]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn idevice_from_fd(
fd: i32,
label: *const c_char,
idevice: *mut *mut IdeviceHandle,
) -> *mut IdeviceFfiError {
if label.is_null() || idevice.is_null() || fd == 0 {
return ffi_err!(IdeviceError::FfiInvalidArg);
}
// Get socket ownership
let fd = unsafe { libc::dup(fd) };
let socket = unsafe { <std::net::TcpStream as std::os::fd::FromRawFd>::from_raw_fd(fd) };
if let Err(e) = socket.set_nonblocking(true) {
return ffi_err!(e);
}
let socket = match run_sync(async move { tokio::net::TcpStream::from_std(socket) }) {
Ok(s) => s,
Err(e) => return ffi_err!(e),
};
// Convert C string to Rust string
let c_str = match unsafe { CStr::from_ptr(label).to_str() } {
Ok(s) => s,
Err(_) => return ffi_err!(IdeviceError::FfiInvalidString),
};
// Create new Idevice instance
let dev = Idevice::new(Box::new(socket), c_str);
let boxed = Box::new(IdeviceHandle(dev));
unsafe { *idevice = Box::into_raw(boxed) };
null_mut()
}
/// Creates a new Idevice connection
///
/// # Arguments
@@ -152,7 +229,7 @@ pub unsafe extern "C" fn idevice_new_tcp_socket(
use crate::util::SockAddr;
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);
}
let addr = addr as *const SockAddr;
@@ -167,7 +244,7 @@ pub unsafe extern "C" fn idevice_new_tcp_socket(
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?;
Ok::<idevice::Idevice, idevice::IdeviceError>(idevice::Idevice::new(
Box::new(stream),
@@ -210,7 +287,7 @@ pub unsafe extern "C" fn idevice_get_type(
let dev = unsafe { &mut (*idevice).0 };
// Run the get_type method in the runtime
let result = RUNTIME.block_on(async { dev.get_type().await });
let result = run_sync(async { dev.get_type().await });
match result {
Ok(type_str) => match CString::new(type_str) {
@@ -244,7 +321,7 @@ pub unsafe extern "C" fn idevice_rsd_checkin(idevice: *mut IdeviceHandle) -> *mu
let dev = unsafe { &mut (*idevice).0 };
// Run the rsd_checkin method in the runtime
let result = RUNTIME.block_on(async { dev.rsd_checkin().await });
let result = run_sync(async { dev.rsd_checkin().await });
match result {
Ok(_) => null_mut(),
@@ -268,6 +345,7 @@ pub unsafe extern "C" fn idevice_rsd_checkin(idevice: *mut IdeviceHandle) -> *mu
pub unsafe extern "C" fn idevice_start_session(
idevice: *mut IdeviceHandle,
pairing_file: *const IdevicePairingFile,
legacy: bool,
) -> *mut IdeviceFfiError {
if idevice.is_null() || pairing_file.is_null() {
return ffi_err!(IdeviceError::FfiInvalidArg);
@@ -280,7 +358,7 @@ pub unsafe extern "C" fn idevice_start_session(
let pf = unsafe { &(*pairing_file).0 };
// Run the start_session method in the runtime
let result = RUNTIME.block_on(async { dev.start_session(pf).await });
let result = run_sync(async move { dev.start_session(pf, legacy).await });
match result {
Ok(_) => null_mut(),
@@ -303,6 +381,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
///
/// # Arguments
@@ -329,6 +418,36 @@ pub unsafe extern "C" fn idevice_string_free(string: *mut c_char) {
#[unsafe(no_mangle)]
pub unsafe extern "C" fn idevice_data_free(data: *mut u8, len: usize) {
if !data.is_null() {
let _ = unsafe { 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 crate::{
IdeviceFfiError, IdeviceHandle, IdevicePairingFile, RUNTIME, ffi_err,
provider::IdeviceProviderHandle,
IdeviceFfiError, IdeviceHandle, IdevicePairingFile, ffi_err, provider::IdeviceProviderHandle,
run_sync_local,
};
pub struct LockdowndClientHandle(pub LockdownClient);
@@ -30,11 +30,11 @@ pub unsafe extern "C" fn lockdownd_connect(
client: *mut *mut LockdowndClientHandle,
) -> *mut IdeviceFfiError {
if provider.is_null() || client.is_null() {
log::error!("Null pointer provided");
tracing::error!("Null pointer provided");
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 };
LockdownClient::connect(provider_ref).await
});
@@ -97,7 +97,7 @@ pub unsafe extern "C" fn lockdownd_start_session(
client: *mut LockdowndClientHandle,
pairing_file: *mut IdevicePairingFile,
) -> *mut IdeviceFfiError {
let res: Result<(), IdeviceError> = RUNTIME.block_on(async move {
let res: Result<(), IdeviceError> = run_sync_local(async move {
let client_ref = unsafe { &mut (*client).0 };
let pairing_file_ref = unsafe { &(*pairing_file).0 };
@@ -140,7 +140,7 @@ pub unsafe extern "C" fn lockdownd_start_service(
.to_string_lossy()
.into_owned();
let res: Result<(u16, bool), IdeviceError> = RUNTIME.block_on(async move {
let res: Result<(u16, bool), IdeviceError> = run_sync_local(async move {
let client_ref = unsafe { &mut (*client).0 };
client_ref.start_service(identifier).await
});
@@ -179,7 +179,7 @@ pub unsafe extern "C" fn lockdownd_get_value(
domain: *const libc::c_char,
out_plist: *mut plist_t,
) -> *mut IdeviceFfiError {
if out_plist.is_null() {
if client.is_null() || out_plist.is_null() {
return ffi_err!(IdeviceError::FfiInvalidArg);
}
@@ -205,7 +205,7 @@ pub unsafe extern "C" fn lockdownd_get_value(
})
};
let res: Result<plist::Value, IdeviceError> = RUNTIME.block_on(async move {
let res: Result<plist::Value, IdeviceError> = run_sync_local(async move {
let client_ref = unsafe { &mut (*client).0 };
client_ref.get_value(value, domain).await
});
@@ -221,6 +221,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
///
/// # Arguments
@@ -232,7 +315,7 @@ pub unsafe extern "C" fn lockdownd_get_value(
#[unsafe(no_mangle)]
pub unsafe extern "C" fn lockdownd_client_free(handle: *mut LockdowndClientHandle) {
if !handle.is_null() {
log::debug!("Freeing lockdownd_client");
tracing::debug!("Freeing lockdownd_client");
let _ = unsafe { Box::from_raw(handle) };
}
}

View File

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

View File

@@ -6,7 +6,7 @@ use std::ptr::null_mut;
use idevice::{IdeviceError, IdeviceService, misagent::MisagentClient, provider::IdeviceProvider};
use crate::{IdeviceFfiError, RUNTIME, ffi_err, provider::IdeviceProviderHandle};
use crate::{IdeviceFfiError, ffi_err, provider::IdeviceProviderHandle, run_sync_local};
pub struct MisagentClientHandle(pub MisagentClient);
@@ -28,11 +28,11 @@ pub unsafe extern "C" fn misagent_connect(
client: *mut *mut MisagentClientHandle,
) -> *mut IdeviceFfiError {
if provider.is_null() || client.is_null() {
log::error!("Null pointer provided");
tracing::error!("Null pointer provided");
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 };
MisagentClient::connect(provider_ref).await
});
@@ -72,7 +72,7 @@ pub unsafe extern "C" fn misagent_install(
let profile = unsafe { std::slice::from_raw_parts(profile_data, profile_len) }.to_vec();
let res = 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 {
Ok(_) => null_mut(),
@@ -105,7 +105,7 @@ pub unsafe extern "C" fn misagent_remove(
.to_string_lossy()
.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 {
Ok(_) => null_mut(),
@@ -143,7 +143,7 @@ pub unsafe extern "C" fn misagent_copy_all(
}
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 {
Ok(profiles) => {
@@ -199,7 +199,7 @@ pub unsafe extern "C" fn misagent_free_profiles(
for (ptr, len) in profiles.iter_mut().zip(lens.iter()) {
if !ptr.is_null() {
let _ = unsafe { Box::from_raw(std::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)]
pub unsafe extern "C" fn misagent_client_free(handle: *mut MisagentClientHandle) {
if !handle.is_null() {
log::debug!("Freeing misagent_client");
tracing::debug!("Freeing misagent_client");
let _ = unsafe { Box::from_raw(handle) };
}
}

View File

@@ -8,7 +8,9 @@ use idevice::{
use plist::Value;
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);
@@ -30,11 +32,11 @@ pub unsafe extern "C" fn image_mounter_connect(
client: *mut *mut ImageMounterHandle,
) -> *mut IdeviceFfiError {
if provider.is_null() || client.is_null() {
log::error!("Null pointer provided");
tracing::error!("Null pointer provided");
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 };
ImageMounter::connect(provider_ref).await
});
@@ -90,7 +92,7 @@ pub unsafe extern "C" fn image_mounter_new(
#[unsafe(no_mangle)]
pub unsafe extern "C" fn image_mounter_free(handle: *mut ImageMounterHandle) {
if !handle.is_null() {
log::debug!("Freeing image_mounter_client");
tracing::debug!("Freeing image_mounter_client");
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_len: *mut libc::size_t,
) -> *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 };
client_ref.copy_devices().await
});
@@ -171,7 +173,7 @@ pub unsafe extern "C" fn image_mounter_lookup_image(
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 };
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 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 };
client_ref
.upload_image(image_type, image_slice, signature_slice.to_vec())
@@ -295,7 +297,7 @@ pub unsafe extern "C" fn image_mounter_mount_image(
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 };
client_ref
.mount_image(
@@ -340,7 +342,7 @@ pub unsafe extern "C" fn image_mounter_unmount_image(
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 };
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);
}
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 };
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 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 };
client_ref
.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 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 };
client_ref
.query_personalization_manifest(image_type, signature_slice.to_vec())
@@ -521,7 +523,7 @@ pub unsafe extern "C" fn image_mounter_query_nonce(
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 };
client_ref.query_nonce(image_type).await
});
@@ -573,7 +575,7 @@ pub unsafe extern "C" fn image_mounter_query_personalization_identifiers(
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 };
client_ref
.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(
client: *mut ImageMounterHandle,
) -> *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 };
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(
client: *mut ImageMounterHandle,
) -> *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 };
client_ref.roll_cryptex_nonce().await
});
@@ -692,7 +694,7 @@ pub unsafe extern "C" fn image_mounter_mount_personalized(
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 provider_ref: &dyn IdeviceProvider = unsafe { &*(*provider).0 };
client_ref
@@ -769,7 +771,7 @@ pub unsafe extern "C" fn image_mounter_mount_personalized_with_callback(
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 provider_ref: &dyn IdeviceProvider = unsafe { &*(*provider).0 };

View File

@@ -5,7 +5,8 @@ use idevice::{
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 OsTraceRelayReceiverHandle(pub idevice::os_trace_relay::OsTraceRelayReceiver);
@@ -46,11 +47,11 @@ pub unsafe extern "C" fn os_trace_relay_connect(
client: *mut *mut OsTraceRelayClientHandle,
) -> *mut IdeviceFfiError {
if provider.is_null() {
log::error!("Null pointer provided");
tracing::error!("Null pointer provided");
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 };
OsTraceRelayClient::connect(provider_ref).await
});
@@ -78,7 +79,7 @@ pub unsafe extern "C" fn os_trace_relay_connect(
#[unsafe(no_mangle)]
pub unsafe extern "C" fn os_trace_relay_free(handle: *mut OsTraceRelayClientHandle) {
if !handle.is_null() {
log::debug!("Freeing os trace relay client");
tracing::debug!("Freeing os trace relay client");
let _ = unsafe { Box::from_raw(handle) };
}
}
@@ -102,7 +103,7 @@ pub unsafe extern "C" fn os_trace_relay_start_trace(
pid: *const u32,
) -> *mut IdeviceFfiError {
if receiver.is_null() || client.is_null() {
log::error!("Null pointer provided");
tracing::error!("Null pointer provided");
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 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 {
Ok(relay) => {
@@ -137,7 +138,7 @@ pub unsafe extern "C" fn os_trace_relay_start_trace(
#[unsafe(no_mangle)]
pub unsafe extern "C" fn os_trace_relay_receiver_free(handle: *mut OsTraceRelayReceiverHandle) {
if !handle.is_null() {
log::debug!("Freeing syslog relay client");
tracing::debug!("Freeing syslog relay client");
let _ = unsafe { Box::from_raw(handle) };
}
}
@@ -158,7 +159,7 @@ pub unsafe extern "C" fn os_trace_relay_get_pid_list(
client: *mut OsTraceRelayClientHandle,
list: *mut *mut Vec<u64>,
) -> *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 {
Ok(r) => {
@@ -186,11 +187,11 @@ pub unsafe extern "C" fn os_trace_relay_next(
log: *mut *mut OsTraceLog,
) -> *mut IdeviceFfiError {
if client.is_null() {
log::error!("Null pointer provided");
tracing::error!("Null pointer provided");
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 {
Ok(r) => {

View File

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

View File

@@ -7,7 +7,7 @@ use std::ptr::{self, null_mut};
use idevice::rsd::RsdHandshake;
use crate::{IdeviceFfiError, RUNTIME, ReadWriteOpaque, ffi_err};
use crate::{IdeviceFfiError, ReadWriteOpaque, ffi_err, run_sync};
/// Opaque handle to an RsdHandshake
#[derive(Clone)]
@@ -63,10 +63,10 @@ pub unsafe extern "C" fn rsd_handshake_new(
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let wrapper = unsafe { &mut *socket };
let mut wrapper = unsafe { Box::from_raw(socket) };
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 => {
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

@@ -8,7 +8,10 @@ use idevice::{
springboardservices::SpringBoardServicesClient,
};
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);
@@ -30,11 +33,11 @@ pub unsafe extern "C" fn springboard_services_connect(
client: *mut *mut SpringBoardServicesClientHandle,
) -> *mut IdeviceFfiError {
if provider.is_null() || client.is_null() {
log::error!("Null pointer provided");
tracing::error!("Null pointer provided");
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 };
SpringBoardServicesClient::connect(provider_ref).await
});
@@ -103,7 +106,7 @@ pub unsafe extern "C" fn springboard_services_get_icon(
out_result_len: *mut libc::size_t,
) -> *mut IdeviceFfiError {
if client.is_null() || out_result.is_null() || out_result_len.is_null() {
log::error!("Invalid arguments: {client:?}, {out_result:?}");
tracing::error!("Invalid arguments: {client:?}, {out_result:?}");
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let client = unsafe { &mut *client };
@@ -115,7 +118,7 @@ pub unsafe extern "C" fn springboard_services_get_icon(
};
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 {
Ok(r) => {
@@ -134,6 +137,132 @@ 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),
}
}
/// Frees an SpringBoardServicesClient handle
///
/// # Arguments
@@ -145,7 +274,7 @@ pub unsafe extern "C" fn springboard_services_get_icon(
#[unsafe(no_mangle)]
pub unsafe extern "C" fn springboard_services_free(handle: *mut SpringBoardServicesClientHandle) {
if !handle.is_null() {
log::debug!("Freeing springboard_services_client");
tracing::debug!("Freeing springboard_services_client");
let _ = unsafe { Box::from_raw(handle) };
}
}

View File

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

View File

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

View File

@@ -2,22 +2,27 @@
use std::{
ffi::{CStr, CString, c_char},
pin::Pin,
ptr::null_mut,
};
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},
};
use futures::{Stream, StreamExt};
use idevice::{
IdeviceError,
usbmuxd::{UsbmuxdAddr, UsbmuxdConnection, UsbmuxdDevice},
usbmuxd::{UsbmuxdAddr, UsbmuxdConnection, UsbmuxdDevice, UsbmuxdListenEvent},
};
use log::error;
use tracing::error;
pub struct UsbmuxdConnectionHandle(pub UsbmuxdConnection);
pub struct UsbmuxdAddrHandle(pub UsbmuxdAddr);
pub struct UsbmuxdDeviceHandle(pub UsbmuxdDevice);
pub struct UsbmuxdListenerHandle<'a>(
Pin<Box<dyn Stream<Item = Result<UsbmuxdListenEvent, IdeviceError>> + 'a>>,
);
/// Connects to a usbmuxd instance over TCP
///
@@ -52,7 +57,7 @@ pub unsafe extern "C" fn idevice_usbmuxd_new_tcp_connection(
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?;
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),
};
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?;
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() {
Ok(a) => a,
Err(e) => {
log::error!("Invalid address set: {e:?}");
tracing::error!("Invalid address set: {e:?}");
return ffi_err!(IdeviceError::FfiInvalidArg);
}
};
let res: Result<UsbmuxdConnection, IdeviceError> =
RUNTIME.block_on(async move { addr.connect(tag).await });
run_sync(async move { addr.connect(tag).await });
match res {
Ok(r) => {
@@ -171,7 +176,7 @@ pub unsafe extern "C" fn idevice_usbmuxd_get_devices(
}
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 {
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 {
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 {
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.
///
/// 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 res = RUNTIME.block_on(async { conn.get_buid().await });
let res = run_sync(async { conn.get_buid().await });
match res {
Ok(buid_str) => match CString::new(buid_str) {

View File

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

View File

@@ -2,7 +2,7 @@
name = "idevice"
description = "A Rust library to interact with services on iOS devices."
authors = ["Jackson Coxson"]
version = "0.1.42"
version = "0.1.53"
edition = "2024"
license = "MIT"
documentation = "https://docs.rs/idevice"
@@ -11,26 +11,28 @@ keywords = ["lockdownd", "ios"]
[dependencies]
tokio = { version = "1.43", features = ["io-util"] }
tokio-rustls = { version = "0.26", default-features = false }
tokio = { version = "1", features = ["io-util"] }
tokio-rustls = { version = "0.26", default-features = false, optional = true }
rustls = { version = "0.23", default-features = false, features = [
"std",
"tls12",
] }
crossfire = { version = "2.0", optional = true } # TODO: update to 2.1 when it comes out
], optional = true }
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"] }
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" }
log = { version = "0.4" }
env_logger = { version = "0.11" }
tracing = { version = "0.1.41" }
base64 = { version = "0.22" }
indexmap = { version = "2.7", features = ["serde"], optional = true }
uuid = { version = "1.12", features = ["serde", "v4"], optional = true }
chrono = { version = "0.4.40", optional = true, default-features = false, features = [
indexmap = { version = "2.11", features = ["serde"], optional = true }
uuid = { version = "1.18", features = ["serde", "v4"], optional = true }
chrono = { version = "0.4", optional = true, default-features = false, features = [
"serde",
] }
@@ -39,7 +41,7 @@ json = { version = "0.12", optional = true }
byteorder = { version = "1.5", optional = true }
bytes = { version = "1.10", optional = true }
reqwest = { version = "0.12", features = [
reqwest = { version = "0.13", features = [
"json",
], optional = true, default-features = false }
rand = { version = "0.9", optional = true }
@@ -65,14 +67,16 @@ bytes = "1.10.1"
[features]
default = ["aws-lc"]
aws-lc = ["rustls/aws-lc-rs", "tokio-rustls/aws-lc-rs"]
ring = ["rustls/ring", "tokio-rustls/ring"]
aws-lc = ["rustls", "rustls/aws-lc-rs", "tokio-rustls/aws-lc-rs"]
ring = ["rustls", "rustls/ring", "tokio-rustls/ring"]
rustls = ["dep:rustls", "dep:tokio-rustls"]
openssl = ["dep:openssl", "dep:tokio-openssl"]
afc = ["dep:chrono"]
afc = ["dep:chrono", "dep:futures"]
amfi = []
bt_packet_logger = []
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"]
crashreportcopymobile = ["afc"]
debug_proxy = []
@@ -87,6 +91,7 @@ installation_proxy = [
"async_zip/deflate",
"tokio/fs",
]
installcoordination_proxy = []
springboardservices = []
misagent = []
mobile_image_mounter = ["dep:sha2"]
@@ -111,7 +116,7 @@ tunnel_tcp_stack = [
]
tss = ["dep:uuid", "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"]
full = [
"afc",
@@ -127,6 +132,7 @@ full = [
"heartbeat",
"house_arrest",
"installation_proxy",
"installcoordination_proxy",
"location_simulation",
"misagent",
"mobile_image_mounter",

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")]
#![warn(missing_debug_implementations)]
#![warn(missing_copy_implementations)]
// Jackson Coxson
#[cfg(feature = "pair")]
#[cfg(all(feature = "pair", feature = "rustls"))]
mod ca;
pub mod cursor;
mod obfuscation;
pub mod pairing_file;
mod plist_macro;
pub mod provider;
#[cfg(feature = "rustls")]
mod sni;
#[cfg(feature = "tunnel_tcp_stack")]
pub mod tcp;
@@ -15,7 +19,6 @@ pub mod tss;
pub mod tunneld;
#[cfg(feature = "usbmuxd")]
pub mod usbmuxd;
mod util;
pub mod utils;
#[cfg(feature = "xpc")]
pub mod xpc;
@@ -26,8 +29,9 @@ pub use services::*;
#[cfg(feature = "xpc")]
pub use xpc::RemoteXpcClient;
use log::{debug, error, trace};
use plist_macro::{plist, pretty_print_dictionary, pretty_print_plist};
use provider::{IdeviceProvider, RsdProvider};
#[cfg(feature = "rustls")]
use rustls::{crypto::CryptoProvider, pki_types::ServerName};
use std::{
io::{self, BufWriter},
@@ -35,8 +39,7 @@ use std::{
};
use thiserror::Error;
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
pub use util::{pretty_print_dictionary, pretty_print_plist};
use tracing::{debug, trace};
use crate::services::lockdown::LockdownClient;
@@ -72,6 +75,22 @@ pub trait IdeviceService: Sized {
#[allow(async_fn_in_trait)]
async fn connect(provider: &dyn IdeviceProvider) -> Result<Self, IdeviceError> {
let mut lockdown = LockdownClient::connect(provider).await?;
#[cfg(feature = "openssl")]
let legacy = lockdown
.get_value(Some("ProductVersion"), None)
.await
.ok()
.as_ref()
.and_then(|x| x.as_string())
.and_then(|x| x.split(".").next())
.and_then(|x| x.parse::<u8>().ok())
.map(|x| x < 5)
.unwrap_or(false);
#[cfg(not(feature = "openssl"))]
let legacy = false;
lockdown
.start_session(&provider.get_pairing_file().await?)
.await?;
@@ -86,7 +105,7 @@ pub trait IdeviceService: Sized {
let mut idevice = provider.connect(port).await?;
if ssl {
idevice
.start_session(&provider.get_pairing_file().await?)
.start_session(&provider.get_pairing_file().await?, legacy)
.await?;
}
@@ -128,6 +147,7 @@ pub type IdeviceSocket = Box<dyn ReadWrite>;
///
/// Manages the connection socket and provides methods for common device operations
/// and message exchange.
#[derive(Debug)]
pub struct Idevice {
/// The underlying connection socket, boxed for dynamic dispatch
socket: Option<Box<dyn ReadWrite>>,
@@ -175,7 +195,7 @@ impl Idevice {
/// # Errors
/// Returns `IdeviceError` if communication fails or response is invalid
pub async fn get_type(&mut self) -> Result<String, IdeviceError> {
let req = crate::plist!({
let req = plist!({
"Label": self.label.clone(),
"Request": "QueryType",
});
@@ -195,7 +215,7 @@ impl Idevice {
/// # Errors
/// Returns `IdeviceError` if the protocol sequence isn't followed correctly
pub async fn rsd_checkin(&mut self) -> Result<(), IdeviceError> {
let req = crate::plist!({
let req = plist!({
"Label": self.label.clone(),
"ProtocolVersion": "2",
"Request": "RSDCheckin",
@@ -287,6 +307,75 @@ impl Idevice {
self.send_raw_with_progress(message, |_| async {}, ()).await
}
/// Sends raw binary data via vectored I/O
///
/// # Arguments
/// * `bufs` - The buffers to send
///
/// # Errors
/// Returns `IdeviceError` if transmission fails
pub async fn send_raw_vectored(
&mut self,
bufs: &[std::io::IoSlice<'_>],
) -> Result<(), IdeviceError> {
if let Some(socket) = &mut self.socket {
let mut curr_idx = 0;
let mut curr_offset = 0;
while curr_idx < bufs.len() {
let mut iovec = Vec::new();
let mut accumulated_len = 0;
let max_chunk = 1024 * 64;
// Add partial first slice
let first_avail = bufs[curr_idx].len() - curr_offset;
let to_take_first = std::cmp::min(first_avail, max_chunk);
iovec.push(std::io::IoSlice::new(
&bufs[curr_idx][curr_offset..curr_offset + to_take_first],
));
accumulated_len += to_take_first;
// Add others up to max_chunk
let mut temp_idx = curr_idx + 1;
while temp_idx < bufs.len() && accumulated_len < max_chunk {
let needed = max_chunk - accumulated_len;
let avail = bufs[temp_idx].len();
let take = std::cmp::min(avail, needed);
iovec.push(std::io::IoSlice::new(&bufs[temp_idx][..take]));
accumulated_len += take;
temp_idx += 1;
}
let n = socket.write_vectored(&iovec).await?;
if n == 0 {
return Err(io::Error::new(
io::ErrorKind::WriteZero,
"failed to write whole buffer",
)
.into());
}
// Advance cursor by n
let mut advanced = n;
while advanced > 0 && curr_idx < bufs.len() {
let available = bufs[curr_idx].len() - curr_offset;
if advanced < available {
curr_offset += advanced;
advanced = 0;
} else {
advanced -= available;
curr_idx += 1;
curr_offset = 0;
}
}
}
socket.flush().await?;
Ok(())
} else {
Err(IdeviceError::NoEstablishedConnection)
}
}
/// Sends raw binary data with progress callbacks
///
/// # Arguments
@@ -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);
}
};
if let Some(e) = IdeviceError::from_device_error_type(e.as_str(), &res) {
return Err(e);
} 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)
@@ -459,7 +554,16 @@ impl Idevice {
pub async fn start_session(
&mut self,
pairing_file: &pairing_file::PairingFile,
legacy: bool,
) -> Result<(), IdeviceError> {
#[cfg(feature = "rustls")]
{
if legacy {
tracing::warn!(
"Compiled with rustls, but connecting to legacy device! rustls does not support old SSL, this will fail."
);
}
if CryptoProvider::get_default().is_none() {
// rust-analyzer will choke on this block, don't worry about it
let crypto_provider: CryptoProvider = {
@@ -488,7 +592,9 @@ impl Idevice {
// My sanity while debugging the workspace crates are more important.
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()
}
};
@@ -497,7 +603,7 @@ impl Idevice {
// For whatever reason, getting the default provider will return None on iOS at
// random. Installing the default provider a second time will return an error, so
// we will log it but not propogate it. An issue should be opened with rustls.
log::error!("Failed to set crypto provider: {e:?}");
tracing::error!("Failed to set crypto provider: {e:?}");
}
}
let config = sni::create_client_config(pairing_file)?;
@@ -512,6 +618,30 @@ impl Idevice {
Ok(())
}
#[cfg(all(feature = "openssl", not(feature = "rustls")))]
{
let mut connector =
openssl::ssl::SslConnector::builder(openssl::ssl::SslMethod::tls())?;
if legacy {
connector.set_min_proto_version(Some(openssl::ssl::SslVersion::SSL3))?;
connector.set_max_proto_version(Some(openssl::ssl::SslVersion::TLS1))?;
connector.set_cipher_list("ALL:!aNULL:!eNULL:@SECLEVEL=0")?;
connector.set_options(openssl::ssl::SslOptions::ALLOW_UNSAFE_LEGACY_RENEGOTIATION);
}
let mut connector = connector.build().configure()?.into_ssl("ur mom")?;
connector.set_certificate(&pairing_file.host_certificate)?;
connector.set_private_key(&pairing_file.host_private_key)?;
connector.set_verify(openssl::ssl::SslVerifyMode::empty());
let socket = self.socket.take().unwrap();
let mut ssl_stream = tokio_openssl::SslStream::new(connector, socket)?;
std::pin::Pin::new(&mut ssl_stream).connect().await?;
self.socket = Some(Box::new(ssl_stream));
Ok(())
}
}
}
/// Comprehensive error type for all device communication failures
@@ -521,12 +651,24 @@ impl Idevice {
pub enum IdeviceError {
#[error("device socket io failed")]
Socket(#[from] io::Error) = -1,
#[cfg(feature = "rustls")]
#[error("PEM parse failed")]
PemParseFailed(#[from] rustls::pki_types::pem::Error) = -2,
#[cfg(feature = "rustls")]
#[error("TLS error")]
Rustls(#[from] rustls::Error) = -3,
#[cfg(all(feature = "openssl", not(feature = "rustls")))]
#[error("TLS error")]
Rustls(#[from] openssl::ssl::Error) = -3,
#[cfg(feature = "rustls")]
#[error("TLS verifiction build failed")]
TlsBuilderFailed(#[from] rustls::server::VerifierBuilderError) = -4,
#[cfg(all(feature = "openssl", not(feature = "rustls")))]
#[error("TLS verifiction build failed")]
TlsBuilderFailed(#[from] openssl::error::ErrorStack) = -4,
#[error("io on plist")]
Plist(#[from] plist::Error) = -5,
#[error("can't convert bytes to utf8")]
@@ -773,6 +915,7 @@ impl IdeviceError {
pub fn code(&self) -> i32 {
match self {
IdeviceError::Socket(_) => -1,
#[cfg(feature = "rustls")]
IdeviceError::PemParseFailed(_) => -2,
IdeviceError::Rustls(_) => -3,
IdeviceError::TlsBuilderFailed(_) => -4,

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

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
use std::io::SeekFrom;
use std::{io::SeekFrom, marker::PhantomPinned, pin::Pin};
use crate::IdeviceError;
use tokio::io::{AsyncRead, AsyncSeek, AsyncWrite};
use super::{
opcode::AfcOpcode,
packet::{AfcPacket, AfcPacketHeader},
use crate::{
IdeviceError,
afc::{
AfcClient,
inner_file::{InnerFileDescriptor, OwnedInnerFileDescriptor},
},
};
/// Maximum transfer size for file operations (64KB)
const MAX_TRANSFER: u64 = 64 * 1024; // this is what go-ios uses
/// Handle for an open file on the device.
/// Call close before dropping
#[derive(Debug)]
pub struct FileDescriptor<'a> {
pub(crate) client: &'a mut super::AfcClient,
pub(crate) fd: u64,
pub(crate) path: String,
inner: Pin<Box<InnerFileDescriptor<'a>>>,
}
impl FileDescriptor<'_> {
/// Generic helper to send an AFC packet and read the response
async fn send_packet(
&mut self,
opcode: AfcOpcode,
header_payload: Vec<u8>,
payload: Vec<u8>,
) -> Result<AfcPacket, IdeviceError> {
let header_len = header_payload.len() as u64 + AfcPacketHeader::LEN;
let header = AfcPacketHeader {
magic: super::MAGIC,
entire_len: header_len + payload.len() as u64,
header_payload_len: header_len,
packet_num: self.client.package_number,
operation: opcode,
};
self.client.package_number += 1;
#[derive(Debug)]
pub struct OwnedFileDescriptor {
inner: Pin<Box<OwnedInnerFileDescriptor>>,
}
let packet = AfcPacket {
header,
header_payload,
payload,
};
impl<'a> FileDescriptor<'a> {
/// create a new FileDescriptor from a raw fd
///
/// # Safety
/// make sure the fd is an opened file, and that you got it from a previous
/// FileDescriptor via `as_raw_fd()` method
pub unsafe fn new(client: &'a mut AfcClient, fd: u64, path: String) -> Self {
Self {
inner: Box::pin(InnerFileDescriptor {
client,
fd,
path,
pending_fut: None,
_m: PhantomPinned,
}),
}
}
self.client.send(packet).await?;
self.client.read().await
/// Closes the file descriptor
pub async fn close(self) -> Result<(), IdeviceError> {
self.inner.close().await
}
}
impl OwnedFileDescriptor {
/// create a new OwnedFileDescriptor from a raw fd
///
/// # Safety
/// make sure the fd is an opened file, and that you got it from a previous
/// OwnedFileDescriptor via `as_raw_fd()` method
pub unsafe fn new(client: AfcClient, fd: u64, path: String) -> Self {
Self {
inner: Box::pin(OwnedInnerFileDescriptor {
client,
fd,
path,
pending_fut: None,
_m: PhantomPinned,
}),
}
}
/// Closes the file descriptor
pub async fn close(self) -> Result<AfcClient, IdeviceError> {
self.inner.close().await
}
/// gets the owned afc
///
/// # Safety
/// this get's the afc out without closing, if you want to get the afc and close the file, use
/// `.close()`
pub unsafe fn get_inner_afc(self) -> AfcClient {
self.inner.get_inner_afc()
}
}
crate::impl_to_structs!(FileDescriptor<'_>, OwnedFileDescriptor; {
pub fn as_raw_fd(&self) -> u64 {
self.inner.fd
}
/// Returns the current cursor position for the file
pub async fn seek_tell(&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
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(())
self.inner.as_mut().seek_tell().await
}
/// Reads the entire contents of the file
///
/// # Returns
/// A vector containing the file's data
pub async fn read(&mut self) -> Result<Vec<u8>, IdeviceError> {
let seek_pos = self.seek_tell().await? as usize;
let file_info = self.client.get_file_info(&self.path).await?;
let mut bytes_left = file_info.size.saturating_sub(seek_pos);
let mut collected_bytes = Vec::with_capacity(bytes_left);
while bytes_left > 0 {
let mut header_payload = self.fd.to_le_bytes().to_vec();
header_payload.extend_from_slice(&MAX_TRANSFER.to_le_bytes());
let res = self
.send_packet(AfcOpcode::Read, header_payload, Vec::new())
.await?;
bytes_left -= res.payload.len();
collected_bytes.extend(res.payload);
}
Ok(collected_bytes)
pub async fn read_entire(&mut self) -> Result<Vec<u8>, IdeviceError> {
self.inner.as_mut().read().await
}
/// Writes data to the file
///
/// # Arguments
/// * `bytes` - Data to write to the file
pub async fn write(&mut self, bytes: &[u8]) -> Result<(), IdeviceError> {
for chunk in bytes.chunks(MAX_TRANSFER as usize) {
let header_payload = self.fd.to_le_bytes().to_vec();
self.send_packet(AfcOpcode::Write, header_payload, chunk.to_vec())
.await?;
pub async fn write_entire(&mut self, bytes: &[u8]) -> Result<(), IdeviceError> {
self.inner.as_mut().write(bytes).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 errors::AfcError;
use file::FileDescriptor;
use log::warn;
use opcode::{AfcFopenMode, AfcOpcode};
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 file;
mod inner_file;
mod inner_file_impl_macro;
pub mod opcode;
pub mod packet;
@@ -22,6 +28,7 @@ pub mod packet;
pub const MAGIC: u64 = 0x4141504c36414643;
/// Client for interacting with the AFC service on iOS devices
#[derive(Debug)]
pub struct AfcClient {
/// The underlying iDevice connection
pub idevice: Idevice,
@@ -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
///
/// # Arguments
@@ -408,11 +452,54 @@ impl AfcClient {
return Err(IdeviceError::UnexpectedResponse);
}
let fd = u64::from_le_bytes(res.header_payload[..8].try_into().unwrap());
Ok(FileDescriptor {
client: self,
fd,
path,
})
// we know it's a valid fd
Ok(unsafe { FileDescriptor::new(self, fd, path) })
}
/// Opens an owned file on the device
///
/// # Arguments
/// * `path` - Path to the file to open
/// * `mode` - Opening mode (read, write, etc.)
///
/// # Returns
/// A `OwnedFileDescriptor` struct for the opened file
pub async fn open_owned(
mut self,
path: impl Into<String>,
mode: AfcFopenMode,
) -> Result<OwnedFileDescriptor, IdeviceError> {
let path = path.into();
let mut header_payload = (mode as u64).to_le_bytes().to_vec();
header_payload.extend(path.as_bytes());
let header_len = header_payload.len() as u64 + AfcPacketHeader::LEN;
let header = AfcPacketHeader {
magic: MAGIC,
entire_len: header_len, // it's the same since the payload is empty for this
header_payload_len: header_len,
packet_num: self.package_number,
operation: AfcOpcode::FileOpen,
};
self.package_number += 1;
let packet = AfcPacket {
header,
header_payload,
payload: Vec::new(),
};
self.send(packet).await?;
let res = self.read().await?;
if res.header_payload.len() < 8 {
warn!("Header payload fd is less than 8 bytes");
return Err(IdeviceError::UnexpectedResponse);
}
let fd = u64::from_le_bytes(res.header_payload[..8].try_into().unwrap());
// we know it's a valid fd
Ok(unsafe { OwnedFileDescriptor::new(self, fd, path) })
}
/// Creates a hard or symbolic link
@@ -508,7 +595,7 @@ impl AfcClient {
let res = AfcPacket::read(&mut self.idevice).await?;
if res.header.operation == AfcOpcode::Status {
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);
}
let code = u64::from_le_bytes(res.header_payload[..8].try_into().unwrap());

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,13 +1,15 @@
//! 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};
#[derive(Debug)]
pub struct CompanionProxy {
idevice: Idevice,
}
#[derive(Debug)]
pub struct CompanionProxyStream {
proxy: CompanionProxy,
}

View File

@@ -1,7 +1,8 @@
// Jackson Coxson
use log::warn;
use plist_macro::plist_to_xml_bytes;
use serde::Deserialize;
use tracing::warn;
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> {
inner: CoreDeviceServiceClient<R>,
}
@@ -215,7 +217,7 @@ impl<R: ReadWrite> AppServiceClient<R> {
"user": {
"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 futures::Stream;
use log::warn;
use tracing::warn;
use crate::{IdeviceError, ReadWrite, RsdService, obf};
@@ -19,6 +19,7 @@ impl RsdService for DiagnostisServiceClient<Box<dyn ReadWrite>> {
}
}
#[derive(Debug)]
pub struct DiagnostisServiceClient<R: ReadWrite> {
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
// Ported from pymobiledevice3
use log::warn;
use tracing::warn;
use crate::{
IdeviceError, ReadWrite, RemoteXpcClient,
@@ -17,6 +17,7 @@ pub use openstdiosocket::*;
const CORE_SERVICE_VERSION: &str = "443.18";
#[derive(Debug)]
pub struct CoreDeviceServiceClient<R: ReadWrite> {
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
/// the launched app. Inner is exposed to read and write to, using Tokio's AsyncReadExt/AsyncWriteExt
#[derive(Debug)]
pub struct OpenStdioSocketClient {
pub inner: Box<dyn ReadWrite>,
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -16,6 +16,7 @@ use crate::{
///
/// Provides methods for take screnn_shot through the
/// instruments protocol. Each instance maintains its own communication channel.
#[derive(Debug)]
pub struct ScreenshotClient<'a, R: ReadWrite> {
/// The underlying channel for communication
channel: Channel<'a, R>,

View File

@@ -12,6 +12,7 @@ use crate::{Idevice, IdeviceError, IdeviceService, obf};
/// Note that a running heartbeat client is required to access other services on the device.
/// Implements the standard "Marco-Polo" protocol
/// where the host sends "Polo" in response to the device's "Marco".
#[derive(Debug)]
pub struct HeartbeatClient {
/// The underlying device connection with established heartbeat service
pub idevice: Idevice,

View File

@@ -12,6 +12,7 @@ use super::afc::AfcClient;
///
/// HouseArrest is used to expose the container or Documents directory of an app to a host machine
/// over AFC (Apple File Conduit).
#[derive(Debug)]
pub struct HouseArrestClient {
/// The underlying device connection with the HouseArrest service
pub idevice: Idevice,

View File

@@ -5,8 +5,8 @@
use std::collections::HashMap;
use log::warn;
use plist::Dictionary;
use tracing::warn;
use crate::{Idevice, IdeviceError, IdeviceService, obf};
@@ -14,6 +14,7 @@ use crate::{Idevice, IdeviceError, IdeviceService, obf};
///
/// This service provides access to information about installed applications
/// and can perform application management operations.
#[derive(Debug)]
pub struct InstallationProxyClient {
/// The underlying device connection with established installation_proxy service
pub idevice: Idevice,

View File

@@ -0,0 +1,106 @@
// Jackson Coxson
use crate::{IdeviceError, ReadWrite, RemoteXpcClient, RsdService, obf};
impl RsdService for InstallcoordinationProxy<Box<dyn ReadWrite>> {
fn rsd_service_name() -> std::borrow::Cow<'static, str> {
obf!("com.apple.remote.installcoordination_proxy")
}
async fn from_stream(stream: Box<dyn ReadWrite>) -> Result<Self, IdeviceError> {
let mut client = RemoteXpcClient::new(stream).await?;
client.do_handshake().await?;
Ok(Self { inner: client })
}
}
#[derive(Debug)]
pub struct InstallcoordinationProxy<R: ReadWrite> {
inner: RemoteXpcClient<R>,
}
impl<R: ReadWrite> InstallcoordinationProxy<R> {
// TODO: implement 2 missing functions
//
// # REVERT STASH
// Revert Stashed App (RequestType: 2)
// This request rolls back an application to a previously "stashed" version, which is typically done after a failed update.
//
// Handler: _handleRevertStashMessage_forRemoteConnection_
//
// RequestType: 2
// ProtocolVersion: 1
// BundleID: The bundle identifier of the app to revert.
//
// Action: The service creates an IXSRemoteReverter object, which calls the IXAppInstallCoordinator to perform the revert. It responds with a success or failure message.
//
// # INSTALL
// This is the most complex request. It tells the service to install a new application.
// Purpose: To stream an application binary from a client and install it on the device.
//
// Handler: _handleInstallBeginMessage_forRemoteConnection_
// RequestType: 1
// ProtocolVersion: 1
// AssetSize: The total size of the app binary.
// AssetStreamFD: A file descriptor from which the service will read the app binary.
// RemoteInstallOptions: A dictionary containing all the app's metadata, like:
//
// {
// BundleID: (String) The application's bundle identifier (e.g., com.example.myapp).
// LocalizedName: (String) The display name of the app.
// InstallMode: (uint64) An enum specifying the installation mode (e.g., full install, update).
// Importance: (uint64) An enum defining the install's priority. 1 for "user" and 2 for "system".
// InstallableType: (uint64) Specifies the type of content being installed (e.g., app, system component).
// StoreMetadata: (Dictionary) A dictionary containing App Store metadata.
// SINFData: (Data) The legacy iTunes Sinf data for DRM.
// ProvisioningProfiles: (Array of Data) An array of provisioning profiles to install alongside the app.
// IconData: (Data, Optional) The raw data for the application's icon.
// IconDataType: (uint64, Optional) An enum specifying the format of the IconData
// }
//
// Action: The service creates an IXSRemoteInstaller object. It reads the app data from the file descriptor and passes it to the system's IXAppInstallCoordinator to handle the installation, placeholder creation, and data management. It sends back progress updates and a final completion message.
pub async fn uninstall_app(&mut self, bundle_id: &str) -> Result<(), IdeviceError> {
let req = crate::xpc!({
"RequestVersion": 1u64,
"ProtocolVersion": 1u64,
"RequestType": 3u64,
"BundleID": bundle_id,
});
self.inner.send_object(req, true).await?;
let res = self.inner.recv_root().await?; // it responds on the root??
match res
.as_dictionary()
.and_then(|x| x.get("Success"))
.and_then(|x| x.as_boolean())
{
Some(true) => Ok(()),
_ => Err(IdeviceError::UnexpectedResponse),
}
}
pub async fn query_app_path(&mut self, bundle_id: &str) -> Result<String, IdeviceError> {
let req = crate::xpc!({
"RequestVersion": 1u64,
"ProtocolVersion": 1u64,
"RequestType": 4u64,
"BundleID": bundle_id,
});
self.inner.send_object(req, true).await?;
let res = self.inner.recv_root().await?; // it responds on the root??
match res
.as_dictionary()
.and_then(|x| x.get("InstallPath"))
.and_then(|x| x.as_dictionary())
.and_then(|x| x.get("com.apple.CFURL.string"))
.and_then(|x| x.as_string())
{
Some(s) => Ok(s.to_string()),
None => Err(IdeviceError::UnexpectedResponse),
}
}
}

View File

@@ -3,9 +3,8 @@
//! Provides functionality for interacting with the lockdown service on iOS devices,
//! which is the primary service for device management and service discovery.
use log::error;
use plist::Value;
use serde::{Deserialize, Serialize};
use tracing::error;
use crate::{Idevice, IdeviceError, IdeviceService, obf, pairing_file};
@@ -15,6 +14,7 @@ use crate::{Idevice, IdeviceError, IdeviceService, obf, pairing_file};
/// - Access to device information and settings
/// - Service discovery and port allocation
/// - Session management and security
#[derive(Debug)]
pub struct LockdownClient {
/// The underlying device connection with established lockdown service
pub idevice: crate::Idevice,
@@ -48,15 +48,6 @@ impl IdeviceService for LockdownClient {
}
}
/// Internal structure for lockdown protocol requests
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
struct LockdownRequest {
label: String,
key: Option<String>,
request: String,
}
impl LockdownClient {
/// The default TCP port for the lockdown service
pub const LOCKDOWND_PORT: u16 = 62078;
@@ -166,6 +157,17 @@ impl LockdownClient {
return Err(IdeviceError::NoEstablishedConnection);
}
let legacy = self
.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);
let request = crate::plist!({
"Label": self.idevice.label.clone(),
"Request": "StartSession",
@@ -187,7 +189,7 @@ impl LockdownClient {
}
}
self.idevice.start_session(pairing_file).await?;
self.idevice.start_session(pairing_file, legacy).await?;
Ok(())
}
@@ -253,11 +255,12 @@ impl LockdownClient {
///
/// # Errors
/// Returns `IdeviceError`
#[cfg(feature = "pair")]
#[cfg(all(feature = "pair", feature = "rustls"))]
pub async fn pair(
&mut self,
host_id: impl Into<String>,
system_buid: impl Into<String>,
host_name: Option<&str>,
) -> Result<crate::pairing_file::PairingFile, IdeviceError> {
let host_id = host_id.into();
let system_buid = system_buid.into();
@@ -266,7 +269,7 @@ impl LockdownClient {
let pub_key = match pub_key.as_data().map(|x| x.to_vec()) {
Some(p) => p,
None => {
log::warn!("Did not get public key data response");
tracing::warn!("Did not get public key data response");
return Err(IdeviceError::UnexpectedResponse);
}
};
@@ -275,7 +278,7 @@ impl LockdownClient {
let wifi_mac = match wifi_mac.as_string() {
Some(w) => w,
None => {
log::warn!("Did not get WiFiAddress string");
tracing::warn!("Did not get WiFiAddress string");
return Err(IdeviceError::UnexpectedResponse);
}
};
@@ -295,6 +298,7 @@ impl LockdownClient {
let req = crate::plist!({
"Label": self.idevice.label.clone(),
"Request": "Pair",
"HostName":? host_name,
"PairRecord": pair_record.clone(),
"ProtocolVersion": "2",
"PairingOptions": {
@@ -324,6 +328,23 @@ impl LockdownClient {
}
}
}
/// Tell the device to enter recovery mode
pub async fn enter_recovery(&mut self) -> Result<(), IdeviceError> {
self.idevice
.send_plist(crate::plist!({
"Request": "EnterRecovery"
}))
.await?;
let res = self.idevice.read_plist().await?;
if res.get("Request").and_then(|x| x.as_string()) == Some("EnterRecovery") {
Ok(())
} else {
Err(IdeviceError::UnexpectedResponse)
}
}
}
impl From<Idevice> for LockdownClient {

View File

@@ -3,7 +3,7 @@
//! Provides functionality for interacting with the misagent service on iOS devices,
//! which manages provisioning profiles and certificates.
use log::warn;
use tracing::warn;
use crate::{Idevice, IdeviceError, IdeviceService, RsdService, obf};
@@ -13,6 +13,7 @@ use crate::{Idevice, IdeviceError, IdeviceService, RsdService, obf};
/// - Installation of provisioning profiles
/// - Removal of provisioning profiles
/// - Querying installed profiles
#[derive(Debug)]
pub struct MisagentClient {
/// The underlying device connection with established misagent service
pub idevice: Idevice,

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