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
|
- name: Install rustup targets
|
||||||
run: |
|
run: |
|
||||||
|
rustup target add aarch64-apple-ios && \
|
||||||
|
rustup target add x86_64-apple-ios && \
|
||||||
|
rustup target add aarch64-apple-ios-sim && \
|
||||||
rustup target add aarch64-apple-darwin && \
|
rustup target add aarch64-apple-darwin && \
|
||||||
rustup target add x86_64-apple-darwin && cargo install --force --locked bindgen-cli
|
rustup target add x86_64-apple-darwin && \
|
||||||
|
rustup target add aarch64-apple-ios-macabi && \
|
||||||
|
rustup target add x86_64-apple-ios-macabi && \
|
||||||
|
cargo install --force --locked bindgen-cli
|
||||||
|
|
||||||
- name: Build all Apple targets and examples/tools
|
- name: Build all Apple targets and examples/tools
|
||||||
run: |
|
run: |
|
||||||
@@ -44,6 +50,12 @@ jobs:
|
|||||||
path: |
|
path: |
|
||||||
target/*apple*/release/libidevice_ffi.a
|
target/*apple*/release/libidevice_ffi.a
|
||||||
|
|
||||||
|
- name: Upload macOS+iOS XCFramework
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: idevice-xcframework
|
||||||
|
path: swift/bundle.zip
|
||||||
|
|
||||||
- name: Upload C examples/tools
|
- name: Upload C examples/tools
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
|
|||||||
1593
Cargo.lock
generated
1593
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.|
|
| `afc` | Apple File Conduit for file system access.|
|
||||||
| `amfi` | Apple mobile file integrity service |
|
| `amfi` | Apple mobile file integrity service |
|
||||||
|
| `bt_packet_logger` | Capture Bluetooth packets. |
|
||||||
|
| `companion_proxy` | Manage paired Apple Watches. |
|
||||||
| `core_device_proxy` | Start a secure tunnel to access protected services. |
|
| `core_device_proxy` | Start a secure tunnel to access protected services. |
|
||||||
| `crashreportcopymobile`| Copy crash reports.|
|
| `crashreportcopymobile`| Copy crash reports.|
|
||||||
| `debug_proxy` | Send GDB commands to the device.|
|
| `debug_proxy` | Send GDB commands to the device.|
|
||||||
@@ -55,34 +57,36 @@ To keep dependency bloat and compile time down, everything is contained in featu
|
|||||||
| `heartbeat` | Maintain a heartbeat connection.|
|
| `heartbeat` | Maintain a heartbeat connection.|
|
||||||
| `house_arrest` | Manage files in app containers |
|
| `house_arrest` | Manage files in app containers |
|
||||||
| `installation_proxy` | Manage app installation and uninstallation.|
|
| `installation_proxy` | Manage app installation and uninstallation.|
|
||||||
| `springboardservices` | Control SpringBoard (e.g. UI interactions). Partial support.|
|
| `installcoordination_proxy` | Manage app installation coordination.|
|
||||||
| `misagent` | Manage provisioning profiles on the device.|
|
|
||||||
| `mobilebackup2` | Manage backups.|
|
|
||||||
| `mobile_image_mounter` | Manage DDI images.|
|
|
||||||
| `location_simulation` | Simulate GPS locations on the device.|
|
| `location_simulation` | Simulate GPS locations on the device.|
|
||||||
|
| `misagent` | Manage provisioning profiles on the device.|
|
||||||
|
| `mobile_image_mounter` | Manage DDI images.|
|
||||||
|
| `mobileactivationd` | Activate/Deactivate device.|
|
||||||
|
| `mobilebackup2` | Manage backups.|
|
||||||
| `pair` | Pair the device.|
|
| `pair` | Pair the device.|
|
||||||
| `syslog_relay` | Relay system logs from the device |
|
| `pcapd` | Capture network packets.|
|
||||||
|
| `preboard_service` | Interface with Preboard.|
|
||||||
|
| `restore_service` | Restore service (recovery/reboot).|
|
||||||
|
| `screenshotr` | Take screenshots.|
|
||||||
|
| `springboardservices` | Control SpringBoard (icons, wallpaper, orientation, etc.).|
|
||||||
|
| `syslog_relay` | Relay system logs and OS trace logs from the device. |
|
||||||
| `tcp` | Connect to devices over TCP.|
|
| `tcp` | Connect to devices over TCP.|
|
||||||
| `tunnel_tcp_stack` | Naive in-process TCP stack for `core_device_proxy`.|
|
| `tunnel_tcp_stack` | Naive in-process TCP stack for `core_device_proxy`.|
|
||||||
| `tss` | Make requests to Apple's TSS servers. Partial support.|
|
| `tss` | Make requests to Apple's TSS servers. Partial support.|
|
||||||
| `tunneld` | Interface with [pymobiledevice3](https://github.com/doronz88/pymobiledevice3)'s tunneld. |
|
| `tunneld` | Interface with [pymobiledevice3](https://github.com/doronz88/pymobiledevice3)'s tunneld. |
|
||||||
| `usbmuxd` | Connect using the usbmuxd daemon.|
|
| `usbmuxd` | Connect using the usbmuxd daemon.|
|
||||||
| `xpc` | Access protected services via XPC over RSD. |
|
| `xpc` | Access protected services via XPC over RSD. |
|
||||||
|
| `notification_proxy` | Post and observe iOS notifications. |
|
||||||
|
|
||||||
### Planned/TODO
|
### Planned/TODO
|
||||||
|
|
||||||
Finish the following:
|
Finish the following:
|
||||||
|
|
||||||
- springboard
|
- webinspector
|
||||||
|
|
||||||
Implement the following:
|
Implement the following:
|
||||||
|
|
||||||
- companion_proxy
|
- file_relay
|
||||||
- diagnostics
|
|
||||||
- mobilebackup2
|
|
||||||
- notification_proxy
|
|
||||||
- screenshot
|
|
||||||
- webinspector
|
|
||||||
|
|
||||||
As this project is done in my free time within my busy schedule, there
|
As this project is done in my free time within my busy schedule, there
|
||||||
is no ETA for any of these. Feel free to contribute or donate!
|
is no ETA for any of these. Feel free to contribute or donate!
|
||||||
|
|||||||
@@ -32,8 +32,8 @@ endif()
|
|||||||
# ---- Build the C++ wrapper library -----------------------------------------
|
# ---- Build the C++ wrapper library -----------------------------------------
|
||||||
|
|
||||||
# Collect your .cpps
|
# Collect your .cpps
|
||||||
file(GLOB IDEVICE_CPP_SOURCES
|
file(GLOB_RECURSE IDEVICE_CPP_SOURCES
|
||||||
${IDEVICE_CPP_SRC_DIR}/*.cpp
|
"${IDEVICE_CPP_SRC_DIR}/*.cpp"
|
||||||
)
|
)
|
||||||
|
|
||||||
file(GLOB PLIST_CPP_SOURCES
|
file(GLOB PLIST_CPP_SOURCES
|
||||||
|
|||||||
@@ -6,11 +6,11 @@
|
|||||||
#include <thread>
|
#include <thread>
|
||||||
|
|
||||||
#include <idevice++/core_device_proxy.hpp>
|
#include <idevice++/core_device_proxy.hpp>
|
||||||
|
#include <idevice++/dvt/location_simulation.hpp>
|
||||||
|
#include <idevice++/dvt/remote_server.hpp>
|
||||||
#include <idevice++/ffi.hpp>
|
#include <idevice++/ffi.hpp>
|
||||||
#include <idevice++/location_simulation.hpp>
|
|
||||||
#include <idevice++/provider.hpp>
|
#include <idevice++/provider.hpp>
|
||||||
#include <idevice++/readwrite.hpp>
|
#include <idevice++/readwrite.hpp>
|
||||||
#include <idevice++/remote_server.hpp>
|
|
||||||
#include <idevice++/rsd.hpp>
|
#include <idevice++/rsd.hpp>
|
||||||
#include <idevice++/usbmuxd.hpp>
|
#include <idevice++/usbmuxd.hpp>
|
||||||
|
|
||||||
|
|||||||
108
cpp/examples/screenshot.cpp
Normal file
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 = {
|
objects = {
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
1914C7972E623CC2002EAB6E /* option.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 1914C7962E623CC2002EAB6E /* option.hpp */; };
|
196C57452EA7F5DC00188F4B /* location_simulation.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 196C56B42EA7F53000188F4B /* location_simulation.cpp */; };
|
||||||
1914C7992E623CC8002EAB6E /* result.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 1914C7982E623CC8002EAB6E /* result.hpp */; };
|
196C57462EA7F5DC00188F4B /* process_control.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 196C56B52EA7F53000188F4B /* process_control.cpp */; };
|
||||||
198077932E5CA6EF00CB501E /* adapter_stream.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1980776C2E5CA69800CB501E /* adapter_stream.cpp */; };
|
196C57472EA7F5DC00188F4B /* remote_server.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 196C56B62EA7F53000188F4B /* remote_server.cpp */; };
|
||||||
198077942E5CA6EF00CB501E /* app_service.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1980776D2E5CA69800CB501E /* app_service.cpp */; };
|
196C57482EA7F5DC00188F4B /* screenshot.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 196C56B72EA7F53000188F4B /* screenshot.cpp */; };
|
||||||
198077952E5CA6EF00CB501E /* core_device.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1980776E2E5CA69800CB501E /* core_device.cpp */; };
|
196C57492EA7F5DC00188F4B /* adapter_stream.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 196C56B92EA7F53000188F4B /* adapter_stream.cpp */; };
|
||||||
198077962E5CA6EF00CB501E /* debug_proxy.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1980776F2E5CA69800CB501E /* debug_proxy.cpp */; };
|
196C574A2EA7F5DC00188F4B /* app_service.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 196C56BA2EA7F53000188F4B /* app_service.cpp */; };
|
||||||
198077972E5CA6EF00CB501E /* diagnosticsservice.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 198077702E5CA69800CB501E /* diagnosticsservice.cpp */; };
|
196C574B2EA7F5DC00188F4B /* core_device.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 196C56BB2EA7F53000188F4B /* core_device.cpp */; };
|
||||||
198077982E5CA6EF00CB501E /* ffi.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 198077712E5CA69800CB501E /* ffi.cpp */; };
|
196C574C2EA7F5DC00188F4B /* debug_proxy.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 196C56BC2EA7F53000188F4B /* debug_proxy.cpp */; };
|
||||||
198077992E5CA6EF00CB501E /* idevice.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 198077722E5CA69800CB501E /* idevice.cpp */; };
|
196C574D2EA7F5DC00188F4B /* diagnosticsservice.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 196C56BD2EA7F53000188F4B /* diagnosticsservice.cpp */; };
|
||||||
1980779A2E5CA6EF00CB501E /* location_simulation.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 198077732E5CA69800CB501E /* location_simulation.cpp */; };
|
196C574E2EA7F5DC00188F4B /* ffi.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 196C56BE2EA7F53000188F4B /* ffi.cpp */; };
|
||||||
1980779B2E5CA6EF00CB501E /* lockdown.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 198077742E5CA69800CB501E /* lockdown.cpp */; };
|
196C574F2EA7F5DC00188F4B /* heartbeat.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 196C56BF2EA7F53000188F4B /* heartbeat.cpp */; };
|
||||||
1980779C2E5CA6EF00CB501E /* pairing_file.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 198077752E5CA69800CB501E /* pairing_file.cpp */; };
|
196C57502EA7F5DC00188F4B /* idevice.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 196C56C02EA7F53000188F4B /* idevice.cpp */; };
|
||||||
1980779D2E5CA6EF00CB501E /* provider.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 198077762E5CA69800CB501E /* provider.cpp */; };
|
196C57512EA7F5DC00188F4B /* installation_proxy.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 196C56C12EA7F53000188F4B /* installation_proxy.cpp */; };
|
||||||
1980779E2E5CA6EF00CB501E /* remote_server.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 198077772E5CA69800CB501E /* remote_server.cpp */; };
|
196C57522EA7F5DC00188F4B /* lockdown.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 196C56C22EA7F53000188F4B /* lockdown.cpp */; };
|
||||||
1980779F2E5CA6EF00CB501E /* rsd.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 198077782E5CA69800CB501E /* rsd.cpp */; };
|
196C57532EA7F5DC00188F4B /* mobile_image_mounter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 196C56C32EA7F53000188F4B /* mobile_image_mounter.cpp */; };
|
||||||
198077A02E5CA6EF00CB501E /* tcp_callback_feeder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 198077792E5CA69800CB501E /* tcp_callback_feeder.cpp */; };
|
196C57542EA7F5DC00188F4B /* pairing_file.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 196C56C42EA7F53000188F4B /* pairing_file.cpp */; };
|
||||||
198077A12E5CA6EF00CB501E /* usbmuxd.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1980777A2E5CA69800CB501E /* usbmuxd.cpp */; };
|
196C57552EA7F5DC00188F4B /* provider.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 196C56C52EA7F53000188F4B /* provider.cpp */; };
|
||||||
198077B72E5CA6FC00CB501E /* core_device_proxy.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 198077A62E5CA6FC00CB501E /* core_device_proxy.hpp */; };
|
196C57562EA7F5DC00188F4B /* rsd.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 196C56C62EA7F53000188F4B /* rsd.cpp */; };
|
||||||
198077B82E5CA6FC00CB501E /* pairing_file.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 198077AD2E5CA6FC00CB501E /* pairing_file.hpp */; };
|
196C57572EA7F5DC00188F4B /* tcp_callback_feeder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 196C56C72EA7F53000188F4B /* tcp_callback_feeder.cpp */; };
|
||||||
198077B92E5CA6FC00CB501E /* bindings.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 198077A52E5CA6FC00CB501E /* bindings.hpp */; };
|
196C57582EA7F5DC00188F4B /* usbmuxd.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 196C56C82EA7F53000188F4B /* usbmuxd.cpp */; };
|
||||||
198077BA2E5CA6FC00CB501E /* diagnosticsservice.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 198077A82E5CA6FC00CB501E /* diagnosticsservice.hpp */; };
|
196C57592EA7F5F400188F4B /* location_simulation.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 196C56DE2EA7F54900188F4B /* location_simulation.hpp */; };
|
||||||
198077BB2E5CA6FC00CB501E /* idevice.h in Headers */ = {isa = PBXBuildFile; fileRef = 198077B52E5CA6FC00CB501E /* idevice.h */; };
|
196C575A2EA7F5F400188F4B /* process_control.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 196C56DF2EA7F54900188F4B /* process_control.hpp */; };
|
||||||
198077BC2E5CA6FC00CB501E /* debug_proxy.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 198077A72E5CA6FC00CB501E /* debug_proxy.hpp */; };
|
196C575B2EA7F5F400188F4B /* remote_server.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 196C56E02EA7F54900188F4B /* remote_server.hpp */; };
|
||||||
198077BD2E5CA6FC00CB501E /* ffi.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 198077A92E5CA6FC00CB501E /* ffi.hpp */; };
|
196C575C2EA7F5F400188F4B /* screenshot.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 196C56E12EA7F54900188F4B /* screenshot.hpp */; };
|
||||||
198077BE2E5CA6FC00CB501E /* rsd.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 198077B12E5CA6FC00CB501E /* rsd.hpp */; };
|
196C575D2EA7F5F400188F4B /* adapter_stream.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 196C56E32EA7F54900188F4B /* adapter_stream.hpp */; };
|
||||||
198077BF2E5CA6FC00CB501E /* tcp_object_stack.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 198077B22E5CA6FC00CB501E /* tcp_object_stack.hpp */; };
|
196C575E2EA7F5F400188F4B /* app_service.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 196C56E42EA7F54900188F4B /* app_service.hpp */; };
|
||||||
198077C02E5CA6FC00CB501E /* remote_server.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 198077B02E5CA6FC00CB501E /* remote_server.hpp */; };
|
196C575F2EA7F5F400188F4B /* bindings.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 196C56E52EA7F54900188F4B /* bindings.hpp */; };
|
||||||
198077C12E5CA6FC00CB501E /* readwrite.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 198077AF2E5CA6FC00CB501E /* readwrite.hpp */; };
|
196C57602EA7F5F400188F4B /* core_device_proxy.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 196C56E62EA7F54900188F4B /* core_device_proxy.hpp */; };
|
||||||
198077C22E5CA6FC00CB501E /* location_simulation.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 198077AB2E5CA6FC00CB501E /* location_simulation.hpp */; };
|
196C57612EA7F5F400188F4B /* debug_proxy.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 196C56E72EA7F54900188F4B /* debug_proxy.hpp */; };
|
||||||
198077C32E5CA6FC00CB501E /* adapter_stream.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 198077A32E5CA6FC00CB501E /* adapter_stream.hpp */; };
|
196C57622EA7F5F400188F4B /* diagnosticsservice.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 196C56E82EA7F54900188F4B /* diagnosticsservice.hpp */; };
|
||||||
198077C42E5CA6FC00CB501E /* lockdown.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 198077AC2E5CA6FC00CB501E /* lockdown.hpp */; };
|
196C57632EA7F5F400188F4B /* ffi.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 196C56E92EA7F54900188F4B /* ffi.hpp */; };
|
||||||
198077C52E5CA6FC00CB501E /* usbmuxd.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 198077B32E5CA6FC00CB501E /* usbmuxd.hpp */; };
|
196C57642EA7F5F400188F4B /* heartbeat.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 196C56EA2EA7F54900188F4B /* heartbeat.hpp */; };
|
||||||
198077C62E5CA6FC00CB501E /* app_service.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 198077A42E5CA6FC00CB501E /* app_service.hpp */; };
|
196C57652EA7F5F400188F4B /* idevice.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 196C56EB2EA7F54900188F4B /* idevice.hpp */; };
|
||||||
198077C72E5CA6FC00CB501E /* idevice.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 198077AA2E5CA6FC00CB501E /* idevice.hpp */; };
|
196C57662EA7F5F400188F4B /* installation_proxy.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 196C56EC2EA7F54900188F4B /* installation_proxy.hpp */; };
|
||||||
198077C82E5CA6FC00CB501E /* provider.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 198077AE2E5CA6FC00CB501E /* provider.hpp */; };
|
196C57672EA7F5F400188F4B /* lockdown.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 196C56ED2EA7F54900188F4B /* lockdown.hpp */; };
|
||||||
|
196C57682EA7F5F400188F4B /* mobile_image_mounter.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 196C56EE2EA7F54900188F4B /* mobile_image_mounter.hpp */; };
|
||||||
|
196C57692EA7F5F400188F4B /* option.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 196C56EF2EA7F54900188F4B /* option.hpp */; };
|
||||||
|
196C576A2EA7F5F400188F4B /* pairing_file.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 196C56F02EA7F54900188F4B /* pairing_file.hpp */; };
|
||||||
|
196C576B2EA7F5F400188F4B /* provider.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 196C56F12EA7F54900188F4B /* provider.hpp */; };
|
||||||
|
196C576C2EA7F5F400188F4B /* readwrite.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 196C56F22EA7F54900188F4B /* readwrite.hpp */; };
|
||||||
|
196C576D2EA7F5F400188F4B /* result.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 196C56F32EA7F54900188F4B /* result.hpp */; };
|
||||||
|
196C576E2EA7F5F400188F4B /* rsd.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 196C56F42EA7F54900188F4B /* rsd.hpp */; };
|
||||||
|
196C576F2EA7F5F400188F4B /* tcp_object_stack.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 196C56F52EA7F54900188F4B /* tcp_object_stack.hpp */; };
|
||||||
|
196C57702EA7F5F400188F4B /* usbmuxd.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 196C56F62EA7F54900188F4B /* usbmuxd.hpp */; };
|
||||||
|
196C57712EA7F5F400188F4B /* idevice.h in Headers */ = {isa = PBXBuildFile; fileRef = 196C56F82EA7F54900188F4B /* idevice.h */; };
|
||||||
198077DF2E5CCA2900CB501E /* libidevice_ffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 198077DB2E5CC33000CB501E /* libidevice_ffi.a */; };
|
198077DF2E5CCA2900CB501E /* libidevice_ffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 198077DB2E5CC33000CB501E /* libidevice_ffi.a */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
1914C7962E623CC2002EAB6E /* option.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = option.hpp; sourceTree = "<group>"; };
|
196C56B42EA7F53000188F4B /* location_simulation.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = location_simulation.cpp; sourceTree = "<group>"; };
|
||||||
1914C7982E623CC8002EAB6E /* result.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = result.hpp; sourceTree = "<group>"; };
|
196C56B52EA7F53000188F4B /* process_control.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = process_control.cpp; sourceTree = "<group>"; };
|
||||||
1980776C2E5CA69800CB501E /* adapter_stream.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = adapter_stream.cpp; sourceTree = "<group>"; };
|
196C56B62EA7F53000188F4B /* remote_server.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = remote_server.cpp; sourceTree = "<group>"; };
|
||||||
1980776D2E5CA69800CB501E /* app_service.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = app_service.cpp; sourceTree = "<group>"; };
|
196C56B72EA7F53000188F4B /* screenshot.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = screenshot.cpp; sourceTree = "<group>"; };
|
||||||
1980776E2E5CA69800CB501E /* core_device.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = core_device.cpp; sourceTree = "<group>"; };
|
196C56B92EA7F53000188F4B /* adapter_stream.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = adapter_stream.cpp; sourceTree = "<group>"; };
|
||||||
1980776F2E5CA69800CB501E /* debug_proxy.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = debug_proxy.cpp; sourceTree = "<group>"; };
|
196C56BA2EA7F53000188F4B /* app_service.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = app_service.cpp; sourceTree = "<group>"; };
|
||||||
198077702E5CA69800CB501E /* diagnosticsservice.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = diagnosticsservice.cpp; sourceTree = "<group>"; };
|
196C56BB2EA7F53000188F4B /* core_device.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = core_device.cpp; sourceTree = "<group>"; };
|
||||||
198077712E5CA69800CB501E /* ffi.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = ffi.cpp; sourceTree = "<group>"; };
|
196C56BC2EA7F53000188F4B /* debug_proxy.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = debug_proxy.cpp; sourceTree = "<group>"; };
|
||||||
198077722E5CA69800CB501E /* idevice.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = idevice.cpp; sourceTree = "<group>"; };
|
196C56BD2EA7F53000188F4B /* diagnosticsservice.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = diagnosticsservice.cpp; sourceTree = "<group>"; };
|
||||||
198077732E5CA69800CB501E /* location_simulation.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = location_simulation.cpp; sourceTree = "<group>"; };
|
196C56BE2EA7F53000188F4B /* ffi.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = ffi.cpp; sourceTree = "<group>"; };
|
||||||
198077742E5CA69800CB501E /* lockdown.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = lockdown.cpp; sourceTree = "<group>"; };
|
196C56BF2EA7F53000188F4B /* heartbeat.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = heartbeat.cpp; sourceTree = "<group>"; };
|
||||||
198077752E5CA69800CB501E /* pairing_file.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = pairing_file.cpp; sourceTree = "<group>"; };
|
196C56C02EA7F53000188F4B /* idevice.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = idevice.cpp; sourceTree = "<group>"; };
|
||||||
198077762E5CA69800CB501E /* provider.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = provider.cpp; sourceTree = "<group>"; };
|
196C56C12EA7F53000188F4B /* installation_proxy.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = installation_proxy.cpp; sourceTree = "<group>"; };
|
||||||
198077772E5CA69800CB501E /* remote_server.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = remote_server.cpp; sourceTree = "<group>"; };
|
196C56C22EA7F53000188F4B /* lockdown.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = lockdown.cpp; sourceTree = "<group>"; };
|
||||||
198077782E5CA69800CB501E /* rsd.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = rsd.cpp; sourceTree = "<group>"; };
|
196C56C32EA7F53000188F4B /* mobile_image_mounter.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = mobile_image_mounter.cpp; sourceTree = "<group>"; };
|
||||||
198077792E5CA69800CB501E /* tcp_callback_feeder.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = tcp_callback_feeder.cpp; sourceTree = "<group>"; };
|
196C56C42EA7F53000188F4B /* pairing_file.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = pairing_file.cpp; sourceTree = "<group>"; };
|
||||||
1980777A2E5CA69800CB501E /* usbmuxd.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = usbmuxd.cpp; sourceTree = "<group>"; };
|
196C56C52EA7F53000188F4B /* provider.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = provider.cpp; sourceTree = "<group>"; };
|
||||||
|
196C56C62EA7F53000188F4B /* rsd.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = rsd.cpp; sourceTree = "<group>"; };
|
||||||
|
196C56C72EA7F53000188F4B /* tcp_callback_feeder.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = tcp_callback_feeder.cpp; sourceTree = "<group>"; };
|
||||||
|
196C56C82EA7F53000188F4B /* usbmuxd.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = usbmuxd.cpp; sourceTree = "<group>"; };
|
||||||
|
196C56DE2EA7F54900188F4B /* location_simulation.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = location_simulation.hpp; sourceTree = "<group>"; };
|
||||||
|
196C56DF2EA7F54900188F4B /* process_control.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = process_control.hpp; sourceTree = "<group>"; };
|
||||||
|
196C56E02EA7F54900188F4B /* remote_server.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = remote_server.hpp; sourceTree = "<group>"; };
|
||||||
|
196C56E12EA7F54900188F4B /* screenshot.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = screenshot.hpp; sourceTree = "<group>"; };
|
||||||
|
196C56E32EA7F54900188F4B /* adapter_stream.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = adapter_stream.hpp; sourceTree = "<group>"; };
|
||||||
|
196C56E42EA7F54900188F4B /* app_service.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = app_service.hpp; sourceTree = "<group>"; };
|
||||||
|
196C56E52EA7F54900188F4B /* bindings.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = bindings.hpp; sourceTree = "<group>"; };
|
||||||
|
196C56E62EA7F54900188F4B /* core_device_proxy.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = core_device_proxy.hpp; sourceTree = "<group>"; };
|
||||||
|
196C56E72EA7F54900188F4B /* debug_proxy.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = debug_proxy.hpp; sourceTree = "<group>"; };
|
||||||
|
196C56E82EA7F54900188F4B /* diagnosticsservice.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = diagnosticsservice.hpp; sourceTree = "<group>"; };
|
||||||
|
196C56E92EA7F54900188F4B /* ffi.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ffi.hpp; sourceTree = "<group>"; };
|
||||||
|
196C56EA2EA7F54900188F4B /* heartbeat.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = heartbeat.hpp; sourceTree = "<group>"; };
|
||||||
|
196C56EB2EA7F54900188F4B /* idevice.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = idevice.hpp; sourceTree = "<group>"; };
|
||||||
|
196C56EC2EA7F54900188F4B /* installation_proxy.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = installation_proxy.hpp; sourceTree = "<group>"; };
|
||||||
|
196C56ED2EA7F54900188F4B /* lockdown.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = lockdown.hpp; sourceTree = "<group>"; };
|
||||||
|
196C56EE2EA7F54900188F4B /* mobile_image_mounter.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = mobile_image_mounter.hpp; sourceTree = "<group>"; };
|
||||||
|
196C56EF2EA7F54900188F4B /* option.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = option.hpp; sourceTree = "<group>"; };
|
||||||
|
196C56F02EA7F54900188F4B /* pairing_file.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = pairing_file.hpp; sourceTree = "<group>"; };
|
||||||
|
196C56F12EA7F54900188F4B /* provider.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = provider.hpp; sourceTree = "<group>"; };
|
||||||
|
196C56F22EA7F54900188F4B /* readwrite.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = readwrite.hpp; sourceTree = "<group>"; };
|
||||||
|
196C56F32EA7F54900188F4B /* result.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = result.hpp; sourceTree = "<group>"; };
|
||||||
|
196C56F42EA7F54900188F4B /* rsd.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = rsd.hpp; sourceTree = "<group>"; };
|
||||||
|
196C56F52EA7F54900188F4B /* tcp_object_stack.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = tcp_object_stack.hpp; sourceTree = "<group>"; };
|
||||||
|
196C56F62EA7F54900188F4B /* usbmuxd.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = usbmuxd.hpp; sourceTree = "<group>"; };
|
||||||
|
196C56F82EA7F54900188F4B /* idevice.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = idevice.h; sourceTree = "<group>"; };
|
||||||
1980778F2E5CA6C700CB501E /* libidevice++.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libidevice++.a"; sourceTree = BUILT_PRODUCTS_DIR; };
|
1980778F2E5CA6C700CB501E /* libidevice++.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libidevice++.a"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
198077A32E5CA6FC00CB501E /* adapter_stream.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = adapter_stream.hpp; sourceTree = "<group>"; };
|
|
||||||
198077A42E5CA6FC00CB501E /* app_service.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = app_service.hpp; sourceTree = "<group>"; };
|
|
||||||
198077A52E5CA6FC00CB501E /* bindings.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = bindings.hpp; sourceTree = "<group>"; };
|
|
||||||
198077A62E5CA6FC00CB501E /* core_device_proxy.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = core_device_proxy.hpp; sourceTree = "<group>"; };
|
|
||||||
198077A72E5CA6FC00CB501E /* debug_proxy.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = debug_proxy.hpp; sourceTree = "<group>"; };
|
|
||||||
198077A82E5CA6FC00CB501E /* diagnosticsservice.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = diagnosticsservice.hpp; sourceTree = "<group>"; };
|
|
||||||
198077A92E5CA6FC00CB501E /* ffi.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ffi.hpp; sourceTree = "<group>"; };
|
|
||||||
198077AA2E5CA6FC00CB501E /* idevice.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = idevice.hpp; sourceTree = "<group>"; };
|
|
||||||
198077AB2E5CA6FC00CB501E /* location_simulation.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = location_simulation.hpp; sourceTree = "<group>"; };
|
|
||||||
198077AC2E5CA6FC00CB501E /* lockdown.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = lockdown.hpp; sourceTree = "<group>"; };
|
|
||||||
198077AD2E5CA6FC00CB501E /* pairing_file.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = pairing_file.hpp; sourceTree = "<group>"; };
|
|
||||||
198077AE2E5CA6FC00CB501E /* provider.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = provider.hpp; sourceTree = "<group>"; };
|
|
||||||
198077AF2E5CA6FC00CB501E /* readwrite.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = readwrite.hpp; sourceTree = "<group>"; };
|
|
||||||
198077B02E5CA6FC00CB501E /* remote_server.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = remote_server.hpp; sourceTree = "<group>"; };
|
|
||||||
198077B12E5CA6FC00CB501E /* rsd.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = rsd.hpp; sourceTree = "<group>"; };
|
|
||||||
198077B22E5CA6FC00CB501E /* tcp_object_stack.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = tcp_object_stack.hpp; sourceTree = "<group>"; };
|
|
||||||
198077B32E5CA6FC00CB501E /* usbmuxd.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = usbmuxd.hpp; sourceTree = "<group>"; };
|
|
||||||
198077B52E5CA6FC00CB501E /* idevice.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = idevice.h; sourceTree = "<group>"; };
|
|
||||||
198077DB2E5CC33000CB501E /* libidevice_ffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libidevice_ffi.a; path = "${DESTINATION_PATH}/libidevice_ffi.a"; sourceTree = "<group>"; };
|
198077DB2E5CC33000CB501E /* libidevice_ffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libidevice_ffi.a; path = "${DESTINATION_PATH}/libidevice_ffi.a"; sourceTree = "<group>"; };
|
||||||
198077E02E5CD49200CB501E /* xcode_build_rust.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = xcode_build_rust.sh; sourceTree = "<group>"; };
|
198077E02E5CD49200CB501E /* xcode_build_rust.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = xcode_build_rust.sh; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
@@ -98,12 +118,95 @@
|
|||||||
/* End PBXFrameworksBuildPhase section */
|
/* End PBXFrameworksBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXGroup section */
|
/* Begin PBXGroup section */
|
||||||
|
196C56B82EA7F53000188F4B /* dvt */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
196C56B42EA7F53000188F4B /* location_simulation.cpp */,
|
||||||
|
196C56B52EA7F53000188F4B /* process_control.cpp */,
|
||||||
|
196C56B62EA7F53000188F4B /* remote_server.cpp */,
|
||||||
|
196C56B72EA7F53000188F4B /* screenshot.cpp */,
|
||||||
|
);
|
||||||
|
path = dvt;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
196C56C92EA7F53000188F4B /* src */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
196C56B82EA7F53000188F4B /* dvt */,
|
||||||
|
196C56B92EA7F53000188F4B /* adapter_stream.cpp */,
|
||||||
|
196C56BA2EA7F53000188F4B /* app_service.cpp */,
|
||||||
|
196C56BB2EA7F53000188F4B /* core_device.cpp */,
|
||||||
|
196C56BC2EA7F53000188F4B /* debug_proxy.cpp */,
|
||||||
|
196C56BD2EA7F53000188F4B /* diagnosticsservice.cpp */,
|
||||||
|
196C56BE2EA7F53000188F4B /* ffi.cpp */,
|
||||||
|
196C56BF2EA7F53000188F4B /* heartbeat.cpp */,
|
||||||
|
196C56C02EA7F53000188F4B /* idevice.cpp */,
|
||||||
|
196C56C12EA7F53000188F4B /* installation_proxy.cpp */,
|
||||||
|
196C56C22EA7F53000188F4B /* lockdown.cpp */,
|
||||||
|
196C56C32EA7F53000188F4B /* mobile_image_mounter.cpp */,
|
||||||
|
196C56C42EA7F53000188F4B /* pairing_file.cpp */,
|
||||||
|
196C56C52EA7F53000188F4B /* provider.cpp */,
|
||||||
|
196C56C62EA7F53000188F4B /* rsd.cpp */,
|
||||||
|
196C56C72EA7F53000188F4B /* tcp_callback_feeder.cpp */,
|
||||||
|
196C56C82EA7F53000188F4B /* usbmuxd.cpp */,
|
||||||
|
);
|
||||||
|
path = src;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
196C56E22EA7F54900188F4B /* dvt */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
196C56DE2EA7F54900188F4B /* location_simulation.hpp */,
|
||||||
|
196C56DF2EA7F54900188F4B /* process_control.hpp */,
|
||||||
|
196C56E02EA7F54900188F4B /* remote_server.hpp */,
|
||||||
|
196C56E12EA7F54900188F4B /* screenshot.hpp */,
|
||||||
|
);
|
||||||
|
path = dvt;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
196C56F72EA7F54900188F4B /* idevice++ */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
196C56E22EA7F54900188F4B /* dvt */,
|
||||||
|
196C56E32EA7F54900188F4B /* adapter_stream.hpp */,
|
||||||
|
196C56E42EA7F54900188F4B /* app_service.hpp */,
|
||||||
|
196C56E52EA7F54900188F4B /* bindings.hpp */,
|
||||||
|
196C56E62EA7F54900188F4B /* core_device_proxy.hpp */,
|
||||||
|
196C56E72EA7F54900188F4B /* debug_proxy.hpp */,
|
||||||
|
196C56E82EA7F54900188F4B /* diagnosticsservice.hpp */,
|
||||||
|
196C56E92EA7F54900188F4B /* ffi.hpp */,
|
||||||
|
196C56EA2EA7F54900188F4B /* heartbeat.hpp */,
|
||||||
|
196C56EB2EA7F54900188F4B /* idevice.hpp */,
|
||||||
|
196C56EC2EA7F54900188F4B /* installation_proxy.hpp */,
|
||||||
|
196C56ED2EA7F54900188F4B /* lockdown.hpp */,
|
||||||
|
196C56EE2EA7F54900188F4B /* mobile_image_mounter.hpp */,
|
||||||
|
196C56EF2EA7F54900188F4B /* option.hpp */,
|
||||||
|
196C56F02EA7F54900188F4B /* pairing_file.hpp */,
|
||||||
|
196C56F12EA7F54900188F4B /* provider.hpp */,
|
||||||
|
196C56F22EA7F54900188F4B /* readwrite.hpp */,
|
||||||
|
196C56F32EA7F54900188F4B /* result.hpp */,
|
||||||
|
196C56F42EA7F54900188F4B /* rsd.hpp */,
|
||||||
|
196C56F52EA7F54900188F4B /* tcp_object_stack.hpp */,
|
||||||
|
196C56F62EA7F54900188F4B /* usbmuxd.hpp */,
|
||||||
|
);
|
||||||
|
path = "idevice++";
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
196C56F92EA7F54900188F4B /* include */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
196C56F72EA7F54900188F4B /* idevice++ */,
|
||||||
|
196C56F82EA7F54900188F4B /* idevice.h */,
|
||||||
|
);
|
||||||
|
path = include;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
198077562E5CA62F00CB501E = {
|
198077562E5CA62F00CB501E = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
1980777B2E5CA69800CB501E /* src */,
|
196C56C92EA7F53000188F4B /* src */,
|
||||||
|
196C56F92EA7F54900188F4B /* include */,
|
||||||
198077612E5CA62F00CB501E /* Products */,
|
198077612E5CA62F00CB501E /* Products */,
|
||||||
198077B62E5CA6FC00CB501E /* include */,
|
|
||||||
198077D92E5CC31100CB501E /* Frameworks */,
|
198077D92E5CC31100CB501E /* Frameworks */,
|
||||||
198077E02E5CD49200CB501E /* xcode_build_rust.sh */,
|
198077E02E5CD49200CB501E /* xcode_build_rust.sh */,
|
||||||
);
|
);
|
||||||
@@ -117,63 +220,6 @@
|
|||||||
name = Products;
|
name = Products;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
1980777B2E5CA69800CB501E /* src */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
1980776C2E5CA69800CB501E /* adapter_stream.cpp */,
|
|
||||||
1980776D2E5CA69800CB501E /* app_service.cpp */,
|
|
||||||
1980776E2E5CA69800CB501E /* core_device.cpp */,
|
|
||||||
1980776F2E5CA69800CB501E /* debug_proxy.cpp */,
|
|
||||||
198077702E5CA69800CB501E /* diagnosticsservice.cpp */,
|
|
||||||
198077712E5CA69800CB501E /* ffi.cpp */,
|
|
||||||
198077722E5CA69800CB501E /* idevice.cpp */,
|
|
||||||
198077732E5CA69800CB501E /* location_simulation.cpp */,
|
|
||||||
198077742E5CA69800CB501E /* lockdown.cpp */,
|
|
||||||
198077752E5CA69800CB501E /* pairing_file.cpp */,
|
|
||||||
198077762E5CA69800CB501E /* provider.cpp */,
|
|
||||||
198077772E5CA69800CB501E /* remote_server.cpp */,
|
|
||||||
198077782E5CA69800CB501E /* rsd.cpp */,
|
|
||||||
198077792E5CA69800CB501E /* tcp_callback_feeder.cpp */,
|
|
||||||
1980777A2E5CA69800CB501E /* usbmuxd.cpp */,
|
|
||||||
);
|
|
||||||
path = src;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
198077B42E5CA6FC00CB501E /* idevice++ */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
198077A32E5CA6FC00CB501E /* adapter_stream.hpp */,
|
|
||||||
198077A42E5CA6FC00CB501E /* app_service.hpp */,
|
|
||||||
198077A52E5CA6FC00CB501E /* bindings.hpp */,
|
|
||||||
198077A62E5CA6FC00CB501E /* core_device_proxy.hpp */,
|
|
||||||
198077A72E5CA6FC00CB501E /* debug_proxy.hpp */,
|
|
||||||
198077A82E5CA6FC00CB501E /* diagnosticsservice.hpp */,
|
|
||||||
198077A92E5CA6FC00CB501E /* ffi.hpp */,
|
|
||||||
198077AA2E5CA6FC00CB501E /* idevice.hpp */,
|
|
||||||
198077AB2E5CA6FC00CB501E /* location_simulation.hpp */,
|
|
||||||
198077AC2E5CA6FC00CB501E /* lockdown.hpp */,
|
|
||||||
198077AD2E5CA6FC00CB501E /* pairing_file.hpp */,
|
|
||||||
198077AE2E5CA6FC00CB501E /* provider.hpp */,
|
|
||||||
198077AF2E5CA6FC00CB501E /* readwrite.hpp */,
|
|
||||||
198077B02E5CA6FC00CB501E /* remote_server.hpp */,
|
|
||||||
198077B12E5CA6FC00CB501E /* rsd.hpp */,
|
|
||||||
198077B22E5CA6FC00CB501E /* tcp_object_stack.hpp */,
|
|
||||||
198077B32E5CA6FC00CB501E /* usbmuxd.hpp */,
|
|
||||||
1914C7962E623CC2002EAB6E /* option.hpp */,
|
|
||||||
1914C7982E623CC8002EAB6E /* result.hpp */,
|
|
||||||
);
|
|
||||||
path = "idevice++";
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
198077B62E5CA6FC00CB501E /* include */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
198077B42E5CA6FC00CB501E /* idevice++ */,
|
|
||||||
198077B52E5CA6FC00CB501E /* idevice.h */,
|
|
||||||
);
|
|
||||||
path = include;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
198077D92E5CC31100CB501E /* Frameworks */ = {
|
198077D92E5CC31100CB501E /* Frameworks */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@@ -189,26 +235,31 @@
|
|||||||
isa = PBXHeadersBuildPhase;
|
isa = PBXHeadersBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
198077B72E5CA6FC00CB501E /* core_device_proxy.hpp in Headers */,
|
196C57592EA7F5F400188F4B /* location_simulation.hpp in Headers */,
|
||||||
198077B82E5CA6FC00CB501E /* pairing_file.hpp in Headers */,
|
196C575A2EA7F5F400188F4B /* process_control.hpp in Headers */,
|
||||||
198077B92E5CA6FC00CB501E /* bindings.hpp in Headers */,
|
196C575B2EA7F5F400188F4B /* remote_server.hpp in Headers */,
|
||||||
198077BA2E5CA6FC00CB501E /* diagnosticsservice.hpp in Headers */,
|
196C575C2EA7F5F400188F4B /* screenshot.hpp in Headers */,
|
||||||
198077BB2E5CA6FC00CB501E /* idevice.h in Headers */,
|
196C575D2EA7F5F400188F4B /* adapter_stream.hpp in Headers */,
|
||||||
198077BC2E5CA6FC00CB501E /* debug_proxy.hpp in Headers */,
|
196C575E2EA7F5F400188F4B /* app_service.hpp in Headers */,
|
||||||
198077BD2E5CA6FC00CB501E /* ffi.hpp in Headers */,
|
196C575F2EA7F5F400188F4B /* bindings.hpp in Headers */,
|
||||||
198077BE2E5CA6FC00CB501E /* rsd.hpp in Headers */,
|
196C57602EA7F5F400188F4B /* core_device_proxy.hpp in Headers */,
|
||||||
198077BF2E5CA6FC00CB501E /* tcp_object_stack.hpp in Headers */,
|
196C57612EA7F5F400188F4B /* debug_proxy.hpp in Headers */,
|
||||||
198077C02E5CA6FC00CB501E /* remote_server.hpp in Headers */,
|
196C57622EA7F5F400188F4B /* diagnosticsservice.hpp in Headers */,
|
||||||
198077C12E5CA6FC00CB501E /* readwrite.hpp in Headers */,
|
196C57632EA7F5F400188F4B /* ffi.hpp in Headers */,
|
||||||
198077C22E5CA6FC00CB501E /* location_simulation.hpp in Headers */,
|
196C57642EA7F5F400188F4B /* heartbeat.hpp in Headers */,
|
||||||
198077C32E5CA6FC00CB501E /* adapter_stream.hpp in Headers */,
|
196C57652EA7F5F400188F4B /* idevice.hpp in Headers */,
|
||||||
1914C7972E623CC2002EAB6E /* option.hpp in Headers */,
|
196C57662EA7F5F400188F4B /* installation_proxy.hpp in Headers */,
|
||||||
198077C42E5CA6FC00CB501E /* lockdown.hpp in Headers */,
|
196C57672EA7F5F400188F4B /* lockdown.hpp in Headers */,
|
||||||
198077C52E5CA6FC00CB501E /* usbmuxd.hpp in Headers */,
|
196C57682EA7F5F400188F4B /* mobile_image_mounter.hpp in Headers */,
|
||||||
198077C62E5CA6FC00CB501E /* app_service.hpp in Headers */,
|
196C57692EA7F5F400188F4B /* option.hpp in Headers */,
|
||||||
198077C72E5CA6FC00CB501E /* idevice.hpp in Headers */,
|
196C576A2EA7F5F400188F4B /* pairing_file.hpp in Headers */,
|
||||||
198077C82E5CA6FC00CB501E /* provider.hpp in Headers */,
|
196C576B2EA7F5F400188F4B /* provider.hpp in Headers */,
|
||||||
1914C7992E623CC8002EAB6E /* result.hpp in Headers */,
|
196C576C2EA7F5F400188F4B /* readwrite.hpp in Headers */,
|
||||||
|
196C576D2EA7F5F400188F4B /* result.hpp in Headers */,
|
||||||
|
196C576E2EA7F5F400188F4B /* rsd.hpp in Headers */,
|
||||||
|
196C576F2EA7F5F400188F4B /* tcp_object_stack.hpp in Headers */,
|
||||||
|
196C57702EA7F5F400188F4B /* usbmuxd.hpp in Headers */,
|
||||||
|
196C57712EA7F5F400188F4B /* idevice.h in Headers */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@@ -295,21 +346,26 @@
|
|||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
198077932E5CA6EF00CB501E /* adapter_stream.cpp in Sources */,
|
196C57452EA7F5DC00188F4B /* location_simulation.cpp in Sources */,
|
||||||
198077942E5CA6EF00CB501E /* app_service.cpp in Sources */,
|
196C57462EA7F5DC00188F4B /* process_control.cpp in Sources */,
|
||||||
198077952E5CA6EF00CB501E /* core_device.cpp in Sources */,
|
196C57472EA7F5DC00188F4B /* remote_server.cpp in Sources */,
|
||||||
198077962E5CA6EF00CB501E /* debug_proxy.cpp in Sources */,
|
196C57482EA7F5DC00188F4B /* screenshot.cpp in Sources */,
|
||||||
198077972E5CA6EF00CB501E /* diagnosticsservice.cpp in Sources */,
|
196C57492EA7F5DC00188F4B /* adapter_stream.cpp in Sources */,
|
||||||
198077982E5CA6EF00CB501E /* ffi.cpp in Sources */,
|
196C574A2EA7F5DC00188F4B /* app_service.cpp in Sources */,
|
||||||
198077992E5CA6EF00CB501E /* idevice.cpp in Sources */,
|
196C574B2EA7F5DC00188F4B /* core_device.cpp in Sources */,
|
||||||
1980779A2E5CA6EF00CB501E /* location_simulation.cpp in Sources */,
|
196C574C2EA7F5DC00188F4B /* debug_proxy.cpp in Sources */,
|
||||||
1980779B2E5CA6EF00CB501E /* lockdown.cpp in Sources */,
|
196C574D2EA7F5DC00188F4B /* diagnosticsservice.cpp in Sources */,
|
||||||
1980779C2E5CA6EF00CB501E /* pairing_file.cpp in Sources */,
|
196C574E2EA7F5DC00188F4B /* ffi.cpp in Sources */,
|
||||||
1980779D2E5CA6EF00CB501E /* provider.cpp in Sources */,
|
196C574F2EA7F5DC00188F4B /* heartbeat.cpp in Sources */,
|
||||||
1980779E2E5CA6EF00CB501E /* remote_server.cpp in Sources */,
|
196C57502EA7F5DC00188F4B /* idevice.cpp in Sources */,
|
||||||
1980779F2E5CA6EF00CB501E /* rsd.cpp in Sources */,
|
196C57512EA7F5DC00188F4B /* installation_proxy.cpp in Sources */,
|
||||||
198077A02E5CA6EF00CB501E /* tcp_callback_feeder.cpp in Sources */,
|
196C57522EA7F5DC00188F4B /* lockdown.cpp in Sources */,
|
||||||
198077A12E5CA6EF00CB501E /* usbmuxd.cpp in Sources */,
|
196C57532EA7F5DC00188F4B /* mobile_image_mounter.cpp in Sources */,
|
||||||
|
196C57542EA7F5DC00188F4B /* pairing_file.cpp in Sources */,
|
||||||
|
196C57552EA7F5DC00188F4B /* provider.cpp in Sources */,
|
||||||
|
196C57562EA7F5DC00188F4B /* rsd.cpp in Sources */,
|
||||||
|
196C57572EA7F5DC00188F4B /* tcp_callback_feeder.cpp in Sources */,
|
||||||
|
196C57582EA7F5DC00188F4B /* usbmuxd.cpp in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@@ -451,11 +507,12 @@
|
|||||||
GCC_WARN_UNKNOWN_PRAGMAS = YES;
|
GCC_WARN_UNKNOWN_PRAGMAS = YES;
|
||||||
GCC_WARN_UNUSED_LABEL = YES;
|
GCC_WARN_UNUSED_LABEL = YES;
|
||||||
GCC_WARN_UNUSED_PARAMETER = YES;
|
GCC_WARN_UNUSED_PARAMETER = YES;
|
||||||
|
HEADER_SEARCH_PATHS = "include/**";
|
||||||
LIBRARY_SEARCH_PATHS = (
|
LIBRARY_SEARCH_PATHS = (
|
||||||
"${TARGET_BUILD_DIR}/**",
|
"${TARGET_BUILD_DIR}/**",
|
||||||
"$(PROJECT_DIR)/${DESTINATION_PATH}",
|
"$(PROJECT_DIR)/${DESTINATION_PATH}",
|
||||||
);
|
);
|
||||||
MACOSX_DEPLOYMENT_TARGET = 15.5;
|
MACOSX_DEPLOYMENT_TARGET = 10.12;
|
||||||
OTHER_LDFLAGS = "-Wall";
|
OTHER_LDFLAGS = "-Wall";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SDKROOT = macosx;
|
SDKROOT = macosx;
|
||||||
@@ -479,11 +536,12 @@
|
|||||||
GCC_WARN_UNKNOWN_PRAGMAS = YES;
|
GCC_WARN_UNKNOWN_PRAGMAS = YES;
|
||||||
GCC_WARN_UNUSED_LABEL = YES;
|
GCC_WARN_UNUSED_LABEL = YES;
|
||||||
GCC_WARN_UNUSED_PARAMETER = YES;
|
GCC_WARN_UNUSED_PARAMETER = YES;
|
||||||
|
HEADER_SEARCH_PATHS = "include/**";
|
||||||
LIBRARY_SEARCH_PATHS = (
|
LIBRARY_SEARCH_PATHS = (
|
||||||
"${TARGET_BUILD_DIR}/**",
|
"${TARGET_BUILD_DIR}/**",
|
||||||
"$(PROJECT_DIR)/${DESTINATION_PATH}",
|
"$(PROJECT_DIR)/${DESTINATION_PATH}",
|
||||||
);
|
);
|
||||||
MACOSX_DEPLOYMENT_TARGET = 15.5;
|
MACOSX_DEPLOYMENT_TARGET = 10.12;
|
||||||
OTHER_LDFLAGS = "-Wall";
|
OTHER_LDFLAGS = "-Wall";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SDKROOT = macosx;
|
SDKROOT = macosx;
|
||||||
|
|||||||
@@ -53,6 +53,14 @@ class Adapter {
|
|||||||
return Ok(ReadWrite::adopt(s));
|
return Ok(ReadWrite::adopt(s));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Result<void, FfiError> close() {
|
||||||
|
FfiError e(::adapter_close(handle_.get()));
|
||||||
|
if (e) {
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
explicit Adapter(AdapterHandle* h) noexcept : handle_(h) {}
|
explicit Adapter(AdapterHandle* h) noexcept : handle_(h) {}
|
||||||
AdapterPtr handle_{};
|
AdapterPtr handle_{};
|
||||||
|
|||||||
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
|
#pragma once
|
||||||
#include <idevice++/bindings.hpp>
|
#include <idevice++/bindings.hpp>
|
||||||
#include <idevice++/remote_server.hpp>
|
#include <idevice++/dvt/remote_server.hpp>
|
||||||
#include <idevice++/result.hpp>
|
#include <idevice++/result.hpp>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <idevice++/bindings.hpp>
|
#include <idevice++/bindings.hpp>
|
||||||
#include <idevice++/remote_server.hpp>
|
#include <idevice++/dvt/remote_server.hpp>
|
||||||
#include <idevice++/result.hpp>
|
#include <idevice++/result.hpp>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
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++/ffi.hpp>
|
||||||
#include <idevice++/provider.hpp>
|
#include <idevice++/provider.hpp>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <sys/_types/_u_int64_t.h>
|
|
||||||
|
|
||||||
namespace IdeviceFFI {
|
namespace IdeviceFFI {
|
||||||
|
|
||||||
|
|||||||
@@ -32,6 +32,9 @@ using IdevicePtr = std::unique_ptr<IdeviceHandle, FnDeleter<IdeviceHandle, idevi
|
|||||||
class Idevice {
|
class Idevice {
|
||||||
public:
|
public:
|
||||||
static Result<Idevice, FfiError> create(IdeviceSocketHandle* socket, const std::string& label);
|
static Result<Idevice, FfiError> create(IdeviceSocketHandle* socket, const std::string& label);
|
||||||
|
#if defined(__unix__) || defined(__APPLE__)
|
||||||
|
static Result<Idevice, FfiError> from_fd(int fd, const std::string& label);
|
||||||
|
#endif
|
||||||
|
|
||||||
static Result<Idevice, FfiError>
|
static Result<Idevice, FfiError>
|
||||||
create_tcp(const sockaddr* addr, socklen_t addr_len, const std::string& label);
|
create_tcp(const sockaddr* addr, socklen_t addr_len, const std::string& label);
|
||||||
@@ -39,7 +42,7 @@ class Idevice {
|
|||||||
// Methods
|
// Methods
|
||||||
Result<std::string, FfiError> get_type() const;
|
Result<std::string, FfiError> get_type() const;
|
||||||
Result<void, FfiError> rsd_checkin();
|
Result<void, FfiError> rsd_checkin();
|
||||||
Result<void, FfiError> start_session(const PairingFile& pairing_file);
|
Result<void, FfiError> start_session(const PairingFile& pairing_file, bool legacy);
|
||||||
|
|
||||||
// Ownership/RAII
|
// Ownership/RAII
|
||||||
~Idevice() noexcept = default;
|
~Idevice() noexcept = default;
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
#include <idevice++/ffi.hpp>
|
#include <idevice++/ffi.hpp>
|
||||||
#include <idevice++/provider.hpp>
|
#include <idevice++/provider.hpp>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <sys/_types/_u_int64_t.h>
|
|
||||||
|
|
||||||
namespace IdeviceFFI {
|
namespace IdeviceFFI {
|
||||||
|
|
||||||
|
|||||||
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>
|
</ProjectConfiguration>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClCompile Include="..\src\adapter_stream.cpp" />
|
<ClInclude Include="..\..\ffi\idevice.h" />
|
||||||
<ClCompile Include="..\src\app_service.cpp" />
|
|
||||||
<ClCompile Include="..\src\core_device.cpp" />
|
|
||||||
<ClCompile Include="..\src\debug_proxy.cpp" />
|
|
||||||
<ClCompile Include="..\src\diagnosticsservice.cpp" />
|
|
||||||
<ClCompile Include="..\src\ffi.cpp" />
|
|
||||||
<ClCompile Include="..\src\idevice.cpp" />
|
|
||||||
<ClCompile Include="..\src\location_simulation.cpp" />
|
|
||||||
<ClCompile Include="..\src\lockdown.cpp" />
|
|
||||||
<ClCompile Include="..\src\pairing_file.cpp" />
|
|
||||||
<ClCompile Include="..\src\provider.cpp" />
|
|
||||||
<ClCompile Include="..\src\remote_server.cpp" />
|
|
||||||
<ClCompile Include="..\src\rsd.cpp" />
|
|
||||||
<ClCompile Include="..\src\tcp_callback_feeder.cpp" />
|
|
||||||
<ClCompile Include="..\src\usbmuxd.cpp" />
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<ClInclude Include="..\include\idevice++\adapter_stream.hpp" />
|
<ClInclude Include="..\include\idevice++\adapter_stream.hpp" />
|
||||||
<ClInclude Include="..\include\idevice++\app_service.hpp" />
|
<ClInclude Include="..\include\idevice++\app_service.hpp" />
|
||||||
<ClInclude Include="..\include\idevice++\bindings.hpp" />
|
<ClInclude Include="..\include\idevice++\bindings.hpp" />
|
||||||
<ClInclude Include="..\include\idevice++\core_device_proxy.hpp" />
|
<ClInclude Include="..\include\idevice++\core_device_proxy.hpp" />
|
||||||
<ClInclude Include="..\include\idevice++\debug_proxy.hpp" />
|
<ClInclude Include="..\include\idevice++\debug_proxy.hpp" />
|
||||||
<ClInclude Include="..\include\idevice++\diagnosticsservice.hpp" />
|
<ClInclude Include="..\include\idevice++\diagnosticsservice.hpp" />
|
||||||
|
<ClInclude Include="..\include\idevice++\dvt\location_simulation.hpp" />
|
||||||
|
<ClInclude Include="..\include\idevice++\dvt\process_control.hpp" />
|
||||||
|
<ClInclude Include="..\include\idevice++\dvt\remote_server.hpp" />
|
||||||
|
<ClInclude Include="..\include\idevice++\dvt\screenshot.hpp" />
|
||||||
<ClInclude Include="..\include\idevice++\ffi.hpp" />
|
<ClInclude Include="..\include\idevice++\ffi.hpp" />
|
||||||
|
<ClInclude Include="..\include\idevice++\heartbeat.hpp" />
|
||||||
<ClInclude Include="..\include\idevice++\idevice.hpp" />
|
<ClInclude Include="..\include\idevice++\idevice.hpp" />
|
||||||
<ClInclude Include="..\include\idevice++\location_simulation.hpp" />
|
<ClInclude Include="..\include\idevice++\installation_proxy.hpp" />
|
||||||
<ClInclude Include="..\include\idevice++\lockdown.hpp" />
|
<ClInclude Include="..\include\idevice++\lockdown.hpp" />
|
||||||
|
<ClInclude Include="..\include\idevice++\mobile_image_mounter.hpp" />
|
||||||
<ClInclude Include="..\include\idevice++\option.hpp" />
|
<ClInclude Include="..\include\idevice++\option.hpp" />
|
||||||
<ClInclude Include="..\include\idevice++\pairing_file.hpp" />
|
<ClInclude Include="..\include\idevice++\pairing_file.hpp" />
|
||||||
<ClInclude Include="..\include\idevice++\provider.hpp" />
|
<ClInclude Include="..\include\idevice++\provider.hpp" />
|
||||||
<ClInclude Include="..\include\idevice++\readwrite.hpp" />
|
<ClInclude Include="..\include\idevice++\readwrite.hpp" />
|
||||||
<ClInclude Include="..\include\idevice++\remote_server.hpp" />
|
|
||||||
<ClInclude Include="..\include\idevice++\result.hpp" />
|
<ClInclude Include="..\include\idevice++\result.hpp" />
|
||||||
<ClInclude Include="..\include\idevice++\rsd.hpp" />
|
<ClInclude Include="..\include\idevice++\rsd.hpp" />
|
||||||
<ClInclude Include="..\include\idevice++\tcp_object_stack.hpp" />
|
<ClInclude Include="..\include\idevice++\tcp_object_stack.hpp" />
|
||||||
<ClInclude Include="..\include\idevice++\usbmuxd.hpp" />
|
<ClInclude Include="..\include\idevice++\usbmuxd.hpp" />
|
||||||
<ClInclude Include="..\..\ffi\idevice.h" />
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ClCompile Include="..\src\adapter_stream.cpp" />
|
||||||
|
<ClCompile Include="..\src\app_service.cpp" />
|
||||||
|
<ClCompile Include="..\src\core_device.cpp" />
|
||||||
|
<ClCompile Include="..\src\debug_proxy.cpp" />
|
||||||
|
<ClCompile Include="..\src\diagnosticsservice.cpp" />
|
||||||
|
<ClCompile Include="..\src\dvt\location_simulation.cpp" />
|
||||||
|
<ClCompile Include="..\src\dvt\process_control.cpp" />
|
||||||
|
<ClCompile Include="..\src\dvt\remote_server.cpp" />
|
||||||
|
<ClCompile Include="..\src\dvt\screenshot.cpp" />
|
||||||
|
<ClCompile Include="..\src\ffi.cpp" />
|
||||||
|
<ClCompile Include="..\src\heartbeat.cpp" />
|
||||||
|
<ClCompile Include="..\src\idevice.cpp" />
|
||||||
|
<ClCompile Include="..\src\installation_proxy.cpp" />
|
||||||
|
<ClCompile Include="..\src\lockdown.cpp" />
|
||||||
|
<ClCompile Include="..\src\mobile_image_mounter.cpp" />
|
||||||
|
<ClCompile Include="..\src\pairing_file.cpp" />
|
||||||
|
<ClCompile Include="..\src\provider.cpp" />
|
||||||
|
<ClCompile Include="..\src\rsd.cpp" />
|
||||||
|
<ClCompile Include="..\src\tcp_callback_feeder.cpp" />
|
||||||
|
<ClCompile Include="..\src\usbmuxd.cpp" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<PropertyGroup Label="Globals">
|
<PropertyGroup Label="Globals">
|
||||||
<VCProjectVersion>17.0</VCProjectVersion>
|
<VCProjectVersion>17.0</VCProjectVersion>
|
||||||
|
|||||||
@@ -18,53 +18,21 @@
|
|||||||
</Filter>
|
</Filter>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClCompile Include="..\src\adapter_stream.cpp">
|
<ClInclude Include="..\..\ffi\idevice.h">
|
||||||
<Filter>Source Files</Filter>
|
<Filter>Header Files</Filter>
|
||||||
</ClCompile>
|
</ClInclude>
|
||||||
<ClCompile Include="..\src\app_service.cpp">
|
<ClInclude Include="..\include\idevice++\dvt\location_simulation.hpp">
|
||||||
<Filter>Source Files</Filter>
|
<Filter>Header Files\idevice++</Filter>
|
||||||
</ClCompile>
|
</ClInclude>
|
||||||
<ClCompile Include="..\src\core_device.cpp">
|
<ClInclude Include="..\include\idevice++\dvt\process_control.hpp">
|
||||||
<Filter>Source Files</Filter>
|
<Filter>Header Files\idevice++</Filter>
|
||||||
</ClCompile>
|
</ClInclude>
|
||||||
<ClCompile Include="..\src\debug_proxy.cpp">
|
<ClInclude Include="..\include\idevice++\dvt\remote_server.hpp">
|
||||||
<Filter>Source Files</Filter>
|
<Filter>Header Files\idevice++</Filter>
|
||||||
</ClCompile>
|
</ClInclude>
|
||||||
<ClCompile Include="..\src\diagnosticsservice.cpp">
|
<ClInclude Include="..\include\idevice++\dvt\screenshot.hpp">
|
||||||
<Filter>Source Files</Filter>
|
<Filter>Header Files\idevice++</Filter>
|
||||||
</ClCompile>
|
</ClInclude>
|
||||||
<ClCompile Include="..\src\ffi.cpp">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="..\src\idevice.cpp">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="..\src\location_simulation.cpp">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="..\src\lockdown.cpp">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="..\src\pairing_file.cpp">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="..\src\provider.cpp">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="..\src\remote_server.cpp">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="..\src\rsd.cpp">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="..\src\tcp_callback_feeder.cpp">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="..\src\usbmuxd.cpp">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<ClInclude Include="..\include\idevice++\adapter_stream.hpp">
|
<ClInclude Include="..\include\idevice++\adapter_stream.hpp">
|
||||||
<Filter>Header Files\idevice++</Filter>
|
<Filter>Header Files\idevice++</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
@@ -86,15 +54,24 @@
|
|||||||
<ClInclude Include="..\include\idevice++\ffi.hpp">
|
<ClInclude Include="..\include\idevice++\ffi.hpp">
|
||||||
<Filter>Header Files\idevice++</Filter>
|
<Filter>Header Files\idevice++</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
<ClInclude Include="..\include\idevice++\heartbeat.hpp">
|
||||||
|
<Filter>Header Files\idevice++</Filter>
|
||||||
|
</ClInclude>
|
||||||
<ClInclude Include="..\include\idevice++\idevice.hpp">
|
<ClInclude Include="..\include\idevice++\idevice.hpp">
|
||||||
<Filter>Header Files\idevice++</Filter>
|
<Filter>Header Files\idevice++</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
<ClInclude Include="..\include\idevice++\location_simulation.hpp">
|
<ClInclude Include="..\include\idevice++\installation_proxy.hpp">
|
||||||
<Filter>Header Files\idevice++</Filter>
|
<Filter>Header Files\idevice++</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
<ClInclude Include="..\include\idevice++\lockdown.hpp">
|
<ClInclude Include="..\include\idevice++\lockdown.hpp">
|
||||||
<Filter>Header Files\idevice++</Filter>
|
<Filter>Header Files\idevice++</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
<ClInclude Include="..\include\idevice++\mobile_image_mounter.hpp">
|
||||||
|
<Filter>Header Files\idevice++</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="..\include\idevice++\option.hpp">
|
||||||
|
<Filter>Header Files\idevice++</Filter>
|
||||||
|
</ClInclude>
|
||||||
<ClInclude Include="..\include\idevice++\pairing_file.hpp">
|
<ClInclude Include="..\include\idevice++\pairing_file.hpp">
|
||||||
<Filter>Header Files\idevice++</Filter>
|
<Filter>Header Files\idevice++</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
@@ -104,7 +81,7 @@
|
|||||||
<ClInclude Include="..\include\idevice++\readwrite.hpp">
|
<ClInclude Include="..\include\idevice++\readwrite.hpp">
|
||||||
<Filter>Header Files\idevice++</Filter>
|
<Filter>Header Files\idevice++</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
<ClInclude Include="..\include\idevice++\remote_server.hpp">
|
<ClInclude Include="..\include\idevice++\result.hpp">
|
||||||
<Filter>Header Files\idevice++</Filter>
|
<Filter>Header Files\idevice++</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
<ClInclude Include="..\include\idevice++\rsd.hpp">
|
<ClInclude Include="..\include\idevice++\rsd.hpp">
|
||||||
@@ -116,14 +93,67 @@
|
|||||||
<ClInclude Include="..\include\idevice++\usbmuxd.hpp">
|
<ClInclude Include="..\include\idevice++\usbmuxd.hpp">
|
||||||
<Filter>Header Files\idevice++</Filter>
|
<Filter>Header Files\idevice++</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
<ClInclude Include="..\..\ffi\idevice.h">
|
</ItemGroup>
|
||||||
<Filter>Header Files</Filter>
|
<ItemGroup>
|
||||||
</ClInclude>
|
<ClCompile Include="..\src\adapter_stream.cpp">
|
||||||
<ClInclude Include="..\include\idevice++\option.hpp">
|
<Filter>Source Files</Filter>
|
||||||
<Filter>Header Files\idevice++</Filter>
|
</ClCompile>
|
||||||
</ClInclude>
|
<ClCompile Include="..\src\app_service.cpp">
|
||||||
<ClInclude Include="..\include\idevice++\result.hpp">
|
<Filter>Source Files</Filter>
|
||||||
<Filter>Header Files\idevice++</Filter>
|
</ClCompile>
|
||||||
</ClInclude>
|
<ClCompile Include="..\src\core_device.cpp">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="..\src\debug_proxy.cpp">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="..\src\diagnosticsservice.cpp">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="..\src\ffi.cpp">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="..\src\heartbeat.cpp">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="..\src\idevice.cpp">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="..\src\installation_proxy.cpp">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="..\src\lockdown.cpp">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="..\src\mobile_image_mounter.cpp">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="..\src\pairing_file.cpp">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="..\src\provider.cpp">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="..\src\rsd.cpp">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="..\src\tcp_callback_feeder.cpp">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="..\src\usbmuxd.cpp">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="..\src\dvt\location_simulation.cpp">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="..\src\dvt\process_control.cpp">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="..\src\dvt\remote_server.cpp">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="..\src\dvt\screenshot.cpp">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
@@ -6,45 +6,49 @@
|
|||||||
namespace IdeviceFFI {
|
namespace IdeviceFFI {
|
||||||
|
|
||||||
Result<void, FfiError> AdapterStream::close() {
|
Result<void, FfiError> AdapterStream::close() {
|
||||||
if (!h_)
|
if (!h_) {
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
FfiError e(::adapter_stream_close(h_));
|
||||||
|
if (e) {
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
h_ = nullptr;
|
||||||
return Ok();
|
return Ok();
|
||||||
|
|
||||||
FfiError e(::adapter_close(h_));
|
|
||||||
if (e) {
|
|
||||||
return Err(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
h_ = nullptr;
|
|
||||||
return Ok();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Result<void, FfiError> AdapterStream::send(const uint8_t *data, size_t len) {
|
Result<void, FfiError> AdapterStream::send(const uint8_t* data, size_t len) {
|
||||||
if (!h_)
|
if (!h_) {
|
||||||
return Err(FfiError::NotConnected());
|
return Err(FfiError::NotConnected());
|
||||||
FfiError e(::adapter_send(h_, data, len));
|
}
|
||||||
if (e) {
|
FfiError e(::adapter_send(h_, data, len));
|
||||||
return Err(e);
|
if (e) {
|
||||||
}
|
return Err(e);
|
||||||
return Ok();
|
}
|
||||||
|
return Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
Result<std::vector<uint8_t>, FfiError> AdapterStream::recv(size_t max_hint) {
|
Result<std::vector<uint8_t>, FfiError> AdapterStream::recv(size_t max_hint) {
|
||||||
if (!h_)
|
if (!h_) {
|
||||||
return Err(FfiError::NotConnected());
|
return Err(FfiError::NotConnected());
|
||||||
|
}
|
||||||
|
|
||||||
if (max_hint == 0)
|
if (max_hint == 0) {
|
||||||
max_hint = 2048;
|
max_hint = 2048;
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<uint8_t> out(max_hint);
|
std::vector<uint8_t> out(max_hint);
|
||||||
size_t actual = 0;
|
size_t actual = 0;
|
||||||
|
|
||||||
FfiError e(::adapter_recv(h_, out.data(), &actual, out.size()));
|
FfiError e(::adapter_recv(h_, out.data(), &actual, out.size()));
|
||||||
if (e) {
|
if (e) {
|
||||||
return Err(e);
|
return Err(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
out.resize(actual);
|
out.resize(actual);
|
||||||
return Ok(std::move(out));
|
return Ok(std::move(out));
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace IdeviceFFI
|
} // namespace IdeviceFFI
|
||||||
|
|||||||
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
|
// Jackson Coxson
|
||||||
|
|
||||||
#include <idevice++/location_simulation.hpp>
|
#include <idevice++/dvt/location_simulation.hpp>
|
||||||
|
|
||||||
namespace IdeviceFFI {
|
namespace IdeviceFFI {
|
||||||
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
// Jackson Coxson
|
// Jackson Coxson
|
||||||
|
|
||||||
#include <idevice++/process_control.hpp>
|
#include <idevice++/dvt/process_control.hpp>
|
||||||
|
|
||||||
namespace IdeviceFFI {
|
namespace IdeviceFFI {
|
||||||
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
// Jackson Coxson
|
// Jackson Coxson
|
||||||
|
|
||||||
#include <idevice++/remote_server.hpp>
|
#include <idevice++/dvt/remote_server.hpp>
|
||||||
|
|
||||||
namespace IdeviceFFI {
|
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));
|
return Ok(Idevice(h));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if defined(__unix__) || defined(__APPLE__)
|
||||||
|
Result<Idevice, FfiError> Idevice::from_fd(int fd, const std::string& label) {
|
||||||
|
IdeviceHandle* h = nullptr;
|
||||||
|
FfiError e(idevice_from_fd(fd, label.c_str(), &h));
|
||||||
|
if (e) {
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
return Ok(Idevice(h));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
Result<Idevice, FfiError>
|
Result<Idevice, FfiError>
|
||||||
Idevice::create_tcp(const sockaddr* addr, socklen_t addr_len, const std::string& label) {
|
Idevice::create_tcp(const sockaddr* addr, socklen_t addr_len, const std::string& label) {
|
||||||
IdeviceHandle* h = nullptr;
|
IdeviceHandle* h = nullptr;
|
||||||
@@ -42,8 +53,8 @@ Result<void, FfiError> Idevice::rsd_checkin() {
|
|||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
Result<void, FfiError> Idevice::start_session(const PairingFile& pairing_file) {
|
Result<void, FfiError> Idevice::start_session(const PairingFile& pairing_file, bool legacy) {
|
||||||
FfiError e(idevice_start_session(handle_.get(), pairing_file.raw()));
|
FfiError e(idevice_start_session(handle_.get(), pairing_file.raw(), legacy));
|
||||||
if (e) {
|
if (e) {
|
||||||
return Err(e);
|
return Err(e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <idevice++/bindings.hpp>
|
#include <idevice++/bindings.hpp>
|
||||||
#include <idevice++/installation_proxy.hpp>
|
#include <idevice++/installation_proxy.hpp>
|
||||||
#include <sys/_types/_u_int64_t.h>
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace IdeviceFFI {
|
namespace IdeviceFFI {
|
||||||
|
|||||||
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) {
|
for (int i = 0; i < count; ++i) {
|
||||||
out.emplace_back(UsbmuxdDevice::adopt(list[i]));
|
out.emplace_back(UsbmuxdDevice::adopt(list[i]));
|
||||||
}
|
}
|
||||||
|
idevice_outer_slice_free(list, count);
|
||||||
return Ok(std::move(out));
|
return Ok(std::move(out));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,8 +7,9 @@ edition = "2024"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
idevice = { path = "../idevice", default-features = false }
|
idevice = { path = "../idevice", default-features = false }
|
||||||
futures = { version = "0.3", optional = true }
|
futures = { version = "0.3", optional = true }
|
||||||
log = "0.4.26"
|
tracing = { version = "0.1.41" }
|
||||||
simplelog = "0.12.2"
|
tracing-subscriber = { version = "0.3.20", features = ["env-filter"] }
|
||||||
|
tracing-appender = { version = "0.2" }
|
||||||
once_cell = "1.21.1"
|
once_cell = "1.21.1"
|
||||||
tokio = { version = "1.44.1", features = ["full"] }
|
tokio = { version = "1.44.1", features = ["full"] }
|
||||||
libc = "0.2.171"
|
libc = "0.2.171"
|
||||||
@@ -17,7 +18,7 @@ plist_ffi = { version = "0.1.6" }
|
|||||||
uuid = { version = "1.12", features = ["v4"], optional = true }
|
uuid = { version = "1.12", features = ["v4"], optional = true }
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies]
|
[target.'cfg(windows)'.dependencies]
|
||||||
windows-sys = { version = "0.60", features = ["Win32_Networking_WinSock"] }
|
windows-sys = { version = "0.61", features = ["Win32_Networking_WinSock"] }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
aws-lc = ["idevice/aws-lc"]
|
aws-lc = ["idevice/aws-lc"]
|
||||||
@@ -30,8 +31,10 @@ core_device = ["idevice/core_device", "dep:futures", "dep:uuid"]
|
|||||||
core_device_proxy = ["idevice/core_device_proxy"]
|
core_device_proxy = ["idevice/core_device_proxy"]
|
||||||
crashreportcopymobile = ["idevice/crashreportcopymobile"]
|
crashreportcopymobile = ["idevice/crashreportcopymobile"]
|
||||||
debug_proxy = ["idevice/debug_proxy"]
|
debug_proxy = ["idevice/debug_proxy"]
|
||||||
|
diagnostics_relay = ["idevice/diagnostics_relay"]
|
||||||
dvt = ["idevice/dvt"]
|
dvt = ["idevice/dvt"]
|
||||||
heartbeat = ["idevice/heartbeat"]
|
heartbeat = ["idevice/heartbeat"]
|
||||||
|
notification_proxy = ["idevice/notification_proxy"]
|
||||||
house_arrest = ["idevice/house_arrest"]
|
house_arrest = ["idevice/house_arrest"]
|
||||||
installation_proxy = ["idevice/installation_proxy"]
|
installation_proxy = ["idevice/installation_proxy"]
|
||||||
springboardservices = ["idevice/springboardservices"]
|
springboardservices = ["idevice/springboardservices"]
|
||||||
@@ -48,6 +51,7 @@ tss = ["idevice/tss"]
|
|||||||
tunneld = ["idevice/tunneld"]
|
tunneld = ["idevice/tunneld"]
|
||||||
usbmuxd = ["idevice/usbmuxd"]
|
usbmuxd = ["idevice/usbmuxd"]
|
||||||
xpc = ["idevice/xpc"]
|
xpc = ["idevice/xpc"]
|
||||||
|
screenshotr = ["idevice/screenshotr"]
|
||||||
full = [
|
full = [
|
||||||
"afc",
|
"afc",
|
||||||
"amfi",
|
"amfi",
|
||||||
@@ -55,8 +59,10 @@ full = [
|
|||||||
"core_device_proxy",
|
"core_device_proxy",
|
||||||
"crashreportcopymobile",
|
"crashreportcopymobile",
|
||||||
"debug_proxy",
|
"debug_proxy",
|
||||||
|
"diagnostics_relay",
|
||||||
"dvt",
|
"dvt",
|
||||||
"heartbeat",
|
"heartbeat",
|
||||||
|
"notification_proxy",
|
||||||
"house_arrest",
|
"house_arrest",
|
||||||
"installation_proxy",
|
"installation_proxy",
|
||||||
"misagent",
|
"misagent",
|
||||||
@@ -72,6 +78,7 @@ full = [
|
|||||||
"tunneld",
|
"tunneld",
|
||||||
"springboardservices",
|
"springboardservices",
|
||||||
"syslog_relay",
|
"syslog_relay",
|
||||||
|
"screenshotr",
|
||||||
]
|
]
|
||||||
default = ["full", "aws-lc"]
|
default = ["full", "aws-lc"]
|
||||||
|
|
||||||
@@ -80,4 +87,4 @@ cbindgen = "0.29.0"
|
|||||||
ureq = "3"
|
ureq = "3"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
crate-type = ["staticlib", "cdylib"]
|
crate-type = ["staticlib", "cdylib", "rlib"]
|
||||||
|
|||||||
25
ffi/build.rs
25
ffi/build.rs
@@ -35,16 +35,25 @@ fn main() {
|
|||||||
.expect("Unable to generate bindings")
|
.expect("Unable to generate bindings")
|
||||||
.write_to_file("idevice.h");
|
.write_to_file("idevice.h");
|
||||||
|
|
||||||
// download plist.h
|
// Check if plist.h exists locally first, otherwise download
|
||||||
let h = ureq::get("https://raw.githubusercontent.com/libimobiledevice/libplist/refs/heads/master/include/plist/plist.h")
|
let plist_h_path = "plist.h";
|
||||||
.call()
|
let h = if std::path::Path::new(plist_h_path).exists() {
|
||||||
.expect("failed to download plist.h");
|
std::fs::read_to_string(plist_h_path).expect("failed to read plist.h")
|
||||||
let h = h
|
} else {
|
||||||
.into_body()
|
// download plist.h
|
||||||
.read_to_string()
|
let h = ureq::get("https://raw.githubusercontent.com/libimobiledevice/libplist/refs/heads/master/include/plist/plist.h")
|
||||||
.expect("failed to get string content");
|
.call()
|
||||||
|
.expect("failed to download plist.h");
|
||||||
|
h.into_body()
|
||||||
|
.read_to_string()
|
||||||
|
.expect("failed to get string content")
|
||||||
|
};
|
||||||
|
|
||||||
let mut f = OpenOptions::new().append(true).open("idevice.h").unwrap();
|
let mut f = OpenOptions::new().append(true).open("idevice.h").unwrap();
|
||||||
f.write_all(b"\n\n\n").unwrap();
|
f.write_all(b"\n\n\n").unwrap();
|
||||||
f.write_all(&h.into_bytes())
|
f.write_all(&h.into_bytes())
|
||||||
.expect("failed to append plist.h");
|
.expect("failed to append plist.h");
|
||||||
|
|
||||||
|
let f = std::fs::read_to_string("idevice.h").unwrap();
|
||||||
|
std::fs::write("../cpp/include/idevice.h", f).unwrap();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -254,7 +254,7 @@ int main(int argc, char **argv) {
|
|||||||
} else {
|
} else {
|
||||||
uint8_t *data = NULL;
|
uint8_t *data = NULL;
|
||||||
size_t length = 0;
|
size_t length = 0;
|
||||||
err = afc_file_read(file, &data, &length);
|
err = afc_file_read_entire(file, &data, &length);
|
||||||
if (err == NULL) {
|
if (err == NULL) {
|
||||||
if (write_file(dest_path, data, length)) {
|
if (write_file(dest_path, data, length)) {
|
||||||
printf("File downloaded successfully\n");
|
printf("File downloaded successfully\n");
|
||||||
|
|||||||
@@ -179,7 +179,7 @@ int main(int argc, char **argv) {
|
|||||||
|
|
||||||
// Connect to installation proxy
|
// Connect to installation proxy
|
||||||
InstallationProxyClientHandle *instproxy_client = NULL;
|
InstallationProxyClientHandle *instproxy_client = NULL;
|
||||||
err = installation_proxy_connect_tcp(provider, &instproxy_client);
|
err = installation_proxy_connect(provider, &instproxy_client);
|
||||||
if (err != NULL) {
|
if (err != NULL) {
|
||||||
fprintf(stderr, "Failed to connect to installation proxy: [%d] %s",
|
fprintf(stderr, "Failed to connect to installation proxy: [%d] %s",
|
||||||
err->code, err->message);
|
err->code, err->message);
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ int main() {
|
|||||||
|
|
||||||
// Connect to installation proxy
|
// Connect to installation proxy
|
||||||
InstallationProxyClientHandle *client = NULL;
|
InstallationProxyClientHandle *client = NULL;
|
||||||
err = installation_proxy_connect_tcp(provider, &client);
|
err = installation_proxy_connect(provider, &client);
|
||||||
if (err != NULL) {
|
if (err != NULL) {
|
||||||
fprintf(stderr, "Failed to connect to installation proxy: [%d] %s",
|
fprintf(stderr, "Failed to connect to installation proxy: [%d] %s",
|
||||||
err->code, err->message);
|
err->code, err->message);
|
||||||
|
|||||||
@@ -131,7 +131,6 @@ int main(int argc, char **argv) {
|
|||||||
fprintf(stderr, "Failed to connect to image mounter: [%d] %s", err->code,
|
fprintf(stderr, "Failed to connect to image mounter: [%d] %s", err->code,
|
||||||
err->message);
|
err->message);
|
||||||
idevice_error_free(err);
|
idevice_error_free(err);
|
||||||
idevice_provider_free(provider);
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
idevice_provider_free(provider);
|
idevice_provider_free(provider);
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use idevice::tcp::handle::StreamHandle;
|
|||||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||||
|
|
||||||
use crate::core_device_proxy::AdapterHandle;
|
use crate::core_device_proxy::AdapterHandle;
|
||||||
use crate::{IdeviceFfiError, RUNTIME, ReadWriteOpaque, ffi_err};
|
use crate::{IdeviceFfiError, ReadWriteOpaque, ffi_err, run_sync};
|
||||||
|
|
||||||
pub struct AdapterStreamHandle(pub StreamHandle);
|
pub struct AdapterStreamHandle(pub StreamHandle);
|
||||||
|
|
||||||
@@ -36,7 +36,7 @@ pub unsafe extern "C" fn adapter_connect(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let adapter = unsafe { &mut (*adapter_handle).0 };
|
let adapter = unsafe { &mut (*adapter_handle).0 };
|
||||||
let res = RUNTIME.block_on(async move { adapter.connect(port).await });
|
let res = run_sync(async move { adapter.connect(port).await });
|
||||||
|
|
||||||
match res {
|
match res {
|
||||||
Ok(r) => {
|
Ok(r) => {
|
||||||
@@ -47,7 +47,7 @@ pub unsafe extern "C" fn adapter_connect(
|
|||||||
null_mut()
|
null_mut()
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::error!("Adapter connect failed: {e}");
|
tracing::error!("Adapter connect failed: {e}");
|
||||||
ffi_err!(e)
|
ffi_err!(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -81,12 +81,12 @@ pub unsafe extern "C" fn adapter_pcap(
|
|||||||
Err(_) => return ffi_err!(IdeviceError::FfiInvalidString),
|
Err(_) => return ffi_err!(IdeviceError::FfiInvalidString),
|
||||||
};
|
};
|
||||||
|
|
||||||
let res = RUNTIME.block_on(async move { adapter.pcap(path_str).await });
|
let res = run_sync(async move { adapter.pcap(path_str).await });
|
||||||
|
|
||||||
match res {
|
match res {
|
||||||
Ok(_) => null_mut(),
|
Ok(_) => null_mut(),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::error!("Adapter pcap failed: {e}");
|
tracing::error!("Adapter pcap failed: {e}");
|
||||||
ffi_err!(e)
|
ffi_err!(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -103,13 +103,37 @@ pub unsafe extern "C" fn adapter_pcap(
|
|||||||
/// # Safety
|
/// # Safety
|
||||||
/// `handle` must be a valid pointer to a handle allocated by this library
|
/// `handle` must be a valid pointer to a handle allocated by this library
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
pub unsafe extern "C" fn adapter_close(handle: *mut AdapterStreamHandle) -> *mut IdeviceFfiError {
|
pub unsafe extern "C" fn adapter_stream_close(
|
||||||
|
handle: *mut AdapterStreamHandle,
|
||||||
|
) -> *mut IdeviceFfiError {
|
||||||
if handle.is_null() {
|
if handle.is_null() {
|
||||||
return ffi_err!(IdeviceError::FfiInvalidArg);
|
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||||
}
|
}
|
||||||
|
|
||||||
let adapter = unsafe { &mut (*handle).0 };
|
let adapter = unsafe { &mut (*handle).0 };
|
||||||
RUNTIME.block_on(async move { adapter.close() });
|
run_sync(async move { adapter.close() });
|
||||||
|
|
||||||
|
null_mut()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stops the entire adapter TCP stack
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * [`handle`] - The adapter handle
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// Null on success, an IdeviceFfiError otherwise
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// `handle` must be a valid pointer to a handle allocated by this library
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub unsafe extern "C" fn adapter_close(handle: *mut AdapterHandle) -> *mut IdeviceFfiError {
|
||||||
|
if handle.is_null() {
|
||||||
|
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||||
|
}
|
||||||
|
|
||||||
|
let adapter = unsafe { &mut (*handle).0 };
|
||||||
|
run_sync(async move { adapter.close().await.ok() });
|
||||||
|
|
||||||
null_mut()
|
null_mut()
|
||||||
}
|
}
|
||||||
@@ -140,12 +164,12 @@ pub unsafe extern "C" fn adapter_send(
|
|||||||
let adapter = unsafe { &mut (*handle).0 };
|
let adapter = unsafe { &mut (*handle).0 };
|
||||||
let data_slice = unsafe { std::slice::from_raw_parts(data, length) };
|
let data_slice = unsafe { std::slice::from_raw_parts(data, length) };
|
||||||
|
|
||||||
let res = RUNTIME.block_on(async move { adapter.write_all(data_slice).await });
|
let res = run_sync(async move { adapter.write_all(data_slice).await });
|
||||||
|
|
||||||
match res {
|
match res {
|
||||||
Ok(_) => null_mut(),
|
Ok(_) => null_mut(),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::error!("Adapter send failed: {e}");
|
tracing::error!("Adapter send failed: {e}");
|
||||||
ffi_err!(e)
|
ffi_err!(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -178,7 +202,7 @@ pub unsafe extern "C" fn adapter_recv(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let adapter = unsafe { &mut (*handle).0 };
|
let adapter = unsafe { &mut (*handle).0 };
|
||||||
let res: Result<Vec<u8>, std::io::Error> = RUNTIME.block_on(async move {
|
let res: Result<Vec<u8>, std::io::Error> = run_sync(async move {
|
||||||
let mut buf = [0; 2048];
|
let mut buf = [0; 2048];
|
||||||
let res = adapter.read(&mut buf).await?;
|
let res = adapter.read(&mut buf).await?;
|
||||||
Ok(buf[..res].to_vec())
|
Ok(buf[..res].to_vec())
|
||||||
@@ -199,7 +223,7 @@ pub unsafe extern "C" fn adapter_recv(
|
|||||||
null_mut()
|
null_mut()
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::error!("Adapter recv failed: {e}");
|
tracing::error!("Adapter recv failed: {e}");
|
||||||
ffi_err!(e)
|
ffi_err!(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
226
ffi/src/afc.rs
226
ffi/src/afc.rs
@@ -1,14 +1,18 @@
|
|||||||
// Jackson Coxson
|
// Jackson Coxson
|
||||||
|
|
||||||
use std::ptr::null_mut;
|
use std::{io::SeekFrom, ptr::null_mut};
|
||||||
|
|
||||||
use idevice::{
|
use idevice::{
|
||||||
IdeviceError, IdeviceService,
|
IdeviceError, IdeviceService,
|
||||||
afc::{AfcClient, DeviceInfo, FileInfo},
|
afc::{AfcClient, DeviceInfo, FileInfo, file::FileDescriptor},
|
||||||
provider::IdeviceProvider,
|
provider::IdeviceProvider,
|
||||||
};
|
};
|
||||||
|
use tokio::io::{AsyncReadExt, AsyncSeekExt};
|
||||||
|
|
||||||
use crate::{IdeviceFfiError, IdeviceHandle, RUNTIME, ffi_err, provider::IdeviceProviderHandle};
|
use crate::{
|
||||||
|
IdeviceFfiError, IdeviceHandle, LOCAL_RUNTIME, ffi_err, provider::IdeviceProviderHandle,
|
||||||
|
run_sync, run_sync_local,
|
||||||
|
};
|
||||||
|
|
||||||
pub struct AfcClientHandle(pub AfcClient);
|
pub struct AfcClientHandle(pub AfcClient);
|
||||||
|
|
||||||
@@ -30,11 +34,11 @@ pub unsafe extern "C" fn afc_client_connect(
|
|||||||
client: *mut *mut AfcClientHandle,
|
client: *mut *mut AfcClientHandle,
|
||||||
) -> *mut IdeviceFfiError {
|
) -> *mut IdeviceFfiError {
|
||||||
if provider.is_null() || client.is_null() {
|
if provider.is_null() || client.is_null() {
|
||||||
log::error!("Null pointer provided");
|
tracing::error!("Null pointer provided");
|
||||||
return ffi_err!(IdeviceError::FfiInvalidArg);
|
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||||
}
|
}
|
||||||
|
|
||||||
let res = RUNTIME.block_on(async {
|
let res = run_sync_local(async {
|
||||||
let provider_ref: &dyn IdeviceProvider = unsafe { &*(*provider).0 };
|
let provider_ref: &dyn IdeviceProvider = unsafe { &*(*provider).0 };
|
||||||
|
|
||||||
AfcClient::connect(provider_ref).await
|
AfcClient::connect(provider_ref).await
|
||||||
@@ -50,6 +54,44 @@ pub unsafe extern "C" fn afc_client_connect(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Connects to the AFC2 service using a TCP provider
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * [`provider`] - An IdeviceProvider
|
||||||
|
/// * [`client`] - On success, will be set to point to a newly allocated AfcClient handle
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// An IdeviceFfiError on error, null on success
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// `provider` must be a valid pointer to a handle allocated by this library
|
||||||
|
/// `client` must be a valid, non-null pointer to a location where the handle will be stored
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub unsafe extern "C" fn afc2_client_connect(
|
||||||
|
provider: *mut IdeviceProviderHandle,
|
||||||
|
client: *mut *mut AfcClientHandle,
|
||||||
|
) -> *mut IdeviceFfiError {
|
||||||
|
if provider.is_null() || client.is_null() {
|
||||||
|
tracing::error!("Null pointer provided");
|
||||||
|
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||||
|
}
|
||||||
|
|
||||||
|
let res = run_sync_local(async {
|
||||||
|
let provider_ref: &dyn IdeviceProvider = unsafe { &*(*provider).0 };
|
||||||
|
|
||||||
|
AfcClient::new_afc2(provider_ref).await
|
||||||
|
});
|
||||||
|
|
||||||
|
match res {
|
||||||
|
Ok(r) => {
|
||||||
|
let boxed = Box::new(AfcClientHandle(r));
|
||||||
|
unsafe { *client = Box::into_raw(boxed) };
|
||||||
|
null_mut()
|
||||||
|
}
|
||||||
|
Err(e) => ffi_err!(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Creates a new AfcClient from an existing Idevice connection
|
/// Creates a new AfcClient from an existing Idevice connection
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
@@ -88,7 +130,7 @@ pub unsafe extern "C" fn afc_client_new(
|
|||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
pub unsafe extern "C" fn afc_client_free(handle: *mut AfcClientHandle) {
|
pub unsafe extern "C" fn afc_client_free(handle: *mut AfcClientHandle) {
|
||||||
if !handle.is_null() {
|
if !handle.is_null() {
|
||||||
log::debug!("Freeing afc_client");
|
tracing::debug!("Freeing afc_client");
|
||||||
let _ = unsafe { Box::from_raw(handle) };
|
let _ = unsafe { Box::from_raw(handle) };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -122,7 +164,7 @@ pub unsafe extern "C" fn afc_list_directory(
|
|||||||
// Use to_string_lossy to handle non-UTF8 paths
|
// Use to_string_lossy to handle non-UTF8 paths
|
||||||
let path = path_cstr.to_string_lossy();
|
let path = path_cstr.to_string_lossy();
|
||||||
|
|
||||||
let res: Result<Vec<String>, IdeviceError> = RUNTIME.block_on(async move {
|
let res: Result<Vec<String>, IdeviceError> = run_sync_local(async move {
|
||||||
// SAFETY: We're assuming client is a valid pointer here
|
// SAFETY: We're assuming client is a valid pointer here
|
||||||
let client_ref = unsafe { &mut (*client).0 };
|
let client_ref = unsafe { &mut (*client).0 };
|
||||||
client_ref.list_dir(&path.to_string()).await
|
client_ref.list_dir(&path.to_string()).await
|
||||||
@@ -194,7 +236,7 @@ pub unsafe extern "C" fn afc_make_directory(
|
|||||||
Err(_) => return ffi_err!(IdeviceError::FfiInvalidArg),
|
Err(_) => return ffi_err!(IdeviceError::FfiInvalidArg),
|
||||||
};
|
};
|
||||||
|
|
||||||
let res: Result<(), IdeviceError> = RUNTIME.block_on(async move {
|
let res: Result<(), IdeviceError> = run_sync_local(async move {
|
||||||
let client_ref = unsafe { &mut (*client).0 };
|
let client_ref = unsafe { &mut (*client).0 };
|
||||||
client_ref.mk_dir(path).await
|
client_ref.mk_dir(path).await
|
||||||
});
|
});
|
||||||
@@ -246,7 +288,7 @@ pub unsafe extern "C" fn afc_get_file_info(
|
|||||||
Err(_) => return ffi_err!(IdeviceError::FfiInvalidArg),
|
Err(_) => return ffi_err!(IdeviceError::FfiInvalidArg),
|
||||||
};
|
};
|
||||||
|
|
||||||
let res: Result<FileInfo, IdeviceError> = RUNTIME.block_on(async move {
|
let res: Result<FileInfo, IdeviceError> = run_sync_local(async move {
|
||||||
let client_ref = unsafe { &mut (*client).0 };
|
let client_ref = unsafe { &mut (*client).0 };
|
||||||
client_ref.get_file_info(path).await
|
client_ref.get_file_info(path).await
|
||||||
});
|
});
|
||||||
@@ -331,7 +373,7 @@ pub unsafe extern "C" fn afc_get_device_info(
|
|||||||
return ffi_err!(IdeviceError::FfiInvalidArg);
|
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||||
}
|
}
|
||||||
|
|
||||||
let res: Result<DeviceInfo, IdeviceError> = RUNTIME.block_on(async move {
|
let res: Result<DeviceInfo, IdeviceError> = run_sync_local(async move {
|
||||||
let client_ref = unsafe { &mut (*client).0 };
|
let client_ref = unsafe { &mut (*client).0 };
|
||||||
client_ref.get_device_info().await
|
client_ref.get_device_info().await
|
||||||
});
|
});
|
||||||
@@ -395,7 +437,7 @@ pub unsafe extern "C" fn afc_remove_path(
|
|||||||
Err(_) => return ffi_err!(IdeviceError::FfiInvalidArg),
|
Err(_) => return ffi_err!(IdeviceError::FfiInvalidArg),
|
||||||
};
|
};
|
||||||
|
|
||||||
let res: Result<(), IdeviceError> = RUNTIME.block_on(async move {
|
let res: Result<(), IdeviceError> = run_sync_local(async move {
|
||||||
let client_ref = unsafe { &mut (*client).0 };
|
let client_ref = unsafe { &mut (*client).0 };
|
||||||
client_ref.remove(path).await
|
client_ref.remove(path).await
|
||||||
});
|
});
|
||||||
@@ -433,7 +475,7 @@ pub unsafe extern "C" fn afc_remove_path_and_contents(
|
|||||||
Err(_) => return ffi_err!(IdeviceError::FfiInvalidArg),
|
Err(_) => return ffi_err!(IdeviceError::FfiInvalidArg),
|
||||||
};
|
};
|
||||||
|
|
||||||
let res: Result<(), IdeviceError> = RUNTIME.block_on(async move {
|
let res: Result<(), IdeviceError> = run_sync_local(async move {
|
||||||
let client_ref = unsafe { &mut (*client).0 };
|
let client_ref = unsafe { &mut (*client).0 };
|
||||||
client_ref.remove_all(path).await
|
client_ref.remove_all(path).await
|
||||||
});
|
});
|
||||||
@@ -506,7 +548,7 @@ pub unsafe extern "C" fn afc_file_open(
|
|||||||
|
|
||||||
let mode = mode.into();
|
let mode = mode.into();
|
||||||
|
|
||||||
let res: Result<*mut AfcFileHandle, IdeviceError> = RUNTIME.block_on(async move {
|
let res: Result<*mut AfcFileHandle, IdeviceError> = LOCAL_RUNTIME.block_on(async move {
|
||||||
let client_ref = unsafe { &mut (*client).0 };
|
let client_ref = unsafe { &mut (*client).0 };
|
||||||
let result = client_ref.open(path, mode).await;
|
let result = client_ref.open(path, mode).await;
|
||||||
match result {
|
match result {
|
||||||
@@ -544,7 +586,7 @@ pub unsafe extern "C" fn afc_file_close(handle: *mut AfcFileHandle) -> *mut Idev
|
|||||||
}
|
}
|
||||||
|
|
||||||
let fd = unsafe { Box::from_raw(handle as *mut idevice::afc::file::FileDescriptor) };
|
let fd = unsafe { Box::from_raw(handle as *mut idevice::afc::file::FileDescriptor) };
|
||||||
let res: Result<(), IdeviceError> = RUNTIME.block_on(async move { fd.close().await });
|
let res: Result<(), IdeviceError> = run_sync(async move { fd.close().await });
|
||||||
|
|
||||||
match res {
|
match res {
|
||||||
Ok(_) => null_mut(),
|
Ok(_) => null_mut(),
|
||||||
@@ -552,12 +594,13 @@ pub unsafe extern "C" fn afc_file_close(handle: *mut AfcFileHandle) -> *mut Idev
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reads data from an open file
|
/// Reads data from an open file. This advances the cursor of the file.
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
/// * [`handle`] - File handle to read from
|
/// * [`handle`] - File handle to read from
|
||||||
/// * [`data`] - Will be set to point to the read data
|
/// * [`data`] - Will be set to point to the read data
|
||||||
/// * [`length`] - Will be set to the length of the read data
|
/// * [`len`] - Number of bytes to read from the file
|
||||||
|
/// * [`bytes_read`] - The number of bytes read from the file
|
||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
/// An IdeviceFfiError on error, null on success
|
/// An IdeviceFfiError on error, null on success
|
||||||
@@ -566,6 +609,53 @@ pub unsafe extern "C" fn afc_file_close(handle: *mut AfcFileHandle) -> *mut Idev
|
|||||||
/// All pointers must be valid and non-null
|
/// All pointers must be valid and non-null
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
pub unsafe extern "C" fn afc_file_read(
|
pub unsafe extern "C" fn afc_file_read(
|
||||||
|
handle: *mut AfcFileHandle,
|
||||||
|
data: *mut *mut u8,
|
||||||
|
len: usize,
|
||||||
|
bytes_read: *mut libc::size_t,
|
||||||
|
) -> *mut IdeviceFfiError {
|
||||||
|
if handle.is_null() || data.is_null() || bytes_read.is_null() {
|
||||||
|
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||||
|
}
|
||||||
|
|
||||||
|
let fd = unsafe { &mut *(handle as *mut FileDescriptor) };
|
||||||
|
let res: Result<Vec<u8>, IdeviceError> = run_sync({
|
||||||
|
let mut buf = vec![0u8; len];
|
||||||
|
async move {
|
||||||
|
let r = fd.read(&mut buf).await?;
|
||||||
|
buf.truncate(r);
|
||||||
|
Ok(buf)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
match res {
|
||||||
|
Ok(bytes) => {
|
||||||
|
let mut boxed = bytes.into_boxed_slice();
|
||||||
|
unsafe {
|
||||||
|
*data = boxed.as_mut_ptr();
|
||||||
|
*bytes_read = boxed.len();
|
||||||
|
}
|
||||||
|
std::mem::forget(boxed);
|
||||||
|
null_mut()
|
||||||
|
}
|
||||||
|
Err(e) => ffi_err!(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads all data from an open file.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * [`handle`] - File handle to read from
|
||||||
|
/// * [`data`] - Will be set to point to the read data
|
||||||
|
/// * [`length`] - The number of bytes read from the file
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// An IdeviceFfiError on error, null on success
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// All pointers must be valid and non-null
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub unsafe extern "C" fn afc_file_read_entire(
|
||||||
handle: *mut AfcFileHandle,
|
handle: *mut AfcFileHandle,
|
||||||
data: *mut *mut u8,
|
data: *mut *mut u8,
|
||||||
length: *mut libc::size_t,
|
length: *mut libc::size_t,
|
||||||
@@ -575,7 +665,7 @@ pub unsafe extern "C" fn afc_file_read(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let fd = unsafe { &mut *(handle as *mut idevice::afc::file::FileDescriptor) };
|
let fd = unsafe { &mut *(handle as *mut idevice::afc::file::FileDescriptor) };
|
||||||
let res: Result<Vec<u8>, IdeviceError> = RUNTIME.block_on(async move { fd.read().await });
|
let res: Result<Vec<u8>, IdeviceError> = run_sync(async move { fd.read_entire().await });
|
||||||
|
|
||||||
match res {
|
match res {
|
||||||
Ok(bytes) => {
|
Ok(bytes) => {
|
||||||
@@ -591,6 +681,100 @@ pub unsafe extern "C" fn afc_file_read(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Moves the read/write cursor in an open file.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * [`handle`] - File handle whose cursor should be moved
|
||||||
|
/// * [`offset`] - Distance to move the cursor, interpreted based on `whence`
|
||||||
|
/// * [`whence`] - Origin used for the seek operation:
|
||||||
|
/// * `0` — Seek from the start of the file (`SeekFrom::Start`)
|
||||||
|
/// * `1` — Seek from the current cursor position (`SeekFrom::Current`)
|
||||||
|
/// * `2` — Seek from the end of the file (`SeekFrom::End`)
|
||||||
|
/// * [`new_pos`] - Output parameter; will be set to the new absolute cursor position
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// An [`IdeviceFfiError`] on error, or null on success.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// All pointers must be valid and non-null.
|
||||||
|
///
|
||||||
|
/// # Notes
|
||||||
|
/// * If `whence` is invalid, this function returns `FfiInvalidArg`.
|
||||||
|
/// * The AFC protocol may restrict seeking beyond certain bounds; such errors
|
||||||
|
/// are reported through the returned [`IdeviceFfiError`].
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub unsafe extern "C" fn afc_file_seek(
|
||||||
|
handle: *mut AfcFileHandle,
|
||||||
|
offset: i64,
|
||||||
|
whence: libc::c_int,
|
||||||
|
new_pos: *mut i64,
|
||||||
|
) -> *mut IdeviceFfiError {
|
||||||
|
if handle.is_null() || new_pos.is_null() {
|
||||||
|
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||||
|
}
|
||||||
|
|
||||||
|
let fd = unsafe { &mut *(handle as *mut FileDescriptor) };
|
||||||
|
|
||||||
|
let seek_from = match whence {
|
||||||
|
0 => SeekFrom::Start(offset as u64),
|
||||||
|
1 => SeekFrom::Current(offset),
|
||||||
|
2 => SeekFrom::End(offset),
|
||||||
|
_ => return ffi_err!(IdeviceError::FfiInvalidArg),
|
||||||
|
};
|
||||||
|
|
||||||
|
let res: Result<u64, IdeviceError> = run_sync(async move { Ok(fd.seek(seek_from).await?) });
|
||||||
|
|
||||||
|
match res {
|
||||||
|
Ok(pos) => {
|
||||||
|
unsafe {
|
||||||
|
*new_pos = pos as i64;
|
||||||
|
}
|
||||||
|
null_mut()
|
||||||
|
}
|
||||||
|
Err(e) => ffi_err!(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the current read/write cursor position of an open file.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * [`handle`] - File handle whose cursor should be queried
|
||||||
|
/// * [`pos`] - Output parameter; will be set to the current absolute cursor position
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// An [`IdeviceFfiError`] on error, or null on success.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// All pointers must be valid and non-null.
|
||||||
|
///
|
||||||
|
/// # Notes
|
||||||
|
/// This function is equivalent to performing a seek operation with
|
||||||
|
/// `SeekFrom::Current(0)` internally.
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub unsafe extern "C" fn afc_file_tell(
|
||||||
|
handle: *mut AfcFileHandle,
|
||||||
|
pos: *mut i64,
|
||||||
|
) -> *mut IdeviceFfiError {
|
||||||
|
if handle.is_null() || pos.is_null() {
|
||||||
|
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||||
|
}
|
||||||
|
|
||||||
|
let fd = unsafe { &mut *(handle as *mut FileDescriptor) };
|
||||||
|
|
||||||
|
let res: Result<u64, IdeviceError> =
|
||||||
|
run_sync(async { Ok(fd.seek(SeekFrom::Current(0)).await?) });
|
||||||
|
|
||||||
|
match res {
|
||||||
|
Ok(cur) => {
|
||||||
|
unsafe {
|
||||||
|
*pos = cur as i64;
|
||||||
|
}
|
||||||
|
null_mut()
|
||||||
|
}
|
||||||
|
Err(e) => ffi_err!(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Writes data to an open file
|
/// Writes data to an open file
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
@@ -617,7 +801,7 @@ pub unsafe extern "C" fn afc_file_write(
|
|||||||
let fd = unsafe { &mut *(handle as *mut idevice::afc::file::FileDescriptor) };
|
let fd = unsafe { &mut *(handle as *mut idevice::afc::file::FileDescriptor) };
|
||||||
let data_slice = unsafe { std::slice::from_raw_parts(data, length) };
|
let data_slice = unsafe { std::slice::from_raw_parts(data, length) };
|
||||||
|
|
||||||
let res: Result<(), IdeviceError> = RUNTIME.block_on(async move { fd.write(data_slice).await });
|
let res: Result<(), IdeviceError> = run_sync(async move { fd.write_entire(data_slice).await });
|
||||||
|
|
||||||
match res {
|
match res {
|
||||||
Ok(_) => null_mut(),
|
Ok(_) => null_mut(),
|
||||||
@@ -674,7 +858,7 @@ pub unsafe extern "C" fn afc_make_link(
|
|||||||
AfcLinkType::Symbolic => idevice::afc::opcode::LinkType::Symlink,
|
AfcLinkType::Symbolic => idevice::afc::opcode::LinkType::Symlink,
|
||||||
};
|
};
|
||||||
|
|
||||||
let res: Result<(), IdeviceError> = RUNTIME.block_on(async move {
|
let res: Result<(), IdeviceError> = run_sync_local(async move {
|
||||||
let client_ref = unsafe { &mut (*client).0 };
|
let client_ref = unsafe { &mut (*client).0 };
|
||||||
client_ref.link(target, source, link_type).await
|
client_ref.link(target, source, link_type).await
|
||||||
});
|
});
|
||||||
@@ -720,7 +904,7 @@ pub unsafe extern "C" fn afc_rename_path(
|
|||||||
Err(_) => return ffi_err!(IdeviceError::FfiInvalidArg),
|
Err(_) => return ffi_err!(IdeviceError::FfiInvalidArg),
|
||||||
};
|
};
|
||||||
|
|
||||||
let res: Result<(), IdeviceError> = RUNTIME.block_on(async move {
|
let res: Result<(), IdeviceError> = run_sync_local(async move {
|
||||||
let client_ref = unsafe { &mut (*client).0 };
|
let client_ref = unsafe { &mut (*client).0 };
|
||||||
client_ref.rename(source, target).await
|
client_ref.rename(source, target).await
|
||||||
});
|
});
|
||||||
@@ -741,7 +925,7 @@ pub unsafe extern "C" fn afc_rename_path(
|
|||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
pub unsafe extern "C" fn afc_file_read_data_free(data: *mut u8, length: libc::size_t) {
|
pub unsafe extern "C" fn afc_file_read_data_free(data: *mut u8, length: libc::size_t) {
|
||||||
if !data.is_null() {
|
if !data.is_null() {
|
||||||
let boxed = unsafe { Box::from_raw(std::slice::from_raw_parts_mut(data, length)) };
|
let boxed = unsafe { Box::from_raw(std::ptr::slice_from_raw_parts_mut(data, length)) };
|
||||||
drop(boxed);
|
drop(boxed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,9 @@ use std::ptr::null_mut;
|
|||||||
|
|
||||||
use idevice::{IdeviceError, IdeviceService, amfi::AmfiClient, provider::IdeviceProvider};
|
use idevice::{IdeviceError, IdeviceService, amfi::AmfiClient, provider::IdeviceProvider};
|
||||||
|
|
||||||
use crate::{IdeviceFfiError, IdeviceHandle, RUNTIME, ffi_err, provider::IdeviceProviderHandle};
|
use crate::{
|
||||||
|
IdeviceFfiError, IdeviceHandle, ffi_err, provider::IdeviceProviderHandle, run_sync_local,
|
||||||
|
};
|
||||||
|
|
||||||
pub struct AmfiClientHandle(pub AmfiClient);
|
pub struct AmfiClientHandle(pub AmfiClient);
|
||||||
|
|
||||||
@@ -26,11 +28,11 @@ pub unsafe extern "C" fn amfi_connect(
|
|||||||
client: *mut *mut AmfiClientHandle,
|
client: *mut *mut AmfiClientHandle,
|
||||||
) -> *mut IdeviceFfiError {
|
) -> *mut IdeviceFfiError {
|
||||||
if provider.is_null() || client.is_null() {
|
if provider.is_null() || client.is_null() {
|
||||||
log::error!("Null pointer provided");
|
tracing::error!("Null pointer provided");
|
||||||
return ffi_err!(IdeviceError::FfiInvalidArg);
|
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||||
}
|
}
|
||||||
|
|
||||||
let res: Result<AmfiClient, IdeviceError> = RUNTIME.block_on(async move {
|
let res: Result<AmfiClient, IdeviceError> = run_sync_local(async move {
|
||||||
let provider_ref: &dyn IdeviceProvider = unsafe { &*(*provider).0 };
|
let provider_ref: &dyn IdeviceProvider = unsafe { &*(*provider).0 };
|
||||||
|
|
||||||
// Connect using the reference
|
// Connect using the reference
|
||||||
@@ -94,7 +96,7 @@ pub unsafe extern "C" fn amfi_reveal_developer_mode_option_in_ui(
|
|||||||
return ffi_err!(IdeviceError::FfiInvalidArg);
|
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||||
}
|
}
|
||||||
|
|
||||||
let res: Result<(), IdeviceError> = RUNTIME.block_on(async move {
|
let res: Result<(), IdeviceError> = run_sync_local(async move {
|
||||||
let client_ref = unsafe { &mut (*client).0 };
|
let client_ref = unsafe { &mut (*client).0 };
|
||||||
client_ref.reveal_developer_mode_option_in_ui().await
|
client_ref.reveal_developer_mode_option_in_ui().await
|
||||||
});
|
});
|
||||||
@@ -122,7 +124,7 @@ pub unsafe extern "C" fn amfi_enable_developer_mode(
|
|||||||
return ffi_err!(IdeviceError::FfiInvalidArg);
|
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||||
}
|
}
|
||||||
|
|
||||||
let res: Result<(), IdeviceError> = RUNTIME.block_on(async move {
|
let res: Result<(), IdeviceError> = run_sync_local(async move {
|
||||||
let client_ref = unsafe { &mut (*client).0 };
|
let client_ref = unsafe { &mut (*client).0 };
|
||||||
client_ref.enable_developer_mode().await
|
client_ref.enable_developer_mode().await
|
||||||
});
|
});
|
||||||
@@ -150,7 +152,7 @@ pub unsafe extern "C" fn amfi_accept_developer_mode(
|
|||||||
return ffi_err!(IdeviceError::FfiInvalidArg);
|
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||||
}
|
}
|
||||||
|
|
||||||
let res: Result<(), IdeviceError> = RUNTIME.block_on(async move {
|
let res: Result<(), IdeviceError> = run_sync_local(async move {
|
||||||
let client_ref = unsafe { &mut (*client).0 };
|
let client_ref = unsafe { &mut (*client).0 };
|
||||||
client_ref.accept_developer_mode().await
|
client_ref.accept_developer_mode().await
|
||||||
});
|
});
|
||||||
@@ -171,7 +173,7 @@ pub unsafe extern "C" fn amfi_accept_developer_mode(
|
|||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
pub unsafe extern "C" fn amfi_client_free(handle: *mut AmfiClientHandle) {
|
pub unsafe extern "C" fn amfi_client_free(handle: *mut AmfiClientHandle) {
|
||||||
if !handle.is_null() {
|
if !handle.is_null() {
|
||||||
log::debug!("Freeing AmfiClient handle");
|
tracing::debug!("Freeing AmfiClient handle");
|
||||||
let _ = unsafe { Box::from_raw(handle) };
|
let _ = unsafe { Box::from_raw(handle) };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ use idevice::{IdeviceError, ReadWrite, RsdService};
|
|||||||
|
|
||||||
use crate::core_device_proxy::AdapterHandle;
|
use crate::core_device_proxy::AdapterHandle;
|
||||||
use crate::rsd::RsdHandshakeHandle;
|
use crate::rsd::RsdHandshakeHandle;
|
||||||
use crate::{IdeviceFfiError, RUNTIME, ReadWriteOpaque, ffi_err};
|
use crate::{IdeviceFfiError, ReadWriteOpaque, ffi_err, run_sync, run_sync_local};
|
||||||
|
|
||||||
/// Opaque handle to an AppServiceClient
|
/// Opaque handle to an AppServiceClient
|
||||||
pub struct AppServiceHandle(pub AppServiceClient<Box<dyn ReadWrite>>);
|
pub struct AppServiceHandle(pub AppServiceClient<Box<dyn ReadWrite>>);
|
||||||
@@ -91,7 +91,7 @@ pub unsafe extern "C" fn app_service_connect_rsd(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let res: Result<AppServiceClient<Box<dyn ReadWrite>>, IdeviceError> =
|
let res: Result<AppServiceClient<Box<dyn ReadWrite>>, IdeviceError> =
|
||||||
RUNTIME.block_on(async move {
|
run_sync_local(async move {
|
||||||
let provider_ref = unsafe { &mut (*provider).0 };
|
let provider_ref = unsafe { &mut (*provider).0 };
|
||||||
let handshake_ref = unsafe { &mut (*handshake).0 };
|
let handshake_ref = unsafe { &mut (*handshake).0 };
|
||||||
|
|
||||||
@@ -130,7 +130,7 @@ pub unsafe extern "C" fn app_service_new(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let socket = unsafe { Box::from_raw(socket) };
|
let socket = unsafe { Box::from_raw(socket) };
|
||||||
let res = RUNTIME.block_on(async move { AppServiceClient::new(socket.inner.unwrap()).await });
|
let res = run_sync(async move { AppServiceClient::new(socket.inner.unwrap()).await });
|
||||||
|
|
||||||
match res {
|
match res {
|
||||||
Ok(client) => {
|
Ok(client) => {
|
||||||
@@ -186,7 +186,7 @@ pub unsafe extern "C" fn app_service_list_apps(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let client = unsafe { &mut (*handle).0 };
|
let client = unsafe { &mut (*handle).0 };
|
||||||
let res = RUNTIME.block_on(async move {
|
let res = run_sync(async move {
|
||||||
client
|
client
|
||||||
.list_apps(
|
.list_apps(
|
||||||
app_clips != 0,
|
app_clips != 0,
|
||||||
@@ -347,7 +347,7 @@ pub unsafe extern "C" fn app_service_launch_app(
|
|||||||
};
|
};
|
||||||
|
|
||||||
let client = unsafe { &mut (*handle).0 };
|
let client = unsafe { &mut (*handle).0 };
|
||||||
let res = RUNTIME.block_on(async move {
|
let res = run_sync(async move {
|
||||||
client
|
client
|
||||||
.launch_application(
|
.launch_application(
|
||||||
bundle_id_str,
|
bundle_id_str,
|
||||||
@@ -430,7 +430,7 @@ pub unsafe extern "C" fn app_service_list_processes(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let client = unsafe { &mut (*handle).0 };
|
let client = unsafe { &mut (*handle).0 };
|
||||||
let res = RUNTIME.block_on(async move { client.list_processes().await });
|
let res = run_sync(async move { client.list_processes().await });
|
||||||
|
|
||||||
match res {
|
match res {
|
||||||
Ok(process_list) => {
|
Ok(process_list) => {
|
||||||
@@ -477,7 +477,7 @@ pub unsafe extern "C" fn app_service_free_process_list(
|
|||||||
count: usize,
|
count: usize,
|
||||||
) {
|
) {
|
||||||
if !processes.is_null() && count > 0 {
|
if !processes.is_null() && count > 0 {
|
||||||
let processes_slice = unsafe { std::slice::from_raw_parts_mut(processes, count) };
|
let processes_slice = unsafe { Vec::from_raw_parts(processes, count, count) };
|
||||||
for process in processes_slice {
|
for process in processes_slice {
|
||||||
if !process.executable_url.is_null() {
|
if !process.executable_url.is_null() {
|
||||||
let _ = unsafe { CString::from_raw(process.executable_url) };
|
let _ = unsafe { CString::from_raw(process.executable_url) };
|
||||||
@@ -513,7 +513,7 @@ pub unsafe extern "C" fn app_service_uninstall_app(
|
|||||||
};
|
};
|
||||||
|
|
||||||
let client = unsafe { &mut (*handle).0 };
|
let client = unsafe { &mut (*handle).0 };
|
||||||
let res = RUNTIME.block_on(async move { client.uninstall_app(bundle_id_str).await });
|
let res = run_sync(async move { client.uninstall_app(bundle_id_str).await });
|
||||||
|
|
||||||
match res {
|
match res {
|
||||||
Ok(_) => null_mut(),
|
Ok(_) => null_mut(),
|
||||||
@@ -546,7 +546,7 @@ pub unsafe extern "C" fn app_service_send_signal(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let client = unsafe { &mut (*handle).0 };
|
let client = unsafe { &mut (*handle).0 };
|
||||||
let res = RUNTIME.block_on(async move { client.send_signal(pid, signal).await });
|
let res = run_sync(async move { client.send_signal(pid, signal).await });
|
||||||
|
|
||||||
match res {
|
match res {
|
||||||
Ok(signal_response) => {
|
Ok(signal_response) => {
|
||||||
@@ -628,7 +628,7 @@ pub unsafe extern "C" fn app_service_fetch_app_icon(
|
|||||||
};
|
};
|
||||||
|
|
||||||
let client = unsafe { &mut (*handle).0 };
|
let client = unsafe { &mut (*handle).0 };
|
||||||
let res = RUNTIME.block_on(async move {
|
let res = run_sync(async move {
|
||||||
client
|
client
|
||||||
.fetch_app_icon(bundle_id_str, width, height, scale, allow_placeholder != 0)
|
.fetch_app_icon(bundle_id_str, width, height, scale, allow_placeholder != 0)
|
||||||
.await
|
.await
|
||||||
|
|||||||
@@ -7,11 +7,11 @@ use std::ptr::null_mut;
|
|||||||
use futures::{Stream, StreamExt};
|
use futures::{Stream, StreamExt};
|
||||||
use idevice::core_device::DiagnostisServiceClient;
|
use idevice::core_device::DiagnostisServiceClient;
|
||||||
use idevice::{IdeviceError, ReadWrite, RsdService};
|
use idevice::{IdeviceError, ReadWrite, RsdService};
|
||||||
use log::debug;
|
use tracing::debug;
|
||||||
|
|
||||||
use crate::core_device_proxy::AdapterHandle;
|
use crate::core_device_proxy::AdapterHandle;
|
||||||
use crate::rsd::RsdHandshakeHandle;
|
use crate::rsd::RsdHandshakeHandle;
|
||||||
use crate::{IdeviceFfiError, RUNTIME, ReadWriteOpaque, ffi_err};
|
use crate::{IdeviceFfiError, ReadWriteOpaque, ffi_err, run_sync, run_sync_local};
|
||||||
|
|
||||||
/// Opaque handle to an AppServiceClient
|
/// Opaque handle to an AppServiceClient
|
||||||
pub struct DiagnosticsServiceHandle(pub DiagnostisServiceClient<Box<dyn ReadWrite>>);
|
pub struct DiagnosticsServiceHandle(pub DiagnostisServiceClient<Box<dyn ReadWrite>>);
|
||||||
@@ -43,7 +43,7 @@ pub unsafe extern "C" fn diagnostics_service_connect_rsd(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let res: Result<DiagnostisServiceClient<Box<dyn ReadWrite>>, IdeviceError> =
|
let res: Result<DiagnostisServiceClient<Box<dyn ReadWrite>>, IdeviceError> =
|
||||||
RUNTIME.block_on(async move {
|
run_sync_local(async move {
|
||||||
let provider_ref = unsafe { &mut (*provider).0 };
|
let provider_ref = unsafe { &mut (*provider).0 };
|
||||||
let handshake_ref = unsafe { &mut (*handshake).0 };
|
let handshake_ref = unsafe { &mut (*handshake).0 };
|
||||||
debug!(
|
debug!(
|
||||||
@@ -87,8 +87,8 @@ pub unsafe extern "C" fn diagnostics_service_new(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let socket = unsafe { Box::from_raw(socket) };
|
let socket = unsafe { Box::from_raw(socket) };
|
||||||
let res = RUNTIME
|
let res =
|
||||||
.block_on(async move { DiagnostisServiceClient::from_stream(socket.inner.unwrap()).await });
|
run_sync(async move { DiagnostisServiceClient::from_stream(socket.inner.unwrap()).await });
|
||||||
|
|
||||||
match res {
|
match res {
|
||||||
Ok(client) => {
|
Ok(client) => {
|
||||||
@@ -134,7 +134,7 @@ pub unsafe extern "C" fn diagnostics_service_capture_sysdiagnose(
|
|||||||
return ffi_err!(IdeviceError::FfiInvalidArg);
|
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||||
}
|
}
|
||||||
let handle = unsafe { &mut *handle };
|
let handle = unsafe { &mut *handle };
|
||||||
let res = RUNTIME.block_on(async move { handle.0.capture_sysdiagnose(dry_run).await });
|
let res = run_sync_local(async move { handle.0.capture_sysdiagnose(dry_run).await });
|
||||||
match res {
|
match res {
|
||||||
Ok(res) => {
|
Ok(res) => {
|
||||||
let filename = CString::new(res.preferred_filename).unwrap();
|
let filename = CString::new(res.preferred_filename).unwrap();
|
||||||
@@ -172,7 +172,7 @@ pub unsafe extern "C" fn sysdiagnose_stream_next(
|
|||||||
return ffi_err!(IdeviceError::FfiInvalidArg);
|
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||||
}
|
}
|
||||||
let handle = unsafe { &mut *handle };
|
let handle = unsafe { &mut *handle };
|
||||||
let res = RUNTIME.block_on(async move { handle.0.next().await });
|
let res = run_sync_local(async move { handle.0.next().await });
|
||||||
match res {
|
match res {
|
||||||
Some(Ok(res)) => {
|
Some(Ok(res)) => {
|
||||||
let mut res = res.into_boxed_slice();
|
let mut res = res.into_boxed_slice();
|
||||||
|
|||||||
@@ -9,7 +9,10 @@ use idevice::{
|
|||||||
IdeviceError, IdeviceService, core_device_proxy::CoreDeviceProxy, provider::IdeviceProvider,
|
IdeviceError, IdeviceService, core_device_proxy::CoreDeviceProxy, provider::IdeviceProvider,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{IdeviceFfiError, IdeviceHandle, RUNTIME, ffi_err, provider::IdeviceProviderHandle};
|
use crate::{
|
||||||
|
IdeviceFfiError, IdeviceHandle, ffi_err, provider::IdeviceProviderHandle, run_sync,
|
||||||
|
run_sync_local,
|
||||||
|
};
|
||||||
|
|
||||||
pub struct CoreDeviceProxyHandle(pub CoreDeviceProxy);
|
pub struct CoreDeviceProxyHandle(pub CoreDeviceProxy);
|
||||||
pub struct AdapterHandle(pub idevice::tcp::handle::AdapterHandle);
|
pub struct AdapterHandle(pub idevice::tcp::handle::AdapterHandle);
|
||||||
@@ -32,11 +35,11 @@ pub unsafe extern "C" fn core_device_proxy_connect(
|
|||||||
client: *mut *mut CoreDeviceProxyHandle,
|
client: *mut *mut CoreDeviceProxyHandle,
|
||||||
) -> *mut IdeviceFfiError {
|
) -> *mut IdeviceFfiError {
|
||||||
if provider.is_null() || client.is_null() {
|
if provider.is_null() || client.is_null() {
|
||||||
log::error!("Null pointer provided");
|
tracing::error!("Null pointer provided");
|
||||||
return ffi_err!(IdeviceError::FfiInvalidArg);
|
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||||
}
|
}
|
||||||
|
|
||||||
let res: Result<CoreDeviceProxy, IdeviceError> = RUNTIME.block_on(async move {
|
let res: Result<CoreDeviceProxy, IdeviceError> = run_sync_local(async move {
|
||||||
let provider_ref: &dyn IdeviceProvider = unsafe { &*(*provider).0 };
|
let provider_ref: &dyn IdeviceProvider = unsafe { &*(*provider).0 };
|
||||||
|
|
||||||
// Connect using the reference
|
// Connect using the reference
|
||||||
@@ -76,7 +79,7 @@ pub unsafe extern "C" fn core_device_proxy_new(
|
|||||||
}
|
}
|
||||||
let socket = unsafe { Box::from_raw(socket) }.0;
|
let socket = unsafe { Box::from_raw(socket) }.0;
|
||||||
let r: Result<CoreDeviceProxy, IdeviceError> =
|
let r: Result<CoreDeviceProxy, IdeviceError> =
|
||||||
RUNTIME.block_on(async move { CoreDeviceProxy::new(socket).await });
|
run_sync(async move { CoreDeviceProxy::new(socket).await });
|
||||||
match r {
|
match r {
|
||||||
Ok(r) => {
|
Ok(r) => {
|
||||||
let boxed = Box::new(CoreDeviceProxyHandle(r));
|
let boxed = Box::new(CoreDeviceProxyHandle(r));
|
||||||
@@ -113,7 +116,7 @@ pub unsafe extern "C" fn core_device_proxy_send(
|
|||||||
let proxy = unsafe { &mut (*handle).0 };
|
let proxy = unsafe { &mut (*handle).0 };
|
||||||
let data_slice = unsafe { std::slice::from_raw_parts(data, length) };
|
let data_slice = unsafe { std::slice::from_raw_parts(data, length) };
|
||||||
|
|
||||||
let res = RUNTIME.block_on(async move { proxy.send(data_slice).await });
|
let res = run_sync(async move { proxy.send(data_slice).await });
|
||||||
|
|
||||||
match res {
|
match res {
|
||||||
Ok(_) => null_mut(),
|
Ok(_) => null_mut(),
|
||||||
@@ -149,7 +152,7 @@ pub unsafe extern "C" fn core_device_proxy_recv(
|
|||||||
|
|
||||||
let proxy = unsafe { &mut (*handle).0 };
|
let proxy = unsafe { &mut (*handle).0 };
|
||||||
|
|
||||||
let res = RUNTIME.block_on(async move { proxy.recv().await });
|
let res = run_sync(async move { proxy.recv().await });
|
||||||
|
|
||||||
match res {
|
match res {
|
||||||
Ok(received_data) => {
|
Ok(received_data) => {
|
||||||
@@ -192,7 +195,7 @@ pub unsafe extern "C" fn core_device_proxy_get_client_parameters(
|
|||||||
netmask: *mut *mut c_char,
|
netmask: *mut *mut c_char,
|
||||||
) -> *mut IdeviceFfiError {
|
) -> *mut IdeviceFfiError {
|
||||||
if handle.is_null() {
|
if handle.is_null() {
|
||||||
log::error!("Passed null handle");
|
tracing::error!("Passed null handle");
|
||||||
return ffi_err!(IdeviceError::FfiInvalidArg);
|
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -312,7 +315,7 @@ pub unsafe extern "C" fn core_device_proxy_create_tcp_adapter(
|
|||||||
match result {
|
match result {
|
||||||
Ok(adapter_obj) => {
|
Ok(adapter_obj) => {
|
||||||
// We have to run this in the RUNTIME since we're spawning a new thread
|
// We have to run this in the RUNTIME since we're spawning a new thread
|
||||||
let adapter_handle = RUNTIME.block_on(async move { adapter_obj.to_async_handle() });
|
let adapter_handle = run_sync(async move { adapter_obj.to_async_handle() });
|
||||||
|
|
||||||
let boxed = Box::new(AdapterHandle(adapter_handle));
|
let boxed = Box::new(AdapterHandle(adapter_handle));
|
||||||
unsafe { *adapter = Box::into_raw(boxed) };
|
unsafe { *adapter = Box::into_raw(boxed) };
|
||||||
@@ -333,7 +336,7 @@ pub unsafe extern "C" fn core_device_proxy_create_tcp_adapter(
|
|||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
pub unsafe extern "C" fn core_device_proxy_free(handle: *mut CoreDeviceProxyHandle) {
|
pub unsafe extern "C" fn core_device_proxy_free(handle: *mut CoreDeviceProxyHandle) {
|
||||||
if !handle.is_null() {
|
if !handle.is_null() {
|
||||||
log::debug!("Freeing core_device_proxy");
|
tracing::debug!("Freeing core_device_proxy");
|
||||||
let _ = unsafe { Box::from_raw(handle) };
|
let _ = unsafe { Box::from_raw(handle) };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -349,7 +352,7 @@ pub unsafe extern "C" fn core_device_proxy_free(handle: *mut CoreDeviceProxyHand
|
|||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
pub unsafe extern "C" fn adapter_free(handle: *mut AdapterHandle) {
|
pub unsafe extern "C" fn adapter_free(handle: *mut AdapterHandle) {
|
||||||
if !handle.is_null() {
|
if !handle.is_null() {
|
||||||
log::debug!("Freeing adapter");
|
tracing::debug!("Freeing adapter");
|
||||||
let _ = unsafe { Box::from_raw(handle) };
|
let _ = unsafe { Box::from_raw(handle) };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
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::core_device_proxy::AdapterHandle;
|
||||||
use crate::rsd::RsdHandshakeHandle;
|
use crate::rsd::RsdHandshakeHandle;
|
||||||
use crate::{IdeviceFfiError, RUNTIME, ReadWriteOpaque, ffi_err};
|
use crate::{IdeviceFfiError, ReadWriteOpaque, ffi_err, run_sync, run_sync_local};
|
||||||
|
|
||||||
/// Opaque handle to a DebugProxyClient
|
/// Opaque handle to a DebugProxyClient
|
||||||
pub struct DebugProxyHandle(pub DebugProxyClient<Box<dyn ReadWrite>>);
|
pub struct DebugProxyHandle(pub DebugProxyClient<Box<dyn ReadWrite>>);
|
||||||
@@ -136,7 +136,7 @@ pub unsafe extern "C" fn debug_proxy_connect_rsd(
|
|||||||
return ffi_err!(IdeviceError::FfiInvalidArg);
|
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||||
}
|
}
|
||||||
let res: Result<DebugProxyClient<Box<dyn ReadWrite>>, IdeviceError> =
|
let res: Result<DebugProxyClient<Box<dyn ReadWrite>>, IdeviceError> =
|
||||||
RUNTIME.block_on(async move {
|
run_sync_local(async move {
|
||||||
let provider_ref = unsafe { &mut (*provider).0 };
|
let provider_ref = unsafe { &mut (*provider).0 };
|
||||||
let handshake_ref = unsafe { &mut (*handshake).0 };
|
let handshake_ref = unsafe { &mut (*handshake).0 };
|
||||||
|
|
||||||
@@ -241,7 +241,7 @@ pub unsafe extern "C" fn debug_proxy_send_command(
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
let res = RUNTIME.block_on(async move { client.send_command(cmd).await });
|
let res = run_sync(async move { client.send_command(cmd).await });
|
||||||
|
|
||||||
match res {
|
match res {
|
||||||
Ok(Some(r)) => {
|
Ok(Some(r)) => {
|
||||||
@@ -282,7 +282,7 @@ pub unsafe extern "C" fn debug_proxy_read_response(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let client = unsafe { &mut (*handle).0 };
|
let client = unsafe { &mut (*handle).0 };
|
||||||
let res = RUNTIME.block_on(async move { client.read_response().await });
|
let res = run_sync(async move { client.read_response().await });
|
||||||
|
|
||||||
match res {
|
match res {
|
||||||
Ok(Some(r)) => {
|
Ok(Some(r)) => {
|
||||||
@@ -326,7 +326,7 @@ pub unsafe extern "C" fn debug_proxy_send_raw(
|
|||||||
|
|
||||||
let client = unsafe { &mut (*handle).0 };
|
let client = unsafe { &mut (*handle).0 };
|
||||||
let data_slice = unsafe { std::slice::from_raw_parts(data, len) };
|
let data_slice = unsafe { std::slice::from_raw_parts(data, len) };
|
||||||
let res = RUNTIME.block_on(async move { client.send_raw(data_slice).await });
|
let res = run_sync(async move { client.send_raw(data_slice).await });
|
||||||
|
|
||||||
match res {
|
match res {
|
||||||
Ok(_) => null_mut(),
|
Ok(_) => null_mut(),
|
||||||
@@ -358,7 +358,7 @@ pub unsafe extern "C" fn debug_proxy_read(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let client = unsafe { &mut (*handle).0 };
|
let client = unsafe { &mut (*handle).0 };
|
||||||
let res = RUNTIME.block_on(async move { client.read(len).await });
|
let res = run_sync(async move { client.read(len).await });
|
||||||
|
|
||||||
match res {
|
match res {
|
||||||
Ok(r) => {
|
Ok(r) => {
|
||||||
@@ -416,7 +416,7 @@ pub unsafe extern "C" fn debug_proxy_set_argv(
|
|||||||
.collect()
|
.collect()
|
||||||
};
|
};
|
||||||
|
|
||||||
let res = RUNTIME.block_on(async move { client.set_argv(argv_vec).await });
|
let res = run_sync(async move { client.set_argv(argv_vec).await });
|
||||||
|
|
||||||
match res {
|
match res {
|
||||||
Ok(r) => {
|
Ok(r) => {
|
||||||
@@ -450,7 +450,7 @@ pub unsafe extern "C" fn debug_proxy_send_ack(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let client = unsafe { &mut (*handle).0 };
|
let client = unsafe { &mut (*handle).0 };
|
||||||
let res = RUNTIME.block_on(async move { client.send_ack().await });
|
let res = run_sync(async move { client.send_ack().await });
|
||||||
|
|
||||||
match res {
|
match res {
|
||||||
Ok(_) => null_mut(),
|
Ok(_) => null_mut(),
|
||||||
@@ -477,7 +477,7 @@ pub unsafe extern "C" fn debug_proxy_send_nack(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let client = unsafe { &mut (*handle).0 };
|
let client = unsafe { &mut (*handle).0 };
|
||||||
let res = RUNTIME.block_on(async move { client.send_noack().await });
|
let res = run_sync(async move { client.send_noack().await });
|
||||||
|
|
||||||
match res {
|
match res {
|
||||||
Ok(_) => null_mut(),
|
Ok(_) => null_mut(),
|
||||||
|
|||||||
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 idevice::{ReadWrite, dvt::location_simulation::LocationSimulationClient};
|
||||||
|
|
||||||
use crate::{IdeviceFfiError, RUNTIME, ffi_err, remote_server::RemoteServerHandle};
|
use crate::{IdeviceFfiError, dvt::remote_server::RemoteServerHandle, ffi_err, run_sync};
|
||||||
|
|
||||||
/// Opaque handle to a ProcessControlClient
|
/// Opaque handle to a ProcessControlClient
|
||||||
pub struct LocationSimulationHandle<'a>(pub LocationSimulationClient<'a, Box<dyn ReadWrite>>);
|
pub struct LocationSimulationHandle<'a>(pub LocationSimulationClient<'a, Box<dyn ReadWrite>>);
|
||||||
@@ -31,7 +31,7 @@ pub unsafe extern "C" fn location_simulation_new(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let server = unsafe { &mut (*server).0 };
|
let server = unsafe { &mut (*server).0 };
|
||||||
let res = RUNTIME.block_on(async move { LocationSimulationClient::new(server).await });
|
let res = run_sync(async move { LocationSimulationClient::new(server).await });
|
||||||
|
|
||||||
match res {
|
match res {
|
||||||
Ok(client) => {
|
Ok(client) => {
|
||||||
@@ -76,7 +76,7 @@ pub unsafe extern "C" fn location_simulation_clear(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let client = unsafe { &mut (*handle).0 };
|
let client = unsafe { &mut (*handle).0 };
|
||||||
let res = RUNTIME.block_on(async move { client.clear().await });
|
let res = run_sync(async move { client.clear().await });
|
||||||
|
|
||||||
match res {
|
match res {
|
||||||
Ok(_) => null_mut(),
|
Ok(_) => null_mut(),
|
||||||
@@ -107,7 +107,7 @@ pub unsafe extern "C" fn location_simulation_set(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let client = unsafe { &mut (*handle).0 };
|
let client = unsafe { &mut (*handle).0 };
|
||||||
let res = RUNTIME.block_on(async move { client.set(latitude, longitude).await });
|
let res = run_sync(async move { client.set(latitude, longitude).await });
|
||||||
|
|
||||||
match res {
|
match res {
|
||||||
Ok(_) => null_mut(),
|
Ok(_) => null_mut(),
|
||||||
8
ffi/src/dvt/mod.rs
Normal file
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 idevice::{ReadWrite, dvt::process_control::ProcessControlClient};
|
||||||
use plist::{Dictionary, Value};
|
use plist::{Dictionary, Value};
|
||||||
|
|
||||||
use crate::{IdeviceFfiError, RUNTIME, ffi_err, remote_server::RemoteServerHandle};
|
use crate::{IdeviceFfiError, dvt::remote_server::RemoteServerHandle, ffi_err, run_sync};
|
||||||
|
|
||||||
/// Opaque handle to a ProcessControlClient
|
/// Opaque handle to a ProcessControlClient
|
||||||
pub struct ProcessControlHandle<'a>(pub ProcessControlClient<'a, Box<dyn ReadWrite>>);
|
pub struct ProcessControlHandle<'a>(pub ProcessControlClient<'a, Box<dyn ReadWrite>>);
|
||||||
@@ -35,7 +35,7 @@ pub unsafe extern "C" fn process_control_new(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let server = unsafe { &mut (*server).0 };
|
let server = unsafe { &mut (*server).0 };
|
||||||
let res = RUNTIME.block_on(async move { ProcessControlClient::new(server).await });
|
let res = run_sync(async move { ProcessControlClient::new(server).await });
|
||||||
|
|
||||||
match res {
|
match res {
|
||||||
Ok(client) => {
|
Ok(client) => {
|
||||||
@@ -128,7 +128,7 @@ pub unsafe extern "C" fn process_control_launch_app(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let client = unsafe { &mut (*handle).0 };
|
let client = unsafe { &mut (*handle).0 };
|
||||||
let res = RUNTIME.block_on(async move {
|
let res = run_sync(async move {
|
||||||
client
|
client
|
||||||
.launch_app(
|
.launch_app(
|
||||||
bundle_id,
|
bundle_id,
|
||||||
@@ -170,7 +170,7 @@ pub unsafe extern "C" fn process_control_kill_app(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let client = unsafe { &mut (*handle).0 };
|
let client = unsafe { &mut (*handle).0 };
|
||||||
let res = RUNTIME.block_on(async move { client.kill_app(pid).await });
|
let res = run_sync(async move { client.kill_app(pid).await });
|
||||||
|
|
||||||
match res {
|
match res {
|
||||||
Ok(_) => null_mut(),
|
Ok(_) => null_mut(),
|
||||||
@@ -199,7 +199,7 @@ pub unsafe extern "C" fn process_control_disable_memory_limit(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let client = unsafe { &mut (*handle).0 };
|
let client = unsafe { &mut (*handle).0 };
|
||||||
let res = RUNTIME.block_on(async move { client.disable_memory_limit(pid).await });
|
let res = run_sync(async move { client.disable_memory_limit(pid).await });
|
||||||
|
|
||||||
match res {
|
match res {
|
||||||
Ok(_) => null_mut(),
|
Ok(_) => null_mut(),
|
||||||
@@ -4,7 +4,7 @@ use std::ptr::null_mut;
|
|||||||
|
|
||||||
use crate::core_device_proxy::AdapterHandle;
|
use crate::core_device_proxy::AdapterHandle;
|
||||||
use crate::rsd::RsdHandshakeHandle;
|
use crate::rsd::RsdHandshakeHandle;
|
||||||
use crate::{IdeviceFfiError, RUNTIME, ReadWriteOpaque, ffi_err};
|
use crate::{IdeviceFfiError, ReadWriteOpaque, ffi_err, run_sync, run_sync_local};
|
||||||
use idevice::dvt::remote_server::RemoteServerClient;
|
use idevice::dvt::remote_server::RemoteServerClient;
|
||||||
use idevice::{IdeviceError, ReadWrite, RsdService};
|
use idevice::{IdeviceError, ReadWrite, RsdService};
|
||||||
|
|
||||||
@@ -37,7 +37,7 @@ pub unsafe extern "C" fn remote_server_new(
|
|||||||
|
|
||||||
let res: Result<RemoteServerClient<Box<dyn ReadWrite>>, IdeviceError> =
|
let res: Result<RemoteServerClient<Box<dyn ReadWrite>>, IdeviceError> =
|
||||||
match wrapper.inner.take() {
|
match wrapper.inner.take() {
|
||||||
Some(stream) => RUNTIME.block_on(async move {
|
Some(stream) => run_sync(async move {
|
||||||
let mut client = RemoteServerClient::new(stream);
|
let mut client = RemoteServerClient::new(stream);
|
||||||
client.read_message(0).await?;
|
client.read_message(0).await?;
|
||||||
Ok(client)
|
Ok(client)
|
||||||
@@ -77,7 +77,7 @@ pub unsafe extern "C" fn remote_server_connect_rsd(
|
|||||||
return ffi_err!(IdeviceError::FfiInvalidArg);
|
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||||
}
|
}
|
||||||
let res: Result<RemoteServerClient<Box<dyn ReadWrite>>, IdeviceError> =
|
let res: Result<RemoteServerClient<Box<dyn ReadWrite>>, IdeviceError> =
|
||||||
RUNTIME.block_on(async move {
|
run_sync_local(async move {
|
||||||
let provider_ref = unsafe { &mut (*provider).0 };
|
let provider_ref = unsafe { &mut (*provider).0 };
|
||||||
let handshake_ref = unsafe { &mut (*handshake).0 };
|
let handshake_ref = unsafe { &mut (*handshake).0 };
|
||||||
|
|
||||||
113
ffi/src/dvt/screenshot.rs
Normal file
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 err: IdeviceError = $err.into();
|
||||||
let code = err.code();
|
let code = err.code();
|
||||||
let msg = CString::new(err.to_string())
|
let msg = CString::new(format!("{:?}", err))
|
||||||
.unwrap_or_else(|_| CString::new("invalid error").unwrap());
|
.unwrap_or_else(|_| CString::new("invalid error").unwrap());
|
||||||
let raw_msg = msg.into_raw();
|
let raw_msg = msg.into_raw();
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,9 @@ use idevice::{
|
|||||||
IdeviceError, IdeviceService, heartbeat::HeartbeatClient, provider::IdeviceProvider,
|
IdeviceError, IdeviceService, heartbeat::HeartbeatClient, provider::IdeviceProvider,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{IdeviceFfiError, IdeviceHandle, RUNTIME, ffi_err, provider::IdeviceProviderHandle};
|
use crate::{
|
||||||
|
IdeviceFfiError, IdeviceHandle, ffi_err, provider::IdeviceProviderHandle, run_sync_local,
|
||||||
|
};
|
||||||
|
|
||||||
pub struct HeartbeatClientHandle(pub HeartbeatClient);
|
pub struct HeartbeatClientHandle(pub HeartbeatClient);
|
||||||
|
|
||||||
@@ -28,11 +30,11 @@ pub unsafe extern "C" fn heartbeat_connect(
|
|||||||
client: *mut *mut HeartbeatClientHandle,
|
client: *mut *mut HeartbeatClientHandle,
|
||||||
) -> *mut IdeviceFfiError {
|
) -> *mut IdeviceFfiError {
|
||||||
if provider.is_null() || client.is_null() {
|
if provider.is_null() || client.is_null() {
|
||||||
log::error!("Null pointer provided");
|
tracing::error!("Null pointer provided");
|
||||||
return ffi_err!(IdeviceError::FfiInvalidArg);
|
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||||
}
|
}
|
||||||
|
|
||||||
let res: Result<HeartbeatClient, IdeviceError> = RUNTIME.block_on(async move {
|
let res: Result<HeartbeatClient, IdeviceError> = run_sync_local(async move {
|
||||||
let provider_ref: &dyn IdeviceProvider = unsafe { &*(*provider).0 };
|
let provider_ref: &dyn IdeviceProvider = unsafe { &*(*provider).0 };
|
||||||
// Connect using the reference
|
// Connect using the reference
|
||||||
HeartbeatClient::connect(provider_ref).await
|
HeartbeatClient::connect(provider_ref).await
|
||||||
@@ -95,7 +97,7 @@ pub unsafe extern "C" fn heartbeat_send_polo(
|
|||||||
if client.is_null() {
|
if client.is_null() {
|
||||||
return ffi_err!(IdeviceError::FfiInvalidArg);
|
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||||
}
|
}
|
||||||
let res: Result<(), IdeviceError> = RUNTIME.block_on(async move {
|
let res: Result<(), IdeviceError> = run_sync_local(async move {
|
||||||
let client_ref = unsafe { &mut (*client).0 };
|
let client_ref = unsafe { &mut (*client).0 };
|
||||||
client_ref.send_polo().await
|
client_ref.send_polo().await
|
||||||
});
|
});
|
||||||
@@ -126,7 +128,7 @@ pub unsafe extern "C" fn heartbeat_get_marco(
|
|||||||
if client.is_null() || new_interval.is_null() {
|
if client.is_null() || new_interval.is_null() {
|
||||||
return ffi_err!(IdeviceError::FfiInvalidArg);
|
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||||
}
|
}
|
||||||
let res: Result<u64, IdeviceError> = RUNTIME.block_on(async move {
|
let res: Result<u64, IdeviceError> = run_sync_local(async move {
|
||||||
let client_ref = unsafe { &mut (*client).0 };
|
let client_ref = unsafe { &mut (*client).0 };
|
||||||
client_ref.get_marco(interval).await
|
client_ref.get_marco(interval).await
|
||||||
});
|
});
|
||||||
@@ -150,7 +152,7 @@ pub unsafe extern "C" fn heartbeat_get_marco(
|
|||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
pub unsafe extern "C" fn heartbeat_client_free(handle: *mut HeartbeatClientHandle) {
|
pub unsafe extern "C" fn heartbeat_client_free(handle: *mut HeartbeatClientHandle) {
|
||||||
if !handle.is_null() {
|
if !handle.is_null() {
|
||||||
log::debug!("Freeing installation_proxy_client");
|
tracing::debug!("Freeing installation_proxy_client");
|
||||||
let _ = unsafe { Box::from_raw(handle) };
|
let _ = unsafe { Box::from_raw(handle) };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
183
ffi/src/house_arrest.rs
Normal file
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 plist_ffi::{PlistWrapper, plist_t};
|
||||||
|
|
||||||
use crate::{IdeviceFfiError, IdeviceHandle, RUNTIME, ffi_err, provider::IdeviceProviderHandle};
|
use crate::{
|
||||||
|
IdeviceFfiError, IdeviceHandle, ffi_err, provider::IdeviceProviderHandle, run_sync_local,
|
||||||
|
};
|
||||||
|
|
||||||
pub struct InstallationProxyClientHandle(pub InstallationProxyClient);
|
pub struct InstallationProxyClientHandle(pub InstallationProxyClient);
|
||||||
|
|
||||||
@@ -30,11 +32,11 @@ pub unsafe extern "C" fn installation_proxy_connect(
|
|||||||
client: *mut *mut InstallationProxyClientHandle,
|
client: *mut *mut InstallationProxyClientHandle,
|
||||||
) -> *mut IdeviceFfiError {
|
) -> *mut IdeviceFfiError {
|
||||||
if provider.is_null() || client.is_null() {
|
if provider.is_null() || client.is_null() {
|
||||||
log::error!("Null pointer provided");
|
tracing::error!("Null pointer provided");
|
||||||
return ffi_err!(IdeviceError::FfiInvalidArg);
|
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||||
}
|
}
|
||||||
|
|
||||||
let res: Result<InstallationProxyClient, IdeviceError> = RUNTIME.block_on(async move {
|
let res: Result<InstallationProxyClient, IdeviceError> = run_sync_local(async move {
|
||||||
let provider_ref: &dyn IdeviceProvider = unsafe { &*(*provider).0 };
|
let provider_ref: &dyn IdeviceProvider = unsafe { &*(*provider).0 };
|
||||||
InstallationProxyClient::connect(provider_ref).await
|
InstallationProxyClient::connect(provider_ref).await
|
||||||
});
|
});
|
||||||
@@ -101,7 +103,7 @@ pub unsafe extern "C" fn installation_proxy_get_apps(
|
|||||||
out_result_len: *mut libc::size_t,
|
out_result_len: *mut libc::size_t,
|
||||||
) -> *mut IdeviceFfiError {
|
) -> *mut IdeviceFfiError {
|
||||||
if client.is_null() || out_result.is_null() || out_result_len.is_null() {
|
if client.is_null() || out_result.is_null() || out_result_len.is_null() {
|
||||||
log::error!("Invalid arguments: {client:?}, {out_result:?}");
|
tracing::error!("Invalid arguments: {client:?}, {out_result:?}");
|
||||||
return ffi_err!(IdeviceError::FfiInvalidArg);
|
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||||
}
|
}
|
||||||
let client = unsafe { &mut *client };
|
let client = unsafe { &mut *client };
|
||||||
@@ -134,7 +136,7 @@ pub unsafe extern "C" fn installation_proxy_get_apps(
|
|||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
let res: Result<Vec<plist_t>, IdeviceError> = RUNTIME.block_on(async {
|
let res: Result<Vec<plist_t>, IdeviceError> = run_sync_local(async {
|
||||||
client.0.get_apps(app_type, bundle_ids).await.map(|apps| {
|
client.0.get_apps(app_type, bundle_ids).await.map(|apps| {
|
||||||
apps.into_values()
|
apps.into_values()
|
||||||
.map(|v| PlistWrapper::new_node(v).into_ptr())
|
.map(|v| PlistWrapper::new_node(v).into_ptr())
|
||||||
@@ -143,7 +145,8 @@ pub unsafe extern "C" fn installation_proxy_get_apps(
|
|||||||
});
|
});
|
||||||
|
|
||||||
match res {
|
match res {
|
||||||
Ok(mut r) => {
|
Ok(r) => {
|
||||||
|
let mut r = r.into_boxed_slice();
|
||||||
let ptr = r.as_mut_ptr();
|
let ptr = r.as_mut_ptr();
|
||||||
let len = r.len();
|
let len = r.len();
|
||||||
std::mem::forget(r);
|
std::mem::forget(r);
|
||||||
@@ -171,7 +174,7 @@ pub unsafe extern "C" fn installation_proxy_client_free(
|
|||||||
handle: *mut InstallationProxyClientHandle,
|
handle: *mut InstallationProxyClientHandle,
|
||||||
) {
|
) {
|
||||||
if !handle.is_null() {
|
if !handle.is_null() {
|
||||||
log::debug!("Freeing installation_proxy_client");
|
tracing::debug!("Freeing installation_proxy_client");
|
||||||
let _ = unsafe { Box::from_raw(handle) };
|
let _ = unsafe { Box::from_raw(handle) };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -210,7 +213,7 @@ pub unsafe extern "C" fn installation_proxy_install(
|
|||||||
}
|
}
|
||||||
.map(|x| x.borrow_self().clone());
|
.map(|x| x.borrow_self().clone());
|
||||||
|
|
||||||
let res = RUNTIME.block_on(async {
|
let res = run_sync_local(async {
|
||||||
unsafe { &mut *client }
|
unsafe { &mut *client }
|
||||||
.0
|
.0
|
||||||
.install(package_path, options)
|
.install(package_path, options)
|
||||||
@@ -261,7 +264,7 @@ pub unsafe extern "C" fn installation_proxy_install_with_callback(
|
|||||||
}
|
}
|
||||||
.map(|x| x.borrow_self().clone());
|
.map(|x| x.borrow_self().clone());
|
||||||
|
|
||||||
let res = RUNTIME.block_on(async {
|
let res = run_sync_local(async {
|
||||||
let callback_wrapper = |(progress, context)| async move {
|
let callback_wrapper = |(progress, context)| async move {
|
||||||
callback(progress, context);
|
callback(progress, context);
|
||||||
};
|
};
|
||||||
@@ -312,7 +315,7 @@ pub unsafe extern "C" fn installation_proxy_upgrade(
|
|||||||
}
|
}
|
||||||
.map(|x| x.borrow_self().clone());
|
.map(|x| x.borrow_self().clone());
|
||||||
|
|
||||||
let res = RUNTIME.block_on(async {
|
let res = run_sync_local(async {
|
||||||
unsafe { &mut *client }
|
unsafe { &mut *client }
|
||||||
.0
|
.0
|
||||||
.upgrade(package_path, options)
|
.upgrade(package_path, options)
|
||||||
@@ -363,7 +366,7 @@ pub unsafe extern "C" fn installation_proxy_upgrade_with_callback(
|
|||||||
}
|
}
|
||||||
.map(|x| x.borrow_self().clone());
|
.map(|x| x.borrow_self().clone());
|
||||||
|
|
||||||
let res = RUNTIME.block_on(async {
|
let res = run_sync_local(async {
|
||||||
let callback_wrapper = |(progress, context)| async move {
|
let callback_wrapper = |(progress, context)| async move {
|
||||||
callback(progress, context);
|
callback(progress, context);
|
||||||
};
|
};
|
||||||
@@ -414,7 +417,7 @@ pub unsafe extern "C" fn installation_proxy_uninstall(
|
|||||||
}
|
}
|
||||||
.map(|x| x.borrow_self().clone());
|
.map(|x| x.borrow_self().clone());
|
||||||
|
|
||||||
let res = RUNTIME.block_on(async {
|
let res = run_sync_local(async {
|
||||||
unsafe { &mut *client }
|
unsafe { &mut *client }
|
||||||
.0
|
.0
|
||||||
.uninstall(bundle_id, options)
|
.uninstall(bundle_id, options)
|
||||||
@@ -465,7 +468,7 @@ pub unsafe extern "C" fn installation_proxy_uninstall_with_callback(
|
|||||||
}
|
}
|
||||||
.map(|x| x.borrow_self().clone());
|
.map(|x| x.borrow_self().clone());
|
||||||
|
|
||||||
let res = RUNTIME.block_on(async {
|
let res = run_sync_local(async {
|
||||||
let callback_wrapper = |(progress, context)| async move {
|
let callback_wrapper = |(progress, context)| async move {
|
||||||
callback(progress, context);
|
callback(progress, context);
|
||||||
};
|
};
|
||||||
@@ -527,7 +530,7 @@ pub unsafe extern "C" fn installation_proxy_check_capabilities_match(
|
|||||||
}
|
}
|
||||||
.map(|x| x.borrow_self().clone());
|
.map(|x| x.borrow_self().clone());
|
||||||
|
|
||||||
let res = RUNTIME.block_on(async {
|
let res = run_sync_local(async {
|
||||||
unsafe { &mut *client }
|
unsafe { &mut *client }
|
||||||
.0
|
.0
|
||||||
.check_capabilities_match(capabilities, options)
|
.check_capabilities_match(capabilities, options)
|
||||||
@@ -577,7 +580,7 @@ pub unsafe extern "C" fn installation_proxy_browse(
|
|||||||
}
|
}
|
||||||
.map(|x| x.borrow_self().clone());
|
.map(|x| x.borrow_self().clone());
|
||||||
|
|
||||||
let res: Result<Vec<plist_t>, IdeviceError> = RUNTIME.block_on(async {
|
let res: Result<Vec<plist_t>, IdeviceError> = run_sync_local(async {
|
||||||
unsafe { &mut *client }.0.browse(options).await.map(|apps| {
|
unsafe { &mut *client }.0.browse(options).await.map(|apps| {
|
||||||
apps.into_iter()
|
apps.into_iter()
|
||||||
.map(|v| PlistWrapper::new_node(v).into_ptr())
|
.map(|v| PlistWrapper::new_node(v).into_ptr())
|
||||||
|
|||||||
151
ffi/src/lib.rs
151
ffi/src/lib.rs
@@ -10,31 +10,37 @@ pub mod amfi;
|
|||||||
pub mod core_device;
|
pub mod core_device;
|
||||||
#[cfg(feature = "core_device_proxy")]
|
#[cfg(feature = "core_device_proxy")]
|
||||||
pub mod core_device_proxy;
|
pub mod core_device_proxy;
|
||||||
|
#[cfg(feature = "crashreportcopymobile")]
|
||||||
|
pub mod crashreportcopymobile;
|
||||||
#[cfg(feature = "debug_proxy")]
|
#[cfg(feature = "debug_proxy")]
|
||||||
pub mod debug_proxy;
|
pub mod debug_proxy;
|
||||||
|
#[cfg(feature = "diagnostics_relay")]
|
||||||
|
pub mod diagnostics_relay;
|
||||||
|
#[cfg(feature = "dvt")]
|
||||||
|
pub mod dvt;
|
||||||
mod errors;
|
mod errors;
|
||||||
#[cfg(feature = "heartbeat")]
|
#[cfg(feature = "heartbeat")]
|
||||||
pub mod heartbeat;
|
pub mod heartbeat;
|
||||||
|
#[cfg(feature = "house_arrest")]
|
||||||
|
pub mod house_arrest;
|
||||||
#[cfg(feature = "installation_proxy")]
|
#[cfg(feature = "installation_proxy")]
|
||||||
pub mod installation_proxy;
|
pub mod installation_proxy;
|
||||||
#[cfg(feature = "location_simulation")]
|
|
||||||
pub mod location_simulation;
|
|
||||||
pub mod lockdown;
|
pub mod lockdown;
|
||||||
pub mod logging;
|
pub mod logging;
|
||||||
#[cfg(feature = "misagent")]
|
#[cfg(feature = "misagent")]
|
||||||
pub mod misagent;
|
pub mod misagent;
|
||||||
#[cfg(feature = "mobile_image_mounter")]
|
#[cfg(feature = "mobile_image_mounter")]
|
||||||
pub mod mobile_image_mounter;
|
pub mod mobile_image_mounter;
|
||||||
|
#[cfg(feature = "notification_proxy")]
|
||||||
|
pub mod notification_proxy;
|
||||||
#[cfg(feature = "syslog_relay")]
|
#[cfg(feature = "syslog_relay")]
|
||||||
pub mod os_trace_relay;
|
pub mod os_trace_relay;
|
||||||
mod pairing_file;
|
mod pairing_file;
|
||||||
#[cfg(feature = "dvt")]
|
|
||||||
pub mod process_control;
|
|
||||||
pub mod provider;
|
pub mod provider;
|
||||||
#[cfg(feature = "dvt")]
|
|
||||||
pub mod remote_server;
|
|
||||||
#[cfg(feature = "xpc")]
|
#[cfg(feature = "xpc")]
|
||||||
pub mod rsd;
|
pub mod rsd;
|
||||||
|
#[cfg(feature = "screenshotr")]
|
||||||
|
pub mod screenshotr;
|
||||||
#[cfg(feature = "springboardservices")]
|
#[cfg(feature = "springboardservices")]
|
||||||
pub mod springboardservices;
|
pub mod springboardservices;
|
||||||
#[cfg(feature = "syslog_relay")]
|
#[cfg(feature = "syslog_relay")]
|
||||||
@@ -50,8 +56,9 @@ pub use pairing_file::*;
|
|||||||
|
|
||||||
use idevice::{Idevice, IdeviceSocket, ReadWrite};
|
use idevice::{Idevice, IdeviceSocket, ReadWrite};
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
|
use plist_ffi::PlistWrapper;
|
||||||
use std::{
|
use std::{
|
||||||
ffi::{CStr, CString, c_char},
|
ffi::{CStr, CString, c_char, c_void},
|
||||||
ptr::null_mut,
|
ptr::null_mut,
|
||||||
};
|
};
|
||||||
use tokio::runtime::{self, Runtime};
|
use tokio::runtime::{self, Runtime};
|
||||||
@@ -59,7 +66,7 @@ use tokio::runtime::{self, Runtime};
|
|||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
use crate::util::{idevice_sockaddr, idevice_socklen_t};
|
use crate::util::{idevice_sockaddr, idevice_socklen_t};
|
||||||
|
|
||||||
static RUNTIME: Lazy<Runtime> = Lazy::new(|| {
|
static GLOBAL_RUNTIME: Lazy<Runtime> = Lazy::new(|| {
|
||||||
runtime::Builder::new_multi_thread()
|
runtime::Builder::new_multi_thread()
|
||||||
.enable_io()
|
.enable_io()
|
||||||
.enable_time()
|
.enable_time()
|
||||||
@@ -67,6 +74,39 @@ static RUNTIME: Lazy<Runtime> = Lazy::new(|| {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
static LOCAL_RUNTIME: Lazy<Runtime> = Lazy::new(|| {
|
||||||
|
runtime::Builder::new_multi_thread()
|
||||||
|
.enable_all()
|
||||||
|
.build()
|
||||||
|
.unwrap()
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Spawn the future on the global runtime and block current (FFI) thread until result.
|
||||||
|
/// F and R must be Send + 'static.
|
||||||
|
pub fn run_sync<F, R>(fut: F) -> R
|
||||||
|
where
|
||||||
|
F: std::future::Future<Output = R> + Send + 'static,
|
||||||
|
R: Send + 'static,
|
||||||
|
{
|
||||||
|
let (tx, rx) = std::sync::mpsc::sync_channel(1);
|
||||||
|
|
||||||
|
GLOBAL_RUNTIME.handle().spawn(async move {
|
||||||
|
let res = fut.await;
|
||||||
|
// best-effort send; ignore if receiver dropped
|
||||||
|
let _ = tx.send(res);
|
||||||
|
});
|
||||||
|
|
||||||
|
rx.recv().expect("runtime worker panicked")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run_sync_local<F, R>(fut: F) -> R
|
||||||
|
where
|
||||||
|
F: std::future::Future<Output = R>,
|
||||||
|
R: 'static,
|
||||||
|
{
|
||||||
|
LOCAL_RUNTIME.block_on(fut)
|
||||||
|
}
|
||||||
|
|
||||||
pub const LOCKDOWN_PORT: u16 = 62078;
|
pub const LOCKDOWN_PORT: u16 = 62078;
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
@@ -126,6 +166,47 @@ pub unsafe extern "C" fn idevice_new(
|
|||||||
null_mut()
|
null_mut()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates an Idevice object from a socket file descriptor
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// The socket FD must be valid.
|
||||||
|
/// The pointers must be valid and non-null.
|
||||||
|
#[cfg(unix)]
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub unsafe extern "C" fn idevice_from_fd(
|
||||||
|
fd: i32,
|
||||||
|
label: *const c_char,
|
||||||
|
idevice: *mut *mut IdeviceHandle,
|
||||||
|
) -> *mut IdeviceFfiError {
|
||||||
|
if label.is_null() || idevice.is_null() || fd == 0 {
|
||||||
|
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get socket ownership
|
||||||
|
let fd = unsafe { libc::dup(fd) };
|
||||||
|
let socket = unsafe { <std::net::TcpStream as std::os::fd::FromRawFd>::from_raw_fd(fd) };
|
||||||
|
if let Err(e) = socket.set_nonblocking(true) {
|
||||||
|
return ffi_err!(e);
|
||||||
|
}
|
||||||
|
let socket = match run_sync(async move { tokio::net::TcpStream::from_std(socket) }) {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(e) => return ffi_err!(e),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Convert C string to Rust string
|
||||||
|
let c_str = match unsafe { CStr::from_ptr(label).to_str() } {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(_) => return ffi_err!(IdeviceError::FfiInvalidString),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create new Idevice instance
|
||||||
|
let dev = Idevice::new(Box::new(socket), c_str);
|
||||||
|
let boxed = Box::new(IdeviceHandle(dev));
|
||||||
|
unsafe { *idevice = Box::into_raw(boxed) };
|
||||||
|
|
||||||
|
null_mut()
|
||||||
|
}
|
||||||
|
|
||||||
/// Creates a new Idevice connection
|
/// Creates a new Idevice connection
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
@@ -152,7 +233,7 @@ pub unsafe extern "C" fn idevice_new_tcp_socket(
|
|||||||
use crate::util::SockAddr;
|
use crate::util::SockAddr;
|
||||||
|
|
||||||
if addr.is_null() || label.is_null() || idevice.is_null() {
|
if addr.is_null() || label.is_null() || idevice.is_null() {
|
||||||
log::error!("null pointer(s) to idevice_new_tcp_socket");
|
tracing::error!("null pointer(s) to idevice_new_tcp_socket");
|
||||||
return ffi_err!(IdeviceError::FfiInvalidArg);
|
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||||
}
|
}
|
||||||
let addr = addr as *const SockAddr;
|
let addr = addr as *const SockAddr;
|
||||||
@@ -167,7 +248,7 @@ pub unsafe extern "C" fn idevice_new_tcp_socket(
|
|||||||
Err(e) => return ffi_err!(e),
|
Err(e) => return ffi_err!(e),
|
||||||
};
|
};
|
||||||
|
|
||||||
let device = RUNTIME.block_on(async move {
|
let device = run_sync(async move {
|
||||||
let stream = tokio::net::TcpStream::connect(addr).await?;
|
let stream = tokio::net::TcpStream::connect(addr).await?;
|
||||||
Ok::<idevice::Idevice, idevice::IdeviceError>(idevice::Idevice::new(
|
Ok::<idevice::Idevice, idevice::IdeviceError>(idevice::Idevice::new(
|
||||||
Box::new(stream),
|
Box::new(stream),
|
||||||
@@ -210,7 +291,7 @@ pub unsafe extern "C" fn idevice_get_type(
|
|||||||
let dev = unsafe { &mut (*idevice).0 };
|
let dev = unsafe { &mut (*idevice).0 };
|
||||||
|
|
||||||
// Run the get_type method in the runtime
|
// Run the get_type method in the runtime
|
||||||
let result = RUNTIME.block_on(async { dev.get_type().await });
|
let result = run_sync(async { dev.get_type().await });
|
||||||
|
|
||||||
match result {
|
match result {
|
||||||
Ok(type_str) => match CString::new(type_str) {
|
Ok(type_str) => match CString::new(type_str) {
|
||||||
@@ -244,7 +325,7 @@ pub unsafe extern "C" fn idevice_rsd_checkin(idevice: *mut IdeviceHandle) -> *mu
|
|||||||
let dev = unsafe { &mut (*idevice).0 };
|
let dev = unsafe { &mut (*idevice).0 };
|
||||||
|
|
||||||
// Run the rsd_checkin method in the runtime
|
// Run the rsd_checkin method in the runtime
|
||||||
let result = RUNTIME.block_on(async { dev.rsd_checkin().await });
|
let result = run_sync(async { dev.rsd_checkin().await });
|
||||||
|
|
||||||
match result {
|
match result {
|
||||||
Ok(_) => null_mut(),
|
Ok(_) => null_mut(),
|
||||||
@@ -268,6 +349,7 @@ pub unsafe extern "C" fn idevice_rsd_checkin(idevice: *mut IdeviceHandle) -> *mu
|
|||||||
pub unsafe extern "C" fn idevice_start_session(
|
pub unsafe extern "C" fn idevice_start_session(
|
||||||
idevice: *mut IdeviceHandle,
|
idevice: *mut IdeviceHandle,
|
||||||
pairing_file: *const IdevicePairingFile,
|
pairing_file: *const IdevicePairingFile,
|
||||||
|
legacy: bool,
|
||||||
) -> *mut IdeviceFfiError {
|
) -> *mut IdeviceFfiError {
|
||||||
if idevice.is_null() || pairing_file.is_null() {
|
if idevice.is_null() || pairing_file.is_null() {
|
||||||
return ffi_err!(IdeviceError::FfiInvalidArg);
|
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||||
@@ -280,7 +362,7 @@ pub unsafe extern "C" fn idevice_start_session(
|
|||||||
let pf = unsafe { &(*pairing_file).0 };
|
let pf = unsafe { &(*pairing_file).0 };
|
||||||
|
|
||||||
// Run the start_session method in the runtime
|
// Run the start_session method in the runtime
|
||||||
let result = RUNTIME.block_on(async { dev.start_session(pf).await });
|
let result = run_sync(async move { dev.start_session(pf, legacy).await });
|
||||||
|
|
||||||
match result {
|
match result {
|
||||||
Ok(_) => null_mut(),
|
Ok(_) => null_mut(),
|
||||||
@@ -303,6 +385,17 @@ pub unsafe extern "C" fn idevice_free(idevice: *mut IdeviceHandle) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Frees a stream handle
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// Pass a valid handle allocated by this library
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub unsafe extern "C" fn idevice_stream_free(stream_handle: *mut ReadWriteOpaque) {
|
||||||
|
if !stream_handle.is_null() {
|
||||||
|
let _ = unsafe { Box::from_raw(stream_handle) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Frees a string allocated by this library
|
/// Frees a string allocated by this library
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
@@ -329,6 +422,36 @@ pub unsafe extern "C" fn idevice_string_free(string: *mut c_char) {
|
|||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
pub unsafe extern "C" fn idevice_data_free(data: *mut u8, len: usize) {
|
pub unsafe extern "C" fn idevice_data_free(data: *mut u8, len: usize) {
|
||||||
if !data.is_null() {
|
if !data.is_null() {
|
||||||
let _ = unsafe { std::slice::from_raw_parts(data, len) };
|
let _ = unsafe { Vec::from_raw_parts(data, len, len) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Frees an array of plists allocated by this library
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// `data` must be a pointer to data allocated by this library,
|
||||||
|
/// NOT data allocated by libplist.
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub unsafe extern "C" fn idevice_plist_array_free(plists: *mut plist_t, len: usize) {
|
||||||
|
if !plists.is_null() {
|
||||||
|
let data = unsafe { std::slice::from_raw_parts(plists, len) };
|
||||||
|
for x in data {
|
||||||
|
unsafe { plist_ffi::creation::plist_free((*x) as *mut PlistWrapper) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Frees a slice of pointers allocated by this library that had an underlying
|
||||||
|
/// vec creation.
|
||||||
|
///
|
||||||
|
/// The following functions use an underlying vec and are safe to use:
|
||||||
|
/// - idevice_usbmuxd_get_devices
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// Pass a valid pointer passed by the Vec creating functions
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub unsafe extern "C" fn idevice_outer_slice_free(slice: *mut c_void, len: usize) {
|
||||||
|
if !slice.is_null() {
|
||||||
|
let _ = unsafe { Vec::from_raw_parts(slice, len, len) };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ use idevice::{IdeviceError, IdeviceService, lockdown::LockdownClient, provider::
|
|||||||
use plist_ffi::plist_t;
|
use plist_ffi::plist_t;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
IdeviceFfiError, IdeviceHandle, IdevicePairingFile, RUNTIME, ffi_err,
|
IdeviceFfiError, IdeviceHandle, IdevicePairingFile, ffi_err, provider::IdeviceProviderHandle,
|
||||||
provider::IdeviceProviderHandle,
|
run_sync_local,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct LockdowndClientHandle(pub LockdownClient);
|
pub struct LockdowndClientHandle(pub LockdownClient);
|
||||||
@@ -30,11 +30,11 @@ pub unsafe extern "C" fn lockdownd_connect(
|
|||||||
client: *mut *mut LockdowndClientHandle,
|
client: *mut *mut LockdowndClientHandle,
|
||||||
) -> *mut IdeviceFfiError {
|
) -> *mut IdeviceFfiError {
|
||||||
if provider.is_null() || client.is_null() {
|
if provider.is_null() || client.is_null() {
|
||||||
log::error!("Null pointer provided");
|
tracing::error!("Null pointer provided");
|
||||||
return ffi_err!(IdeviceError::FfiInvalidArg);
|
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||||
}
|
}
|
||||||
|
|
||||||
let res: Result<LockdownClient, IdeviceError> = RUNTIME.block_on(async move {
|
let res: Result<LockdownClient, IdeviceError> = run_sync_local(async move {
|
||||||
let provider_ref: &dyn IdeviceProvider = unsafe { &*(*provider).0 };
|
let provider_ref: &dyn IdeviceProvider = unsafe { &*(*provider).0 };
|
||||||
LockdownClient::connect(provider_ref).await
|
LockdownClient::connect(provider_ref).await
|
||||||
});
|
});
|
||||||
@@ -97,7 +97,7 @@ pub unsafe extern "C" fn lockdownd_start_session(
|
|||||||
client: *mut LockdowndClientHandle,
|
client: *mut LockdowndClientHandle,
|
||||||
pairing_file: *mut IdevicePairingFile,
|
pairing_file: *mut IdevicePairingFile,
|
||||||
) -> *mut IdeviceFfiError {
|
) -> *mut IdeviceFfiError {
|
||||||
let res: Result<(), IdeviceError> = RUNTIME.block_on(async move {
|
let res: Result<(), IdeviceError> = run_sync_local(async move {
|
||||||
let client_ref = unsafe { &mut (*client).0 };
|
let client_ref = unsafe { &mut (*client).0 };
|
||||||
let pairing_file_ref = unsafe { &(*pairing_file).0 };
|
let pairing_file_ref = unsafe { &(*pairing_file).0 };
|
||||||
|
|
||||||
@@ -140,7 +140,7 @@ pub unsafe extern "C" fn lockdownd_start_service(
|
|||||||
.to_string_lossy()
|
.to_string_lossy()
|
||||||
.into_owned();
|
.into_owned();
|
||||||
|
|
||||||
let res: Result<(u16, bool), IdeviceError> = RUNTIME.block_on(async move {
|
let res: Result<(u16, bool), IdeviceError> = run_sync_local(async move {
|
||||||
let client_ref = unsafe { &mut (*client).0 };
|
let client_ref = unsafe { &mut (*client).0 };
|
||||||
client_ref.start_service(identifier).await
|
client_ref.start_service(identifier).await
|
||||||
});
|
});
|
||||||
@@ -157,6 +157,74 @@ pub unsafe extern "C" fn lockdownd_start_service(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Pairs with the device using lockdownd
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `client` - A valid LockdowndClient handle
|
||||||
|
/// * `host_id` - The host ID (null-terminated string)
|
||||||
|
/// * `system_buid` - The system BUID (null-terminated string)
|
||||||
|
/// * `pairing_file` - On success, will be set to point to a newly allocated IdevicePairingFile handle
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// An IdeviceFfiError on error, null on success
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// `client` must be a valid pointer to a handle allocated by this library
|
||||||
|
/// `host_id` must be a valid null-terminated string
|
||||||
|
/// `system_buid` must be a valid null-terminated string
|
||||||
|
/// `pairing_file` must be a valid, non-null pointer to a location where the handle will be stored
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub unsafe extern "C" fn lockdownd_pair(
|
||||||
|
client: *mut LockdowndClientHandle,
|
||||||
|
host_id: *const libc::c_char,
|
||||||
|
system_buid: *const libc::c_char,
|
||||||
|
host_name: *const libc::c_char,
|
||||||
|
pairing_file: *mut *mut IdevicePairingFile,
|
||||||
|
) -> *mut IdeviceFfiError {
|
||||||
|
if client.is_null() || host_id.is_null() || system_buid.is_null() {
|
||||||
|
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||||
|
}
|
||||||
|
|
||||||
|
let host_id = unsafe {
|
||||||
|
std::ffi::CStr::from_ptr(host_id)
|
||||||
|
.to_string_lossy()
|
||||||
|
.into_owned()
|
||||||
|
};
|
||||||
|
let system_buid = unsafe {
|
||||||
|
std::ffi::CStr::from_ptr(system_buid)
|
||||||
|
.to_string_lossy()
|
||||||
|
.into_owned()
|
||||||
|
};
|
||||||
|
|
||||||
|
let host_name = if host_name.is_null() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(
|
||||||
|
match unsafe { std::ffi::CStr::from_ptr(host_name) }.to_str() {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(_) => {
|
||||||
|
return ffi_err!(IdeviceError::InvalidCString);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
let res = run_sync_local(async move {
|
||||||
|
let client_ref = unsafe { &mut (*client).0 };
|
||||||
|
|
||||||
|
client_ref.pair(host_id, system_buid, host_name).await
|
||||||
|
});
|
||||||
|
|
||||||
|
match res {
|
||||||
|
Ok(pairing_file_res) => {
|
||||||
|
let boxed_pairing_file = Box::new(IdevicePairingFile(pairing_file_res));
|
||||||
|
unsafe { *pairing_file = Box::into_raw(boxed_pairing_file) };
|
||||||
|
null_mut()
|
||||||
|
}
|
||||||
|
Err(e) => ffi_err!(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Gets a value from lockdownd
|
/// Gets a value from lockdownd
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
@@ -179,7 +247,7 @@ pub unsafe extern "C" fn lockdownd_get_value(
|
|||||||
domain: *const libc::c_char,
|
domain: *const libc::c_char,
|
||||||
out_plist: *mut plist_t,
|
out_plist: *mut plist_t,
|
||||||
) -> *mut IdeviceFfiError {
|
) -> *mut IdeviceFfiError {
|
||||||
if out_plist.is_null() {
|
if client.is_null() || out_plist.is_null() {
|
||||||
return ffi_err!(IdeviceError::FfiInvalidArg);
|
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -205,7 +273,7 @@ pub unsafe extern "C" fn lockdownd_get_value(
|
|||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
let res: Result<plist::Value, IdeviceError> = RUNTIME.block_on(async move {
|
let res: Result<plist::Value, IdeviceError> = run_sync_local(async move {
|
||||||
let client_ref = unsafe { &mut (*client).0 };
|
let client_ref = unsafe { &mut (*client).0 };
|
||||||
client_ref.get_value(value, domain).await
|
client_ref.get_value(value, domain).await
|
||||||
});
|
});
|
||||||
@@ -221,6 +289,89 @@ pub unsafe extern "C" fn lockdownd_get_value(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Tells the device to enter recovery mode
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `client` - A valid LockdowndClient handle
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// An IdeviceFfiError on error, null on success
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// `client` must be a valid pointer to a handle allocated by this library
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub unsafe extern "C" fn lockdownd_enter_recovery(
|
||||||
|
client: *mut LockdowndClientHandle,
|
||||||
|
) -> *mut IdeviceFfiError {
|
||||||
|
if client.is_null() {
|
||||||
|
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||||
|
}
|
||||||
|
|
||||||
|
let res: Result<(), IdeviceError> = run_sync_local(async move {
|
||||||
|
let client_ref = unsafe { &mut (*client).0 };
|
||||||
|
client_ref.enter_recovery().await
|
||||||
|
});
|
||||||
|
|
||||||
|
match res {
|
||||||
|
Ok(_) => null_mut(),
|
||||||
|
Err(e) => ffi_err!(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets a value in lockdownd
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `client` - A valid LockdowndClient handle
|
||||||
|
/// * `key` - The key to set (null-terminated string)
|
||||||
|
/// * `value` - The value to set as a plist
|
||||||
|
/// * `domain` - The domain to set in (null-terminated string, optional)
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// An IdeviceFfiError on error, null on success
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// `client` must be a valid pointer to a handle allocated by this library
|
||||||
|
/// `key` must be a valid null-terminated string
|
||||||
|
/// `value` must be a valid plist
|
||||||
|
/// `domain` must be a valid null-terminated string or NULL
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub unsafe extern "C" fn lockdownd_set_value(
|
||||||
|
client: *mut LockdowndClientHandle,
|
||||||
|
key: *const libc::c_char,
|
||||||
|
value: plist_t,
|
||||||
|
domain: *const libc::c_char,
|
||||||
|
) -> *mut IdeviceFfiError {
|
||||||
|
if client.is_null() || key.is_null() || value.is_null() {
|
||||||
|
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||||
|
}
|
||||||
|
|
||||||
|
let key = match unsafe { std::ffi::CStr::from_ptr(key) }.to_str() {
|
||||||
|
Ok(k) => k,
|
||||||
|
Err(_) => return ffi_err!(IdeviceError::InvalidCString),
|
||||||
|
};
|
||||||
|
|
||||||
|
let domain = if domain.is_null() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(match unsafe { std::ffi::CStr::from_ptr(domain) }.to_str() {
|
||||||
|
Ok(d) => d,
|
||||||
|
Err(_) => return ffi_err!(IdeviceError::InvalidCString),
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
let value = unsafe { &mut *value }.borrow_self().clone();
|
||||||
|
|
||||||
|
let res: Result<(), IdeviceError> = run_sync_local(async move {
|
||||||
|
let client_ref = unsafe { &mut (*client).0 };
|
||||||
|
client_ref.set_value(key, value, domain).await
|
||||||
|
});
|
||||||
|
|
||||||
|
match res {
|
||||||
|
Ok(_) => null_mut(),
|
||||||
|
Err(e) => ffi_err!(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Frees a LockdowndClient handle
|
/// Frees a LockdowndClient handle
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
@@ -232,7 +383,7 @@ pub unsafe extern "C" fn lockdownd_get_value(
|
|||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
pub unsafe extern "C" fn lockdownd_client_free(handle: *mut LockdowndClientHandle) {
|
pub unsafe extern "C" fn lockdownd_client_free(handle: *mut LockdowndClientHandle) {
|
||||||
if !handle.is_null() {
|
if !handle.is_null() {
|
||||||
log::debug!("Freeing lockdownd_client");
|
tracing::debug!("Freeing lockdownd_client");
|
||||||
let _ = unsafe { Box::from_raw(handle) };
|
let _ = unsafe { Box::from_raw(handle) };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,75 +3,13 @@
|
|||||||
use std::{
|
use std::{
|
||||||
ffi::{CStr, c_char},
|
ffi::{CStr, c_char},
|
||||||
fs::File,
|
fs::File,
|
||||||
|
sync::Once,
|
||||||
};
|
};
|
||||||
|
|
||||||
use log::LevelFilter;
|
use tracing::Level;
|
||||||
use simplelog::{
|
use tracing_appender::non_blocking;
|
||||||
ColorChoice, CombinedLogger, Config, SharedLogger, TermLogger, TerminalMode, WriteLogger,
|
use tracing_subscriber::{EnvFilter, Layer};
|
||||||
};
|
use tracing_subscriber::{Registry, fmt, layer::SubscriberExt, util::SubscriberInitExt};
|
||||||
|
|
||||||
/// Initializes the logger
|
|
||||||
///
|
|
||||||
/// # Arguments
|
|
||||||
/// * [`console_level`] - The level to log to the file
|
|
||||||
/// * [`file_level`] - The level to log to the file
|
|
||||||
/// * [`file_path`] - If not null, the file to write logs to
|
|
||||||
///
|
|
||||||
/// ## Log Level
|
|
||||||
/// 0. Disabled
|
|
||||||
/// 1. Error
|
|
||||||
/// 2. Warn
|
|
||||||
/// 3. Info
|
|
||||||
/// 4. Debug
|
|
||||||
/// 5. Trace
|
|
||||||
///
|
|
||||||
/// # Returns
|
|
||||||
/// 0 for success, -1 if the file couldn't be created, -2 if a logger has been initialized, -3 for invalid path string
|
|
||||||
///
|
|
||||||
/// # Safety
|
|
||||||
/// Pass a valid CString for file_path. Pass valid log levels according to the enum
|
|
||||||
#[unsafe(no_mangle)]
|
|
||||||
pub unsafe extern "C" fn idevice_init_logger(
|
|
||||||
console_level: IdeviceLogLevel,
|
|
||||||
file_level: IdeviceLogLevel,
|
|
||||||
file_path: *mut c_char,
|
|
||||||
) -> IdeviceLoggerError {
|
|
||||||
let mut loggers: Vec<Box<dyn SharedLogger>> = Vec::new();
|
|
||||||
let level: LevelFilter = console_level.into();
|
|
||||||
loggers.push(TermLogger::new(
|
|
||||||
level,
|
|
||||||
Config::default(),
|
|
||||||
TerminalMode::Mixed,
|
|
||||||
ColorChoice::Auto,
|
|
||||||
));
|
|
||||||
|
|
||||||
if !file_path.is_null() {
|
|
||||||
let file_path = match unsafe { CStr::from_ptr(file_path) }.to_str() {
|
|
||||||
Ok(f) => f.to_string(),
|
|
||||||
Err(_) => {
|
|
||||||
return IdeviceLoggerError::InvalidPathString;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let level: LevelFilter = file_level.into();
|
|
||||||
loggers.push(WriteLogger::new(
|
|
||||||
level,
|
|
||||||
Config::default(),
|
|
||||||
match File::create(file_path) {
|
|
||||||
Ok(f) => f,
|
|
||||||
Err(e) => {
|
|
||||||
println!("Failed to create path: {e:?}");
|
|
||||||
return IdeviceLoggerError::FileError;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
if CombinedLogger::init(loggers).is_err() {
|
|
||||||
IdeviceLoggerError::AlreadyInitialized
|
|
||||||
} else {
|
|
||||||
IdeviceLoggerError::Success
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
pub enum IdeviceLoggerError {
|
pub enum IdeviceLoggerError {
|
||||||
@@ -92,33 +30,113 @@ pub enum IdeviceLogLevel {
|
|||||||
Trace = 5,
|
Trace = 5,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<u8> for IdeviceLogLevel {
|
impl From<IdeviceLogLevel> for Level {
|
||||||
type Error = ();
|
|
||||||
|
|
||||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
|
||||||
Ok(match value {
|
|
||||||
0 => Self::Disabled,
|
|
||||||
1 => Self::ErrorLevel,
|
|
||||||
2 => Self::Warn,
|
|
||||||
3 => Self::Info,
|
|
||||||
4 => Self::Debug,
|
|
||||||
5 => Self::Trace,
|
|
||||||
_ => {
|
|
||||||
return Err(());
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<IdeviceLogLevel> for LevelFilter {
|
|
||||||
fn from(value: IdeviceLogLevel) -> Self {
|
fn from(value: IdeviceLogLevel) -> Self {
|
||||||
match value {
|
match value {
|
||||||
IdeviceLogLevel::Disabled => LevelFilter::Off,
|
IdeviceLogLevel::Disabled => Level::ERROR, // won't matter, filter will disable
|
||||||
IdeviceLogLevel::ErrorLevel => LevelFilter::Error,
|
IdeviceLogLevel::ErrorLevel => Level::ERROR,
|
||||||
IdeviceLogLevel::Warn => LevelFilter::Warn,
|
IdeviceLogLevel::Warn => Level::WARN,
|
||||||
IdeviceLogLevel::Info => LevelFilter::Info,
|
IdeviceLogLevel::Info => Level::INFO,
|
||||||
IdeviceLogLevel::Debug => LevelFilter::Debug,
|
IdeviceLogLevel::Debug => Level::DEBUG,
|
||||||
IdeviceLogLevel::Trace => LevelFilter::Trace,
|
IdeviceLogLevel::Trace => Level::TRACE,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl IdeviceLogLevel {
|
||||||
|
fn as_filter(&self) -> String {
|
||||||
|
match self {
|
||||||
|
IdeviceLogLevel::Disabled => "off",
|
||||||
|
IdeviceLogLevel::ErrorLevel => "error",
|
||||||
|
IdeviceLogLevel::Warn => "warn",
|
||||||
|
IdeviceLogLevel::Info => "info",
|
||||||
|
IdeviceLogLevel::Debug => "debug",
|
||||||
|
IdeviceLogLevel::Trace => "trace",
|
||||||
|
}
|
||||||
|
.to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ensures we only init once
|
||||||
|
static INIT: Once = Once::new();
|
||||||
|
static mut GUARDS: Option<(
|
||||||
|
Option<tracing_appender::non_blocking::WorkerGuard>,
|
||||||
|
Option<tracing_appender::non_blocking::WorkerGuard>,
|
||||||
|
)> = None;
|
||||||
|
|
||||||
|
/// Initializes the global logger
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// Pass a valid file path string
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub unsafe extern "C" fn idevice_init_logger(
|
||||||
|
console_level: IdeviceLogLevel,
|
||||||
|
file_level: IdeviceLogLevel,
|
||||||
|
file_path: *mut c_char,
|
||||||
|
) -> IdeviceLoggerError {
|
||||||
|
let mut init_result = IdeviceLoggerError::Success;
|
||||||
|
|
||||||
|
INIT.call_once(|| {
|
||||||
|
let console_filter = console_level.as_filter();
|
||||||
|
let file_filter = file_level.as_filter();
|
||||||
|
|
||||||
|
let mut layers = Vec::new();
|
||||||
|
let mut console_guard = None;
|
||||||
|
let mut file_guard = None;
|
||||||
|
|
||||||
|
// ---- Console layer ----
|
||||||
|
if console_level != IdeviceLogLevel::Disabled {
|
||||||
|
let (non_blocking, guard) = non_blocking(std::io::stdout());
|
||||||
|
console_guard = Some(guard);
|
||||||
|
let console_layer = fmt::layer()
|
||||||
|
.with_writer(non_blocking)
|
||||||
|
.with_ansi(true)
|
||||||
|
.with_target(false)
|
||||||
|
.with_target(true)
|
||||||
|
.with_filter(EnvFilter::new(console_filter));
|
||||||
|
layers.push(console_layer.boxed());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- File layer ----
|
||||||
|
if !file_path.is_null() && file_level != IdeviceLogLevel::Disabled {
|
||||||
|
let path = match unsafe { CStr::from_ptr(file_path).to_str() } {
|
||||||
|
Ok(p) => p,
|
||||||
|
Err(_) => {
|
||||||
|
init_result = IdeviceLoggerError::InvalidPathString;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
match File::create(path) {
|
||||||
|
Ok(f) => {
|
||||||
|
let (non_blocking, guard) = non_blocking(f);
|
||||||
|
file_guard = Some(guard);
|
||||||
|
let file_layer = fmt::layer()
|
||||||
|
.with_writer(non_blocking)
|
||||||
|
.with_ansi(false)
|
||||||
|
.with_target(false)
|
||||||
|
.with_filter(EnvFilter::new(file_filter));
|
||||||
|
layers.push(file_layer.boxed());
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
init_result = IdeviceLoggerError::FileError;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep guards alive (otherwise background threads die)
|
||||||
|
unsafe {
|
||||||
|
GUARDS = Some((console_guard, file_guard));
|
||||||
|
}
|
||||||
|
|
||||||
|
let subscriber = Registry::default().with(layers);
|
||||||
|
subscriber.init();
|
||||||
|
});
|
||||||
|
|
||||||
|
if !INIT.is_completed() {
|
||||||
|
IdeviceLoggerError::AlreadyInitialized
|
||||||
|
} else {
|
||||||
|
init_result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use std::ptr::null_mut;
|
|||||||
|
|
||||||
use idevice::{IdeviceError, IdeviceService, misagent::MisagentClient, provider::IdeviceProvider};
|
use idevice::{IdeviceError, IdeviceService, misagent::MisagentClient, provider::IdeviceProvider};
|
||||||
|
|
||||||
use crate::{IdeviceFfiError, RUNTIME, ffi_err, provider::IdeviceProviderHandle};
|
use crate::{IdeviceFfiError, ffi_err, provider::IdeviceProviderHandle, run_sync_local};
|
||||||
|
|
||||||
pub struct MisagentClientHandle(pub MisagentClient);
|
pub struct MisagentClientHandle(pub MisagentClient);
|
||||||
|
|
||||||
@@ -28,11 +28,11 @@ pub unsafe extern "C" fn misagent_connect(
|
|||||||
client: *mut *mut MisagentClientHandle,
|
client: *mut *mut MisagentClientHandle,
|
||||||
) -> *mut IdeviceFfiError {
|
) -> *mut IdeviceFfiError {
|
||||||
if provider.is_null() || client.is_null() {
|
if provider.is_null() || client.is_null() {
|
||||||
log::error!("Null pointer provided");
|
tracing::error!("Null pointer provided");
|
||||||
return ffi_err!(IdeviceError::FfiInvalidArg);
|
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||||
}
|
}
|
||||||
|
|
||||||
let res: Result<MisagentClient, IdeviceError> = RUNTIME.block_on(async move {
|
let res: Result<MisagentClient, IdeviceError> = run_sync_local(async move {
|
||||||
let provider_ref: &dyn IdeviceProvider = unsafe { &*(*provider).0 };
|
let provider_ref: &dyn IdeviceProvider = unsafe { &*(*provider).0 };
|
||||||
MisagentClient::connect(provider_ref).await
|
MisagentClient::connect(provider_ref).await
|
||||||
});
|
});
|
||||||
@@ -72,7 +72,7 @@ pub unsafe extern "C" fn misagent_install(
|
|||||||
|
|
||||||
let profile = unsafe { std::slice::from_raw_parts(profile_data, profile_len) }.to_vec();
|
let profile = unsafe { std::slice::from_raw_parts(profile_data, profile_len) }.to_vec();
|
||||||
|
|
||||||
let res = RUNTIME.block_on(async { unsafe { &mut *client }.0.install(profile).await });
|
let res = run_sync_local(async { unsafe { &mut *client }.0.install(profile).await });
|
||||||
|
|
||||||
match res {
|
match res {
|
||||||
Ok(_) => null_mut(),
|
Ok(_) => null_mut(),
|
||||||
@@ -105,7 +105,7 @@ pub unsafe extern "C" fn misagent_remove(
|
|||||||
.to_string_lossy()
|
.to_string_lossy()
|
||||||
.into_owned();
|
.into_owned();
|
||||||
|
|
||||||
let res = RUNTIME.block_on(async { unsafe { &mut *client }.0.remove(&id).await });
|
let res = run_sync_local(async { unsafe { &mut *client }.0.remove(&id).await });
|
||||||
|
|
||||||
match res {
|
match res {
|
||||||
Ok(_) => null_mut(),
|
Ok(_) => null_mut(),
|
||||||
@@ -143,7 +143,7 @@ pub unsafe extern "C" fn misagent_copy_all(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let res: Result<Vec<Vec<u8>>, IdeviceError> =
|
let res: Result<Vec<Vec<u8>>, IdeviceError> =
|
||||||
RUNTIME.block_on(async { unsafe { &mut *client }.0.copy_all().await });
|
run_sync_local(async { unsafe { &mut *client }.0.copy_all().await });
|
||||||
|
|
||||||
match res {
|
match res {
|
||||||
Ok(profiles) => {
|
Ok(profiles) => {
|
||||||
@@ -199,7 +199,7 @@ pub unsafe extern "C" fn misagent_free_profiles(
|
|||||||
|
|
||||||
for (ptr, len) in profiles.iter_mut().zip(lens.iter()) {
|
for (ptr, len) in profiles.iter_mut().zip(lens.iter()) {
|
||||||
if !ptr.is_null() {
|
if !ptr.is_null() {
|
||||||
let _ = unsafe { Box::from_raw(std::slice::from_raw_parts_mut(*ptr, *len)) };
|
let _ = unsafe { Box::from_raw(std::ptr::slice_from_raw_parts_mut(*ptr, *len)) };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -218,7 +218,7 @@ pub unsafe extern "C" fn misagent_free_profiles(
|
|||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
pub unsafe extern "C" fn misagent_client_free(handle: *mut MisagentClientHandle) {
|
pub unsafe extern "C" fn misagent_client_free(handle: *mut MisagentClientHandle) {
|
||||||
if !handle.is_null() {
|
if !handle.is_null() {
|
||||||
log::debug!("Freeing misagent_client");
|
tracing::debug!("Freeing misagent_client");
|
||||||
let _ = unsafe { Box::from_raw(handle) };
|
let _ = unsafe { Box::from_raw(handle) };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,9 @@ use idevice::{
|
|||||||
use plist::Value;
|
use plist::Value;
|
||||||
use plist_ffi::{PlistWrapper, plist_t};
|
use plist_ffi::{PlistWrapper, plist_t};
|
||||||
|
|
||||||
use crate::{IdeviceFfiError, IdeviceHandle, RUNTIME, ffi_err, provider::IdeviceProviderHandle};
|
use crate::{
|
||||||
|
IdeviceFfiError, IdeviceHandle, ffi_err, provider::IdeviceProviderHandle, run_sync_local,
|
||||||
|
};
|
||||||
|
|
||||||
pub struct ImageMounterHandle(pub ImageMounter);
|
pub struct ImageMounterHandle(pub ImageMounter);
|
||||||
|
|
||||||
@@ -30,11 +32,11 @@ pub unsafe extern "C" fn image_mounter_connect(
|
|||||||
client: *mut *mut ImageMounterHandle,
|
client: *mut *mut ImageMounterHandle,
|
||||||
) -> *mut IdeviceFfiError {
|
) -> *mut IdeviceFfiError {
|
||||||
if provider.is_null() || client.is_null() {
|
if provider.is_null() || client.is_null() {
|
||||||
log::error!("Null pointer provided");
|
tracing::error!("Null pointer provided");
|
||||||
return ffi_err!(IdeviceError::FfiInvalidArg);
|
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||||
}
|
}
|
||||||
|
|
||||||
let res: Result<ImageMounter, IdeviceError> = RUNTIME.block_on(async move {
|
let res: Result<ImageMounter, IdeviceError> = run_sync_local(async move {
|
||||||
let provider_ref: &dyn IdeviceProvider = unsafe { &*(*provider).0 };
|
let provider_ref: &dyn IdeviceProvider = unsafe { &*(*provider).0 };
|
||||||
ImageMounter::connect(provider_ref).await
|
ImageMounter::connect(provider_ref).await
|
||||||
});
|
});
|
||||||
@@ -90,7 +92,7 @@ pub unsafe extern "C" fn image_mounter_new(
|
|||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
pub unsafe extern "C" fn image_mounter_free(handle: *mut ImageMounterHandle) {
|
pub unsafe extern "C" fn image_mounter_free(handle: *mut ImageMounterHandle) {
|
||||||
if !handle.is_null() {
|
if !handle.is_null() {
|
||||||
log::debug!("Freeing image_mounter_client");
|
tracing::debug!("Freeing image_mounter_client");
|
||||||
let _ = unsafe { Box::from_raw(handle) };
|
let _ = unsafe { Box::from_raw(handle) };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -114,7 +116,7 @@ pub unsafe extern "C" fn image_mounter_copy_devices(
|
|||||||
devices: *mut *mut plist_t,
|
devices: *mut *mut plist_t,
|
||||||
devices_len: *mut libc::size_t,
|
devices_len: *mut libc::size_t,
|
||||||
) -> *mut IdeviceFfiError {
|
) -> *mut IdeviceFfiError {
|
||||||
let res: Result<Vec<Value>, IdeviceError> = RUNTIME.block_on(async move {
|
let res: Result<Vec<Value>, IdeviceError> = run_sync_local(async move {
|
||||||
let client_ref = unsafe { &mut (*client).0 };
|
let client_ref = unsafe { &mut (*client).0 };
|
||||||
client_ref.copy_devices().await
|
client_ref.copy_devices().await
|
||||||
});
|
});
|
||||||
@@ -171,7 +173,7 @@ pub unsafe extern "C" fn image_mounter_lookup_image(
|
|||||||
Err(_) => return ffi_err!(IdeviceError::FfiInvalidArg),
|
Err(_) => return ffi_err!(IdeviceError::FfiInvalidArg),
|
||||||
};
|
};
|
||||||
|
|
||||||
let res: Result<Vec<u8>, IdeviceError> = RUNTIME.block_on(async move {
|
let res: Result<Vec<u8>, IdeviceError> = run_sync_local(async move {
|
||||||
let client_ref = unsafe { &mut (*client).0 };
|
let client_ref = unsafe { &mut (*client).0 };
|
||||||
client_ref.lookup_image(image_type).await
|
client_ref.lookup_image(image_type).await
|
||||||
});
|
});
|
||||||
@@ -228,7 +230,7 @@ pub unsafe extern "C" fn image_mounter_upload_image(
|
|||||||
let image_slice = unsafe { std::slice::from_raw_parts(image, image_len) };
|
let image_slice = unsafe { std::slice::from_raw_parts(image, image_len) };
|
||||||
let signature_slice = unsafe { std::slice::from_raw_parts(signature, signature_len) };
|
let signature_slice = unsafe { std::slice::from_raw_parts(signature, signature_len) };
|
||||||
|
|
||||||
let res: Result<(), IdeviceError> = RUNTIME.block_on(async move {
|
let res: Result<(), IdeviceError> = run_sync_local(async move {
|
||||||
let client_ref = unsafe { &mut (*client).0 };
|
let client_ref = unsafe { &mut (*client).0 };
|
||||||
client_ref
|
client_ref
|
||||||
.upload_image(image_type, image_slice, signature_slice.to_vec())
|
.upload_image(image_type, image_slice, signature_slice.to_vec())
|
||||||
@@ -295,7 +297,7 @@ pub unsafe extern "C" fn image_mounter_mount_image(
|
|||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
let res: Result<(), IdeviceError> = RUNTIME.block_on(async move {
|
let res: Result<(), IdeviceError> = run_sync_local(async move {
|
||||||
let client_ref = unsafe { &mut (*client).0 };
|
let client_ref = unsafe { &mut (*client).0 };
|
||||||
client_ref
|
client_ref
|
||||||
.mount_image(
|
.mount_image(
|
||||||
@@ -340,7 +342,7 @@ pub unsafe extern "C" fn image_mounter_unmount_image(
|
|||||||
Err(_) => return ffi_err!(IdeviceError::FfiInvalidArg),
|
Err(_) => return ffi_err!(IdeviceError::FfiInvalidArg),
|
||||||
};
|
};
|
||||||
|
|
||||||
let res: Result<(), IdeviceError> = RUNTIME.block_on(async move {
|
let res: Result<(), IdeviceError> = run_sync_local(async move {
|
||||||
let client_ref = unsafe { &mut (*client).0 };
|
let client_ref = unsafe { &mut (*client).0 };
|
||||||
client_ref.unmount_image(mount_path).await
|
client_ref.unmount_image(mount_path).await
|
||||||
});
|
});
|
||||||
@@ -372,7 +374,7 @@ pub unsafe extern "C" fn image_mounter_query_developer_mode_status(
|
|||||||
return ffi_err!(IdeviceError::FfiInvalidArg);
|
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||||
}
|
}
|
||||||
|
|
||||||
let res: Result<bool, IdeviceError> = RUNTIME.block_on(async move {
|
let res: Result<bool, IdeviceError> = run_sync_local(async move {
|
||||||
let client_ref = unsafe { &mut (*client).0 };
|
let client_ref = unsafe { &mut (*client).0 };
|
||||||
client_ref.query_developer_mode_status().await
|
client_ref.query_developer_mode_status().await
|
||||||
});
|
});
|
||||||
@@ -415,7 +417,7 @@ pub unsafe extern "C" fn image_mounter_mount_developer(
|
|||||||
let image_slice = unsafe { std::slice::from_raw_parts(image, image_len) };
|
let image_slice = unsafe { std::slice::from_raw_parts(image, image_len) };
|
||||||
let signature_slice = unsafe { std::slice::from_raw_parts(signature, signature_len) };
|
let signature_slice = unsafe { std::slice::from_raw_parts(signature, signature_len) };
|
||||||
|
|
||||||
let res: Result<(), IdeviceError> = RUNTIME.block_on(async move {
|
let res: Result<(), IdeviceError> = run_sync_local(async move {
|
||||||
let client_ref = unsafe { &mut (*client).0 };
|
let client_ref = unsafe { &mut (*client).0 };
|
||||||
client_ref
|
client_ref
|
||||||
.mount_developer(image_slice, signature_slice.to_vec())
|
.mount_developer(image_slice, signature_slice.to_vec())
|
||||||
@@ -465,7 +467,7 @@ pub unsafe extern "C" fn image_mounter_query_personalization_manifest(
|
|||||||
|
|
||||||
let signature_slice = unsafe { std::slice::from_raw_parts(signature, signature_len) };
|
let signature_slice = unsafe { std::slice::from_raw_parts(signature, signature_len) };
|
||||||
|
|
||||||
let res: Result<Vec<u8>, IdeviceError> = RUNTIME.block_on(async move {
|
let res: Result<Vec<u8>, IdeviceError> = run_sync_local(async move {
|
||||||
let client_ref = unsafe { &mut (*client).0 };
|
let client_ref = unsafe { &mut (*client).0 };
|
||||||
client_ref
|
client_ref
|
||||||
.query_personalization_manifest(image_type, signature_slice.to_vec())
|
.query_personalization_manifest(image_type, signature_slice.to_vec())
|
||||||
@@ -521,7 +523,7 @@ pub unsafe extern "C" fn image_mounter_query_nonce(
|
|||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
let res: Result<Vec<u8>, IdeviceError> = RUNTIME.block_on(async move {
|
let res: Result<Vec<u8>, IdeviceError> = run_sync_local(async move {
|
||||||
let client_ref = unsafe { &mut (*client).0 };
|
let client_ref = unsafe { &mut (*client).0 };
|
||||||
client_ref.query_nonce(image_type).await
|
client_ref.query_nonce(image_type).await
|
||||||
});
|
});
|
||||||
@@ -573,7 +575,7 @@ pub unsafe extern "C" fn image_mounter_query_personalization_identifiers(
|
|||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
let res: Result<plist::Dictionary, IdeviceError> = RUNTIME.block_on(async move {
|
let res: Result<plist::Dictionary, IdeviceError> = run_sync_local(async move {
|
||||||
let client_ref = unsafe { &mut (*client).0 };
|
let client_ref = unsafe { &mut (*client).0 };
|
||||||
client_ref
|
client_ref
|
||||||
.query_personalization_identifiers(image_type)
|
.query_personalization_identifiers(image_type)
|
||||||
@@ -604,7 +606,7 @@ pub unsafe extern "C" fn image_mounter_query_personalization_identifiers(
|
|||||||
pub unsafe extern "C" fn image_mounter_roll_personalization_nonce(
|
pub unsafe extern "C" fn image_mounter_roll_personalization_nonce(
|
||||||
client: *mut ImageMounterHandle,
|
client: *mut ImageMounterHandle,
|
||||||
) -> *mut IdeviceFfiError {
|
) -> *mut IdeviceFfiError {
|
||||||
let res: Result<(), IdeviceError> = RUNTIME.block_on(async move {
|
let res: Result<(), IdeviceError> = run_sync_local(async move {
|
||||||
let client_ref = unsafe { &mut (*client).0 };
|
let client_ref = unsafe { &mut (*client).0 };
|
||||||
client_ref.roll_personalization_nonce().await
|
client_ref.roll_personalization_nonce().await
|
||||||
});
|
});
|
||||||
@@ -629,7 +631,7 @@ pub unsafe extern "C" fn image_mounter_roll_personalization_nonce(
|
|||||||
pub unsafe extern "C" fn image_mounter_roll_cryptex_nonce(
|
pub unsafe extern "C" fn image_mounter_roll_cryptex_nonce(
|
||||||
client: *mut ImageMounterHandle,
|
client: *mut ImageMounterHandle,
|
||||||
) -> *mut IdeviceFfiError {
|
) -> *mut IdeviceFfiError {
|
||||||
let res: Result<(), IdeviceError> = RUNTIME.block_on(async move {
|
let res: Result<(), IdeviceError> = run_sync_local(async move {
|
||||||
let client_ref = unsafe { &mut (*client).0 };
|
let client_ref = unsafe { &mut (*client).0 };
|
||||||
client_ref.roll_cryptex_nonce().await
|
client_ref.roll_cryptex_nonce().await
|
||||||
});
|
});
|
||||||
@@ -692,7 +694,7 @@ pub unsafe extern "C" fn image_mounter_mount_personalized(
|
|||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
let res: Result<(), IdeviceError> = RUNTIME.block_on(async move {
|
let res: Result<(), IdeviceError> = run_sync_local(async move {
|
||||||
let client_ref = unsafe { &mut (*client).0 };
|
let client_ref = unsafe { &mut (*client).0 };
|
||||||
let provider_ref: &dyn IdeviceProvider = unsafe { &*(*provider).0 };
|
let provider_ref: &dyn IdeviceProvider = unsafe { &*(*provider).0 };
|
||||||
client_ref
|
client_ref
|
||||||
@@ -769,7 +771,7 @@ pub unsafe extern "C" fn image_mounter_mount_personalized_with_callback(
|
|||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
let res: Result<(), IdeviceError> = RUNTIME.block_on(async move {
|
let res: Result<(), IdeviceError> = run_sync_local(async move {
|
||||||
let client_ref = unsafe { &mut (*client).0 };
|
let client_ref = unsafe { &mut (*client).0 };
|
||||||
let provider_ref: &dyn IdeviceProvider = unsafe { &*(*provider).0 };
|
let provider_ref: &dyn IdeviceProvider = unsafe { &*(*provider).0 };
|
||||||
|
|
||||||
|
|||||||
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,
|
IdeviceError, IdeviceService, os_trace_relay::OsTraceRelayClient, provider::IdeviceProvider,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{IdeviceFfiError, RUNTIME, ffi_err, provider::IdeviceProviderHandle};
|
use crate::run_sync_local;
|
||||||
|
use crate::{IdeviceFfiError, ffi_err, provider::IdeviceProviderHandle};
|
||||||
|
|
||||||
pub struct OsTraceRelayClientHandle(pub OsTraceRelayClient);
|
pub struct OsTraceRelayClientHandle(pub OsTraceRelayClient);
|
||||||
pub struct OsTraceRelayReceiverHandle(pub idevice::os_trace_relay::OsTraceRelayReceiver);
|
pub struct OsTraceRelayReceiverHandle(pub idevice::os_trace_relay::OsTraceRelayReceiver);
|
||||||
@@ -46,11 +47,11 @@ pub unsafe extern "C" fn os_trace_relay_connect(
|
|||||||
client: *mut *mut OsTraceRelayClientHandle,
|
client: *mut *mut OsTraceRelayClientHandle,
|
||||||
) -> *mut IdeviceFfiError {
|
) -> *mut IdeviceFfiError {
|
||||||
if provider.is_null() {
|
if provider.is_null() {
|
||||||
log::error!("Null pointer provided");
|
tracing::error!("Null pointer provided");
|
||||||
return ffi_err!(IdeviceError::FfiInvalidArg);
|
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||||
}
|
}
|
||||||
|
|
||||||
let res: Result<OsTraceRelayClient, IdeviceError> = RUNTIME.block_on(async move {
|
let res: Result<OsTraceRelayClient, IdeviceError> = run_sync_local(async move {
|
||||||
let provider_ref: &dyn IdeviceProvider = unsafe { &*(*provider).0 };
|
let provider_ref: &dyn IdeviceProvider = unsafe { &*(*provider).0 };
|
||||||
OsTraceRelayClient::connect(provider_ref).await
|
OsTraceRelayClient::connect(provider_ref).await
|
||||||
});
|
});
|
||||||
@@ -78,7 +79,7 @@ pub unsafe extern "C" fn os_trace_relay_connect(
|
|||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
pub unsafe extern "C" fn os_trace_relay_free(handle: *mut OsTraceRelayClientHandle) {
|
pub unsafe extern "C" fn os_trace_relay_free(handle: *mut OsTraceRelayClientHandle) {
|
||||||
if !handle.is_null() {
|
if !handle.is_null() {
|
||||||
log::debug!("Freeing os trace relay client");
|
tracing::debug!("Freeing os trace relay client");
|
||||||
let _ = unsafe { Box::from_raw(handle) };
|
let _ = unsafe { Box::from_raw(handle) };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -102,7 +103,7 @@ pub unsafe extern "C" fn os_trace_relay_start_trace(
|
|||||||
pid: *const u32,
|
pid: *const u32,
|
||||||
) -> *mut IdeviceFfiError {
|
) -> *mut IdeviceFfiError {
|
||||||
if receiver.is_null() || client.is_null() {
|
if receiver.is_null() || client.is_null() {
|
||||||
log::error!("Null pointer provided");
|
tracing::error!("Null pointer provided");
|
||||||
return ffi_err!(IdeviceError::FfiInvalidArg);
|
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,7 +115,7 @@ pub unsafe extern "C" fn os_trace_relay_start_trace(
|
|||||||
|
|
||||||
let client_owned = unsafe { Box::from_raw(client) };
|
let client_owned = unsafe { Box::from_raw(client) };
|
||||||
|
|
||||||
let res = RUNTIME.block_on(async { client_owned.0.start_trace(pid_option).await });
|
let res = run_sync_local(async { client_owned.0.start_trace(pid_option).await });
|
||||||
|
|
||||||
match res {
|
match res {
|
||||||
Ok(relay) => {
|
Ok(relay) => {
|
||||||
@@ -137,7 +138,7 @@ pub unsafe extern "C" fn os_trace_relay_start_trace(
|
|||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
pub unsafe extern "C" fn os_trace_relay_receiver_free(handle: *mut OsTraceRelayReceiverHandle) {
|
pub unsafe extern "C" fn os_trace_relay_receiver_free(handle: *mut OsTraceRelayReceiverHandle) {
|
||||||
if !handle.is_null() {
|
if !handle.is_null() {
|
||||||
log::debug!("Freeing syslog relay client");
|
tracing::debug!("Freeing syslog relay client");
|
||||||
let _ = unsafe { Box::from_raw(handle) };
|
let _ = unsafe { Box::from_raw(handle) };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -158,7 +159,7 @@ pub unsafe extern "C" fn os_trace_relay_get_pid_list(
|
|||||||
client: *mut OsTraceRelayClientHandle,
|
client: *mut OsTraceRelayClientHandle,
|
||||||
list: *mut *mut Vec<u64>,
|
list: *mut *mut Vec<u64>,
|
||||||
) -> *mut IdeviceFfiError {
|
) -> *mut IdeviceFfiError {
|
||||||
let res = RUNTIME.block_on(async { unsafe { &mut *client }.0.get_pid_list().await });
|
let res = run_sync_local(async { unsafe { &mut *client }.0.get_pid_list().await });
|
||||||
|
|
||||||
match res {
|
match res {
|
||||||
Ok(r) => {
|
Ok(r) => {
|
||||||
@@ -186,11 +187,11 @@ pub unsafe extern "C" fn os_trace_relay_next(
|
|||||||
log: *mut *mut OsTraceLog,
|
log: *mut *mut OsTraceLog,
|
||||||
) -> *mut IdeviceFfiError {
|
) -> *mut IdeviceFfiError {
|
||||||
if client.is_null() {
|
if client.is_null() {
|
||||||
log::error!("Null pointer provided");
|
tracing::error!("Null pointer provided");
|
||||||
return ffi_err!(IdeviceError::FfiInvalidArg);
|
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||||
}
|
}
|
||||||
|
|
||||||
let res = RUNTIME.block_on(async { unsafe { &mut *client }.0.next().await });
|
let res = run_sync_local(async { unsafe { &mut *client }.0.next().await });
|
||||||
|
|
||||||
match res {
|
match res {
|
||||||
Ok(r) => {
|
Ok(r) => {
|
||||||
|
|||||||
@@ -139,7 +139,7 @@ pub unsafe extern "C" fn idevice_pairing_file_serialize(
|
|||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
pub unsafe extern "C" fn idevice_pairing_file_free(pairing_file: *mut IdevicePairingFile) {
|
pub unsafe extern "C" fn idevice_pairing_file_free(pairing_file: *mut IdevicePairingFile) {
|
||||||
if !pairing_file.is_null() {
|
if !pairing_file.is_null() {
|
||||||
log::debug!("Freeing pairing file");
|
tracing::debug!("Freeing pairing file");
|
||||||
let _ = unsafe { Box::from_raw(pairing_file) };
|
let _ = unsafe { Box::from_raw(pairing_file) };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use std::{ffi::CStr, ptr::null_mut};
|
|||||||
|
|
||||||
use crate::util::{SockAddr, idevice_sockaddr};
|
use crate::util::{SockAddr, idevice_sockaddr};
|
||||||
use crate::{IdeviceFfiError, ffi_err, usbmuxd::UsbmuxdAddrHandle, util};
|
use crate::{IdeviceFfiError, ffi_err, usbmuxd::UsbmuxdAddrHandle, util};
|
||||||
use crate::{IdevicePairingFile, RUNTIME};
|
use crate::{IdevicePairingFile, run_sync};
|
||||||
|
|
||||||
pub struct IdeviceProviderHandle(pub Box<dyn IdeviceProvider>);
|
pub struct IdeviceProviderHandle(pub Box<dyn IdeviceProvider>);
|
||||||
|
|
||||||
@@ -70,7 +70,7 @@ pub unsafe extern "C" fn idevice_tcp_provider_new(
|
|||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
pub unsafe extern "C" fn idevice_provider_free(provider: *mut IdeviceProviderHandle) {
|
pub unsafe extern "C" fn idevice_provider_free(provider: *mut IdeviceProviderHandle) {
|
||||||
if !provider.is_null() {
|
if !provider.is_null() {
|
||||||
log::debug!("Freeing provider");
|
tracing::debug!("Freeing provider");
|
||||||
unsafe { drop(Box::from_raw(provider)) };
|
unsafe { drop(Box::from_raw(provider)) };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -109,7 +109,7 @@ pub unsafe extern "C" fn usbmuxd_provider_new(
|
|||||||
let udid = match unsafe { CStr::from_ptr(udid) }.to_str() {
|
let udid = match unsafe { CStr::from_ptr(udid) }.to_str() {
|
||||||
Ok(u) => u.to_string(),
|
Ok(u) => u.to_string(),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::error!("Invalid UDID string: {e:?}");
|
tracing::error!("Invalid UDID string: {e:?}");
|
||||||
return ffi_err!(IdeviceError::FfiInvalidString);
|
return ffi_err!(IdeviceError::FfiInvalidString);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -117,7 +117,7 @@ pub unsafe extern "C" fn usbmuxd_provider_new(
|
|||||||
let label = match unsafe { CStr::from_ptr(label) }.to_str() {
|
let label = match unsafe { CStr::from_ptr(label) }.to_str() {
|
||||||
Ok(l) => l.to_string(),
|
Ok(l) => l.to_string(),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::error!("Invalid label string: {e:?}");
|
tracing::error!("Invalid label string: {e:?}");
|
||||||
return ffi_err!(IdeviceError::FfiInvalidArg);
|
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -156,7 +156,7 @@ pub unsafe extern "C" fn idevice_provider_get_pairing_file(
|
|||||||
) -> *mut IdeviceFfiError {
|
) -> *mut IdeviceFfiError {
|
||||||
let provider = unsafe { &mut *provider };
|
let provider = unsafe { &mut *provider };
|
||||||
|
|
||||||
let res = RUNTIME.block_on(async move { provider.0.get_pairing_file().await });
|
let res = run_sync(async move { provider.0.get_pairing_file().await });
|
||||||
match res {
|
match res {
|
||||||
Ok(pf) => {
|
Ok(pf) => {
|
||||||
let pf = Box::new(IdevicePairingFile(pf));
|
let pf = Box::new(IdevicePairingFile(pf));
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use std::ptr::{self, null_mut};
|
|||||||
|
|
||||||
use idevice::rsd::RsdHandshake;
|
use idevice::rsd::RsdHandshake;
|
||||||
|
|
||||||
use crate::{IdeviceFfiError, RUNTIME, ReadWriteOpaque, ffi_err};
|
use crate::{IdeviceFfiError, ReadWriteOpaque, ffi_err, run_sync};
|
||||||
|
|
||||||
/// Opaque handle to an RsdHandshake
|
/// Opaque handle to an RsdHandshake
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@@ -63,10 +63,10 @@ pub unsafe extern "C" fn rsd_handshake_new(
|
|||||||
return ffi_err!(IdeviceError::FfiInvalidArg);
|
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||||
}
|
}
|
||||||
|
|
||||||
let wrapper = unsafe { &mut *socket };
|
let mut wrapper = unsafe { Box::from_raw(socket) };
|
||||||
|
|
||||||
let res = match wrapper.inner.take() {
|
let res = match wrapper.inner.take() {
|
||||||
Some(mut w) => RUNTIME.block_on(async move { RsdHandshake::new(w.as_mut()).await }),
|
Some(mut w) => run_sync(async move { RsdHandshake::new(w.as_mut()).await }),
|
||||||
None => {
|
None => {
|
||||||
return ffi_err!(IdeviceError::FfiInvalidArg);
|
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||||
}
|
}
|
||||||
|
|||||||
132
ffi/src/screenshotr.rs
Normal file
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,
|
IdeviceError, IdeviceService, provider::IdeviceProvider,
|
||||||
springboardservices::SpringBoardServicesClient,
|
springboardservices::SpringBoardServicesClient,
|
||||||
};
|
};
|
||||||
|
use plist_ffi::plist_t;
|
||||||
|
|
||||||
use crate::{IdeviceFfiError, IdeviceHandle, RUNTIME, ffi_err, provider::IdeviceProviderHandle};
|
use crate::{
|
||||||
|
IdeviceFfiError, IdeviceHandle, ffi_err, provider::IdeviceProviderHandle, run_sync,
|
||||||
|
run_sync_local,
|
||||||
|
};
|
||||||
|
|
||||||
pub struct SpringBoardServicesClientHandle(pub SpringBoardServicesClient);
|
pub struct SpringBoardServicesClientHandle(pub SpringBoardServicesClient);
|
||||||
|
|
||||||
@@ -30,11 +34,11 @@ pub unsafe extern "C" fn springboard_services_connect(
|
|||||||
client: *mut *mut SpringBoardServicesClientHandle,
|
client: *mut *mut SpringBoardServicesClientHandle,
|
||||||
) -> *mut IdeviceFfiError {
|
) -> *mut IdeviceFfiError {
|
||||||
if provider.is_null() || client.is_null() {
|
if provider.is_null() || client.is_null() {
|
||||||
log::error!("Null pointer provided");
|
tracing::error!("Null pointer provided");
|
||||||
return ffi_err!(IdeviceError::FfiInvalidArg);
|
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||||
}
|
}
|
||||||
|
|
||||||
let res: Result<SpringBoardServicesClient, IdeviceError> = RUNTIME.block_on(async move {
|
let res: Result<SpringBoardServicesClient, IdeviceError> = run_sync_local(async move {
|
||||||
let provider_ref: &dyn IdeviceProvider = unsafe { &*(*provider).0 };
|
let provider_ref: &dyn IdeviceProvider = unsafe { &*(*provider).0 };
|
||||||
SpringBoardServicesClient::connect(provider_ref).await
|
SpringBoardServicesClient::connect(provider_ref).await
|
||||||
});
|
});
|
||||||
@@ -103,7 +107,7 @@ pub unsafe extern "C" fn springboard_services_get_icon(
|
|||||||
out_result_len: *mut libc::size_t,
|
out_result_len: *mut libc::size_t,
|
||||||
) -> *mut IdeviceFfiError {
|
) -> *mut IdeviceFfiError {
|
||||||
if client.is_null() || out_result.is_null() || out_result_len.is_null() {
|
if client.is_null() || out_result.is_null() || out_result_len.is_null() {
|
||||||
log::error!("Invalid arguments: {client:?}, {out_result:?}");
|
tracing::error!("Invalid arguments: {client:?}, {out_result:?}");
|
||||||
return ffi_err!(IdeviceError::FfiInvalidArg);
|
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||||
}
|
}
|
||||||
let client = unsafe { &mut *client };
|
let client = unsafe { &mut *client };
|
||||||
@@ -115,7 +119,7 @@ pub unsafe extern "C" fn springboard_services_get_icon(
|
|||||||
};
|
};
|
||||||
|
|
||||||
let res: Result<Vec<u8>, IdeviceError> =
|
let res: Result<Vec<u8>, IdeviceError> =
|
||||||
RUNTIME.block_on(async { client.0.get_icon_pngdata(bundle_id).await });
|
run_sync(async { client.0.get_icon_pngdata(bundle_id).await });
|
||||||
|
|
||||||
match res {
|
match res {
|
||||||
Ok(r) => {
|
Ok(r) => {
|
||||||
@@ -134,6 +138,169 @@ pub unsafe extern "C" fn springboard_services_get_icon(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets the home screen wallpaper preview as PNG image
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `client` - A valid SpringBoardServicesClient handle
|
||||||
|
/// * `out_result` - On success, will be set to point to newly allocated png image
|
||||||
|
/// * `out_result_len` - On success, will contain the size of the data in bytes
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// An IdeviceFfiError on error, null on success
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// `client` must be a valid pointer to a handle allocated by this library
|
||||||
|
/// `out_result` and `out_result_len` must be valid, non-null pointers
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub unsafe extern "C" fn springboard_services_get_home_screen_wallpaper_preview(
|
||||||
|
client: *mut SpringBoardServicesClientHandle,
|
||||||
|
out_result: *mut *mut c_void,
|
||||||
|
out_result_len: *mut libc::size_t,
|
||||||
|
) -> *mut IdeviceFfiError {
|
||||||
|
if client.is_null() || out_result.is_null() || out_result_len.is_null() {
|
||||||
|
tracing::error!("Invalid arguments: {client:?}, {out_result:?}");
|
||||||
|
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||||
|
}
|
||||||
|
let client = unsafe { &mut *client };
|
||||||
|
|
||||||
|
let res: Result<Vec<u8>, IdeviceError> =
|
||||||
|
run_sync(async { client.0.get_home_screen_wallpaper_preview_pngdata().await });
|
||||||
|
|
||||||
|
match res {
|
||||||
|
Ok(r) => {
|
||||||
|
let len = r.len();
|
||||||
|
let boxed_slice = r.into_boxed_slice();
|
||||||
|
let ptr = boxed_slice.as_ptr();
|
||||||
|
std::mem::forget(boxed_slice);
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
*out_result = ptr as *mut c_void;
|
||||||
|
*out_result_len = len;
|
||||||
|
}
|
||||||
|
null_mut()
|
||||||
|
}
|
||||||
|
Err(e) => ffi_err!(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the lock screen wallpaper preview as PNG image
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `client` - A valid SpringBoardServicesClient handle
|
||||||
|
/// * `out_result` - On success, will be set to point to newly allocated png image
|
||||||
|
/// * `out_result_len` - On success, will contain the size of the data in bytes
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// An IdeviceFfiError on error, null on success
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// `client` must be a valid pointer to a handle allocated by this library
|
||||||
|
/// `out_result` and `out_result_len` must be valid, non-null pointers
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub unsafe extern "C" fn springboard_services_get_lock_screen_wallpaper_preview(
|
||||||
|
client: *mut SpringBoardServicesClientHandle,
|
||||||
|
out_result: *mut *mut c_void,
|
||||||
|
out_result_len: *mut libc::size_t,
|
||||||
|
) -> *mut IdeviceFfiError {
|
||||||
|
if client.is_null() || out_result.is_null() || out_result_len.is_null() {
|
||||||
|
tracing::error!("Invalid arguments: {client:?}, {out_result:?}");
|
||||||
|
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||||
|
}
|
||||||
|
let client = unsafe { &mut *client };
|
||||||
|
|
||||||
|
let res: Result<Vec<u8>, IdeviceError> =
|
||||||
|
run_sync(async { client.0.get_lock_screen_wallpaper_preview_pngdata().await });
|
||||||
|
|
||||||
|
match res {
|
||||||
|
Ok(r) => {
|
||||||
|
let len = r.len();
|
||||||
|
let boxed_slice = r.into_boxed_slice();
|
||||||
|
let ptr = boxed_slice.as_ptr();
|
||||||
|
std::mem::forget(boxed_slice);
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
*out_result = ptr as *mut c_void;
|
||||||
|
*out_result_len = len;
|
||||||
|
}
|
||||||
|
null_mut()
|
||||||
|
}
|
||||||
|
Err(e) => ffi_err!(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the current interface orientation of the device
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `client` - A valid SpringBoardServicesClient handle
|
||||||
|
/// * `out_orientation` - On success, will contain the orientation value (0-4)
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// An IdeviceFfiError on error, null on success
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// `client` must be a valid pointer to a handle allocated by this library
|
||||||
|
/// `out_orientation` must be a valid, non-null pointer
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub unsafe extern "C" fn springboard_services_get_interface_orientation(
|
||||||
|
client: *mut SpringBoardServicesClientHandle,
|
||||||
|
out_orientation: *mut u8,
|
||||||
|
) -> *mut IdeviceFfiError {
|
||||||
|
if client.is_null() || out_orientation.is_null() {
|
||||||
|
tracing::error!("Invalid arguments: {client:?}, {out_orientation:?}");
|
||||||
|
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||||
|
}
|
||||||
|
let client = unsafe { &mut *client };
|
||||||
|
|
||||||
|
let res = run_sync(async { client.0.get_interface_orientation().await });
|
||||||
|
|
||||||
|
match res {
|
||||||
|
Ok(orientation) => {
|
||||||
|
unsafe {
|
||||||
|
*out_orientation = orientation as u8;
|
||||||
|
}
|
||||||
|
null_mut()
|
||||||
|
}
|
||||||
|
Err(e) => ffi_err!(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the home screen icon layout metrics
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `client` - A valid SpringBoardServicesClient handle
|
||||||
|
/// * `res` - On success, will point to a plist dictionary node containing the metrics
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// An IdeviceFfiError on error, null on success
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// `client` must be a valid pointer to a handle allocated by this library
|
||||||
|
/// `res` must be a valid, non-null pointer
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub unsafe extern "C" fn springboard_services_get_homescreen_icon_metrics(
|
||||||
|
client: *mut SpringBoardServicesClientHandle,
|
||||||
|
res: *mut plist_t,
|
||||||
|
) -> *mut IdeviceFfiError {
|
||||||
|
if client.is_null() || res.is_null() {
|
||||||
|
tracing::error!("Invalid arguments: {client:?}, {res:?}");
|
||||||
|
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||||
|
}
|
||||||
|
let client = unsafe { &mut *client };
|
||||||
|
|
||||||
|
let output = run_sync(async { client.0.get_homescreen_icon_metrics().await });
|
||||||
|
|
||||||
|
match output {
|
||||||
|
Ok(metrics) => {
|
||||||
|
unsafe {
|
||||||
|
*res =
|
||||||
|
plist_ffi::PlistWrapper::new_node(plist::Value::Dictionary(metrics)).into_ptr();
|
||||||
|
}
|
||||||
|
null_mut()
|
||||||
|
}
|
||||||
|
Err(e) => ffi_err!(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Frees an SpringBoardServicesClient handle
|
/// Frees an SpringBoardServicesClient handle
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
@@ -145,7 +312,7 @@ pub unsafe extern "C" fn springboard_services_get_icon(
|
|||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
pub unsafe extern "C" fn springboard_services_free(handle: *mut SpringBoardServicesClientHandle) {
|
pub unsafe extern "C" fn springboard_services_free(handle: *mut SpringBoardServicesClientHandle) {
|
||||||
if !handle.is_null() {
|
if !handle.is_null() {
|
||||||
log::debug!("Freeing springboard_services_client");
|
tracing::debug!("Freeing springboard_services_client");
|
||||||
let _ = unsafe { Box::from_raw(handle) };
|
let _ = unsafe { Box::from_raw(handle) };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use idevice::{
|
|||||||
IdeviceError, IdeviceService, provider::IdeviceProvider, syslog_relay::SyslogRelayClient,
|
IdeviceError, IdeviceService, provider::IdeviceProvider, syslog_relay::SyslogRelayClient,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{IdeviceFfiError, RUNTIME, ffi_err, provider::IdeviceProviderHandle};
|
use crate::{IdeviceFfiError, ffi_err, provider::IdeviceProviderHandle, run_sync_local};
|
||||||
|
|
||||||
pub struct SyslogRelayClientHandle(pub SyslogRelayClient);
|
pub struct SyslogRelayClientHandle(pub SyslogRelayClient);
|
||||||
|
|
||||||
@@ -23,11 +23,11 @@ pub unsafe extern "C" fn syslog_relay_connect_tcp(
|
|||||||
client: *mut *mut SyslogRelayClientHandle,
|
client: *mut *mut SyslogRelayClientHandle,
|
||||||
) -> *mut IdeviceFfiError {
|
) -> *mut IdeviceFfiError {
|
||||||
if provider.is_null() {
|
if provider.is_null() {
|
||||||
log::error!("Null pointer provided");
|
tracing::error!("Null pointer provided");
|
||||||
return ffi_err!(IdeviceError::FfiInvalidArg);
|
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||||
}
|
}
|
||||||
|
|
||||||
let res: Result<SyslogRelayClient, IdeviceError> = RUNTIME.block_on(async move {
|
let res: Result<SyslogRelayClient, IdeviceError> = run_sync_local(async move {
|
||||||
let provider_ref: &dyn IdeviceProvider = unsafe { &*(*provider).0 };
|
let provider_ref: &dyn IdeviceProvider = unsafe { &*(*provider).0 };
|
||||||
SyslogRelayClient::connect(provider_ref).await
|
SyslogRelayClient::connect(provider_ref).await
|
||||||
});
|
});
|
||||||
@@ -58,7 +58,7 @@ pub unsafe extern "C" fn syslog_relay_connect_tcp(
|
|||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
pub unsafe extern "C" fn syslog_relay_client_free(handle: *mut SyslogRelayClientHandle) {
|
pub unsafe extern "C" fn syslog_relay_client_free(handle: *mut SyslogRelayClientHandle) {
|
||||||
if !handle.is_null() {
|
if !handle.is_null() {
|
||||||
log::debug!("Freeing syslog relay client");
|
tracing::debug!("Freeing syslog relay client");
|
||||||
let _ = unsafe { Box::from_raw(handle) };
|
let _ = unsafe { Box::from_raw(handle) };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -81,7 +81,7 @@ pub unsafe extern "C" fn syslog_relay_next(
|
|||||||
return ffi_err!(IdeviceError::FfiInvalidArg);
|
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||||
}
|
}
|
||||||
|
|
||||||
let res = RUNTIME.block_on(async { unsafe { &mut *client }.0.next().await });
|
let res = run_sync_local(async { unsafe { &mut *client }.0.next().await });
|
||||||
|
|
||||||
match res {
|
match res {
|
||||||
Ok(log) => {
|
Ok(log) => {
|
||||||
@@ -96,7 +96,7 @@ pub unsafe extern "C" fn syslog_relay_next(
|
|||||||
null_mut()
|
null_mut()
|
||||||
}
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
log::error!("Failed to convert log message to C string");
|
tracing::error!("Failed to convert log message to C string");
|
||||||
ffi_err!(IdeviceError::FfiInvalidString)
|
ffi_err!(IdeviceError::FfiInvalidString)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ use tokio::{
|
|||||||
net::tcp::{OwnedReadHalf, OwnedWriteHalf},
|
net::tcp::{OwnedReadHalf, OwnedWriteHalf},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{IdeviceFfiError, RUNTIME, core_device_proxy::AdapterHandle, ffi_err};
|
use crate::{IdeviceFfiError, core_device_proxy::AdapterHandle, ffi_err, run_sync, run_sync_local};
|
||||||
|
|
||||||
pub struct TcpFeedObject {
|
pub struct TcpFeedObject {
|
||||||
sender: Arc<Mutex<OwnedWriteHalf>>,
|
sender: Arc<Mutex<OwnedWriteHalf>>,
|
||||||
@@ -63,7 +63,7 @@ pub unsafe extern "C" fn idevice_tcp_stack_into_sync_objects(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let res = RUNTIME.block_on(async {
|
let res = run_sync(async {
|
||||||
let mut port = 4000;
|
let mut port = 4000;
|
||||||
loop {
|
loop {
|
||||||
if port > 4050 {
|
if port > 4050 {
|
||||||
@@ -105,7 +105,7 @@ pub unsafe extern "C" fn idevice_tcp_stack_into_sync_objects(
|
|||||||
let eat_object = TcpEatObject { receiver: r };
|
let eat_object = TcpEatObject { receiver: r };
|
||||||
|
|
||||||
// we must be inside the runtime for the inner function to spawn threads
|
// we must be inside the runtime for the inner function to spawn threads
|
||||||
let new_adapter = RUNTIME.block_on(async {
|
let new_adapter = run_sync_local(async {
|
||||||
idevice::tcp::adapter::Adapter::new(Box::new(stream), our_ip, their_ip).to_async_handle()
|
idevice::tcp::adapter::Adapter::new(Box::new(stream), our_ip, their_ip).to_async_handle()
|
||||||
});
|
});
|
||||||
// this object can now be used with the rest of the idevice FFI library
|
// this object can now be used with the rest of the idevice FFI library
|
||||||
@@ -133,7 +133,7 @@ pub unsafe extern "C" fn idevice_tcp_feed_object_write(
|
|||||||
}
|
}
|
||||||
let object = unsafe { &mut *object };
|
let object = unsafe { &mut *object };
|
||||||
let data = unsafe { std::slice::from_raw_parts(data, len) };
|
let data = unsafe { std::slice::from_raw_parts(data, len) };
|
||||||
RUNTIME.block_on(async move {
|
run_sync_local(async move {
|
||||||
let mut lock = object.sender.lock().await;
|
let mut lock = object.sender.lock().await;
|
||||||
match lock.write_all(data).await {
|
match lock.write_all(data).await {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
@@ -163,7 +163,7 @@ pub unsafe extern "C" fn idevice_tcp_eat_object_read(
|
|||||||
) -> *mut IdeviceFfiError {
|
) -> *mut IdeviceFfiError {
|
||||||
let object = unsafe { &mut *object };
|
let object = unsafe { &mut *object };
|
||||||
let mut buf = [0; 2048];
|
let mut buf = [0; 2048];
|
||||||
RUNTIME.block_on(async {
|
run_sync_local(async {
|
||||||
let lock = object.receiver.lock().await;
|
let lock = object.receiver.lock().await;
|
||||||
match lock.try_read(&mut buf) {
|
match lock.try_read(&mut buf) {
|
||||||
Ok(size) => {
|
Ok(size) => {
|
||||||
|
|||||||
@@ -2,22 +2,27 @@
|
|||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
ffi::{CStr, CString, c_char},
|
ffi::{CStr, CString, c_char},
|
||||||
|
pin::Pin,
|
||||||
ptr::null_mut,
|
ptr::null_mut,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
IdeviceFfiError, IdeviceHandle, IdevicePairingFile, RUNTIME, ffi_err,
|
IdeviceFfiError, IdeviceHandle, IdevicePairingFile, ffi_err, run_sync, run_sync_local,
|
||||||
util::{SockAddr, c_socket_to_rust, idevice_sockaddr, idevice_socklen_t},
|
util::{SockAddr, c_socket_to_rust, idevice_sockaddr, idevice_socklen_t},
|
||||||
};
|
};
|
||||||
|
use futures::{Stream, StreamExt};
|
||||||
use idevice::{
|
use idevice::{
|
||||||
IdeviceError,
|
IdeviceError,
|
||||||
usbmuxd::{UsbmuxdAddr, UsbmuxdConnection, UsbmuxdDevice},
|
usbmuxd::{UsbmuxdAddr, UsbmuxdConnection, UsbmuxdDevice, UsbmuxdListenEvent},
|
||||||
};
|
};
|
||||||
use log::error;
|
use tracing::error;
|
||||||
|
|
||||||
pub struct UsbmuxdConnectionHandle(pub UsbmuxdConnection);
|
pub struct UsbmuxdConnectionHandle(pub UsbmuxdConnection);
|
||||||
pub struct UsbmuxdAddrHandle(pub UsbmuxdAddr);
|
pub struct UsbmuxdAddrHandle(pub UsbmuxdAddr);
|
||||||
pub struct UsbmuxdDeviceHandle(pub UsbmuxdDevice);
|
pub struct UsbmuxdDeviceHandle(pub UsbmuxdDevice);
|
||||||
|
pub struct UsbmuxdListenerHandle<'a>(
|
||||||
|
Pin<Box<dyn Stream<Item = Result<UsbmuxdListenEvent, IdeviceError>> + 'a>>,
|
||||||
|
);
|
||||||
|
|
||||||
/// Connects to a usbmuxd instance over TCP
|
/// Connects to a usbmuxd instance over TCP
|
||||||
///
|
///
|
||||||
@@ -52,7 +57,7 @@ pub unsafe extern "C" fn idevice_usbmuxd_new_tcp_connection(
|
|||||||
Err(e) => return ffi_err!(e),
|
Err(e) => return ffi_err!(e),
|
||||||
};
|
};
|
||||||
|
|
||||||
let res = RUNTIME.block_on(async move {
|
let res = run_sync(async move {
|
||||||
let stream = tokio::net::TcpStream::connect(addr).await?;
|
let stream = tokio::net::TcpStream::connect(addr).await?;
|
||||||
Ok::<_, IdeviceError>(UsbmuxdConnection::new(Box::new(stream), tag))
|
Ok::<_, IdeviceError>(UsbmuxdConnection::new(Box::new(stream), tag))
|
||||||
});
|
});
|
||||||
@@ -91,7 +96,7 @@ pub unsafe extern "C" fn idevice_usbmuxd_new_unix_socket_connection(
|
|||||||
Err(_) => return ffi_err!(IdeviceError::FfiInvalidArg),
|
Err(_) => return ffi_err!(IdeviceError::FfiInvalidArg),
|
||||||
};
|
};
|
||||||
|
|
||||||
let res: Result<UsbmuxdConnection, IdeviceError> = RUNTIME.block_on(async move {
|
let res: Result<UsbmuxdConnection, IdeviceError> = run_sync(async move {
|
||||||
let stream = tokio::net::UnixStream::connect(addr).await?;
|
let stream = tokio::net::UnixStream::connect(addr).await?;
|
||||||
Ok(UsbmuxdConnection::new(Box::new(stream), tag))
|
Ok(UsbmuxdConnection::new(Box::new(stream), tag))
|
||||||
});
|
});
|
||||||
@@ -127,13 +132,13 @@ pub unsafe extern "C" fn idevice_usbmuxd_new_default_connection(
|
|||||||
let addr = match UsbmuxdAddr::from_env_var() {
|
let addr = match UsbmuxdAddr::from_env_var() {
|
||||||
Ok(a) => a,
|
Ok(a) => a,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::error!("Invalid address set: {e:?}");
|
tracing::error!("Invalid address set: {e:?}");
|
||||||
return ffi_err!(IdeviceError::FfiInvalidArg);
|
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let res: Result<UsbmuxdConnection, IdeviceError> =
|
let res: Result<UsbmuxdConnection, IdeviceError> =
|
||||||
RUNTIME.block_on(async move { addr.connect(tag).await });
|
run_sync(async move { addr.connect(tag).await });
|
||||||
|
|
||||||
match res {
|
match res {
|
||||||
Ok(r) => {
|
Ok(r) => {
|
||||||
@@ -171,7 +176,7 @@ pub unsafe extern "C" fn idevice_usbmuxd_get_devices(
|
|||||||
}
|
}
|
||||||
let conn = unsafe { &mut (*usbmuxd_conn).0 };
|
let conn = unsafe { &mut (*usbmuxd_conn).0 };
|
||||||
|
|
||||||
let res = RUNTIME.block_on(async { conn.get_devices().await });
|
let res = run_sync(async { conn.get_devices().await });
|
||||||
|
|
||||||
match res {
|
match res {
|
||||||
Ok(device_vec) => {
|
Ok(device_vec) => {
|
||||||
@@ -239,7 +244,7 @@ pub unsafe extern "C" fn idevice_usbmuxd_connect_to_device(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let res = RUNTIME.block_on(async move { conn.connect_to_device(device_id, port, label).await });
|
let res = run_sync(async move { conn.connect_to_device(device_id, port, label).await });
|
||||||
|
|
||||||
match res {
|
match res {
|
||||||
Ok(device_conn) => {
|
Ok(device_conn) => {
|
||||||
@@ -287,7 +292,7 @@ pub unsafe extern "C" fn idevice_usbmuxd_get_pair_record(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let res = RUNTIME.block_on(async { conn.get_pair_record(udid_str).await });
|
let res = run_sync(async { conn.get_pair_record(udid_str).await });
|
||||||
|
|
||||||
match res {
|
match res {
|
||||||
Ok(pf) => {
|
Ok(pf) => {
|
||||||
@@ -301,6 +306,149 @@ pub unsafe extern "C" fn idevice_usbmuxd_get_pair_record(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Saves the pairing record for a given device UDID.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `usbmuxd_conn` - A valid connection to usbmuxd.
|
||||||
|
/// * `device_id` - The muxer ID for the device
|
||||||
|
/// * `udid` - The UDID of the device.
|
||||||
|
/// * `pair_record` - The bytes of the pairing record plist to save
|
||||||
|
/// * `pair_record_len` - the length of the pairing record bytes
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// An `IdeviceFfiError` on error, `null` on success.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// * `usbmuxd_conn` must be a valid pointer.
|
||||||
|
/// * `udid` must be a valid, null-terminated C string.
|
||||||
|
/// * `pair_record` must be a valid, non-null pointer.
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub unsafe extern "C" fn idevice_usbmuxd_save_pair_record(
|
||||||
|
usbmuxd_conn: *mut UsbmuxdConnectionHandle,
|
||||||
|
udid: *const c_char,
|
||||||
|
pair_record: *mut u8,
|
||||||
|
pair_record_len: usize,
|
||||||
|
) -> *mut IdeviceFfiError {
|
||||||
|
if usbmuxd_conn.is_null() || pair_record.is_null() {
|
||||||
|
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||||
|
}
|
||||||
|
let conn = unsafe { &mut (*usbmuxd_conn).0 };
|
||||||
|
let pair_record =
|
||||||
|
unsafe { std::slice::from_raw_parts_mut(pair_record, pair_record_len) }.to_vec();
|
||||||
|
|
||||||
|
let udid_str = unsafe {
|
||||||
|
match CStr::from_ptr(udid).to_str() {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(_) => return ffi_err!(IdeviceError::FfiInvalidArg),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let res = run_sync_local(async { conn.save_pair_record(udid_str, pair_record).await });
|
||||||
|
|
||||||
|
match res {
|
||||||
|
Ok(_) => null_mut(),
|
||||||
|
Err(e) => ffi_err!(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Listens on the socket for connections and disconnections
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// Pass valid pointers. Free the stream with ``idevice_usbmuxd_listener_handle_free``.
|
||||||
|
/// The stream must outlive the usbmuxd connection, and the usbmuxd connection cannot
|
||||||
|
/// be used for other requests.
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub unsafe extern "C" fn idevice_usbmuxd_listen(
|
||||||
|
usbmuxd_conn: *mut UsbmuxdConnectionHandle,
|
||||||
|
stream_handle: *mut *mut UsbmuxdListenerHandle,
|
||||||
|
) -> *mut IdeviceFfiError {
|
||||||
|
if usbmuxd_conn.is_null() || stream_handle.is_null() {
|
||||||
|
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||||
|
}
|
||||||
|
let conn = unsafe { &mut (*usbmuxd_conn).0 };
|
||||||
|
|
||||||
|
let res = run_sync_local(async { conn.listen().await });
|
||||||
|
|
||||||
|
match res {
|
||||||
|
Ok(s) => {
|
||||||
|
unsafe { *stream_handle = Box::into_raw(Box::new(UsbmuxdListenerHandle(s))) };
|
||||||
|
null_mut()
|
||||||
|
}
|
||||||
|
Err(e) => ffi_err!(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Frees a stream created by ``listen`` or does nothing on null
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// Pass a valid pointer.
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub unsafe extern "C" fn idevice_usbmuxd_listener_handle_free(
|
||||||
|
stream_handle: *mut UsbmuxdListenerHandle,
|
||||||
|
) {
|
||||||
|
if stream_handle.is_null() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let _ = unsafe { Box::from_raw(stream_handle) };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the next event from the stream.
|
||||||
|
/// Connect will be set to true if the event is a connection event,
|
||||||
|
/// and the connection_device will be filled with the device information.
|
||||||
|
/// If connection is false, the mux ID of the device will be filled.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `stream_handle` - The handle to the stream returned by listen
|
||||||
|
/// * `connect` - The bool that will be set
|
||||||
|
/// * `connection_device` - The pointer that will be filled on a connect event
|
||||||
|
/// * `disconnection_id` - The mux ID that will be set on a disconnect event
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// Pass valid pointers
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub unsafe extern "C" fn idevice_usbmuxd_listener_next(
|
||||||
|
stream_handle: *mut UsbmuxdListenerHandle,
|
||||||
|
connect: *mut bool,
|
||||||
|
connection_device: *mut *mut UsbmuxdDeviceHandle,
|
||||||
|
disconnection_id: *mut u32,
|
||||||
|
) -> *mut IdeviceFfiError {
|
||||||
|
if stream_handle.is_null()
|
||||||
|
|| connect.is_null()
|
||||||
|
|| connection_device.is_null()
|
||||||
|
|| disconnection_id.is_null()
|
||||||
|
{
|
||||||
|
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||||
|
}
|
||||||
|
let stream = unsafe { &mut (*stream_handle).0 };
|
||||||
|
|
||||||
|
let res = run_sync_local(async { stream.next().await });
|
||||||
|
|
||||||
|
match res {
|
||||||
|
Some(res) => match res {
|
||||||
|
Ok(s) => {
|
||||||
|
match s {
|
||||||
|
UsbmuxdListenEvent::Connected(usbmuxd_device) => {
|
||||||
|
unsafe { *connect = true };
|
||||||
|
unsafe {
|
||||||
|
*connection_device =
|
||||||
|
Box::into_raw(Box::new(UsbmuxdDeviceHandle(usbmuxd_device)))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
UsbmuxdListenEvent::Disconnected(id) => unsafe { *disconnection_id = id },
|
||||||
|
}
|
||||||
|
null_mut()
|
||||||
|
}
|
||||||
|
Err(e) => ffi_err!(e),
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
ffi_err!(IdeviceError::Socket(std::io::Error::new(
|
||||||
|
std::io::ErrorKind::BrokenPipe,
|
||||||
|
"end of stream"
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Reads the BUID (Boot-Unique ID) from usbmuxd.
|
/// Reads the BUID (Boot-Unique ID) from usbmuxd.
|
||||||
///
|
///
|
||||||
/// The returned string must be freed with `idevice_string_free`.
|
/// The returned string must be freed with `idevice_string_free`.
|
||||||
@@ -325,7 +473,7 @@ pub unsafe extern "C" fn idevice_usbmuxd_get_buid(
|
|||||||
}
|
}
|
||||||
let conn = unsafe { &mut (*usbmuxd_conn).0 };
|
let conn = unsafe { &mut (*usbmuxd_conn).0 };
|
||||||
|
|
||||||
let res = RUNTIME.block_on(async { conn.get_buid().await });
|
let res = run_sync(async { conn.get_buid().await });
|
||||||
|
|
||||||
match res {
|
match res {
|
||||||
Ok(buid_str) => match CString::new(buid_str) {
|
Ok(buid_str) => match CString::new(buid_str) {
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ pub(crate) fn c_socket_to_rust(
|
|||||||
addr_len: SockLen,
|
addr_len: SockLen,
|
||||||
) -> Result<SocketAddr, IdeviceError> {
|
) -> Result<SocketAddr, IdeviceError> {
|
||||||
if addr.is_null() {
|
if addr.is_null() {
|
||||||
log::error!("null sockaddr");
|
tracing::error!("null sockaddr");
|
||||||
return invalid_arg();
|
return invalid_arg();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,7 +59,7 @@ pub(crate) fn c_socket_to_rust(
|
|||||||
match family as i32 {
|
match family as i32 {
|
||||||
libc::AF_INET => {
|
libc::AF_INET => {
|
||||||
if (addr_len as usize) < std::mem::size_of::<sockaddr_in>() {
|
if (addr_len as usize) < std::mem::size_of::<sockaddr_in>() {
|
||||||
log::error!("Invalid sockaddr_in size");
|
tracing::error!("Invalid sockaddr_in size");
|
||||||
return invalid_arg();
|
return invalid_arg();
|
||||||
}
|
}
|
||||||
let a = &*(addr as *const sockaddr_in);
|
let a = &*(addr as *const sockaddr_in);
|
||||||
@@ -69,7 +69,7 @@ pub(crate) fn c_socket_to_rust(
|
|||||||
}
|
}
|
||||||
libc::AF_INET6 => {
|
libc::AF_INET6 => {
|
||||||
if (addr_len as usize) < std::mem::size_of::<sockaddr_in6>() {
|
if (addr_len as usize) < std::mem::size_of::<sockaddr_in6>() {
|
||||||
log::error!("Invalid sockaddr_in6 size");
|
tracing::error!("Invalid sockaddr_in6 size");
|
||||||
return invalid_arg();
|
return invalid_arg();
|
||||||
}
|
}
|
||||||
let a = &*(addr as *const sockaddr_in6);
|
let a = &*(addr as *const sockaddr_in6);
|
||||||
@@ -83,7 +83,7 @@ pub(crate) fn c_socket_to_rust(
|
|||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
log::error!(
|
tracing::error!(
|
||||||
"Unsupported socket address family: {}",
|
"Unsupported socket address family: {}",
|
||||||
(*addr).sa_family as i32
|
(*addr).sa_family as i32
|
||||||
);
|
);
|
||||||
@@ -95,7 +95,7 @@ pub(crate) fn c_socket_to_rust(
|
|||||||
match family {
|
match family {
|
||||||
AF_INET => {
|
AF_INET => {
|
||||||
if (addr_len as usize) < std::mem::size_of::<sockaddr_in>() {
|
if (addr_len as usize) < std::mem::size_of::<sockaddr_in>() {
|
||||||
log::error!("Invalid SOCKADDR_IN size");
|
tracing::error!("Invalid SOCKADDR_IN size");
|
||||||
return invalid_arg();
|
return invalid_arg();
|
||||||
}
|
}
|
||||||
let a = &*(addr as *const sockaddr_in);
|
let a = &*(addr as *const sockaddr_in);
|
||||||
@@ -107,7 +107,7 @@ pub(crate) fn c_socket_to_rust(
|
|||||||
}
|
}
|
||||||
AF_INET6 => {
|
AF_INET6 => {
|
||||||
if (addr_len as usize) < std::mem::size_of::<sockaddr_in6>() {
|
if (addr_len as usize) < std::mem::size_of::<sockaddr_in6>() {
|
||||||
log::error!("Invalid SOCKADDR_IN6 size");
|
tracing::error!("Invalid SOCKADDR_IN6 size");
|
||||||
return invalid_arg();
|
return invalid_arg();
|
||||||
}
|
}
|
||||||
let a = &*(addr as *const sockaddr_in6);
|
let a = &*(addr as *const sockaddr_in6);
|
||||||
@@ -124,7 +124,7 @@ pub(crate) fn c_socket_to_rust(
|
|||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
log::error!("Unsupported socket address family: {}", (*addr).sa_family);
|
tracing::error!("Unsupported socket address family: {}", (*addr).sa_family);
|
||||||
invalid_arg()
|
invalid_arg()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -133,7 +133,7 @@ pub(crate) fn c_socket_to_rust(
|
|||||||
|
|
||||||
pub(crate) fn c_addr_to_rust(addr: *const SockAddr) -> Result<IpAddr, IdeviceError> {
|
pub(crate) fn c_addr_to_rust(addr: *const SockAddr) -> Result<IpAddr, IdeviceError> {
|
||||||
if addr.is_null() {
|
if addr.is_null() {
|
||||||
log::error!("null sockaddr");
|
tracing::error!("null sockaddr");
|
||||||
return invalid_arg();
|
return invalid_arg();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,7 +157,7 @@ pub(crate) fn c_addr_to_rust(addr: *const SockAddr) -> Result<IpAddr, IdeviceErr
|
|||||||
Ok(IpAddr::V6(Ipv6Addr::from(a.sin6_addr.s6_addr)))
|
Ok(IpAddr::V6(Ipv6Addr::from(a.sin6_addr.s6_addr)))
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
log::error!(
|
tracing::error!(
|
||||||
"Unsupported socket address family: {}",
|
"Unsupported socket address family: {}",
|
||||||
(*addr).sa_family as i32
|
(*addr).sa_family as i32
|
||||||
);
|
);
|
||||||
@@ -178,7 +178,7 @@ pub(crate) fn c_addr_to_rust(addr: *const SockAddr) -> Result<IpAddr, IdeviceErr
|
|||||||
Ok(IpAddr::V6(Ipv6Addr::from(bytes)))
|
Ok(IpAddr::V6(Ipv6Addr::from(bytes)))
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
log::error!("Unsupported socket address family: {}", (*addr).sa_family);
|
tracing::error!("Unsupported socket address family: {}", (*addr).sa_family);
|
||||||
invalid_arg()
|
invalid_arg()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
name = "idevice"
|
name = "idevice"
|
||||||
description = "A Rust library to interact with services on iOS devices."
|
description = "A Rust library to interact with services on iOS devices."
|
||||||
authors = ["Jackson Coxson"]
|
authors = ["Jackson Coxson"]
|
||||||
version = "0.1.42"
|
version = "0.1.53"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
documentation = "https://docs.rs/idevice"
|
documentation = "https://docs.rs/idevice"
|
||||||
@@ -11,26 +11,28 @@ keywords = ["lockdownd", "ios"]
|
|||||||
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tokio = { version = "1.43", features = ["io-util"] }
|
tokio = { version = "1", features = ["io-util"] }
|
||||||
tokio-rustls = { version = "0.26", default-features = false }
|
tokio-rustls = { version = "0.26", default-features = false, optional = true }
|
||||||
rustls = { version = "0.23", default-features = false, features = [
|
rustls = { version = "0.23", default-features = false, features = [
|
||||||
"std",
|
"std",
|
||||||
"tls12",
|
"tls12",
|
||||||
] }
|
], optional = true }
|
||||||
crossfire = { version = "2.0", optional = true } # TODO: update to 2.1 when it comes out
|
tokio-openssl = { version = "0.6", optional = true }
|
||||||
|
openssl = { version = "0.10", optional = true }
|
||||||
|
|
||||||
plist = { version = "1.7" }
|
plist = { version = "1.8" }
|
||||||
|
plist-macro = { version = "0.1.3" }
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
ns-keyed-archive = { version = "0.1.3", optional = true }
|
ns-keyed-archive = { version = "0.1.4", optional = true }
|
||||||
|
crossfire = { version = "2.1", optional = true }
|
||||||
|
|
||||||
thiserror = { version = "2" }
|
thiserror = { version = "2" }
|
||||||
log = { version = "0.4" }
|
tracing = { version = "0.1.41" }
|
||||||
env_logger = { version = "0.11" }
|
|
||||||
base64 = { version = "0.22" }
|
base64 = { version = "0.22" }
|
||||||
|
|
||||||
indexmap = { version = "2.7", features = ["serde"], optional = true }
|
indexmap = { version = "2.11", features = ["serde"], optional = true }
|
||||||
uuid = { version = "1.12", features = ["serde", "v4"], optional = true }
|
uuid = { version = "1.18", features = ["serde", "v4"], optional = true }
|
||||||
chrono = { version = "0.4.40", optional = true, default-features = false, features = [
|
chrono = { version = "0.4", optional = true, default-features = false, features = [
|
||||||
"serde",
|
"serde",
|
||||||
] }
|
] }
|
||||||
|
|
||||||
@@ -39,7 +41,7 @@ json = { version = "0.12", optional = true }
|
|||||||
byteorder = { version = "1.5", optional = true }
|
byteorder = { version = "1.5", optional = true }
|
||||||
bytes = { version = "1.10", optional = true }
|
bytes = { version = "1.10", optional = true }
|
||||||
|
|
||||||
reqwest = { version = "0.12", features = [
|
reqwest = { version = "0.13", features = [
|
||||||
"json",
|
"json",
|
||||||
], optional = true, default-features = false }
|
], optional = true, default-features = false }
|
||||||
rand = { version = "0.9", optional = true }
|
rand = { version = "0.9", optional = true }
|
||||||
@@ -65,14 +67,16 @@ bytes = "1.10.1"
|
|||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["aws-lc"]
|
default = ["aws-lc"]
|
||||||
aws-lc = ["rustls/aws-lc-rs", "tokio-rustls/aws-lc-rs"]
|
aws-lc = ["rustls", "rustls/aws-lc-rs", "tokio-rustls/aws-lc-rs"]
|
||||||
ring = ["rustls/ring", "tokio-rustls/ring"]
|
ring = ["rustls", "rustls/ring", "tokio-rustls/ring"]
|
||||||
|
rustls = ["dep:rustls", "dep:tokio-rustls"]
|
||||||
|
openssl = ["dep:openssl", "dep:tokio-openssl"]
|
||||||
|
|
||||||
afc = ["dep:chrono"]
|
afc = ["dep:chrono", "dep:futures"]
|
||||||
amfi = []
|
amfi = []
|
||||||
bt_packet_logger = []
|
bt_packet_logger = []
|
||||||
companion_proxy = []
|
companion_proxy = []
|
||||||
core_device = ["xpc", "dep:uuid"]
|
core_device = ["xpc", "dep:uuid", "dep:ns-keyed-archive"]
|
||||||
core_device_proxy = ["dep:serde_json", "dep:json", "dep:byteorder"]
|
core_device_proxy = ["dep:serde_json", "dep:json", "dep:byteorder"]
|
||||||
crashreportcopymobile = ["afc"]
|
crashreportcopymobile = ["afc"]
|
||||||
debug_proxy = []
|
debug_proxy = []
|
||||||
@@ -87,11 +91,13 @@ installation_proxy = [
|
|||||||
"async_zip/deflate",
|
"async_zip/deflate",
|
||||||
"tokio/fs",
|
"tokio/fs",
|
||||||
]
|
]
|
||||||
|
installcoordination_proxy = []
|
||||||
springboardservices = []
|
springboardservices = []
|
||||||
misagent = []
|
misagent = []
|
||||||
mobile_image_mounter = ["dep:sha2"]
|
mobile_image_mounter = ["dep:sha2"]
|
||||||
mobileactivationd = ["dep:reqwest"]
|
mobileactivationd = ["dep:reqwest"]
|
||||||
mobilebackup2 = []
|
mobilebackup2 = []
|
||||||
|
notification_proxy = ["tokio/macros", "tokio/time", "dep:async-stream", "dep:futures"]
|
||||||
location_simulation = []
|
location_simulation = []
|
||||||
pair = ["chrono/default", "tokio/time", "dep:sha2", "dep:rsa", "dep:x509-cert"]
|
pair = ["chrono/default", "tokio/time", "dep:sha2", "dep:rsa", "dep:x509-cert"]
|
||||||
pcapd = []
|
pcapd = []
|
||||||
@@ -111,7 +117,7 @@ tunnel_tcp_stack = [
|
|||||||
]
|
]
|
||||||
tss = ["dep:uuid", "dep:reqwest"]
|
tss = ["dep:uuid", "dep:reqwest"]
|
||||||
tunneld = ["dep:serde_json", "dep:json", "dep:reqwest"]
|
tunneld = ["dep:serde_json", "dep:json", "dep:reqwest"]
|
||||||
usbmuxd = ["tokio/net"]
|
usbmuxd = ["tokio/net", "dep:futures"]
|
||||||
xpc = ["dep:indexmap", "dep:uuid", "dep:async-stream"]
|
xpc = ["dep:indexmap", "dep:uuid", "dep:async-stream"]
|
||||||
full = [
|
full = [
|
||||||
"afc",
|
"afc",
|
||||||
@@ -127,11 +133,13 @@ full = [
|
|||||||
"heartbeat",
|
"heartbeat",
|
||||||
"house_arrest",
|
"house_arrest",
|
||||||
"installation_proxy",
|
"installation_proxy",
|
||||||
|
"installcoordination_proxy",
|
||||||
"location_simulation",
|
"location_simulation",
|
||||||
"misagent",
|
"misagent",
|
||||||
"mobile_image_mounter",
|
"mobile_image_mounter",
|
||||||
"mobileactivationd",
|
"mobileactivationd",
|
||||||
"mobilebackup2",
|
"mobilebackup2",
|
||||||
|
"notification_proxy",
|
||||||
"pair",
|
"pair",
|
||||||
"pcapd",
|
"pcapd",
|
||||||
"preboard_service",
|
"preboard_service",
|
||||||
|
|||||||
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")]
|
#![doc = include_str!("../README.md")]
|
||||||
|
#![warn(missing_debug_implementations)]
|
||||||
|
#![warn(missing_copy_implementations)]
|
||||||
// Jackson Coxson
|
// Jackson Coxson
|
||||||
|
|
||||||
#[cfg(feature = "pair")]
|
#[cfg(all(feature = "pair", feature = "rustls"))]
|
||||||
mod ca;
|
mod ca;
|
||||||
|
pub mod cursor;
|
||||||
|
mod obfuscation;
|
||||||
pub mod pairing_file;
|
pub mod pairing_file;
|
||||||
mod plist_macro;
|
|
||||||
pub mod provider;
|
pub mod provider;
|
||||||
|
#[cfg(feature = "rustls")]
|
||||||
mod sni;
|
mod sni;
|
||||||
#[cfg(feature = "tunnel_tcp_stack")]
|
#[cfg(feature = "tunnel_tcp_stack")]
|
||||||
pub mod tcp;
|
pub mod tcp;
|
||||||
@@ -15,7 +19,6 @@ pub mod tss;
|
|||||||
pub mod tunneld;
|
pub mod tunneld;
|
||||||
#[cfg(feature = "usbmuxd")]
|
#[cfg(feature = "usbmuxd")]
|
||||||
pub mod usbmuxd;
|
pub mod usbmuxd;
|
||||||
mod util;
|
|
||||||
pub mod utils;
|
pub mod utils;
|
||||||
#[cfg(feature = "xpc")]
|
#[cfg(feature = "xpc")]
|
||||||
pub mod xpc;
|
pub mod xpc;
|
||||||
@@ -26,8 +29,9 @@ pub use services::*;
|
|||||||
#[cfg(feature = "xpc")]
|
#[cfg(feature = "xpc")]
|
||||||
pub use xpc::RemoteXpcClient;
|
pub use xpc::RemoteXpcClient;
|
||||||
|
|
||||||
use log::{debug, error, trace};
|
use plist_macro::{plist, pretty_print_dictionary, pretty_print_plist};
|
||||||
use provider::{IdeviceProvider, RsdProvider};
|
use provider::{IdeviceProvider, RsdProvider};
|
||||||
|
#[cfg(feature = "rustls")]
|
||||||
use rustls::{crypto::CryptoProvider, pki_types::ServerName};
|
use rustls::{crypto::CryptoProvider, pki_types::ServerName};
|
||||||
use std::{
|
use std::{
|
||||||
io::{self, BufWriter},
|
io::{self, BufWriter},
|
||||||
@@ -35,8 +39,7 @@ use std::{
|
|||||||
};
|
};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
|
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
|
||||||
|
use tracing::{debug, trace};
|
||||||
pub use util::{pretty_print_dictionary, pretty_print_plist};
|
|
||||||
|
|
||||||
use crate::services::lockdown::LockdownClient;
|
use crate::services::lockdown::LockdownClient;
|
||||||
|
|
||||||
@@ -72,6 +75,22 @@ pub trait IdeviceService: Sized {
|
|||||||
#[allow(async_fn_in_trait)]
|
#[allow(async_fn_in_trait)]
|
||||||
async fn connect(provider: &dyn IdeviceProvider) -> Result<Self, IdeviceError> {
|
async fn connect(provider: &dyn IdeviceProvider) -> Result<Self, IdeviceError> {
|
||||||
let mut lockdown = LockdownClient::connect(provider).await?;
|
let mut lockdown = LockdownClient::connect(provider).await?;
|
||||||
|
|
||||||
|
#[cfg(feature = "openssl")]
|
||||||
|
let legacy = lockdown
|
||||||
|
.get_value(Some("ProductVersion"), None)
|
||||||
|
.await
|
||||||
|
.ok()
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|x| x.as_string())
|
||||||
|
.and_then(|x| x.split(".").next())
|
||||||
|
.and_then(|x| x.parse::<u8>().ok())
|
||||||
|
.map(|x| x < 5)
|
||||||
|
.unwrap_or(false);
|
||||||
|
|
||||||
|
#[cfg(not(feature = "openssl"))]
|
||||||
|
let legacy = false;
|
||||||
|
|
||||||
lockdown
|
lockdown
|
||||||
.start_session(&provider.get_pairing_file().await?)
|
.start_session(&provider.get_pairing_file().await?)
|
||||||
.await?;
|
.await?;
|
||||||
@@ -86,7 +105,7 @@ pub trait IdeviceService: Sized {
|
|||||||
let mut idevice = provider.connect(port).await?;
|
let mut idevice = provider.connect(port).await?;
|
||||||
if ssl {
|
if ssl {
|
||||||
idevice
|
idevice
|
||||||
.start_session(&provider.get_pairing_file().await?)
|
.start_session(&provider.get_pairing_file().await?, legacy)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,6 +147,7 @@ pub type IdeviceSocket = Box<dyn ReadWrite>;
|
|||||||
///
|
///
|
||||||
/// Manages the connection socket and provides methods for common device operations
|
/// Manages the connection socket and provides methods for common device operations
|
||||||
/// and message exchange.
|
/// and message exchange.
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct Idevice {
|
pub struct Idevice {
|
||||||
/// The underlying connection socket, boxed for dynamic dispatch
|
/// The underlying connection socket, boxed for dynamic dispatch
|
||||||
socket: Option<Box<dyn ReadWrite>>,
|
socket: Option<Box<dyn ReadWrite>>,
|
||||||
@@ -175,7 +195,7 @@ impl Idevice {
|
|||||||
/// # Errors
|
/// # Errors
|
||||||
/// Returns `IdeviceError` if communication fails or response is invalid
|
/// Returns `IdeviceError` if communication fails or response is invalid
|
||||||
pub async fn get_type(&mut self) -> Result<String, IdeviceError> {
|
pub async fn get_type(&mut self) -> Result<String, IdeviceError> {
|
||||||
let req = crate::plist!({
|
let req = plist!({
|
||||||
"Label": self.label.clone(),
|
"Label": self.label.clone(),
|
||||||
"Request": "QueryType",
|
"Request": "QueryType",
|
||||||
});
|
});
|
||||||
@@ -195,7 +215,7 @@ impl Idevice {
|
|||||||
/// # Errors
|
/// # Errors
|
||||||
/// Returns `IdeviceError` if the protocol sequence isn't followed correctly
|
/// Returns `IdeviceError` if the protocol sequence isn't followed correctly
|
||||||
pub async fn rsd_checkin(&mut self) -> Result<(), IdeviceError> {
|
pub async fn rsd_checkin(&mut self) -> Result<(), IdeviceError> {
|
||||||
let req = crate::plist!({
|
let req = plist!({
|
||||||
"Label": self.label.clone(),
|
"Label": self.label.clone(),
|
||||||
"ProtocolVersion": "2",
|
"ProtocolVersion": "2",
|
||||||
"Request": "RSDCheckin",
|
"Request": "RSDCheckin",
|
||||||
@@ -287,6 +307,75 @@ impl Idevice {
|
|||||||
self.send_raw_with_progress(message, |_| async {}, ()).await
|
self.send_raw_with_progress(message, |_| async {}, ()).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sends raw binary data via vectored I/O
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `bufs` - The buffers to send
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// Returns `IdeviceError` if transmission fails
|
||||||
|
pub async fn send_raw_vectored(
|
||||||
|
&mut self,
|
||||||
|
bufs: &[std::io::IoSlice<'_>],
|
||||||
|
) -> Result<(), IdeviceError> {
|
||||||
|
if let Some(socket) = &mut self.socket {
|
||||||
|
let mut curr_idx = 0;
|
||||||
|
let mut curr_offset = 0;
|
||||||
|
|
||||||
|
while curr_idx < bufs.len() {
|
||||||
|
let mut iovec = Vec::new();
|
||||||
|
let mut accumulated_len = 0;
|
||||||
|
let max_chunk = 1024 * 64;
|
||||||
|
|
||||||
|
// Add partial first slice
|
||||||
|
let first_avail = bufs[curr_idx].len() - curr_offset;
|
||||||
|
let to_take_first = std::cmp::min(first_avail, max_chunk);
|
||||||
|
iovec.push(std::io::IoSlice::new(
|
||||||
|
&bufs[curr_idx][curr_offset..curr_offset + to_take_first],
|
||||||
|
));
|
||||||
|
accumulated_len += to_take_first;
|
||||||
|
|
||||||
|
// Add others up to max_chunk
|
||||||
|
let mut temp_idx = curr_idx + 1;
|
||||||
|
while temp_idx < bufs.len() && accumulated_len < max_chunk {
|
||||||
|
let needed = max_chunk - accumulated_len;
|
||||||
|
let avail = bufs[temp_idx].len();
|
||||||
|
let take = std::cmp::min(avail, needed);
|
||||||
|
iovec.push(std::io::IoSlice::new(&bufs[temp_idx][..take]));
|
||||||
|
accumulated_len += take;
|
||||||
|
temp_idx += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
let n = socket.write_vectored(&iovec).await?;
|
||||||
|
if n == 0 {
|
||||||
|
return Err(io::Error::new(
|
||||||
|
io::ErrorKind::WriteZero,
|
||||||
|
"failed to write whole buffer",
|
||||||
|
)
|
||||||
|
.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Advance cursor by n
|
||||||
|
let mut advanced = n;
|
||||||
|
while advanced > 0 && curr_idx < bufs.len() {
|
||||||
|
let available = bufs[curr_idx].len() - curr_offset;
|
||||||
|
if advanced < available {
|
||||||
|
curr_offset += advanced;
|
||||||
|
advanced = 0;
|
||||||
|
} else {
|
||||||
|
advanced -= available;
|
||||||
|
curr_idx += 1;
|
||||||
|
curr_offset = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
socket.flush().await?;
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(IdeviceError::NoEstablishedConnection)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Sends raw binary data with progress callbacks
|
/// Sends raw binary data with progress callbacks
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
@@ -389,14 +478,20 @@ impl Idevice {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
log::error!("Error is not a string or integer from read_plist: {e:?}");
|
tracing::error!("Error is not a string or integer from read_plist: {e:?}");
|
||||||
return Err(IdeviceError::UnexpectedResponse);
|
return Err(IdeviceError::UnexpectedResponse);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if let Some(e) = IdeviceError::from_device_error_type(e.as_str(), &res) {
|
if let Some(e) = IdeviceError::from_device_error_type(e.as_str(), &res) {
|
||||||
return Err(e);
|
return Err(e);
|
||||||
} else {
|
} else {
|
||||||
return Err(IdeviceError::UnknownErrorType(e));
|
let msg =
|
||||||
|
if let Some(desc) = res.get("ErrorDescription").and_then(|x| x.as_string()) {
|
||||||
|
format!("{} ({})", e, desc)
|
||||||
|
} else {
|
||||||
|
e
|
||||||
|
};
|
||||||
|
return Err(IdeviceError::UnknownErrorType(msg));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(res)
|
Ok(res)
|
||||||
@@ -459,58 +554,93 @@ impl Idevice {
|
|||||||
pub async fn start_session(
|
pub async fn start_session(
|
||||||
&mut self,
|
&mut self,
|
||||||
pairing_file: &pairing_file::PairingFile,
|
pairing_file: &pairing_file::PairingFile,
|
||||||
|
legacy: bool,
|
||||||
) -> Result<(), IdeviceError> {
|
) -> Result<(), IdeviceError> {
|
||||||
if CryptoProvider::get_default().is_none() {
|
#[cfg(feature = "rustls")]
|
||||||
// rust-analyzer will choke on this block, don't worry about it
|
{
|
||||||
let crypto_provider: CryptoProvider = {
|
if legacy {
|
||||||
#[cfg(all(feature = "ring", not(feature = "aws-lc")))]
|
tracing::warn!(
|
||||||
{
|
"Compiled with rustls, but connecting to legacy device! rustls does not support old SSL, this will fail."
|
||||||
debug!("Using ring crypto backend");
|
);
|
||||||
rustls::crypto::ring::default_provider()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(all(feature = "aws-lc", not(feature = "ring")))]
|
|
||||||
{
|
|
||||||
debug!("Using aws-lc crypto backend");
|
|
||||||
rustls::crypto::aws_lc_rs::default_provider()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(any(feature = "ring", feature = "aws-lc")))]
|
|
||||||
{
|
|
||||||
compile_error!(
|
|
||||||
"No crypto backend was selected! Specify an idevice feature for a crypto backend"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(all(feature = "ring", feature = "aws-lc"))]
|
|
||||||
{
|
|
||||||
// We can't throw a compile error because it breaks rust-analyzer.
|
|
||||||
// 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!");
|
|
||||||
rustls::crypto::ring::default_provider()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Err(e) = CryptoProvider::install_default(crypto_provider) {
|
|
||||||
// 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:?}");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if CryptoProvider::get_default().is_none() {
|
||||||
|
// rust-analyzer will choke on this block, don't worry about it
|
||||||
|
let crypto_provider: CryptoProvider = {
|
||||||
|
#[cfg(all(feature = "ring", not(feature = "aws-lc")))]
|
||||||
|
{
|
||||||
|
debug!("Using ring crypto backend");
|
||||||
|
rustls::crypto::ring::default_provider()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(all(feature = "aws-lc", not(feature = "ring")))]
|
||||||
|
{
|
||||||
|
debug!("Using aws-lc crypto backend");
|
||||||
|
rustls::crypto::aws_lc_rs::default_provider()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(any(feature = "ring", feature = "aws-lc")))]
|
||||||
|
{
|
||||||
|
compile_error!(
|
||||||
|
"No crypto backend was selected! Specify an idevice feature for a crypto backend"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(all(feature = "ring", feature = "aws-lc"))]
|
||||||
|
{
|
||||||
|
// We can't throw a compile error because it breaks rust-analyzer.
|
||||||
|
// My sanity while debugging the workspace crates are more important.
|
||||||
|
|
||||||
|
debug!("Using ring crypto backend, because both were passed");
|
||||||
|
tracing::warn!(
|
||||||
|
"Both ring && aws-lc are selected as idevice crypto backends!"
|
||||||
|
);
|
||||||
|
rustls::crypto::ring::default_provider()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Err(e) = CryptoProvider::install_default(crypto_provider) {
|
||||||
|
// For whatever reason, getting the default provider will return None on iOS at
|
||||||
|
// random. Installing the default provider a second time will return an error, so
|
||||||
|
// we will log it but not propogate it. An issue should be opened with rustls.
|
||||||
|
tracing::error!("Failed to set crypto provider: {e:?}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let config = sni::create_client_config(pairing_file)?;
|
||||||
|
let connector = tokio_rustls::TlsConnector::from(Arc::new(config));
|
||||||
|
|
||||||
|
let socket = self.socket.take().unwrap();
|
||||||
|
let socket = connector
|
||||||
|
.connect(ServerName::try_from("Device").unwrap(), socket)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
self.socket = Some(Box::new(socket));
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
let config = sni::create_client_config(pairing_file)?;
|
#[cfg(all(feature = "openssl", not(feature = "rustls")))]
|
||||||
let connector = tokio_rustls::TlsConnector::from(Arc::new(config));
|
{
|
||||||
|
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 socket = self.socket.take().unwrap();
|
let mut connector = connector.build().configure()?.into_ssl("ur mom")?;
|
||||||
let socket = connector
|
|
||||||
.connect(ServerName::try_from("Device").unwrap(), socket)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
self.socket = Some(Box::new(socket));
|
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(())
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -521,12 +651,24 @@ impl Idevice {
|
|||||||
pub enum IdeviceError {
|
pub enum IdeviceError {
|
||||||
#[error("device socket io failed")]
|
#[error("device socket io failed")]
|
||||||
Socket(#[from] io::Error) = -1,
|
Socket(#[from] io::Error) = -1,
|
||||||
|
#[cfg(feature = "rustls")]
|
||||||
#[error("PEM parse failed")]
|
#[error("PEM parse failed")]
|
||||||
PemParseFailed(#[from] rustls::pki_types::pem::Error) = -2,
|
PemParseFailed(#[from] rustls::pki_types::pem::Error) = -2,
|
||||||
|
|
||||||
|
#[cfg(feature = "rustls")]
|
||||||
#[error("TLS error")]
|
#[error("TLS error")]
|
||||||
Rustls(#[from] rustls::Error) = -3,
|
Rustls(#[from] rustls::Error) = -3,
|
||||||
|
#[cfg(all(feature = "openssl", not(feature = "rustls")))]
|
||||||
|
#[error("TLS error")]
|
||||||
|
Rustls(#[from] openssl::ssl::Error) = -3,
|
||||||
|
|
||||||
|
#[cfg(feature = "rustls")]
|
||||||
#[error("TLS verifiction build failed")]
|
#[error("TLS verifiction build failed")]
|
||||||
TlsBuilderFailed(#[from] rustls::server::VerifierBuilderError) = -4,
|
TlsBuilderFailed(#[from] rustls::server::VerifierBuilderError) = -4,
|
||||||
|
#[cfg(all(feature = "openssl", not(feature = "rustls")))]
|
||||||
|
#[error("TLS verifiction build failed")]
|
||||||
|
TlsBuilderFailed(#[from] openssl::error::ErrorStack) = -4,
|
||||||
|
|
||||||
#[error("io on plist")]
|
#[error("io on plist")]
|
||||||
Plist(#[from] plist::Error) = -5,
|
Plist(#[from] plist::Error) = -5,
|
||||||
#[error("can't convert bytes to utf8")]
|
#[error("can't convert bytes to utf8")]
|
||||||
@@ -723,6 +865,14 @@ pub enum IdeviceError {
|
|||||||
|
|
||||||
#[error("Developer mode is not enabled")]
|
#[error("Developer mode is not enabled")]
|
||||||
DeveloperModeNotEnabled = -68,
|
DeveloperModeNotEnabled = -68,
|
||||||
|
|
||||||
|
#[cfg(feature = "notification_proxy")]
|
||||||
|
#[error("notification proxy died")]
|
||||||
|
NotificationProxyDeath = -69,
|
||||||
|
|
||||||
|
#[cfg(feature = "installation_proxy")]
|
||||||
|
#[error("Application verification failed: {0}")]
|
||||||
|
ApplicationVerificationFailed(String) = -70,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IdeviceError {
|
impl IdeviceError {
|
||||||
@@ -766,6 +916,15 @@ impl IdeviceError {
|
|||||||
Some(Self::InternalError(detailed_error))
|
Some(Self::InternalError(detailed_error))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#[cfg(feature = "installation_proxy")]
|
||||||
|
"ApplicationVerificationFailed" => {
|
||||||
|
let msg = context
|
||||||
|
.get("ErrorDescription")
|
||||||
|
.and_then(|x| x.as_string())
|
||||||
|
.unwrap_or("No context")
|
||||||
|
.to_string();
|
||||||
|
Some(Self::ApplicationVerificationFailed(msg))
|
||||||
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -773,6 +932,7 @@ impl IdeviceError {
|
|||||||
pub fn code(&self) -> i32 {
|
pub fn code(&self) -> i32 {
|
||||||
match self {
|
match self {
|
||||||
IdeviceError::Socket(_) => -1,
|
IdeviceError::Socket(_) => -1,
|
||||||
|
#[cfg(feature = "rustls")]
|
||||||
IdeviceError::PemParseFailed(_) => -2,
|
IdeviceError::PemParseFailed(_) => -2,
|
||||||
IdeviceError::Rustls(_) => -3,
|
IdeviceError::Rustls(_) => -3,
|
||||||
IdeviceError::TlsBuilderFailed(_) => -4,
|
IdeviceError::TlsBuilderFailed(_) => -4,
|
||||||
@@ -887,6 +1047,12 @@ impl IdeviceError {
|
|||||||
#[cfg(feature = "installation_proxy")]
|
#[cfg(feature = "installation_proxy")]
|
||||||
IdeviceError::MalformedPackageArchive(_) => -67,
|
IdeviceError::MalformedPackageArchive(_) => -67,
|
||||||
IdeviceError::DeveloperModeNotEnabled => -68,
|
IdeviceError::DeveloperModeNotEnabled => -68,
|
||||||
|
|
||||||
|
#[cfg(feature = "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 std::path::Path;
|
||||||
|
|
||||||
use log::warn;
|
#[cfg(all(feature = "openssl", not(feature = "rustls")))]
|
||||||
|
use openssl::{
|
||||||
|
pkey::{PKey, Private},
|
||||||
|
x509::X509,
|
||||||
|
};
|
||||||
use plist::Data;
|
use plist::Data;
|
||||||
|
#[cfg(feature = "rustls")]
|
||||||
use rustls::pki_types::{CertificateDer, pem::PemObject};
|
use rustls::pki_types::{CertificateDer, pem::PemObject};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use tracing::warn;
|
||||||
|
|
||||||
/// Represents a complete iOS device pairing record
|
/// Represents a complete iOS device pairing record
|
||||||
///
|
///
|
||||||
/// Contains all cryptographic materials and identifiers needed for secure communication
|
/// Contains all cryptographic materials and identifiers needed for secure communication
|
||||||
/// with an iOS device, including certificates, private keys, and device identifiers.
|
/// with an iOS device, including certificates, private keys, and device identifiers.
|
||||||
|
#[cfg(feature = "rustls")]
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct PairingFile {
|
pub struct PairingFile {
|
||||||
/// Device's certificate in DER format
|
/// Device's certificate in DER format
|
||||||
@@ -31,13 +38,28 @@ pub struct PairingFile {
|
|||||||
/// Host identifier
|
/// Host identifier
|
||||||
pub host_id: String,
|
pub host_id: String,
|
||||||
/// Escrow bag allowing for access while locked
|
/// Escrow bag allowing for access while locked
|
||||||
pub escrow_bag: Vec<u8>,
|
pub escrow_bag: Option<Vec<u8>>,
|
||||||
/// Device's WiFi MAC address
|
/// Device's WiFi MAC address
|
||||||
pub wifi_mac_address: String,
|
pub wifi_mac_address: String,
|
||||||
/// Device's Unique Device Identifier (optional)
|
/// Device's Unique Device Identifier (optional)
|
||||||
pub udid: Option<String>,
|
pub udid: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(all(feature = "openssl", not(feature = "rustls")))]
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct PairingFile {
|
||||||
|
pub device_certificate: X509,
|
||||||
|
pub host_private_key: PKey<Private>,
|
||||||
|
pub host_certificate: X509,
|
||||||
|
pub root_private_key: PKey<Private>,
|
||||||
|
pub root_certificate: X509,
|
||||||
|
pub system_buid: String,
|
||||||
|
pub host_id: String,
|
||||||
|
pub escrow_bag: Vec<u8>,
|
||||||
|
pub wifi_mac_address: String,
|
||||||
|
pub udid: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
/// Internal representation of a pairing file for serialization/deserialization
|
/// Internal representation of a pairing file for serialization/deserialization
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
#[serde(rename_all = "PascalCase")]
|
#[serde(rename_all = "PascalCase")]
|
||||||
@@ -51,7 +73,7 @@ struct RawPairingFile {
|
|||||||
system_buid: String,
|
system_buid: String,
|
||||||
#[serde(rename = "HostID")]
|
#[serde(rename = "HostID")]
|
||||||
host_id: String,
|
host_id: String,
|
||||||
escrow_bag: Data,
|
escrow_bag: Option<Data>, // None on Apple Watch
|
||||||
#[serde(rename = "WiFiMACAddress")]
|
#[serde(rename = "WiFiMACAddress")]
|
||||||
wifi_mac_address: String,
|
wifi_mac_address: String,
|
||||||
#[serde(rename = "UDID")]
|
#[serde(rename = "UDID")]
|
||||||
@@ -133,6 +155,7 @@ impl PairingFile {
|
|||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
/// Returns `IdeviceError` if serialization fails
|
/// Returns `IdeviceError` if serialization fails
|
||||||
|
#[cfg(feature = "rustls")]
|
||||||
pub fn serialize(self) -> Result<Vec<u8>, crate::IdeviceError> {
|
pub fn serialize(self) -> Result<Vec<u8>, crate::IdeviceError> {
|
||||||
let raw = RawPairingFile::from(self);
|
let raw = RawPairingFile::from(self);
|
||||||
|
|
||||||
@@ -140,8 +163,18 @@ impl PairingFile {
|
|||||||
plist::to_writer_xml(&mut buf, &raw)?;
|
plist::to_writer_xml(&mut buf, &raw)?;
|
||||||
Ok(buf)
|
Ok(buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(all(feature = "openssl", not(feature = "rustls")))]
|
||||||
|
pub fn serialize(self) -> Result<Vec<u8>, crate::IdeviceError> {
|
||||||
|
let raw = RawPairingFile::try_from(self)?;
|
||||||
|
|
||||||
|
let mut buf = Vec::new();
|
||||||
|
plist::to_writer_xml(&mut buf, &raw)?;
|
||||||
|
Ok(buf)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "rustls")]
|
||||||
impl TryFrom<RawPairingFile> for PairingFile {
|
impl TryFrom<RawPairingFile> for PairingFile {
|
||||||
type Error = rustls::pki_types::pem::Error;
|
type Error = rustls::pki_types::pem::Error;
|
||||||
|
|
||||||
@@ -173,13 +206,38 @@ impl TryFrom<RawPairingFile> for PairingFile {
|
|||||||
root_certificate: CertificateDer::from_pem_slice(&root_certificate_pem)?,
|
root_certificate: CertificateDer::from_pem_slice(&root_certificate_pem)?,
|
||||||
system_buid: value.system_buid,
|
system_buid: value.system_buid,
|
||||||
host_id: value.host_id,
|
host_id: value.host_id,
|
||||||
escrow_bag: value.escrow_bag.into(),
|
escrow_bag: value.escrow_bag.map(|x| x.into()),
|
||||||
wifi_mac_address: value.wifi_mac_address,
|
wifi_mac_address: value.wifi_mac_address,
|
||||||
udid: value.udid,
|
udid: value.udid,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(all(feature = "openssl", not(feature = "rustls")))]
|
||||||
|
impl TryFrom<RawPairingFile> for PairingFile {
|
||||||
|
type Error = openssl::error::ErrorStack;
|
||||||
|
|
||||||
|
fn try_from(value: RawPairingFile) -> Result<Self, Self::Error> {
|
||||||
|
Ok(Self {
|
||||||
|
device_certificate: X509::from_pem(&Into::<Vec<u8>>::into(value.device_certificate))?,
|
||||||
|
host_private_key: PKey::private_key_from_pem(&Into::<Vec<u8>>::into(
|
||||||
|
value.host_private_key,
|
||||||
|
))?,
|
||||||
|
host_certificate: X509::from_pem(&Into::<Vec<u8>>::into(value.host_certificate))?,
|
||||||
|
root_private_key: PKey::private_key_from_pem(&Into::<Vec<u8>>::into(
|
||||||
|
value.root_private_key,
|
||||||
|
))?,
|
||||||
|
root_certificate: X509::from_pem(&Into::<Vec<u8>>::into(value.root_certificate))?,
|
||||||
|
system_buid: value.system_buid,
|
||||||
|
host_id: value.host_id,
|
||||||
|
escrow_bag: value.escrow_bag.map(|x| x.into()),
|
||||||
|
wifi_mac_address: value.wifi_mac_address,
|
||||||
|
udid: value.udid,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "rustls")]
|
||||||
impl From<PairingFile> for RawPairingFile {
|
impl From<PairingFile> for RawPairingFile {
|
||||||
/// Converts a structured pairing file into a raw pairing file for serialization
|
/// Converts a structured pairing file into a raw pairing file for serialization
|
||||||
fn from(value: PairingFile) -> Self {
|
fn from(value: PairingFile) -> Self {
|
||||||
@@ -200,13 +258,33 @@ impl From<PairingFile> for RawPairingFile {
|
|||||||
root_certificate: Data::new(root_cert_data),
|
root_certificate: Data::new(root_cert_data),
|
||||||
system_buid: value.system_buid,
|
system_buid: value.system_buid,
|
||||||
host_id: value.host_id.clone(),
|
host_id: value.host_id.clone(),
|
||||||
escrow_bag: Data::new(value.escrow_bag),
|
escrow_bag: value.escrow_bag.map(Data::new),
|
||||||
wifi_mac_address: value.wifi_mac_address,
|
wifi_mac_address: value.wifi_mac_address,
|
||||||
udid: value.udid,
|
udid: value.udid,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(all(feature = "openssl", not(feature = "rustls")))]
|
||||||
|
impl TryFrom<PairingFile> for RawPairingFile {
|
||||||
|
type Error = openssl::error::ErrorStack;
|
||||||
|
|
||||||
|
fn try_from(value: PairingFile) -> Result<Self, Self::Error> {
|
||||||
|
Ok(Self {
|
||||||
|
device_certificate: Data::new(value.device_certificate.to_pem()?),
|
||||||
|
host_private_key: Data::new(value.host_private_key.private_key_to_pem_pkcs8()?),
|
||||||
|
host_certificate: Data::new(value.host_certificate.to_pem()?),
|
||||||
|
root_private_key: Data::new(value.root_private_key.private_key_to_pem_pkcs8()?),
|
||||||
|
root_certificate: Data::new(value.root_certificate.to_pem()?),
|
||||||
|
system_buid: value.system_buid,
|
||||||
|
host_id: value.host_id.clone(),
|
||||||
|
escrow_bag: value.escrow_bag.map(Data::new),
|
||||||
|
wifi_mac_address: value.wifi_mac_address,
|
||||||
|
udid: value.udid,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Helper function to ensure data has proper PEM headers
|
/// Helper function to ensure data has proper PEM headers
|
||||||
/// If the data already has headers, it returns it as is
|
/// If the data already has headers, it returns it as is
|
||||||
/// If not, it adds the appropriate BEGIN and END headers
|
/// If not, it adds the appropriate BEGIN and END headers
|
||||||
|
|||||||
@@ -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
|
// Jackson Coxson
|
||||||
|
|
||||||
use std::io::SeekFrom;
|
use std::{io::SeekFrom, marker::PhantomPinned, pin::Pin};
|
||||||
|
|
||||||
use crate::IdeviceError;
|
use tokio::io::{AsyncRead, AsyncSeek, AsyncWrite};
|
||||||
|
|
||||||
use super::{
|
use crate::{
|
||||||
opcode::AfcOpcode,
|
IdeviceError,
|
||||||
packet::{AfcPacket, AfcPacketHeader},
|
afc::{
|
||||||
|
AfcClient,
|
||||||
|
inner_file::{InnerFileDescriptor, OwnedInnerFileDescriptor},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Maximum transfer size for file operations (64KB)
|
#[derive(Debug)]
|
||||||
const MAX_TRANSFER: u64 = 64 * 1024; // this is what go-ios uses
|
|
||||||
|
|
||||||
/// Handle for an open file on the device.
|
|
||||||
/// Call close before dropping
|
|
||||||
pub struct FileDescriptor<'a> {
|
pub struct FileDescriptor<'a> {
|
||||||
pub(crate) client: &'a mut super::AfcClient,
|
inner: Pin<Box<InnerFileDescriptor<'a>>>,
|
||||||
pub(crate) fd: u64,
|
|
||||||
pub(crate) path: String,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FileDescriptor<'_> {
|
#[derive(Debug)]
|
||||||
/// Generic helper to send an AFC packet and read the response
|
pub struct OwnedFileDescriptor {
|
||||||
async fn send_packet(
|
inner: Pin<Box<OwnedInnerFileDescriptor>>,
|
||||||
&mut self,
|
}
|
||||||
opcode: AfcOpcode,
|
|
||||||
header_payload: Vec<u8>,
|
|
||||||
payload: Vec<u8>,
|
|
||||||
) -> Result<AfcPacket, IdeviceError> {
|
|
||||||
let header_len = header_payload.len() as u64 + AfcPacketHeader::LEN;
|
|
||||||
let header = AfcPacketHeader {
|
|
||||||
magic: super::MAGIC,
|
|
||||||
entire_len: header_len + payload.len() as u64,
|
|
||||||
header_payload_len: header_len,
|
|
||||||
packet_num: self.client.package_number,
|
|
||||||
operation: opcode,
|
|
||||||
};
|
|
||||||
self.client.package_number += 1;
|
|
||||||
|
|
||||||
let packet = AfcPacket {
|
impl<'a> FileDescriptor<'a> {
|
||||||
header,
|
/// create a new FileDescriptor from a raw fd
|
||||||
header_payload,
|
///
|
||||||
payload,
|
/// # Safety
|
||||||
};
|
/// make sure the fd is an opened file, and that you got it from a previous
|
||||||
|
/// FileDescriptor via `as_raw_fd()` method
|
||||||
|
pub unsafe fn new(client: &'a mut AfcClient, fd: u64, path: String) -> Self {
|
||||||
|
Self {
|
||||||
|
inner: Box::pin(InnerFileDescriptor {
|
||||||
|
client,
|
||||||
|
fd,
|
||||||
|
path,
|
||||||
|
pending_fut: None,
|
||||||
|
_m: PhantomPinned,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
self.client.send(packet).await?;
|
/// Closes the file descriptor
|
||||||
self.client.read().await
|
pub async fn close(self) -> Result<(), IdeviceError> {
|
||||||
|
self.inner.close().await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OwnedFileDescriptor {
|
||||||
|
/// create a new OwnedFileDescriptor from a raw fd
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// make sure the fd is an opened file, and that you got it from a previous
|
||||||
|
/// OwnedFileDescriptor via `as_raw_fd()` method
|
||||||
|
pub unsafe fn new(client: AfcClient, fd: u64, path: String) -> Self {
|
||||||
|
Self {
|
||||||
|
inner: Box::pin(OwnedInnerFileDescriptor {
|
||||||
|
client,
|
||||||
|
fd,
|
||||||
|
path,
|
||||||
|
pending_fut: None,
|
||||||
|
_m: PhantomPinned,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Closes the file descriptor
|
||||||
|
pub async fn close(self) -> Result<AfcClient, IdeviceError> {
|
||||||
|
self.inner.close().await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// gets the owned afc
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// this get's the afc out without closing, if you want to get the afc and close the file, use
|
||||||
|
/// `.close()`
|
||||||
|
pub unsafe fn get_inner_afc(self) -> AfcClient {
|
||||||
|
self.inner.get_inner_afc()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
crate::impl_to_structs!(FileDescriptor<'_>, OwnedFileDescriptor; {
|
||||||
|
pub fn as_raw_fd(&self) -> u64 {
|
||||||
|
self.inner.fd
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the current cursor position for the file
|
/// Returns the current cursor position for the file
|
||||||
pub async fn seek_tell(&mut self) -> Result<u64, IdeviceError> {
|
pub async fn seek_tell(&mut self) -> Result<u64, IdeviceError> {
|
||||||
let header_payload = self.fd.to_le_bytes().to_vec();
|
self.inner.as_mut().seek_tell().await
|
||||||
let res = self
|
|
||||||
.send_packet(AfcOpcode::FileTell, header_payload, Vec::new())
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let cur_pos = res
|
|
||||||
.header_payload
|
|
||||||
.get(..8)
|
|
||||||
.ok_or(IdeviceError::UnexpectedResponse)?
|
|
||||||
.try_into()
|
|
||||||
.map(u64::from_le_bytes)
|
|
||||||
.map_err(|_| IdeviceError::UnexpectedResponse)?;
|
|
||||||
|
|
||||||
Ok(cur_pos)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Moves the file cursor
|
|
||||||
pub async fn seek(&mut self, pos: SeekFrom) -> Result<(), IdeviceError> {
|
|
||||||
let (offset, whence) = match pos {
|
|
||||||
SeekFrom::Start(off) => (off as i64, 0),
|
|
||||||
SeekFrom::Current(off) => (off, 1),
|
|
||||||
SeekFrom::End(off) => (off, 2),
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut header_payload = Vec::new();
|
|
||||||
header_payload.extend(self.fd.to_le_bytes());
|
|
||||||
header_payload.extend((whence as u64).to_le_bytes());
|
|
||||||
header_payload.extend(offset.to_le_bytes());
|
|
||||||
|
|
||||||
self.send_packet(AfcOpcode::FileSeek, header_payload, Vec::new())
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Closes the file descriptor
|
|
||||||
pub async fn close(mut self) -> Result<(), IdeviceError> {
|
|
||||||
let header_payload = self.fd.to_le_bytes().to_vec();
|
|
||||||
|
|
||||||
self.send_packet(AfcOpcode::FileClose, header_payload, Vec::new())
|
|
||||||
.await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reads the entire contents of the file
|
/// Reads the entire contents of the file
|
||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
/// A vector containing the file's data
|
/// A vector containing the file's data
|
||||||
pub async fn read(&mut self) -> Result<Vec<u8>, IdeviceError> {
|
pub async fn read_entire(&mut self) -> Result<Vec<u8>, IdeviceError> {
|
||||||
let seek_pos = self.seek_tell().await? as usize;
|
self.inner.as_mut().read().await
|
||||||
let file_info = self.client.get_file_info(&self.path).await?;
|
|
||||||
let mut bytes_left = file_info.size.saturating_sub(seek_pos);
|
|
||||||
let mut collected_bytes = Vec::with_capacity(bytes_left);
|
|
||||||
|
|
||||||
while bytes_left > 0 {
|
|
||||||
let mut header_payload = self.fd.to_le_bytes().to_vec();
|
|
||||||
header_payload.extend_from_slice(&MAX_TRANSFER.to_le_bytes());
|
|
||||||
let res = self
|
|
||||||
.send_packet(AfcOpcode::Read, header_payload, Vec::new())
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
bytes_left -= res.payload.len();
|
|
||||||
collected_bytes.extend(res.payload);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(collected_bytes)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Writes data to the file
|
/// Writes data to the file
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
/// * `bytes` - Data to write to the file
|
/// * `bytes` - Data to write to the file
|
||||||
pub async fn write(&mut self, bytes: &[u8]) -> Result<(), IdeviceError> {
|
pub async fn write_entire(&mut self, bytes: &[u8]) -> Result<(), IdeviceError> {
|
||||||
for chunk in bytes.chunks(MAX_TRANSFER as usize) {
|
self.inner.as_mut().write(bytes).await
|
||||||
let header_payload = self.fd.to_le_bytes().to_vec();
|
|
||||||
self.send_packet(AfcOpcode::Write, header_payload, chunk.to_vec())
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
|
crate::impl_trait_to_structs!(AsyncRead for FileDescriptor<'_>, OwnedFileDescriptor; {
|
||||||
|
fn poll_read(
|
||||||
|
mut self: std::pin::Pin<&mut Self>,
|
||||||
|
cx: &mut std::task::Context<'_>,
|
||||||
|
buf: &mut tokio::io::ReadBuf<'_>,
|
||||||
|
) -> std::task::Poll<std::io::Result<()>> {
|
||||||
|
let inner = self.inner.as_mut();
|
||||||
|
inner.poll_read(cx, buf)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
crate::impl_trait_to_structs!(AsyncWrite for FileDescriptor<'_>, OwnedFileDescriptor; {
|
||||||
|
fn poll_write(
|
||||||
|
mut self: std::pin::Pin<&mut Self>,
|
||||||
|
cx: &mut std::task::Context<'_>,
|
||||||
|
buf: &[u8],
|
||||||
|
) -> std::task::Poll<Result<usize, std::io::Error>> {
|
||||||
|
let inner = self.inner.as_mut();
|
||||||
|
inner.poll_write(cx, buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_flush(
|
||||||
|
mut self: std::pin::Pin<&mut Self>,
|
||||||
|
cx: &mut std::task::Context<'_>,
|
||||||
|
) -> std::task::Poll<Result<(), std::io::Error>> {
|
||||||
|
let inner = self.inner.as_mut();
|
||||||
|
inner.poll_flush(cx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_shutdown(
|
||||||
|
mut self: std::pin::Pin<&mut Self>,
|
||||||
|
cx: &mut std::task::Context<'_>,
|
||||||
|
) -> std::task::Poll<Result<(), std::io::Error>> {
|
||||||
|
let inner = self.inner.as_mut();
|
||||||
|
inner.poll_shutdown(cx)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
crate::impl_trait_to_structs!(AsyncSeek for FileDescriptor<'_>, OwnedFileDescriptor; {
|
||||||
|
fn start_seek(mut self: Pin<&mut Self>, position: SeekFrom) -> std::io::Result<()> {
|
||||||
|
let this = self.inner.as_mut();
|
||||||
|
this.start_seek(position)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_complete(
|
||||||
|
mut self: Pin<&mut Self>,
|
||||||
|
cx: &mut std::task::Context<'_>,
|
||||||
|
) -> std::task::Poll<std::io::Result<u64>> {
|
||||||
|
let this = self.inner.as_mut();
|
||||||
|
this.poll_complete(cx)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|||||||
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 std::collections::HashMap;
|
||||||
|
|
||||||
use errors::AfcError;
|
use errors::AfcError;
|
||||||
use file::FileDescriptor;
|
|
||||||
use log::warn;
|
|
||||||
use opcode::{AfcFopenMode, AfcOpcode};
|
use opcode::{AfcFopenMode, AfcOpcode};
|
||||||
use packet::{AfcPacket, AfcPacketHeader};
|
use packet::{AfcPacket, AfcPacketHeader};
|
||||||
|
use tracing::warn;
|
||||||
|
|
||||||
use crate::{Idevice, IdeviceError, IdeviceService, obf};
|
use crate::{
|
||||||
|
Idevice, IdeviceError, IdeviceService,
|
||||||
|
afc::file::{FileDescriptor, OwnedFileDescriptor},
|
||||||
|
lockdown::LockdownClient,
|
||||||
|
obf,
|
||||||
|
};
|
||||||
|
|
||||||
pub mod errors;
|
pub mod errors;
|
||||||
pub mod file;
|
pub mod file;
|
||||||
|
mod inner_file;
|
||||||
|
mod inner_file_impl_macro;
|
||||||
pub mod opcode;
|
pub mod opcode;
|
||||||
pub mod packet;
|
pub mod packet;
|
||||||
|
|
||||||
@@ -22,6 +28,7 @@ pub mod packet;
|
|||||||
pub const MAGIC: u64 = 0x4141504c36414643;
|
pub const MAGIC: u64 = 0x4141504c36414643;
|
||||||
|
|
||||||
/// Client for interacting with the AFC service on iOS devices
|
/// Client for interacting with the AFC service on iOS devices
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct AfcClient {
|
pub struct AfcClient {
|
||||||
/// The underlying iDevice connection
|
/// The underlying iDevice connection
|
||||||
pub idevice: Idevice,
|
pub idevice: Idevice,
|
||||||
@@ -85,6 +92,43 @@ impl AfcClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Connects to afc2 from a provider
|
||||||
|
pub async fn new_afc2(
|
||||||
|
provider: &dyn crate::provider::IdeviceProvider,
|
||||||
|
) -> Result<Self, IdeviceError> {
|
||||||
|
let mut lockdown = LockdownClient::connect(provider).await?;
|
||||||
|
|
||||||
|
#[cfg(feature = "openssl")]
|
||||||
|
let legacy = lockdown
|
||||||
|
.get_value(Some("ProductVersion"), None)
|
||||||
|
.await
|
||||||
|
.ok()
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|x| x.as_string())
|
||||||
|
.and_then(|x| x.split(".").next())
|
||||||
|
.and_then(|x| x.parse::<u8>().ok())
|
||||||
|
.map(|x| x < 5)
|
||||||
|
.unwrap_or(false);
|
||||||
|
|
||||||
|
#[cfg(not(feature = "openssl"))]
|
||||||
|
let legacy = false;
|
||||||
|
|
||||||
|
lockdown
|
||||||
|
.start_session(&provider.get_pairing_file().await?)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let (port, ssl) = lockdown.start_service(obf!("com.apple.afc2")).await?;
|
||||||
|
|
||||||
|
let mut idevice = provider.connect(port).await?;
|
||||||
|
if ssl {
|
||||||
|
idevice
|
||||||
|
.start_session(&provider.get_pairing_file().await?, legacy)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Self::from_stream(idevice).await
|
||||||
|
}
|
||||||
|
|
||||||
/// Lists the contents of a directory on the device
|
/// Lists the contents of a directory on the device
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
@@ -408,11 +452,54 @@ impl AfcClient {
|
|||||||
return Err(IdeviceError::UnexpectedResponse);
|
return Err(IdeviceError::UnexpectedResponse);
|
||||||
}
|
}
|
||||||
let fd = u64::from_le_bytes(res.header_payload[..8].try_into().unwrap());
|
let fd = u64::from_le_bytes(res.header_payload[..8].try_into().unwrap());
|
||||||
Ok(FileDescriptor {
|
|
||||||
client: self,
|
// we know it's a valid fd
|
||||||
fd,
|
Ok(unsafe { FileDescriptor::new(self, fd, path) })
|
||||||
path,
|
}
|
||||||
})
|
|
||||||
|
/// Opens an owned file on the device
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `path` - Path to the file to open
|
||||||
|
/// * `mode` - Opening mode (read, write, etc.)
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// A `OwnedFileDescriptor` struct for the opened file
|
||||||
|
pub async fn open_owned(
|
||||||
|
mut self,
|
||||||
|
path: impl Into<String>,
|
||||||
|
mode: AfcFopenMode,
|
||||||
|
) -> Result<OwnedFileDescriptor, IdeviceError> {
|
||||||
|
let path = path.into();
|
||||||
|
let mut header_payload = (mode as u64).to_le_bytes().to_vec();
|
||||||
|
header_payload.extend(path.as_bytes());
|
||||||
|
let header_len = header_payload.len() as u64 + AfcPacketHeader::LEN;
|
||||||
|
|
||||||
|
let header = AfcPacketHeader {
|
||||||
|
magic: MAGIC,
|
||||||
|
entire_len: header_len, // it's the same since the payload is empty for this
|
||||||
|
header_payload_len: header_len,
|
||||||
|
packet_num: self.package_number,
|
||||||
|
operation: AfcOpcode::FileOpen,
|
||||||
|
};
|
||||||
|
self.package_number += 1;
|
||||||
|
|
||||||
|
let packet = AfcPacket {
|
||||||
|
header,
|
||||||
|
header_payload,
|
||||||
|
payload: Vec::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
self.send(packet).await?;
|
||||||
|
let res = self.read().await?;
|
||||||
|
if res.header_payload.len() < 8 {
|
||||||
|
warn!("Header payload fd is less than 8 bytes");
|
||||||
|
return Err(IdeviceError::UnexpectedResponse);
|
||||||
|
}
|
||||||
|
let fd = u64::from_le_bytes(res.header_payload[..8].try_into().unwrap());
|
||||||
|
|
||||||
|
// we know it's a valid fd
|
||||||
|
Ok(unsafe { OwnedFileDescriptor::new(self, fd, path) })
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a hard or symbolic link
|
/// Creates a hard or symbolic link
|
||||||
@@ -508,7 +595,7 @@ impl AfcClient {
|
|||||||
let res = AfcPacket::read(&mut self.idevice).await?;
|
let res = AfcPacket::read(&mut self.idevice).await?;
|
||||||
if res.header.operation == AfcOpcode::Status {
|
if res.header.operation == AfcOpcode::Status {
|
||||||
if res.header_payload.len() < 8 {
|
if res.header_payload.len() < 8 {
|
||||||
log::error!("AFC returned error opcode, but not a code");
|
tracing::error!("AFC returned error opcode, but not a code");
|
||||||
return Err(IdeviceError::UnexpectedResponse);
|
return Err(IdeviceError::UnexpectedResponse);
|
||||||
}
|
}
|
||||||
let code = u64::from_le_bytes(res.header_payload[..8].try_into().unwrap());
|
let code = u64::from_le_bytes(res.header_payload[..8].try_into().unwrap());
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// Jackson Coxson
|
// Jackson Coxson
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
#[repr(u64)]
|
#[repr(u64)]
|
||||||
pub enum AfcOpcode {
|
pub enum AfcOpcode {
|
||||||
Status = 0x00000001,
|
Status = 0x00000001,
|
||||||
@@ -36,6 +36,7 @@ pub enum AfcOpcode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[repr(u64)]
|
#[repr(u64)]
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub enum AfcFopenMode {
|
pub enum AfcFopenMode {
|
||||||
RdOnly = 0x00000001, // r O_RDONLY
|
RdOnly = 0x00000001, // r O_RDONLY
|
||||||
Rw = 0x00000002, // r+ O_RDWR | O_CREAT
|
Rw = 0x00000002, // r+ O_RDWR | O_CREAT
|
||||||
@@ -46,6 +47,7 @@ pub enum AfcFopenMode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[repr(u64)]
|
#[repr(u64)]
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub enum LinkType {
|
pub enum LinkType {
|
||||||
Hardlink = 0x00000001,
|
Hardlink = 0x00000001,
|
||||||
Symlink = 0x00000002,
|
Symlink = 0x00000002,
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
// Jackson Coxson
|
// Jackson Coxson
|
||||||
|
|
||||||
use log::debug;
|
use tracing::debug;
|
||||||
|
|
||||||
use crate::{Idevice, IdeviceError};
|
use crate::{Idevice, IdeviceError};
|
||||||
|
|
||||||
use super::opcode::AfcOpcode;
|
use super::opcode::AfcOpcode;
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub struct AfcPacketHeader {
|
pub struct AfcPacketHeader {
|
||||||
pub magic: u64,
|
pub magic: u64,
|
||||||
pub entire_len: u64,
|
pub entire_len: u64,
|
||||||
@@ -32,7 +32,7 @@ impl AfcPacketHeader {
|
|||||||
res.extend_from_slice(&self.entire_len.to_le_bytes());
|
res.extend_from_slice(&self.entire_len.to_le_bytes());
|
||||||
res.extend_from_slice(&self.header_payload_len.to_le_bytes());
|
res.extend_from_slice(&self.header_payload_len.to_le_bytes());
|
||||||
res.extend_from_slice(&self.packet_num.to_le_bytes());
|
res.extend_from_slice(&self.packet_num.to_le_bytes());
|
||||||
res.extend_from_slice(&(self.operation.clone() as u64).to_le_bytes());
|
res.extend_from_slice(&(self.operation as u64).to_le_bytes());
|
||||||
|
|
||||||
res
|
res
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
use crate::{Idevice, IdeviceError, IdeviceService, obf};
|
use crate::{Idevice, IdeviceError, IdeviceService, obf};
|
||||||
|
|
||||||
/// Client for interacting with the AMFI service on the device
|
/// Client for interacting with the AMFI service on the device
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct AmfiClient {
|
pub struct AmfiClient {
|
||||||
/// The underlying device connection with established amfi service
|
/// The underlying device connection with established amfi service
|
||||||
pub idevice: Idevice,
|
pub idevice: Idevice,
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
|
|
||||||
use futures::Stream;
|
use futures::Stream;
|
||||||
use log::{debug, warn};
|
use tracing::warn;
|
||||||
|
|
||||||
use crate::{Idevice, IdeviceError, IdeviceService, obf};
|
use crate::{Idevice, IdeviceError, IdeviceService, obf};
|
||||||
|
|
||||||
@@ -13,6 +13,7 @@ use crate::{Idevice, IdeviceError, IdeviceService, obf};
|
|||||||
/// You must have the Bluetooth profile installed, or you'll get no data.
|
/// You must have the Bluetooth profile installed, or you'll get no data.
|
||||||
///
|
///
|
||||||
/// ``https://developer.apple.com/bug-reporting/profiles-and-logs/?name=bluetooth``
|
/// ``https://developer.apple.com/bug-reporting/profiles-and-logs/?name=bluetooth``
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct BtPacketLoggerClient {
|
pub struct BtPacketLoggerClient {
|
||||||
/// The underlying device connection with established logger service
|
/// The underlying device connection with established logger service
|
||||||
pub idevice: Idevice,
|
pub idevice: Idevice,
|
||||||
@@ -110,16 +111,6 @@ impl BtPacketLoggerClient {
|
|||||||
let kind = BtPacketKind::from_byte(frame[off]);
|
let kind = BtPacketKind::from_byte(frame[off]);
|
||||||
let payload = &frame[off + 1..]; // whatever remains
|
let payload = &frame[off + 1..]; // whatever remains
|
||||||
|
|
||||||
// Optional soft check of advisory header.length
|
|
||||||
let advisory = hdr.length as usize;
|
|
||||||
let actual = 1 + payload.len(); // kind + payload
|
|
||||||
if advisory != actual {
|
|
||||||
debug!(
|
|
||||||
"BTPacketLogger advisory length {} != actual {}, proceeding",
|
|
||||||
advisory, actual
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build H4 buffer (prepend type byte)
|
// Build H4 buffer (prepend type byte)
|
||||||
let mut h4 = Vec::with_capacity(1 + payload.len());
|
let mut h4 = Vec::with_capacity(1 + payload.len());
|
||||||
if let Some(t) = kind.h4_type() {
|
if let Some(t) = kind.h4_type() {
|
||||||
@@ -158,13 +149,6 @@ impl BtPacketLoggerClient {
|
|||||||
let kind = BtPacketKind::from_byte(frame[off]);
|
let kind = BtPacketKind::from_byte(frame[off]);
|
||||||
let payload = &frame[off + 1..];
|
let payload = &frame[off + 1..];
|
||||||
|
|
||||||
// soft advisory check
|
|
||||||
let advisory = hdr.length as usize;
|
|
||||||
let actual = 1 + payload.len();
|
|
||||||
if advisory != actual {
|
|
||||||
debug!("BTPacketLogger advisory length {} != actual {}", advisory, actual);
|
|
||||||
}
|
|
||||||
|
|
||||||
// make H4 buffer
|
// make H4 buffer
|
||||||
let mut h4 = Vec::with_capacity(1 + payload.len());
|
let mut h4 = Vec::with_capacity(1 + payload.len());
|
||||||
if let Some(t) = kind.h4_type() {
|
if let Some(t) = kind.h4_type() {
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
//! Companion Proxy is Apple's bridge to connect to the Apple Watch
|
//! Companion Proxy is Apple's bridge to connect to the Apple Watch
|
||||||
|
|
||||||
use log::warn;
|
use tracing::warn;
|
||||||
|
|
||||||
use crate::{Idevice, IdeviceError, IdeviceService, RsdService, obf};
|
use crate::{Idevice, IdeviceError, IdeviceService, RsdService, obf};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct CompanionProxy {
|
pub struct CompanionProxy {
|
||||||
idevice: Idevice,
|
idevice: Idevice,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct CompanionProxyStream {
|
pub struct CompanionProxyStream {
|
||||||
proxy: CompanionProxy,
|
proxy: CompanionProxy,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
// Jackson Coxson
|
// Jackson Coxson
|
||||||
|
|
||||||
use log::warn;
|
use plist_macro::plist_to_xml_bytes;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
use tracing::warn;
|
||||||
|
|
||||||
use crate::{IdeviceError, ReadWrite, RsdService, obf, xpc::XPCObject};
|
use crate::{IdeviceError, ReadWrite, RsdService, obf, xpc::XPCObject};
|
||||||
|
|
||||||
@@ -19,6 +20,7 @@ impl RsdService for AppServiceClient<Box<dyn ReadWrite>> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct AppServiceClient<R: ReadWrite> {
|
pub struct AppServiceClient<R: ReadWrite> {
|
||||||
inner: CoreDeviceServiceClient<R>,
|
inner: CoreDeviceServiceClient<R>,
|
||||||
}
|
}
|
||||||
@@ -215,7 +217,7 @@ impl<R: ReadWrite> AppServiceClient<R> {
|
|||||||
"user": {
|
"user": {
|
||||||
"active": true,
|
"active": true,
|
||||||
},
|
},
|
||||||
"platformSpecificOptions": plist::Value::Data(crate::util::plist_to_xml_bytes(&platform_options.unwrap_or_default())),
|
"platformSpecificOptions": plist::Value::Data(plist_to_xml_bytes(&platform_options.unwrap_or_default())),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
|
|
||||||
use futures::Stream;
|
use futures::Stream;
|
||||||
use log::warn;
|
use tracing::warn;
|
||||||
|
|
||||||
use crate::{IdeviceError, ReadWrite, RsdService, obf};
|
use crate::{IdeviceError, ReadWrite, RsdService, obf};
|
||||||
|
|
||||||
@@ -19,6 +19,7 @@ impl RsdService for DiagnostisServiceClient<Box<dyn ReadWrite>> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct DiagnostisServiceClient<R: ReadWrite> {
|
pub struct DiagnostisServiceClient<R: ReadWrite> {
|
||||||
inner: super::CoreDeviceServiceClient<R>,
|
inner: super::CoreDeviceServiceClient<R>,
|
||||||
}
|
}
|
||||||
@@ -70,3 +71,12 @@ impl<R: ReadWrite> DiagnostisServiceClient<R> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for SysdiagnoseResponse<'_> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.debug_struct("SysdiagnoseResponse")
|
||||||
|
.field("preferred_filename", &self.preferred_filename)
|
||||||
|
.field("expected_length", &self.expected_length)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Jackson Coxson
|
// Jackson Coxson
|
||||||
// Ported from pymobiledevice3
|
// Ported from pymobiledevice3
|
||||||
|
|
||||||
use log::warn;
|
use tracing::warn;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
IdeviceError, ReadWrite, RemoteXpcClient,
|
IdeviceError, ReadWrite, RemoteXpcClient,
|
||||||
@@ -17,6 +17,7 @@ pub use openstdiosocket::*;
|
|||||||
|
|
||||||
const CORE_SERVICE_VERSION: &str = "443.18";
|
const CORE_SERVICE_VERSION: &str = "443.18";
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct CoreDeviceServiceClient<R: ReadWrite> {
|
pub struct CoreDeviceServiceClient<R: ReadWrite> {
|
||||||
inner: RemoteXpcClient<R>,
|
inner: RemoteXpcClient<R>,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ impl RsdService for OpenStdioSocketClient {
|
|||||||
|
|
||||||
/// Call ``read_uuid`` to get the UUID. Pass that to app service launch to connect to the stream of
|
/// Call ``read_uuid`` to get the UUID. Pass that to app service launch to connect to the stream of
|
||||||
/// the launched app. Inner is exposed to read and write to, using Tokio's AsyncReadExt/AsyncWriteExt
|
/// the launched app. Inner is exposed to read and write to, using Tokio's AsyncReadExt/AsyncWriteExt
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct OpenStdioSocketClient {
|
pub struct OpenStdioSocketClient {
|
||||||
pub inner: Box<dyn ReadWrite>,
|
pub inner: Box<dyn ReadWrite>,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ use crate::{Idevice, IdeviceError, IdeviceService, obf};
|
|||||||
|
|
||||||
use byteorder::{BigEndian, WriteBytesExt};
|
use byteorder::{BigEndian, WriteBytesExt};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::io::{self, Write};
|
use std::io::{self, IoSlice, Write};
|
||||||
|
|
||||||
/// A representation of a CDTunnel packet used in the CoreDeviceProxy protocol.
|
/// A representation of a CDTunnel packet used in the CoreDeviceProxy protocol.
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
@@ -82,6 +82,7 @@ impl CDTunnelPacket {
|
|||||||
/// A high-level client for the `com.apple.internal.devicecompute.CoreDeviceProxy` service.
|
/// A high-level client for the `com.apple.internal.devicecompute.CoreDeviceProxy` service.
|
||||||
///
|
///
|
||||||
/// Handles session negotiation, handshake, and tunnel communication.
|
/// Handles session negotiation, handshake, and tunnel communication.
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct CoreDeviceProxy {
|
pub struct CoreDeviceProxy {
|
||||||
/// The underlying idevice connection used for communication.
|
/// The underlying idevice connection used for communication.
|
||||||
pub idevice: Idevice,
|
pub idevice: Idevice,
|
||||||
@@ -194,6 +195,20 @@ impl CoreDeviceProxy {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sends a raw data packet through the tunnel using vectored I/O.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `bufs` - The buffers to send.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// * `Ok(())` if the data is successfully sent.
|
||||||
|
/// * `Err(IdeviceError)` if sending fails.
|
||||||
|
pub async fn send_vectored(&mut self, bufs: &[IoSlice<'_>]) -> Result<(), IdeviceError> {
|
||||||
|
self.idevice.send_raw_vectored(bufs).await
|
||||||
|
}
|
||||||
|
|
||||||
/// Receives up to `mtu` bytes from the tunnel.
|
/// Receives up to `mtu` bytes from the tunnel.
|
||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
//! function to trigger a flush of crash logs from system storage into the
|
//! function to trigger a flush of crash logs from system storage into the
|
||||||
//! crash reports directory by connecting to the `com.apple.crashreportmover` service.
|
//! crash reports directory by connecting to the `com.apple.crashreportmover` service.
|
||||||
|
|
||||||
use log::{debug, warn};
|
use tracing::{debug, warn};
|
||||||
|
|
||||||
use crate::{Idevice, IdeviceError, IdeviceService, afc::AfcClient, lockdown::LockdownClient, obf};
|
use crate::{Idevice, IdeviceError, IdeviceService, afc::AfcClient, lockdown::LockdownClient, obf};
|
||||||
|
|
||||||
@@ -15,6 +15,7 @@ use crate::{Idevice, IdeviceError, IdeviceService, afc::AfcClient, lockdown::Loc
|
|||||||
///
|
///
|
||||||
/// This client wraps access to the `com.apple.crashreportcopymobile` service,
|
/// This client wraps access to the `com.apple.crashreportcopymobile` service,
|
||||||
/// which exposes crash logs through the Apple File Conduit (AFC).
|
/// which exposes crash logs through the Apple File Conduit (AFC).
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct CrashReportCopyMobileClient {
|
pub struct CrashReportCopyMobileClient {
|
||||||
/// The underlying AFC client connected to the crash logs directory.
|
/// The underlying AFC client connected to the crash logs directory.
|
||||||
pub afc_client: AfcClient,
|
pub afc_client: AfcClient,
|
||||||
@@ -84,7 +85,7 @@ impl CrashReportCopyMobileClient {
|
|||||||
.open(format!("/{log}"), crate::afc::opcode::AfcFopenMode::RdOnly)
|
.open(format!("/{log}"), crate::afc::opcode::AfcFopenMode::RdOnly)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
f.read().await
|
f.read_entire().await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Removes a specified crash log file from the device.
|
/// Removes a specified crash log file from the device.
|
||||||
@@ -123,6 +124,18 @@ pub async fn flush_reports(
|
|||||||
provider: &dyn crate::provider::IdeviceProvider,
|
provider: &dyn crate::provider::IdeviceProvider,
|
||||||
) -> Result<(), IdeviceError> {
|
) -> Result<(), IdeviceError> {
|
||||||
let mut lockdown = LockdownClient::connect(provider).await?;
|
let mut lockdown = LockdownClient::connect(provider).await?;
|
||||||
|
|
||||||
|
let legacy = lockdown
|
||||||
|
.get_value(Some("ProductVersion"), None)
|
||||||
|
.await
|
||||||
|
.ok()
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|x| x.as_string())
|
||||||
|
.and_then(|x| x.split(".").next())
|
||||||
|
.and_then(|x| x.parse::<u8>().ok())
|
||||||
|
.map(|x| x < 5)
|
||||||
|
.unwrap_or(false);
|
||||||
|
|
||||||
lockdown
|
lockdown
|
||||||
.start_session(&provider.get_pairing_file().await?)
|
.start_session(&provider.get_pairing_file().await?)
|
||||||
.await?;
|
.await?;
|
||||||
@@ -134,7 +147,7 @@ pub async fn flush_reports(
|
|||||||
let mut idevice = provider.connect(port).await?;
|
let mut idevice = provider.connect(port).await?;
|
||||||
if ssl {
|
if ssl {
|
||||||
idevice
|
idevice
|
||||||
.start_session(&provider.get_pairing_file().await?)
|
.start_session(&provider.get_pairing_file().await?, legacy)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,9 +4,9 @@
|
|||||||
//! GDB Remote Serial Protocol as documented at:
|
//! GDB Remote Serial Protocol as documented at:
|
||||||
//! https://sourceware.org/gdb/current/onlinedocs/gdb.html/Packets.html#Packets
|
//! https://sourceware.org/gdb/current/onlinedocs/gdb.html/Packets.html#Packets
|
||||||
|
|
||||||
use log::debug;
|
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||||
|
use tracing::debug;
|
||||||
|
|
||||||
use crate::{IdeviceError, ReadWrite, RsdService, obf};
|
use crate::{IdeviceError, ReadWrite, RsdService, obf};
|
||||||
|
|
||||||
@@ -27,6 +27,7 @@ impl RsdService for DebugProxyClient<Box<dyn ReadWrite>> {
|
|||||||
///
|
///
|
||||||
/// Implements the GDB Remote Serial Protocol for communicating with debugserver
|
/// Implements the GDB Remote Serial Protocol for communicating with debugserver
|
||||||
/// on iOS devices. Handles packet formatting, checksums, and acknowledgments.
|
/// on iOS devices. Handles packet formatting, checksums, and acknowledgments.
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct DebugProxyClient<R: ReadWrite> {
|
pub struct DebugProxyClient<R: ReadWrite> {
|
||||||
/// The underlying socket connection to debugproxy
|
/// The underlying socket connection to debugproxy
|
||||||
pub socket: R,
|
pub socket: R,
|
||||||
@@ -38,6 +39,7 @@ pub struct DebugProxyClient<R: ReadWrite> {
|
|||||||
///
|
///
|
||||||
/// Commands follow the GDB Remote Serial Protocol format:
|
/// Commands follow the GDB Remote Serial Protocol format:
|
||||||
/// $<command>[<hex-encoded args>]#<checksum>
|
/// $<command>[<hex-encoded args>]#<checksum>
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct DebugserverCommand {
|
pub struct DebugserverCommand {
|
||||||
/// The command name (e.g. "qSupported", "vCont")
|
/// The command name (e.g. "qSupported", "vCont")
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
use crate::{Idevice, IdeviceError, IdeviceService, obf};
|
use crate::{Idevice, IdeviceError, IdeviceService, obf};
|
||||||
|
|
||||||
/// Client for interacting with the Diagnostics Relay
|
/// Client for interacting with the Diagnostics Relay
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct DiagnosticsRelayClient {
|
pub struct DiagnosticsRelayClient {
|
||||||
/// The underlying device connection with established service
|
/// The underlying device connection with established service
|
||||||
pub idevice: Idevice,
|
pub idevice: Idevice,
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ use crate::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
/// A client for the location simulation service
|
/// A client for the location simulation service
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct LocationSimulationClient<'a, R: ReadWrite> {
|
pub struct LocationSimulationClient<'a, R: ReadWrite> {
|
||||||
/// The underlying channel used for communication
|
/// The underlying channel used for communication
|
||||||
channel: Channel<'a, R>,
|
channel: Channel<'a, R>,
|
||||||
|
|||||||
@@ -61,12 +61,12 @@
|
|||||||
use plist::Value;
|
use plist::Value;
|
||||||
use tokio::io::{AsyncRead, AsyncReadExt};
|
use tokio::io::{AsyncRead, AsyncReadExt};
|
||||||
|
|
||||||
use crate::IdeviceError;
|
use crate::{IdeviceError, pretty_print_plist};
|
||||||
|
|
||||||
/// Message header containing metadata about the message
|
/// Message header containing metadata about the message
|
||||||
///
|
///
|
||||||
/// 32-byte structure that appears at the start of every message
|
/// 32-byte structure that appears at the start of every message
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
pub struct MessageHeader {
|
pub struct MessageHeader {
|
||||||
/// Magic number identifying the protocol (0x1F3D5B79)
|
/// Magic number identifying the protocol (0x1F3D5B79)
|
||||||
magic: u32,
|
magic: u32,
|
||||||
@@ -91,7 +91,7 @@ pub struct MessageHeader {
|
|||||||
/// Payload header containing information about the message contents
|
/// Payload header containing information about the message contents
|
||||||
///
|
///
|
||||||
/// 16-byte structure following the message header
|
/// 16-byte structure following the message header
|
||||||
#[derive(Debug, Default, Clone, PartialEq)]
|
#[derive(Debug, Default, Clone, Copy, PartialEq)]
|
||||||
pub struct PayloadHeader {
|
pub struct PayloadHeader {
|
||||||
/// Flags controlling message processing
|
/// Flags controlling message processing
|
||||||
flags: u32,
|
flags: u32,
|
||||||
@@ -104,7 +104,7 @@ pub struct PayloadHeader {
|
|||||||
/// Header for auxiliary data section
|
/// Header for auxiliary data section
|
||||||
///
|
///
|
||||||
/// 16-byte structure preceding auxiliary data
|
/// 16-byte structure preceding auxiliary data
|
||||||
#[derive(Debug, Default, PartialEq)]
|
#[derive(Debug, Default, Clone, Copy, PartialEq)]
|
||||||
pub struct AuxHeader {
|
pub struct AuxHeader {
|
||||||
/// Buffer size hint (often 496)
|
/// Buffer size hint (often 496)
|
||||||
buffer_size: u32,
|
buffer_size: u32,
|
||||||
@@ -141,7 +141,7 @@ pub enum AuxValue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Complete protocol message
|
/// Complete protocol message
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(PartialEq)]
|
||||||
pub struct Message {
|
pub struct Message {
|
||||||
/// Message metadata header
|
/// Message metadata header
|
||||||
pub message_header: MessageHeader,
|
pub message_header: MessageHeader,
|
||||||
@@ -525,3 +525,14 @@ impl std::fmt::Debug for AuxValue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for Message {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.debug_struct("Message")
|
||||||
|
.field("message_header", &self.message_header)
|
||||||
|
.field("payload_header", &self.payload_header)
|
||||||
|
.field("aux", &self.aux)
|
||||||
|
.field("data", &self.data.as_ref().map(pretty_print_plist))
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -39,6 +39,18 @@ impl crate::IdeviceService for remote_server::RemoteServerClient<Box<dyn ReadWri
|
|||||||
async fn connect(provider: &dyn IdeviceProvider) -> Result<Self, IdeviceError> {
|
async fn connect(provider: &dyn IdeviceProvider) -> Result<Self, IdeviceError> {
|
||||||
// Establish Lockdown session
|
// Establish Lockdown session
|
||||||
let mut lockdown = LockdownClient::connect(provider).await?;
|
let mut lockdown = LockdownClient::connect(provider).await?;
|
||||||
|
|
||||||
|
let legacy = lockdown
|
||||||
|
.get_value(Some("ProductVersion"), None)
|
||||||
|
.await
|
||||||
|
.ok()
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|x| x.as_string())
|
||||||
|
.and_then(|x| x.split(".").next())
|
||||||
|
.and_then(|x| x.parse::<u8>().ok())
|
||||||
|
.map(|x| x < 5)
|
||||||
|
.unwrap_or(false);
|
||||||
|
|
||||||
lockdown
|
lockdown
|
||||||
.start_session(&provider.get_pairing_file().await?)
|
.start_session(&provider.get_pairing_file().await?)
|
||||||
.await?;
|
.await?;
|
||||||
@@ -56,7 +68,7 @@ impl crate::IdeviceService for remote_server::RemoteServerClient<Box<dyn ReadWri
|
|||||||
let mut idevice = provider.connect(port).await?;
|
let mut idevice = provider.connect(port).await?;
|
||||||
if ssl {
|
if ssl {
|
||||||
idevice
|
idevice
|
||||||
.start_session(&provider.get_pairing_file().await?)
|
.start_session(&provider.get_pairing_file().await?, legacy)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
// Convert to transport and build client
|
// Convert to transport and build client
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ use crate::{
|
|||||||
},
|
},
|
||||||
obf,
|
obf,
|
||||||
};
|
};
|
||||||
use log::warn;
|
|
||||||
use plist::Value;
|
use plist::Value;
|
||||||
|
use tracing::warn;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct NotificationInfo {
|
pub struct NotificationInfo {
|
||||||
@@ -23,6 +23,7 @@ pub struct NotificationInfo {
|
|||||||
state_description: String,
|
state_description: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct NotificationsClient<'a, R: ReadWrite> {
|
pub struct NotificationsClient<'a, R: ReadWrite> {
|
||||||
/// The underlying channel used for communication
|
/// The underlying channel used for communication
|
||||||
pub channel: Channel<'a, R>,
|
pub channel: Channel<'a, R>,
|
||||||
|
|||||||
@@ -34,8 +34,8 @@
|
|||||||
//! }
|
//! }
|
||||||
//! ```
|
//! ```
|
||||||
|
|
||||||
use log::warn;
|
|
||||||
use plist::{Dictionary, Value};
|
use plist::{Dictionary, Value};
|
||||||
|
use tracing::warn;
|
||||||
|
|
||||||
use crate::{IdeviceError, ReadWrite, dvt::message::AuxValue, obf};
|
use crate::{IdeviceError, ReadWrite, dvt::message::AuxValue, obf};
|
||||||
|
|
||||||
@@ -45,6 +45,7 @@ use super::remote_server::{Channel, RemoteServerClient};
|
|||||||
///
|
///
|
||||||
/// Provides methods for launching, killing, and managing processes through the
|
/// Provides methods for launching, killing, and managing processes through the
|
||||||
/// instruments protocol. Each instance maintains its own communication channel.
|
/// instruments protocol. Each instance maintains its own communication channel.
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct ProcessControlClient<'a, R: ReadWrite> {
|
pub struct ProcessControlClient<'a, R: ReadWrite> {
|
||||||
/// The underlying channel for communication
|
/// The underlying channel for communication
|
||||||
channel: Channel<'a, R>,
|
channel: Channel<'a, R>,
|
||||||
|
|||||||
@@ -51,8 +51,8 @@
|
|||||||
|
|
||||||
use std::collections::{HashMap, VecDeque};
|
use std::collections::{HashMap, VecDeque};
|
||||||
|
|
||||||
use log::{debug, warn};
|
|
||||||
use tokio::io::AsyncWriteExt;
|
use tokio::io::AsyncWriteExt;
|
||||||
|
use tracing::{debug, warn};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
IdeviceError, ReadWrite,
|
IdeviceError, ReadWrite,
|
||||||
@@ -66,6 +66,7 @@ pub const INSTRUMENTS_MESSAGE_TYPE: u32 = 2;
|
|||||||
///
|
///
|
||||||
/// Manages multiple communication channels and handles message serialization/deserialization.
|
/// Manages multiple communication channels and handles message serialization/deserialization.
|
||||||
/// Each channel operates independently and maintains its own message queue.
|
/// Each channel operates independently and maintains its own message queue.
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct RemoteServerClient<R: ReadWrite> {
|
pub struct RemoteServerClient<R: ReadWrite> {
|
||||||
/// The underlying device connection
|
/// The underlying device connection
|
||||||
idevice: R,
|
idevice: R,
|
||||||
@@ -80,6 +81,7 @@ pub struct RemoteServerClient<R: ReadWrite> {
|
|||||||
/// Handle to a specific communication channel
|
/// Handle to a specific communication channel
|
||||||
///
|
///
|
||||||
/// Provides channel-specific operations for use on the remote server client.
|
/// Provides channel-specific operations for use on the remote server client.
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct Channel<'a, R: ReadWrite> {
|
pub struct Channel<'a, R: ReadWrite> {
|
||||||
/// Reference to parent client
|
/// Reference to parent client
|
||||||
client: &'a mut RemoteServerClient<R>,
|
client: &'a mut RemoteServerClient<R>,
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user