mirror of
https://github.com/jkcoxson/idevice.git
synced 2026-03-02 14:36:16 +01:00
Compare commits
100 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c752ee92c5 | ||
|
|
76d847664b | ||
|
|
1f7924b773 | ||
|
|
b459eebe9d | ||
|
|
c246362f54 | ||
|
|
bfe44e16e4 | ||
|
|
54439b85dd | ||
|
|
a523f0cb9c | ||
|
|
cb375f88a1 | ||
|
|
c5aa731ee5 | ||
|
|
38a3a558b5 | ||
|
|
496e099187 | ||
|
|
77ea34f820 | ||
|
|
9a71279fe9 | ||
|
|
142708c289 | ||
|
|
5bb9330cf6 | ||
|
|
ead5fbf3d3 | ||
|
|
f44a5c0779 | ||
|
|
1db78e6a8d | ||
|
|
bb64dc0b1c | ||
|
|
a4e17ea076 | ||
|
|
6dcfd4bc4c | ||
|
|
602e1ba855 | ||
|
|
ae39fcb7df | ||
|
|
13be1ae377 | ||
|
|
96b380ebc9 | ||
|
|
189dd5caf2 | ||
|
|
2eebbff172 | ||
|
|
166c497878 | ||
|
|
6d9f0987c1 | ||
|
|
081cb2f8d8 | ||
|
|
35c3d61355 | ||
|
|
328224d46c | ||
|
|
d8bff83753 | ||
|
|
ae5071a309 | ||
|
|
83e43aa3d6 | ||
|
|
2a6631f3da | ||
|
|
e3c12ddf98 | ||
|
|
44e8b83698 | ||
|
|
9776516544 | ||
|
|
080fea02eb | ||
|
|
39d454d77d | ||
|
|
a3dcac93b2 | ||
|
|
d2375e8f5c | ||
|
|
9e8abb7d37 | ||
|
|
c60f0d102b | ||
|
|
5f1e03911f | ||
|
|
c1b7009a7b | ||
|
|
a708db6307 | ||
|
|
c432627659 | ||
|
|
c0ec9b4bcd | ||
|
|
c1bc887fd4 | ||
|
|
761cb06418 | ||
|
|
db4547e0da | ||
|
|
59c3c3a12c | ||
|
|
f11a1bafff | ||
|
|
c9ca113239 | ||
|
|
08d6b41536 | ||
|
|
db894120da | ||
|
|
13c5b48b1c | ||
|
|
e31f39eac0 | ||
|
|
fbdc290d88 | ||
|
|
247acb192d | ||
|
|
d15c255524 | ||
|
|
c8e5f52ccd | ||
|
|
6d4bd7e853 | ||
|
|
4fa46e4c54 | ||
|
|
b26dd17b13 | ||
|
|
105a9b2837 | ||
|
|
7da735f141 | ||
|
|
7527cdff7b | ||
|
|
ec4663e93d | ||
|
|
20f00e38dd | ||
|
|
a297eed156 | ||
|
|
18b8b7295c | ||
|
|
0ccec70ed8 | ||
|
|
7805f943af | ||
|
|
5ed2144d9e | ||
|
|
dd2db92967 | ||
|
|
94624f07af | ||
|
|
a7daac3a46 | ||
|
|
779db7ecec | ||
|
|
c10f4da9f1 | ||
|
|
ea61459df2 | ||
|
|
cf7c92f8ad | ||
|
|
2ffeb0f25c | ||
|
|
aff3ef589f | ||
|
|
11c8f98b4f | ||
|
|
ef14b7669d | ||
|
|
0490c246be | ||
|
|
9a656a2a0e | ||
|
|
87ad894875 | ||
|
|
7853f2d6d0 | ||
|
|
526773d8ca | ||
|
|
eb37facd04 | ||
|
|
b897c3a126 | ||
|
|
fb3043b3e0 | ||
|
|
b0f871af99 | ||
|
|
7507b9609c | ||
|
|
4710ffce34 |
14
.github/workflows/ci.yml
vendored
14
.github/workflows/ci.yml
vendored
@@ -30,8 +30,14 @@ 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 && \
|
||||
rustup target add aarch64-apple-ios-macabi && \
|
||||
rustup target add x86_64-apple-ios-macabi && \
|
||||
cargo install --force --locked bindgen-cli
|
||||
|
||||
- name: Build all Apple targets and examples/tools
|
||||
run: |
|
||||
@@ -44,6 +50,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:
|
||||
|
||||
1557
Cargo.lock
generated
1557
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
28
README.md
28
README.md
@@ -47,6 +47,8 @@ To keep dependency bloat and compile time down, everything is contained in featu
|
||||
|------------------------|-----------------------------------------------------------------------------|
|
||||
| `afc` | Apple File Conduit for file system access.|
|
||||
| `amfi` | Apple mobile file integrity service |
|
||||
| `bt_packet_logger` | Capture Bluetooth packets. |
|
||||
| `companion_proxy` | Manage paired Apple Watches. |
|
||||
| `core_device_proxy` | Start a secure tunnel to access protected services. |
|
||||
| `crashreportcopymobile`| Copy crash reports.|
|
||||
| `debug_proxy` | Send GDB commands to the device.|
|
||||
@@ -55,34 +57,36 @@ To keep dependency bloat and compile time down, everything is contained in featu
|
||||
| `heartbeat` | Maintain a heartbeat connection.|
|
||||
| `house_arrest` | Manage files in app containers |
|
||||
| `installation_proxy` | Manage app installation and uninstallation.|
|
||||
| `springboardservices` | Control SpringBoard (e.g. UI interactions). Partial support.|
|
||||
| `misagent` | Manage provisioning profiles on the device.|
|
||||
| `mobilebackup2` | Manage backups.|
|
||||
| `mobile_image_mounter` | Manage DDI images.|
|
||||
| `installcoordination_proxy` | Manage app installation coordination.|
|
||||
| `location_simulation` | Simulate GPS locations on the device.|
|
||||
| `misagent` | Manage provisioning profiles on the device.|
|
||||
| `mobile_image_mounter` | Manage DDI images.|
|
||||
| `mobileactivationd` | Activate/Deactivate device.|
|
||||
| `mobilebackup2` | Manage backups.|
|
||||
| `pair` | Pair the device.|
|
||||
| `syslog_relay` | Relay system logs from the device |
|
||||
| `pcapd` | Capture network packets.|
|
||||
| `preboard_service` | Interface with Preboard.|
|
||||
| `restore_service` | Restore service (recovery/reboot).|
|
||||
| `screenshotr` | Take screenshots.|
|
||||
| `springboardservices` | Control SpringBoard (icons, wallpaper, orientation, etc.).|
|
||||
| `syslog_relay` | Relay system logs and OS trace logs from the device. |
|
||||
| `tcp` | Connect to devices over TCP.|
|
||||
| `tunnel_tcp_stack` | Naive in-process TCP stack for `core_device_proxy`.|
|
||||
| `tss` | Make requests to Apple's TSS servers. Partial support.|
|
||||
| `tunneld` | Interface with [pymobiledevice3](https://github.com/doronz88/pymobiledevice3)'s tunneld. |
|
||||
| `usbmuxd` | Connect using the usbmuxd daemon.|
|
||||
| `xpc` | Access protected services via XPC over RSD. |
|
||||
| `notification_proxy` | Post and observe iOS notifications. |
|
||||
|
||||
### Planned/TODO
|
||||
|
||||
Finish the following:
|
||||
|
||||
- springboard
|
||||
- webinspector
|
||||
|
||||
Implement the following:
|
||||
|
||||
- companion_proxy
|
||||
- diagnostics
|
||||
- mobilebackup2
|
||||
- notification_proxy
|
||||
- screenshot
|
||||
- webinspector
|
||||
- file_relay
|
||||
|
||||
As this project is done in my free time within my busy schedule, there
|
||||
is no ETA for any of these. Feel free to contribute or donate!
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
108
cpp/examples/screenshot.cpp
Normal 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;
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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_{};
|
||||
|
||||
50
cpp/include/idevice++/crashreportcopymobile.hpp
Normal file
50
cpp/include/idevice++/crashreportcopymobile.hpp
Normal file
@@ -0,0 +1,50 @@
|
||||
// Jackson Coxson
|
||||
|
||||
#pragma once
|
||||
#include <idevice++/bindings.hpp>
|
||||
#include <idevice++/ffi.hpp>
|
||||
#include <idevice++/provider.hpp>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace IdeviceFFI {
|
||||
|
||||
using CrashReportCopyMobilePtr =
|
||||
std::unique_ptr<CrashReportCopyMobileHandle,
|
||||
FnDeleter<CrashReportCopyMobileHandle, crash_report_client_free>>;
|
||||
|
||||
class CrashReportCopyMobile {
|
||||
public:
|
||||
// Factory: connect via Provider
|
||||
static Result<CrashReportCopyMobile, FfiError> connect(Provider& provider);
|
||||
|
||||
// Factory: wrap an existing Idevice socket (consumes it on success)
|
||||
static Result<CrashReportCopyMobile, FfiError> from_socket(Idevice&& socket);
|
||||
|
||||
// Static: flush crash reports from system storage
|
||||
static Result<void, FfiError> flush(Provider& provider);
|
||||
|
||||
// Ops
|
||||
Result<std::vector<std::string>, FfiError> ls(const char* dir_path = nullptr);
|
||||
Result<std::vector<char>, FfiError> pull(const std::string& log_name);
|
||||
Result<void, FfiError> remove(const std::string& log_name);
|
||||
|
||||
// RAII / moves
|
||||
~CrashReportCopyMobile() noexcept = default;
|
||||
CrashReportCopyMobile(CrashReportCopyMobile&&) noexcept = default;
|
||||
CrashReportCopyMobile& operator=(CrashReportCopyMobile&&) noexcept = default;
|
||||
CrashReportCopyMobile(const CrashReportCopyMobile&) = delete;
|
||||
CrashReportCopyMobile& operator=(const CrashReportCopyMobile&) = delete;
|
||||
|
||||
CrashReportCopyMobileHandle* raw() const noexcept { return handle_.get(); }
|
||||
static CrashReportCopyMobile adopt(CrashReportCopyMobileHandle* h) noexcept {
|
||||
return CrashReportCopyMobile(h);
|
||||
}
|
||||
|
||||
private:
|
||||
explicit CrashReportCopyMobile(CrashReportCopyMobileHandle* h) noexcept : handle_(h) {}
|
||||
CrashReportCopyMobilePtr handle_{};
|
||||
};
|
||||
|
||||
} // namespace IdeviceFFI
|
||||
60
cpp/include/idevice++/diagnostics_relay.hpp
Normal file
60
cpp/include/idevice++/diagnostics_relay.hpp
Normal 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
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
49
cpp/include/idevice++/dvt/screenshot.hpp
Normal file
49
cpp/include/idevice++/dvt/screenshot.hpp
Normal 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
|
||||
@@ -3,7 +3,6 @@
|
||||
#include <idevice++/ffi.hpp>
|
||||
#include <idevice++/provider.hpp>
|
||||
#include <memory>
|
||||
#include <sys/_types/_u_int64_t.h>
|
||||
|
||||
namespace IdeviceFFI {
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
#include <idevice++/ffi.hpp>
|
||||
#include <idevice++/provider.hpp>
|
||||
#include <memory>
|
||||
#include <sys/_types/_u_int64_t.h>
|
||||
|
||||
namespace IdeviceFFI {
|
||||
|
||||
|
||||
46
cpp/include/idevice++/notification_proxy.hpp
Normal file
46
cpp/include/idevice++/notification_proxy.hpp
Normal file
@@ -0,0 +1,46 @@
|
||||
#pragma once
|
||||
#include <idevice++/bindings.hpp>
|
||||
#include <idevice++/ffi.hpp>
|
||||
#include <idevice++/provider.hpp>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace IdeviceFFI {
|
||||
|
||||
using NotificationProxyPtr = std::unique_ptr<NotificationProxyClientHandle,
|
||||
FnDeleter<NotificationProxyClientHandle, notification_proxy_client_free>>;
|
||||
|
||||
class NotificationProxy {
|
||||
public:
|
||||
// Factory: connect via Provider
|
||||
static Result<NotificationProxy, FfiError> connect(Provider& provider);
|
||||
|
||||
// Factory: wrap an existing Idevice socket (consumes it on success)
|
||||
static Result<NotificationProxy, FfiError> from_socket(Idevice&& socket);
|
||||
|
||||
// Ops
|
||||
Result<void, FfiError> post_notification(const std::string& name);
|
||||
Result<void, FfiError> observe_notification(const std::string& name);
|
||||
Result<void, FfiError> observe_notifications(const std::vector<std::string>& names);
|
||||
Result<std::string, FfiError> receive_notification();
|
||||
Result<std::string, FfiError> receive_notification_with_timeout(u_int64_t interval);
|
||||
|
||||
// RAII / moves
|
||||
~NotificationProxy() noexcept = default;
|
||||
NotificationProxy(NotificationProxy&&) noexcept = default;
|
||||
NotificationProxy& operator=(NotificationProxy&&) noexcept = default;
|
||||
NotificationProxy(const NotificationProxy&) = delete;
|
||||
NotificationProxy& operator=(const NotificationProxy&) = delete;
|
||||
|
||||
NotificationProxyClientHandle* raw() const noexcept { return handle_.get(); }
|
||||
static NotificationProxy adopt(NotificationProxyClientHandle* h) noexcept {
|
||||
return NotificationProxy(h);
|
||||
}
|
||||
|
||||
private:
|
||||
explicit NotificationProxy(NotificationProxyClientHandle* h) noexcept : handle_(h) {}
|
||||
NotificationProxyPtr handle_{};
|
||||
};
|
||||
|
||||
} // namespace IdeviceFFI
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
}
|
||||
@@ -18,9 +19,10 @@ Result<void, FfiError> AdapterStream::close() {
|
||||
return Ok();
|
||||
}
|
||||
|
||||
Result<void, FfiError> AdapterStream::send(const uint8_t *data, size_t len) {
|
||||
if (!h_)
|
||||
Result<void, FfiError> AdapterStream::send(const uint8_t* data, size_t len) {
|
||||
if (!h_) {
|
||||
return Err(FfiError::NotConnected());
|
||||
}
|
||||
FfiError e(::adapter_send(h_, data, len));
|
||||
if (e) {
|
||||
return Err(e);
|
||||
@@ -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;
|
||||
|
||||
94
cpp/src/crashreportcopymobile.cpp
Normal file
94
cpp/src/crashreportcopymobile.cpp
Normal file
@@ -0,0 +1,94 @@
|
||||
// Jackson Coxson
|
||||
|
||||
#include <cstring>
|
||||
#include <idevice++/bindings.hpp>
|
||||
#include <idevice++/crashreportcopymobile.hpp>
|
||||
#include <idevice++/ffi.hpp>
|
||||
#include <idevice++/provider.hpp>
|
||||
|
||||
namespace IdeviceFFI {
|
||||
|
||||
// -------- Factory Methods --------
|
||||
|
||||
Result<CrashReportCopyMobile, FfiError> CrashReportCopyMobile::connect(Provider& provider) {
|
||||
CrashReportCopyMobileHandle* out = nullptr;
|
||||
FfiError e(::crash_report_client_connect(provider.raw(), &out));
|
||||
if (e) {
|
||||
return Err(e);
|
||||
}
|
||||
return Ok(CrashReportCopyMobile::adopt(out));
|
||||
}
|
||||
|
||||
Result<CrashReportCopyMobile, FfiError> CrashReportCopyMobile::from_socket(Idevice&& socket) {
|
||||
CrashReportCopyMobileHandle* out = nullptr;
|
||||
FfiError e(::crash_report_client_new(socket.raw(), &out));
|
||||
if (e) {
|
||||
return Err(e);
|
||||
}
|
||||
socket.release();
|
||||
return Ok(CrashReportCopyMobile::adopt(out));
|
||||
}
|
||||
|
||||
Result<void, FfiError> CrashReportCopyMobile::flush(Provider& provider) {
|
||||
FfiError e(::crash_report_flush(provider.raw()));
|
||||
if (e) {
|
||||
return Err(e);
|
||||
}
|
||||
return Ok();
|
||||
}
|
||||
|
||||
// -------- Ops --------
|
||||
|
||||
Result<std::vector<std::string>, FfiError>
|
||||
CrashReportCopyMobile::ls(const char* dir_path) {
|
||||
char** entries_raw = nullptr;
|
||||
size_t count = 0;
|
||||
|
||||
FfiError e(::crash_report_client_ls(handle_.get(), dir_path, &entries_raw, &count));
|
||||
if (e) {
|
||||
return Err(e);
|
||||
}
|
||||
|
||||
std::vector<std::string> result;
|
||||
if (entries_raw) {
|
||||
result.reserve(count);
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
if (entries_raw[i]) {
|
||||
result.emplace_back(entries_raw[i]);
|
||||
::idevice_string_free(entries_raw[i]);
|
||||
}
|
||||
}
|
||||
std::free(entries_raw);
|
||||
}
|
||||
|
||||
return Ok(std::move(result));
|
||||
}
|
||||
|
||||
Result<std::vector<char>, FfiError>
|
||||
CrashReportCopyMobile::pull(const std::string& log_name) {
|
||||
uint8_t* data = nullptr;
|
||||
size_t length = 0;
|
||||
|
||||
FfiError e(::crash_report_client_pull(handle_.get(), log_name.c_str(), &data, &length));
|
||||
if (e) {
|
||||
return Err(e);
|
||||
}
|
||||
|
||||
std::vector<char> result;
|
||||
if (data && length > 0) {
|
||||
result.assign(reinterpret_cast<char*>(data), reinterpret_cast<char*>(data) + length);
|
||||
::idevice_data_free(data, length);
|
||||
}
|
||||
|
||||
return Ok(std::move(result));
|
||||
}
|
||||
|
||||
Result<void, FfiError> CrashReportCopyMobile::remove(const std::string& log_name) {
|
||||
FfiError e(::crash_report_client_remove(handle_.get(), log_name.c_str()));
|
||||
if (e) {
|
||||
return Err(e);
|
||||
}
|
||||
return Ok();
|
||||
}
|
||||
|
||||
} // namespace IdeviceFFI
|
||||
159
cpp/src/diagnostics_relay.cpp
Normal file
159
cpp/src/diagnostics_relay.cpp
Normal 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
|
||||
@@ -1,6 +1,6 @@
|
||||
// Jackson Coxson
|
||||
|
||||
#include <idevice++/location_simulation.hpp>
|
||||
#include <idevice++/dvt/location_simulation.hpp>
|
||||
|
||||
namespace IdeviceFFI {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Jackson Coxson
|
||||
|
||||
#include <idevice++/process_control.hpp>
|
||||
#include <idevice++/dvt/process_control.hpp>
|
||||
|
||||
namespace IdeviceFFI {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Jackson Coxson
|
||||
|
||||
#include <idevice++/remote_server.hpp>
|
||||
#include <idevice++/dvt/remote_server.hpp>
|
||||
|
||||
namespace IdeviceFFI {
|
||||
|
||||
37
cpp/src/dvt/screenshot.cpp
Normal file
37
cpp/src/dvt/screenshot.cpp
Normal 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
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
82
cpp/src/notification_proxy.cpp
Normal file
82
cpp/src/notification_proxy.cpp
Normal file
@@ -0,0 +1,82 @@
|
||||
// Jackson Coxson
|
||||
|
||||
#include <idevice++/bindings.hpp>
|
||||
#include <idevice++/ffi.hpp>
|
||||
#include <idevice++/notification_proxy.hpp>
|
||||
#include <idevice++/provider.hpp>
|
||||
|
||||
namespace IdeviceFFI {
|
||||
|
||||
Result<NotificationProxy, FfiError> NotificationProxy::connect(Provider& provider) {
|
||||
NotificationProxyClientHandle* out = nullptr;
|
||||
FfiError e(::notification_proxy_connect(provider.raw(), &out));
|
||||
if (e) {
|
||||
provider.release();
|
||||
return Err(e);
|
||||
}
|
||||
return Ok(NotificationProxy::adopt(out));
|
||||
}
|
||||
|
||||
Result<NotificationProxy, FfiError> NotificationProxy::from_socket(Idevice&& socket) {
|
||||
NotificationProxyClientHandle* out = nullptr;
|
||||
FfiError e(::notification_proxy_new(socket.raw(), &out));
|
||||
if (e) {
|
||||
return Err(e);
|
||||
}
|
||||
socket.release();
|
||||
return Ok(NotificationProxy::adopt(out));
|
||||
}
|
||||
|
||||
Result<void, FfiError> NotificationProxy::post_notification(const std::string& name) {
|
||||
FfiError e(::notification_proxy_post(handle_.get(), name.c_str()));
|
||||
if (e) {
|
||||
return Err(e);
|
||||
}
|
||||
return Ok();
|
||||
}
|
||||
|
||||
Result<void, FfiError> NotificationProxy::observe_notification(const std::string& name) {
|
||||
FfiError e(::notification_proxy_observe(handle_.get(), name.c_str()));
|
||||
if (e) {
|
||||
return Err(e);
|
||||
}
|
||||
return Ok();
|
||||
}
|
||||
|
||||
Result<void, FfiError> NotificationProxy::observe_notifications(const std::vector<std::string>& names) {
|
||||
std::vector<const char*> ptrs;
|
||||
ptrs.reserve(names.size() + 1);
|
||||
for (const auto& n : names) {
|
||||
ptrs.push_back(n.c_str());
|
||||
}
|
||||
ptrs.push_back(nullptr);
|
||||
FfiError e(::notification_proxy_observe_multiple(handle_.get(), ptrs.data()));
|
||||
if (e) {
|
||||
return Err(e);
|
||||
}
|
||||
return Ok();
|
||||
}
|
||||
|
||||
Result<std::string, FfiError> NotificationProxy::receive_notification() {
|
||||
char* name_ptr = nullptr;
|
||||
FfiError e(::notification_proxy_receive(handle_.get(), &name_ptr));
|
||||
if (e) {
|
||||
return Err(e);
|
||||
}
|
||||
std::string name(name_ptr);
|
||||
::notification_proxy_free_string(name_ptr);
|
||||
return Ok(std::move(name));
|
||||
}
|
||||
|
||||
Result<std::string, FfiError> NotificationProxy::receive_notification_with_timeout(u_int64_t interval) {
|
||||
char* name_ptr = nullptr;
|
||||
FfiError e(::notification_proxy_receive_with_timeout(handle_.get(), interval, &name_ptr));
|
||||
if (e) {
|
||||
return Err(e);
|
||||
}
|
||||
std::string name(name_ptr);
|
||||
::notification_proxy_free_string(name_ptr);
|
||||
return Ok(std::move(name));
|
||||
}
|
||||
|
||||
} // namespace IdeviceFFI
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
@@ -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,8 +31,10 @@ core_device = ["idevice/core_device", "dep:futures", "dep:uuid"]
|
||||
core_device_proxy = ["idevice/core_device_proxy"]
|
||||
crashreportcopymobile = ["idevice/crashreportcopymobile"]
|
||||
debug_proxy = ["idevice/debug_proxy"]
|
||||
diagnostics_relay = ["idevice/diagnostics_relay"]
|
||||
dvt = ["idevice/dvt"]
|
||||
heartbeat = ["idevice/heartbeat"]
|
||||
notification_proxy = ["idevice/notification_proxy"]
|
||||
house_arrest = ["idevice/house_arrest"]
|
||||
installation_proxy = ["idevice/installation_proxy"]
|
||||
springboardservices = ["idevice/springboardservices"]
|
||||
@@ -48,6 +51,7 @@ tss = ["idevice/tss"]
|
||||
tunneld = ["idevice/tunneld"]
|
||||
usbmuxd = ["idevice/usbmuxd"]
|
||||
xpc = ["idevice/xpc"]
|
||||
screenshotr = ["idevice/screenshotr"]
|
||||
full = [
|
||||
"afc",
|
||||
"amfi",
|
||||
@@ -55,8 +59,10 @@ full = [
|
||||
"core_device_proxy",
|
||||
"crashreportcopymobile",
|
||||
"debug_proxy",
|
||||
"diagnostics_relay",
|
||||
"dvt",
|
||||
"heartbeat",
|
||||
"notification_proxy",
|
||||
"house_arrest",
|
||||
"installation_proxy",
|
||||
"misagent",
|
||||
@@ -72,6 +78,7 @@ full = [
|
||||
"tunneld",
|
||||
"springboardservices",
|
||||
"syslog_relay",
|
||||
"screenshotr",
|
||||
]
|
||||
default = ["full", "aws-lc"]
|
||||
|
||||
@@ -80,4 +87,4 @@ cbindgen = "0.29.0"
|
||||
ureq = "3"
|
||||
|
||||
[lib]
|
||||
crate-type = ["staticlib", "cdylib"]
|
||||
crate-type = ["staticlib", "cdylib", "rlib"]
|
||||
|
||||
15
ffi/build.rs
15
ffi/build.rs
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
226
ffi/src/afc.rs
226
ffi/src/afc.rs
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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) };
|
||||
}
|
||||
}
|
||||
|
||||
325
ffi/src/crashreportcopymobile.rs
Normal file
325
ffi/src/crashreportcopymobile.rs
Normal file
@@ -0,0 +1,325 @@
|
||||
// Jackson Coxson
|
||||
|
||||
use std::{
|
||||
ffi::{CStr, c_char},
|
||||
ptr::null_mut,
|
||||
};
|
||||
|
||||
use idevice::{
|
||||
IdeviceError, IdeviceService,
|
||||
provider::IdeviceProvider,
|
||||
services::crashreportcopymobile::{CrashReportCopyMobileClient, flush_reports},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
IdeviceFfiError, IdeviceHandle, afc::AfcClientHandle, ffi_err, provider::IdeviceProviderHandle,
|
||||
run_sync_local,
|
||||
};
|
||||
|
||||
pub struct CrashReportCopyMobileHandle(pub CrashReportCopyMobileClient);
|
||||
|
||||
/// Automatically creates and connects to the crash report copy mobile service,
|
||||
/// returning a client handle
|
||||
///
|
||||
/// # Arguments
|
||||
/// * [`provider`] - An IdeviceProvider
|
||||
/// * [`client`] - On success, will be set to point to a newly allocated handle
|
||||
///
|
||||
/// # Returns
|
||||
/// An IdeviceFfiError on error, null on success
|
||||
///
|
||||
/// # Safety
|
||||
/// `provider` must be a valid pointer to a handle allocated by this library
|
||||
/// `client` must be a valid, non-null pointer to a location where the handle will be stored
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn crash_report_client_connect(
|
||||
provider: *mut IdeviceProviderHandle,
|
||||
client: *mut *mut CrashReportCopyMobileHandle,
|
||||
) -> *mut IdeviceFfiError {
|
||||
if provider.is_null() || client.is_null() {
|
||||
tracing::error!("Null pointer provided");
|
||||
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||
}
|
||||
|
||||
let res: Result<CrashReportCopyMobileClient, IdeviceError> = run_sync_local(async move {
|
||||
let provider_ref: &dyn IdeviceProvider = unsafe { &*(*provider).0 };
|
||||
CrashReportCopyMobileClient::connect(provider_ref).await
|
||||
});
|
||||
|
||||
match res {
|
||||
Ok(r) => {
|
||||
let boxed = Box::new(CrashReportCopyMobileHandle(r));
|
||||
unsafe { *client = Box::into_raw(boxed) };
|
||||
null_mut()
|
||||
}
|
||||
Err(e) => ffi_err!(e),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new CrashReportCopyMobile client from an existing Idevice connection
|
||||
///
|
||||
/// # Arguments
|
||||
/// * [`socket`] - An IdeviceSocket handle
|
||||
/// * [`client`] - On success, will be set to point to a newly allocated handle
|
||||
///
|
||||
/// # Returns
|
||||
/// An IdeviceFfiError on error, null on success
|
||||
///
|
||||
/// # Safety
|
||||
/// `socket` must be a valid pointer to a handle allocated by this library
|
||||
/// `client` must be a valid, non-null pointer to a location where the handle will be stored
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn crash_report_client_new(
|
||||
socket: *mut IdeviceHandle,
|
||||
client: *mut *mut CrashReportCopyMobileHandle,
|
||||
) -> *mut IdeviceFfiError {
|
||||
if socket.is_null() || client.is_null() {
|
||||
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||
}
|
||||
let socket = unsafe { Box::from_raw(socket) }.0;
|
||||
let r = CrashReportCopyMobileClient::new(socket);
|
||||
let boxed = Box::new(CrashReportCopyMobileHandle(r));
|
||||
unsafe { *client = Box::into_raw(boxed) };
|
||||
null_mut()
|
||||
}
|
||||
|
||||
/// Lists crash report files in the specified directory
|
||||
///
|
||||
/// # Arguments
|
||||
/// * [`client`] - A valid CrashReportCopyMobile handle
|
||||
/// * [`dir_path`] - Optional directory path (NULL for root "/")
|
||||
/// * [`entries`] - Will be set to point to an array of C strings
|
||||
/// * [`count`] - Will be set to the number of entries
|
||||
///
|
||||
/// # Returns
|
||||
/// An IdeviceFfiError on error, null on success
|
||||
///
|
||||
/// # Safety
|
||||
/// All pointers must be valid and non-null
|
||||
/// `dir_path` may be NULL (defaults to root)
|
||||
/// Caller must free the returned array with `afc_free_directory_entries`
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn crash_report_client_ls(
|
||||
client: *mut CrashReportCopyMobileHandle,
|
||||
dir_path: *const c_char,
|
||||
entries: *mut *mut *mut c_char,
|
||||
count: *mut libc::size_t,
|
||||
) -> *mut IdeviceFfiError {
|
||||
if client.is_null() || entries.is_null() || count.is_null() {
|
||||
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||
}
|
||||
|
||||
let path = if dir_path.is_null() {
|
||||
None
|
||||
} else {
|
||||
match unsafe { CStr::from_ptr(dir_path) }.to_str() {
|
||||
Ok(s) => Some(s),
|
||||
Err(_) => return ffi_err!(IdeviceError::FfiInvalidString),
|
||||
}
|
||||
};
|
||||
|
||||
let res: Result<Vec<String>, IdeviceError> = run_sync_local(async {
|
||||
let client_ref = unsafe { &mut (*client).0 };
|
||||
client_ref.ls(path).await
|
||||
});
|
||||
|
||||
match res {
|
||||
Ok(items) => {
|
||||
let c_strings = items
|
||||
.into_iter()
|
||||
.filter_map(|s| std::ffi::CString::new(s).ok())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let string_count = c_strings.len();
|
||||
|
||||
// Allocate array for char pointers (with NULL terminator)
|
||||
let layout = std::alloc::Layout::array::<*mut c_char>(string_count + 1).unwrap();
|
||||
let ptr = unsafe { std::alloc::alloc(layout) as *mut *mut c_char };
|
||||
if ptr.is_null() {
|
||||
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||
}
|
||||
|
||||
for (i, cstring) in c_strings.into_iter().enumerate() {
|
||||
let string_ptr = cstring.into_raw();
|
||||
unsafe { *ptr.add(i) = string_ptr };
|
||||
}
|
||||
|
||||
// NULL terminator
|
||||
unsafe { *ptr.add(string_count) = std::ptr::null_mut() };
|
||||
|
||||
unsafe {
|
||||
*entries = ptr;
|
||||
*count = string_count;
|
||||
}
|
||||
|
||||
null_mut()
|
||||
}
|
||||
Err(e) => ffi_err!(e),
|
||||
}
|
||||
}
|
||||
|
||||
/// Downloads a crash report file from the device
|
||||
///
|
||||
/// # Arguments
|
||||
/// * [`client`] - A valid CrashReportCopyMobile handle
|
||||
/// * [`log_name`] - Name of the log file to download (C string)
|
||||
/// * [`data`] - Will be set to point to the file contents
|
||||
/// * [`length`] - Will be set to the size of the data
|
||||
///
|
||||
/// # Returns
|
||||
/// An IdeviceFfiError on error, null on success
|
||||
///
|
||||
/// # Safety
|
||||
/// All pointers must be valid and non-null
|
||||
/// `log_name` must be a valid C string
|
||||
/// Caller must free the returned data with `idevice_data_free`
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn crash_report_client_pull(
|
||||
client: *mut CrashReportCopyMobileHandle,
|
||||
log_name: *const c_char,
|
||||
data: *mut *mut u8,
|
||||
length: *mut libc::size_t,
|
||||
) -> *mut IdeviceFfiError {
|
||||
if client.is_null() || log_name.is_null() || data.is_null() || length.is_null() {
|
||||
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||
}
|
||||
|
||||
let name = match unsafe { CStr::from_ptr(log_name) }.to_str() {
|
||||
Ok(s) => s.to_string(),
|
||||
Err(_) => return ffi_err!(IdeviceError::FfiInvalidString),
|
||||
};
|
||||
|
||||
let res: Result<Vec<u8>, IdeviceError> = run_sync_local(async {
|
||||
let client_ref = unsafe { &mut (*client).0 };
|
||||
client_ref.pull(name).await
|
||||
});
|
||||
|
||||
match res {
|
||||
Ok(file_data) => {
|
||||
let len = file_data.len();
|
||||
let mut boxed = file_data.into_boxed_slice();
|
||||
unsafe {
|
||||
*data = boxed.as_mut_ptr();
|
||||
*length = len;
|
||||
}
|
||||
std::mem::forget(boxed);
|
||||
null_mut()
|
||||
}
|
||||
Err(e) => ffi_err!(e),
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes a crash report file from the device
|
||||
///
|
||||
/// # Arguments
|
||||
/// * [`client`] - A valid CrashReportCopyMobile handle
|
||||
/// * [`log_name`] - Name of the log file to remove (C string)
|
||||
///
|
||||
/// # Returns
|
||||
/// An IdeviceFfiError on error, null on success
|
||||
///
|
||||
/// # Safety
|
||||
/// `client` must be a valid pointer to a handle allocated by this library
|
||||
/// `log_name` must be a valid C string
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn crash_report_client_remove(
|
||||
client: *mut CrashReportCopyMobileHandle,
|
||||
log_name: *const c_char,
|
||||
) -> *mut IdeviceFfiError {
|
||||
if client.is_null() || log_name.is_null() {
|
||||
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||
}
|
||||
|
||||
let name = match unsafe { CStr::from_ptr(log_name) }.to_str() {
|
||||
Ok(s) => s.to_string(),
|
||||
Err(_) => return ffi_err!(IdeviceError::FfiInvalidString),
|
||||
};
|
||||
|
||||
let res = run_sync_local(async {
|
||||
let client_ref = unsafe { &mut (*client).0 };
|
||||
client_ref.remove(name).await
|
||||
});
|
||||
|
||||
match res {
|
||||
Ok(_) => null_mut(),
|
||||
Err(e) => ffi_err!(e),
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts this client to an AFC client for advanced file operations
|
||||
///
|
||||
/// # Arguments
|
||||
/// * [`client`] - A valid CrashReportCopyMobile handle (will be consumed)
|
||||
/// * [`afc_client`] - On success, will be set to an AFC client handle
|
||||
///
|
||||
/// # Returns
|
||||
/// An IdeviceFfiError on error, null on success
|
||||
///
|
||||
/// # Safety
|
||||
/// `client` must be a valid pointer (will be freed after this call)
|
||||
/// `afc_client` must be a valid, non-null pointer where the new AFC client will be stored
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn crash_report_client_to_afc(
|
||||
client: *mut CrashReportCopyMobileHandle,
|
||||
afc_client: *mut *mut AfcClientHandle,
|
||||
) -> *mut IdeviceFfiError {
|
||||
if client.is_null() || afc_client.is_null() {
|
||||
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||
}
|
||||
|
||||
let crash_client = unsafe { Box::from_raw(client) }.0;
|
||||
let afc = crash_client.to_afc_client();
|
||||
|
||||
let a = Box::into_raw(Box::new(AfcClientHandle(afc)));
|
||||
unsafe { *afc_client = a };
|
||||
|
||||
null_mut()
|
||||
}
|
||||
|
||||
/// Triggers a flush of crash logs from system storage
|
||||
///
|
||||
/// This connects to the crashreportmover service to move crash logs
|
||||
/// into the AFC-accessible directory. Should be called before listing logs.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * [`provider`] - An IdeviceProvider
|
||||
///
|
||||
/// # Returns
|
||||
/// An IdeviceFfiError on error, null on success
|
||||
///
|
||||
/// # Safety
|
||||
/// `provider` must be a valid pointer to a handle allocated by this library
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn crash_report_flush(
|
||||
provider: *mut IdeviceProviderHandle,
|
||||
) -> *mut IdeviceFfiError {
|
||||
if provider.is_null() {
|
||||
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||
}
|
||||
|
||||
let res = run_sync_local(async {
|
||||
let provider_ref: &dyn IdeviceProvider = unsafe { &*(*provider).0 };
|
||||
flush_reports(provider_ref).await
|
||||
});
|
||||
|
||||
match res {
|
||||
Ok(_) => null_mut(),
|
||||
Err(e) => ffi_err!(e),
|
||||
}
|
||||
}
|
||||
|
||||
/// Frees a CrashReportCopyMobile client handle
|
||||
///
|
||||
/// # Arguments
|
||||
/// * [`handle`] - The handle to free
|
||||
///
|
||||
/// # Safety
|
||||
/// `handle` must be a valid pointer to the handle that was allocated by this library,
|
||||
/// or NULL (in which case this function does nothing)
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn crash_report_client_free(handle: *mut CrashReportCopyMobileHandle) {
|
||||
if !handle.is_null() {
|
||||
tracing::debug!("Freeing crash_report_client");
|
||||
let _ = unsafe { Box::from_raw(handle) };
|
||||
}
|
||||
}
|
||||
@@ -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(),
|
||||
|
||||
533
ffi/src/diagnostics_relay.rs
Normal file
533
ffi/src/diagnostics_relay.rs
Normal 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) };
|
||||
}
|
||||
}
|
||||
@@ -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
8
ffi/src/dvt/mod.rs
Normal 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;
|
||||
@@ -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(),
|
||||
@@ -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
113
ffi/src/dvt/screenshot.rs
Normal 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),
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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
183
ffi/src/house_arrest.rs
Normal 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) };
|
||||
}
|
||||
}
|
||||
@@ -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())
|
||||
|
||||
151
ffi/src/lib.rs
151
ffi/src/lib.rs
@@ -10,31 +10,37 @@ pub mod amfi;
|
||||
pub mod core_device;
|
||||
#[cfg(feature = "core_device_proxy")]
|
||||
pub mod core_device_proxy;
|
||||
#[cfg(feature = "crashreportcopymobile")]
|
||||
pub mod crashreportcopymobile;
|
||||
#[cfg(feature = "debug_proxy")]
|
||||
pub mod debug_proxy;
|
||||
#[cfg(feature = "diagnostics_relay")]
|
||||
pub mod diagnostics_relay;
|
||||
#[cfg(feature = "dvt")]
|
||||
pub mod dvt;
|
||||
mod errors;
|
||||
#[cfg(feature = "heartbeat")]
|
||||
pub mod heartbeat;
|
||||
#[cfg(feature = "house_arrest")]
|
||||
pub mod house_arrest;
|
||||
#[cfg(feature = "installation_proxy")]
|
||||
pub mod installation_proxy;
|
||||
#[cfg(feature = "location_simulation")]
|
||||
pub mod location_simulation;
|
||||
pub mod lockdown;
|
||||
pub mod logging;
|
||||
#[cfg(feature = "misagent")]
|
||||
pub mod misagent;
|
||||
#[cfg(feature = "mobile_image_mounter")]
|
||||
pub mod mobile_image_mounter;
|
||||
#[cfg(feature = "notification_proxy")]
|
||||
pub mod notification_proxy;
|
||||
#[cfg(feature = "syslog_relay")]
|
||||
pub mod os_trace_relay;
|
||||
mod pairing_file;
|
||||
#[cfg(feature = "dvt")]
|
||||
pub mod process_control;
|
||||
pub mod provider;
|
||||
#[cfg(feature = "dvt")]
|
||||
pub mod remote_server;
|
||||
#[cfg(feature = "xpc")]
|
||||
pub mod rsd;
|
||||
#[cfg(feature = "screenshotr")]
|
||||
pub mod screenshotr;
|
||||
#[cfg(feature = "springboardservices")]
|
||||
pub mod springboardservices;
|
||||
#[cfg(feature = "syslog_relay")]
|
||||
@@ -50,8 +56,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 +66,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 +74,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 +166,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 +233,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 +248,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 +291,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 +325,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 +349,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 +362,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 +385,17 @@ pub unsafe extern "C" fn idevice_free(idevice: *mut IdeviceHandle) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Frees a stream handle
|
||||
///
|
||||
/// # Safety
|
||||
/// Pass a valid handle allocated by this library
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn idevice_stream_free(stream_handle: *mut ReadWriteOpaque) {
|
||||
if !stream_handle.is_null() {
|
||||
let _ = unsafe { Box::from_raw(stream_handle) };
|
||||
}
|
||||
}
|
||||
|
||||
/// Frees a string allocated by this library
|
||||
///
|
||||
/// # Arguments
|
||||
@@ -329,6 +422,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) };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
});
|
||||
@@ -157,6 +157,74 @@ pub unsafe extern "C" fn lockdownd_start_service(
|
||||
}
|
||||
}
|
||||
|
||||
/// Pairs with the device using lockdownd
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `client` - A valid LockdowndClient handle
|
||||
/// * `host_id` - The host ID (null-terminated string)
|
||||
/// * `system_buid` - The system BUID (null-terminated string)
|
||||
/// * `pairing_file` - On success, will be set to point to a newly allocated IdevicePairingFile handle
|
||||
///
|
||||
/// # Returns
|
||||
/// An IdeviceFfiError on error, null on success
|
||||
///
|
||||
/// # Safety
|
||||
/// `client` must be a valid pointer to a handle allocated by this library
|
||||
/// `host_id` must be a valid null-terminated string
|
||||
/// `system_buid` must be a valid null-terminated string
|
||||
/// `pairing_file` must be a valid, non-null pointer to a location where the handle will be stored
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn lockdownd_pair(
|
||||
client: *mut LockdowndClientHandle,
|
||||
host_id: *const libc::c_char,
|
||||
system_buid: *const libc::c_char,
|
||||
host_name: *const libc::c_char,
|
||||
pairing_file: *mut *mut IdevicePairingFile,
|
||||
) -> *mut IdeviceFfiError {
|
||||
if client.is_null() || host_id.is_null() || system_buid.is_null() {
|
||||
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||
}
|
||||
|
||||
let host_id = unsafe {
|
||||
std::ffi::CStr::from_ptr(host_id)
|
||||
.to_string_lossy()
|
||||
.into_owned()
|
||||
};
|
||||
let system_buid = unsafe {
|
||||
std::ffi::CStr::from_ptr(system_buid)
|
||||
.to_string_lossy()
|
||||
.into_owned()
|
||||
};
|
||||
|
||||
let host_name = if host_name.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(
|
||||
match unsafe { std::ffi::CStr::from_ptr(host_name) }.to_str() {
|
||||
Ok(v) => v,
|
||||
Err(_) => {
|
||||
return ffi_err!(IdeviceError::InvalidCString);
|
||||
}
|
||||
},
|
||||
)
|
||||
};
|
||||
|
||||
let res = run_sync_local(async move {
|
||||
let client_ref = unsafe { &mut (*client).0 };
|
||||
|
||||
client_ref.pair(host_id, system_buid, host_name).await
|
||||
});
|
||||
|
||||
match res {
|
||||
Ok(pairing_file_res) => {
|
||||
let boxed_pairing_file = Box::new(IdevicePairingFile(pairing_file_res));
|
||||
unsafe { *pairing_file = Box::into_raw(boxed_pairing_file) };
|
||||
null_mut()
|
||||
}
|
||||
Err(e) => ffi_err!(e),
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets a value from lockdownd
|
||||
///
|
||||
/// # Arguments
|
||||
@@ -179,7 +247,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 +273,7 @@ pub unsafe extern "C" fn lockdownd_get_value(
|
||||
})
|
||||
};
|
||||
|
||||
let res: Result<plist::Value, IdeviceError> = RUNTIME.block_on(async move {
|
||||
let res: Result<plist::Value, IdeviceError> = run_sync_local(async move {
|
||||
let client_ref = unsafe { &mut (*client).0 };
|
||||
client_ref.get_value(value, domain).await
|
||||
});
|
||||
@@ -221,6 +289,89 @@ pub unsafe extern "C" fn lockdownd_get_value(
|
||||
}
|
||||
}
|
||||
|
||||
/// Tells the device to enter recovery mode
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `client` - A valid LockdowndClient handle
|
||||
///
|
||||
/// # Returns
|
||||
/// An IdeviceFfiError on error, null on success
|
||||
///
|
||||
/// # Safety
|
||||
/// `client` must be a valid pointer to a handle allocated by this library
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn lockdownd_enter_recovery(
|
||||
client: *mut LockdowndClientHandle,
|
||||
) -> *mut IdeviceFfiError {
|
||||
if client.is_null() {
|
||||
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||
}
|
||||
|
||||
let res: Result<(), IdeviceError> = run_sync_local(async move {
|
||||
let client_ref = unsafe { &mut (*client).0 };
|
||||
client_ref.enter_recovery().await
|
||||
});
|
||||
|
||||
match res {
|
||||
Ok(_) => null_mut(),
|
||||
Err(e) => ffi_err!(e),
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets a value in lockdownd
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `client` - A valid LockdowndClient handle
|
||||
/// * `key` - The key to set (null-terminated string)
|
||||
/// * `value` - The value to set as a plist
|
||||
/// * `domain` - The domain to set in (null-terminated string, optional)
|
||||
///
|
||||
/// # Returns
|
||||
/// An IdeviceFfiError on error, null on success
|
||||
///
|
||||
/// # Safety
|
||||
/// `client` must be a valid pointer to a handle allocated by this library
|
||||
/// `key` must be a valid null-terminated string
|
||||
/// `value` must be a valid plist
|
||||
/// `domain` must be a valid null-terminated string or NULL
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn lockdownd_set_value(
|
||||
client: *mut LockdowndClientHandle,
|
||||
key: *const libc::c_char,
|
||||
value: plist_t,
|
||||
domain: *const libc::c_char,
|
||||
) -> *mut IdeviceFfiError {
|
||||
if client.is_null() || key.is_null() || value.is_null() {
|
||||
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||
}
|
||||
|
||||
let key = match unsafe { std::ffi::CStr::from_ptr(key) }.to_str() {
|
||||
Ok(k) => k,
|
||||
Err(_) => return ffi_err!(IdeviceError::InvalidCString),
|
||||
};
|
||||
|
||||
let domain = if domain.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(match unsafe { std::ffi::CStr::from_ptr(domain) }.to_str() {
|
||||
Ok(d) => d,
|
||||
Err(_) => return ffi_err!(IdeviceError::InvalidCString),
|
||||
})
|
||||
};
|
||||
|
||||
let value = unsafe { &mut *value }.borrow_self().clone();
|
||||
|
||||
let res: Result<(), IdeviceError> = run_sync_local(async move {
|
||||
let client_ref = unsafe { &mut (*client).0 };
|
||||
client_ref.set_value(key, value, domain).await
|
||||
});
|
||||
|
||||
match res {
|
||||
Ok(_) => null_mut(),
|
||||
Err(e) => ffi_err!(e),
|
||||
}
|
||||
}
|
||||
|
||||
/// Frees a LockdowndClient handle
|
||||
///
|
||||
/// # Arguments
|
||||
@@ -232,7 +383,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) };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 };
|
||||
|
||||
|
||||
311
ffi/src/notification_proxy.rs
Normal file
311
ffi/src/notification_proxy.rs
Normal file
@@ -0,0 +1,311 @@
|
||||
// Jackson Coxson
|
||||
|
||||
use std::ffi::{CStr, CString, c_char};
|
||||
use std::ptr::null_mut;
|
||||
|
||||
use idevice::{
|
||||
IdeviceError, IdeviceService, notification_proxy::NotificationProxyClient,
|
||||
provider::IdeviceProvider,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
IdeviceFfiError, IdeviceHandle, ffi_err, provider::IdeviceProviderHandle, run_sync_local,
|
||||
};
|
||||
|
||||
pub struct NotificationProxyClientHandle(pub NotificationProxyClient);
|
||||
|
||||
/// Automatically creates and connects to Notification Proxy, returning a client handle
|
||||
///
|
||||
/// # Arguments
|
||||
/// * [`provider`] - An IdeviceProvider
|
||||
/// * [`client`] - On success, will be set to point to a newly allocated NotificationProxyClient handle
|
||||
///
|
||||
/// # Returns
|
||||
/// An IdeviceFfiError on error, null on success
|
||||
///
|
||||
/// # Safety
|
||||
/// `provider` must be a valid pointer to a handle allocated by this library
|
||||
/// `client` must be a valid, non-null pointer to a location where the handle will be stored
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn notification_proxy_connect(
|
||||
provider: *mut IdeviceProviderHandle,
|
||||
client: *mut *mut NotificationProxyClientHandle,
|
||||
) -> *mut IdeviceFfiError {
|
||||
if provider.is_null() || client.is_null() {
|
||||
tracing::error!("Null pointer provided");
|
||||
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||
}
|
||||
|
||||
let res: Result<NotificationProxyClient, IdeviceError> = run_sync_local(async move {
|
||||
let provider_ref: &dyn IdeviceProvider = unsafe { &*(*provider).0 };
|
||||
NotificationProxyClient::connect(provider_ref).await
|
||||
});
|
||||
|
||||
match res {
|
||||
Ok(r) => {
|
||||
let boxed = Box::new(NotificationProxyClientHandle(r));
|
||||
unsafe { *client = Box::into_raw(boxed) };
|
||||
null_mut()
|
||||
}
|
||||
Err(e) => {
|
||||
ffi_err!(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new NotificationProxyClient from an existing Idevice connection
|
||||
///
|
||||
/// # Arguments
|
||||
/// * [`socket`] - An IdeviceSocket handle
|
||||
/// * [`client`] - On success, will be set to point to a newly allocated NotificationProxyClient handle
|
||||
///
|
||||
/// # Returns
|
||||
/// An IdeviceFfiError on error, null on success
|
||||
///
|
||||
/// # Safety
|
||||
/// `socket` must be a valid pointer to a handle allocated by this library. The socket is consumed,
|
||||
/// and may not be used again.
|
||||
/// `client` must be a valid, non-null pointer to a location where the handle will be stored
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn notification_proxy_new(
|
||||
socket: *mut IdeviceHandle,
|
||||
client: *mut *mut NotificationProxyClientHandle,
|
||||
) -> *mut IdeviceFfiError {
|
||||
if socket.is_null() || client.is_null() {
|
||||
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||
}
|
||||
let socket = unsafe { Box::from_raw(socket) }.0;
|
||||
let r = NotificationProxyClient::new(socket);
|
||||
let boxed = Box::new(NotificationProxyClientHandle(r));
|
||||
unsafe { *client = Box::into_raw(boxed) };
|
||||
null_mut()
|
||||
}
|
||||
|
||||
/// Posts a notification to the device
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `client` - A valid NotificationProxyClient handle
|
||||
/// * `name` - C string containing the notification name
|
||||
///
|
||||
/// # Returns
|
||||
/// An IdeviceFfiError on error, null on success
|
||||
///
|
||||
/// # Safety
|
||||
/// `client` must be a valid pointer to a handle allocated by this library
|
||||
/// `name` must be a valid null-terminated C string
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn notification_proxy_post(
|
||||
client: *mut NotificationProxyClientHandle,
|
||||
name: *const c_char,
|
||||
) -> *mut IdeviceFfiError {
|
||||
if client.is_null() || name.is_null() {
|
||||
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||
}
|
||||
|
||||
let name_str = match unsafe { CStr::from_ptr(name) }.to_str() {
|
||||
Ok(s) => s.to_string(),
|
||||
Err(_) => return ffi_err!(IdeviceError::FfiInvalidString),
|
||||
};
|
||||
|
||||
let res: Result<(), IdeviceError> = run_sync_local(async move {
|
||||
let client_ref = unsafe { &mut (*client).0 };
|
||||
client_ref.post_notification(name_str).await
|
||||
});
|
||||
|
||||
match res {
|
||||
Ok(_) => null_mut(),
|
||||
Err(e) => ffi_err!(e),
|
||||
}
|
||||
}
|
||||
|
||||
/// Observes a specific notification
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `client` - A valid NotificationProxyClient handle
|
||||
/// * `name` - C string containing the notification name to observe
|
||||
///
|
||||
/// # Returns
|
||||
/// An IdeviceFfiError on error, null on success
|
||||
///
|
||||
/// # Safety
|
||||
/// `client` must be a valid pointer to a handle allocated by this library
|
||||
/// `name` must be a valid null-terminated C string
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn notification_proxy_observe(
|
||||
client: *mut NotificationProxyClientHandle,
|
||||
name: *const c_char,
|
||||
) -> *mut IdeviceFfiError {
|
||||
if client.is_null() || name.is_null() {
|
||||
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||
}
|
||||
|
||||
let name_str = match unsafe { CStr::from_ptr(name) }.to_str() {
|
||||
Ok(s) => s.to_string(),
|
||||
Err(_) => return ffi_err!(IdeviceError::FfiInvalidString),
|
||||
};
|
||||
|
||||
let res: Result<(), IdeviceError> = run_sync_local(async move {
|
||||
let client_ref = unsafe { &mut (*client).0 };
|
||||
client_ref.observe_notification(name_str).await
|
||||
});
|
||||
|
||||
match res {
|
||||
Ok(_) => null_mut(),
|
||||
Err(e) => ffi_err!(e),
|
||||
}
|
||||
}
|
||||
|
||||
/// Observes multiple notifications at once
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `client` - A valid NotificationProxyClient handle
|
||||
/// * `names` - A null-terminated array of C strings containing notification names
|
||||
///
|
||||
/// # Returns
|
||||
/// An IdeviceFfiError on error, null on success
|
||||
///
|
||||
/// # Safety
|
||||
/// `client` must be a valid pointer to a handle allocated by this library
|
||||
/// `names` must be a valid pointer to a null-terminated array of null-terminated C strings
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn notification_proxy_observe_multiple(
|
||||
client: *mut NotificationProxyClientHandle,
|
||||
names: *const *const c_char,
|
||||
) -> *mut IdeviceFfiError {
|
||||
if client.is_null() || names.is_null() {
|
||||
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||
}
|
||||
|
||||
let mut notification_names: Vec<String> = Vec::new();
|
||||
let mut i = 0;
|
||||
loop {
|
||||
let ptr = unsafe { *names.add(i) };
|
||||
if ptr.is_null() {
|
||||
break;
|
||||
}
|
||||
match unsafe { CStr::from_ptr(ptr) }.to_str() {
|
||||
Ok(s) => notification_names.push(s.to_string()),
|
||||
Err(_) => return ffi_err!(IdeviceError::FfiInvalidString),
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
|
||||
let refs: Vec<&str> = notification_names.iter().map(|s| s.as_str()).collect();
|
||||
|
||||
let res: Result<(), IdeviceError> = run_sync_local(async move {
|
||||
let client_ref = unsafe { &mut (*client).0 };
|
||||
client_ref.observe_notifications(&refs).await
|
||||
});
|
||||
|
||||
match res {
|
||||
Ok(_) => null_mut(),
|
||||
Err(e) => ffi_err!(e),
|
||||
}
|
||||
}
|
||||
|
||||
/// Receives the next notification from the device
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `client` - A valid NotificationProxyClient handle
|
||||
/// * `name_out` - On success, will be set to a newly allocated C string containing the notification name
|
||||
///
|
||||
/// # Returns
|
||||
/// An IdeviceFfiError on error, null on success
|
||||
///
|
||||
/// # Safety
|
||||
/// `client` must be a valid pointer to a handle allocated by this library
|
||||
/// `name_out` must be a valid pointer. The returned string must be freed with `notification_proxy_free_string`
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn notification_proxy_receive(
|
||||
client: *mut NotificationProxyClientHandle,
|
||||
name_out: *mut *mut c_char,
|
||||
) -> *mut IdeviceFfiError {
|
||||
if client.is_null() || name_out.is_null() {
|
||||
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||
}
|
||||
|
||||
let res: Result<String, IdeviceError> = run_sync_local(async move {
|
||||
let client_ref = unsafe { &mut (*client).0 };
|
||||
client_ref.receive_notification().await
|
||||
});
|
||||
|
||||
match res {
|
||||
Ok(name) => match CString::new(name) {
|
||||
Ok(c_string) => {
|
||||
unsafe { *name_out = c_string.into_raw() };
|
||||
null_mut()
|
||||
}
|
||||
Err(_) => ffi_err!(IdeviceError::FfiInvalidString),
|
||||
},
|
||||
Err(e) => ffi_err!(e),
|
||||
}
|
||||
}
|
||||
|
||||
/// Receives the next notification with a timeout
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `client` - A valid NotificationProxyClient handle
|
||||
/// * `interval` - Timeout in seconds to wait for a notification
|
||||
/// * `name_out` - On success, will be set to a newly allocated C string containing the notification name
|
||||
///
|
||||
/// # Returns
|
||||
/// An IdeviceFfiError on error, null on success
|
||||
///
|
||||
/// # Safety
|
||||
/// `client` must be a valid pointer to a handle allocated by this library
|
||||
/// `name_out` must be a valid pointer. The returned string must be freed with `notification_proxy_free_string`
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn notification_proxy_receive_with_timeout(
|
||||
client: *mut NotificationProxyClientHandle,
|
||||
interval: u64,
|
||||
name_out: *mut *mut c_char,
|
||||
) -> *mut IdeviceFfiError {
|
||||
if client.is_null() || name_out.is_null() {
|
||||
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||
}
|
||||
|
||||
let res: Result<String, IdeviceError> = run_sync_local(async move {
|
||||
let client_ref = unsafe { &mut (*client).0 };
|
||||
client_ref.receive_notification_with_timeout(interval).await
|
||||
});
|
||||
|
||||
match res {
|
||||
Ok(name) => match CString::new(name) {
|
||||
Ok(c_string) => {
|
||||
unsafe { *name_out = c_string.into_raw() };
|
||||
null_mut()
|
||||
}
|
||||
Err(_) => ffi_err!(IdeviceError::FfiInvalidString),
|
||||
},
|
||||
Err(e) => ffi_err!(e),
|
||||
}
|
||||
}
|
||||
|
||||
/// Frees a string returned by notification_proxy_receive
|
||||
///
|
||||
/// # Safety
|
||||
/// `s` must be a valid pointer returned from `notification_proxy_receive`
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn notification_proxy_free_string(s: *mut c_char) {
|
||||
if !s.is_null() {
|
||||
let _ = unsafe { CString::from_raw(s) };
|
||||
}
|
||||
}
|
||||
|
||||
/// Frees a handle
|
||||
///
|
||||
/// # Arguments
|
||||
/// * [`handle`] - The handle to free
|
||||
///
|
||||
/// # Safety
|
||||
/// `handle` must be a valid pointer to the handle that was allocated by this library,
|
||||
/// or NULL (in which case this function does nothing)
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn notification_proxy_client_free(
|
||||
handle: *mut NotificationProxyClientHandle,
|
||||
) {
|
||||
if !handle.is_null() {
|
||||
tracing::debug!("Freeing notification_proxy_client");
|
||||
let _ = unsafe { Box::from_raw(handle) };
|
||||
}
|
||||
}
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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) };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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
132
ffi/src/screenshotr.rs
Normal 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) };
|
||||
}
|
||||
}
|
||||
@@ -7,8 +7,12 @@ use idevice::{
|
||||
IdeviceError, IdeviceService, provider::IdeviceProvider,
|
||||
springboardservices::SpringBoardServicesClient,
|
||||
};
|
||||
use plist_ffi::plist_t;
|
||||
|
||||
use crate::{IdeviceFfiError, IdeviceHandle, RUNTIME, ffi_err, provider::IdeviceProviderHandle};
|
||||
use crate::{
|
||||
IdeviceFfiError, IdeviceHandle, ffi_err, provider::IdeviceProviderHandle, run_sync,
|
||||
run_sync_local,
|
||||
};
|
||||
|
||||
pub struct SpringBoardServicesClientHandle(pub SpringBoardServicesClient);
|
||||
|
||||
@@ -30,11 +34,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 +107,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 +119,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 +138,169 @@ pub unsafe extern "C" fn springboard_services_get_icon(
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the home screen wallpaper preview as PNG image
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `client` - A valid SpringBoardServicesClient handle
|
||||
/// * `out_result` - On success, will be set to point to newly allocated png image
|
||||
/// * `out_result_len` - On success, will contain the size of the data in bytes
|
||||
///
|
||||
/// # Returns
|
||||
/// An IdeviceFfiError on error, null on success
|
||||
///
|
||||
/// # Safety
|
||||
/// `client` must be a valid pointer to a handle allocated by this library
|
||||
/// `out_result` and `out_result_len` must be valid, non-null pointers
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn springboard_services_get_home_screen_wallpaper_preview(
|
||||
client: *mut SpringBoardServicesClientHandle,
|
||||
out_result: *mut *mut c_void,
|
||||
out_result_len: *mut libc::size_t,
|
||||
) -> *mut IdeviceFfiError {
|
||||
if client.is_null() || out_result.is_null() || out_result_len.is_null() {
|
||||
tracing::error!("Invalid arguments: {client:?}, {out_result:?}");
|
||||
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||
}
|
||||
let client = unsafe { &mut *client };
|
||||
|
||||
let res: Result<Vec<u8>, IdeviceError> =
|
||||
run_sync(async { client.0.get_home_screen_wallpaper_preview_pngdata().await });
|
||||
|
||||
match res {
|
||||
Ok(r) => {
|
||||
let len = r.len();
|
||||
let boxed_slice = r.into_boxed_slice();
|
||||
let ptr = boxed_slice.as_ptr();
|
||||
std::mem::forget(boxed_slice);
|
||||
|
||||
unsafe {
|
||||
*out_result = ptr as *mut c_void;
|
||||
*out_result_len = len;
|
||||
}
|
||||
null_mut()
|
||||
}
|
||||
Err(e) => ffi_err!(e),
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the lock screen wallpaper preview as PNG image
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `client` - A valid SpringBoardServicesClient handle
|
||||
/// * `out_result` - On success, will be set to point to newly allocated png image
|
||||
/// * `out_result_len` - On success, will contain the size of the data in bytes
|
||||
///
|
||||
/// # Returns
|
||||
/// An IdeviceFfiError on error, null on success
|
||||
///
|
||||
/// # Safety
|
||||
/// `client` must be a valid pointer to a handle allocated by this library
|
||||
/// `out_result` and `out_result_len` must be valid, non-null pointers
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn springboard_services_get_lock_screen_wallpaper_preview(
|
||||
client: *mut SpringBoardServicesClientHandle,
|
||||
out_result: *mut *mut c_void,
|
||||
out_result_len: *mut libc::size_t,
|
||||
) -> *mut IdeviceFfiError {
|
||||
if client.is_null() || out_result.is_null() || out_result_len.is_null() {
|
||||
tracing::error!("Invalid arguments: {client:?}, {out_result:?}");
|
||||
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||
}
|
||||
let client = unsafe { &mut *client };
|
||||
|
||||
let res: Result<Vec<u8>, IdeviceError> =
|
||||
run_sync(async { client.0.get_lock_screen_wallpaper_preview_pngdata().await });
|
||||
|
||||
match res {
|
||||
Ok(r) => {
|
||||
let len = r.len();
|
||||
let boxed_slice = r.into_boxed_slice();
|
||||
let ptr = boxed_slice.as_ptr();
|
||||
std::mem::forget(boxed_slice);
|
||||
|
||||
unsafe {
|
||||
*out_result = ptr as *mut c_void;
|
||||
*out_result_len = len;
|
||||
}
|
||||
null_mut()
|
||||
}
|
||||
Err(e) => ffi_err!(e),
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the current interface orientation of the device
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `client` - A valid SpringBoardServicesClient handle
|
||||
/// * `out_orientation` - On success, will contain the orientation value (0-4)
|
||||
///
|
||||
/// # Returns
|
||||
/// An IdeviceFfiError on error, null on success
|
||||
///
|
||||
/// # Safety
|
||||
/// `client` must be a valid pointer to a handle allocated by this library
|
||||
/// `out_orientation` must be a valid, non-null pointer
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn springboard_services_get_interface_orientation(
|
||||
client: *mut SpringBoardServicesClientHandle,
|
||||
out_orientation: *mut u8,
|
||||
) -> *mut IdeviceFfiError {
|
||||
if client.is_null() || out_orientation.is_null() {
|
||||
tracing::error!("Invalid arguments: {client:?}, {out_orientation:?}");
|
||||
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||
}
|
||||
let client = unsafe { &mut *client };
|
||||
|
||||
let res = run_sync(async { client.0.get_interface_orientation().await });
|
||||
|
||||
match res {
|
||||
Ok(orientation) => {
|
||||
unsafe {
|
||||
*out_orientation = orientation as u8;
|
||||
}
|
||||
null_mut()
|
||||
}
|
||||
Err(e) => ffi_err!(e),
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the home screen icon layout metrics
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `client` - A valid SpringBoardServicesClient handle
|
||||
/// * `res` - On success, will point to a plist dictionary node containing the metrics
|
||||
///
|
||||
/// # Returns
|
||||
/// An IdeviceFfiError on error, null on success
|
||||
///
|
||||
/// # Safety
|
||||
/// `client` must be a valid pointer to a handle allocated by this library
|
||||
/// `res` must be a valid, non-null pointer
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn springboard_services_get_homescreen_icon_metrics(
|
||||
client: *mut SpringBoardServicesClientHandle,
|
||||
res: *mut plist_t,
|
||||
) -> *mut IdeviceFfiError {
|
||||
if client.is_null() || res.is_null() {
|
||||
tracing::error!("Invalid arguments: {client:?}, {res:?}");
|
||||
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||
}
|
||||
let client = unsafe { &mut *client };
|
||||
|
||||
let output = run_sync(async { client.0.get_homescreen_icon_metrics().await });
|
||||
|
||||
match output {
|
||||
Ok(metrics) => {
|
||||
unsafe {
|
||||
*res =
|
||||
plist_ffi::PlistWrapper::new_node(plist::Value::Dictionary(metrics)).into_ptr();
|
||||
}
|
||||
null_mut()
|
||||
}
|
||||
Err(e) => ffi_err!(e),
|
||||
}
|
||||
}
|
||||
|
||||
/// Frees an SpringBoardServicesClient handle
|
||||
///
|
||||
/// # Arguments
|
||||
@@ -145,7 +312,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) };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
@@ -53,10 +55,6 @@ x509-cert = { version = "0.2", optional = true, features = [
|
||||
"builder",
|
||||
"pem",
|
||||
], default-features = false }
|
||||
x25519-dalek = { version = "2", optional = true }
|
||||
ed25519-dalek = { version = "2", features = ["rand_core"], optional = true }
|
||||
hkdf = { version = "0.12", optional = true }
|
||||
chacha20poly1305 = { version = "0.10", optional = true }
|
||||
|
||||
obfstr = { version = "0.4", optional = true }
|
||||
|
||||
@@ -69,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 = []
|
||||
@@ -91,25 +91,19 @@ installation_proxy = [
|
||||
"async_zip/deflate",
|
||||
"tokio/fs",
|
||||
]
|
||||
installcoordination_proxy = []
|
||||
springboardservices = []
|
||||
misagent = []
|
||||
mobile_image_mounter = ["dep:sha2"]
|
||||
mobileactivationd = ["dep:reqwest"]
|
||||
mobilebackup2 = []
|
||||
notification_proxy = ["tokio/macros", "tokio/time", "dep:async-stream", "dep:futures"]
|
||||
location_simulation = []
|
||||
pair = ["chrono/default", "tokio/time", "dep:sha2", "dep:rsa", "dep:x509-cert"]
|
||||
pcapd = []
|
||||
preboard_service = []
|
||||
obfuscate = ["dep:obfstr"]
|
||||
restore_service = []
|
||||
remote_pairing = [
|
||||
"dep:json",
|
||||
"dep:x25519-dalek",
|
||||
"dep:ed25519-dalek",
|
||||
"dep:hkdf",
|
||||
"dep:chacha20poly1305",
|
||||
"dep:uuid",
|
||||
]
|
||||
rsd = ["xpc"]
|
||||
screenshotr = []
|
||||
syslog_relay = ["dep:bytes"]
|
||||
@@ -123,7 +117,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",
|
||||
@@ -139,19 +133,17 @@ full = [
|
||||
"heartbeat",
|
||||
"house_arrest",
|
||||
"installation_proxy",
|
||||
"installcoordination_proxy",
|
||||
"location_simulation",
|
||||
"misagent",
|
||||
"mobile_image_mounter",
|
||||
"mobileactivationd",
|
||||
"mobilebackup2",
|
||||
"notification_proxy",
|
||||
"pair",
|
||||
"pcapd",
|
||||
"preboard_service",
|
||||
"restore_service",
|
||||
"usbmuxd",
|
||||
"xpc",
|
||||
"location_simulation",
|
||||
"remote_pairing",
|
||||
"rsd",
|
||||
"screenshotr",
|
||||
"springboardservices",
|
||||
|
||||
334
idevice/src/cursor.rs
Normal file
334
idevice/src/cursor.rs
Normal 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())
|
||||
}
|
||||
}
|
||||
@@ -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")]
|
||||
@@ -724,24 +866,13 @@ pub enum IdeviceError {
|
||||
#[error("Developer mode is not enabled")]
|
||||
DeveloperModeNotEnabled = -68,
|
||||
|
||||
#[cfg(feature = "remote_pairing")]
|
||||
#[error("could not parse as JSON")]
|
||||
JsonParseFailed(#[from] json::Error) = -69,
|
||||
#[cfg(feature = "notification_proxy")]
|
||||
#[error("notification proxy died")]
|
||||
NotificationProxyDeath = -69,
|
||||
|
||||
#[cfg(feature = "remote_pairing")]
|
||||
#[error("unknown TLV type: {0}")]
|
||||
UnknownTlv(u8) = -70,
|
||||
|
||||
#[cfg(feature = "remote_pairing")]
|
||||
#[error("malformed TLV")]
|
||||
MalformedTlv = -71,
|
||||
|
||||
#[error("failed to decode base64 string")]
|
||||
Base64Decode(#[from] base64::DecodeError) = -72,
|
||||
|
||||
#[cfg(feature = "remote_pairing")]
|
||||
#[error("pair verify failed")]
|
||||
PairVerifyFailed = -73,
|
||||
#[cfg(feature = "installation_proxy")]
|
||||
#[error("Application verification failed: {0}")]
|
||||
ApplicationVerificationFailed(String) = -70,
|
||||
}
|
||||
|
||||
impl IdeviceError {
|
||||
@@ -785,6 +916,15 @@ impl IdeviceError {
|
||||
Some(Self::InternalError(detailed_error))
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "installation_proxy")]
|
||||
"ApplicationVerificationFailed" => {
|
||||
let msg = context
|
||||
.get("ErrorDescription")
|
||||
.and_then(|x| x.as_string())
|
||||
.unwrap_or("No context")
|
||||
.to_string();
|
||||
Some(Self::ApplicationVerificationFailed(msg))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -792,6 +932,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,
|
||||
@@ -906,15 +1047,12 @@ impl IdeviceError {
|
||||
#[cfg(feature = "installation_proxy")]
|
||||
IdeviceError::MalformedPackageArchive(_) => -67,
|
||||
IdeviceError::DeveloperModeNotEnabled => -68,
|
||||
#[cfg(feature = "remote_pairing")]
|
||||
IdeviceError::JsonParseFailed(_) => -69,
|
||||
#[cfg(feature = "remote_pairing")]
|
||||
IdeviceError::UnknownTlv(_) => -70,
|
||||
#[cfg(feature = "remote_pairing")]
|
||||
IdeviceError::MalformedTlv => -71,
|
||||
IdeviceError::Base64Decode(_) => -72,
|
||||
#[cfg(feature = "remote_pairing")]
|
||||
IdeviceError::PairVerifyFailed => -73,
|
||||
|
||||
#[cfg(feature = "notification_proxy")]
|
||||
IdeviceError::NotificationProxyDeath => -69,
|
||||
|
||||
#[cfg(feature = "installation_proxy")]
|
||||
IdeviceError::ApplicationVerificationFailed(_) => -70,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
15
idevice/src/obfuscation.rs
Normal file
15
idevice/src/obfuscation.rs
Normal 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)
|
||||
}
|
||||
}};
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
});
|
||||
|
||||
818
idevice/src/services/afc/inner_file.rs
Normal file
818
idevice/src/services/afc/inner_file.rs
Normal 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 didn’t 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();
|
||||
}
|
||||
}
|
||||
23
idevice/src/services/afc/inner_file_impl_macro.rs
Normal file
23
idevice/src/services/afc/inner_file_impl_macro.rs
Normal 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
|
||||
)+
|
||||
};
|
||||
}
|
||||
@@ -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());
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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())),
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>,
|
||||
}
|
||||
|
||||
@@ -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>,
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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?;
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -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>,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user