Merge branch 'master' into rppairing

a
This commit is contained in:
Jackson Coxson
2025-09-05 11:58:05 -06:00
166 changed files with 13521 additions and 2544 deletions

241
.github/workflows/ci.yml vendored Normal file
View File

@@ -0,0 +1,241 @@
name: CI
on:
push:
branches: [master]
pull_request:
jobs:
macos:
name: macOS Build
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
with:
submodules: "true"
- name: Install just
run: |
brew install just
- name: Cache Cargo
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: macos-cargo-${{ hashFiles('**/Cargo.lock', 'justfile') }}
restore-keys: macos-cargo-
- name: Install rustup targets
run: |
rustup target add aarch64-apple-ios && rustup target add x86_64-apple-ios && \
rustup target add aarch64-apple-ios-sim && rustup target add aarch64-apple-darwin && \
rustup target add x86_64-apple-darwin && cargo install --force --locked bindgen-cli
- name: Build all Apple targets and examples/tools
run: |
just macos-ci-check
- name: Upload static libraries
uses: actions/upload-artifact@v4
with:
name: libidevice-macos-a
path: |
target/*apple*/release/libidevice_ffi.a
- name: Upload macOS+iOS XCFramework
uses: actions/upload-artifact@v4
with:
name: idevice-xcframework
path: swift/bundle.zip
- name: Upload C examples/tools
uses: actions/upload-artifact@v4
with:
name: idevice-c-examples-macos
path: ffi/examples/build/bin/*
- name: Upload C++ examples/tools
uses: actions/upload-artifact@v4
with:
name: idevice-cpp-examples-macos
path: cpp/examples/build/bin/*
- name: Stage Rust tools (arm64)
shell: bash
run: |
mkdir -p dist/arm64
find target/release -maxdepth 1 -type f -exec sh -c '
for f in "$@"; do
if file "$f" | grep -Eq "Mach-O .* executable|ELF .* executable"; then
cp "$f" dist/arm64/
fi
done
' sh {} +
- name: Upload Rust tools (arm64)
uses: actions/upload-artifact@v4
with:
name: idevice-tools-macos-arm
path: dist/arm64/*
if-no-files-found: error
- name: Stage Rust tools (x64)
shell: bash
run: |
mkdir -p dist/x64
find target/x86_64-apple-darwin/release -maxdepth 1 -type f -exec sh -c '
for f in "$@"; do
if file "$f" | grep -Eq "Mach-O .* executable|ELF .* executable"; then
cp "$f" dist/x64/
fi
done
' sh {} +
- name: Upload Rust tools (x64)
uses: actions/upload-artifact@v4
with:
name: idevice-tools-macos-intel
path: dist/x64/*
if-no-files-found: error
linux:
name: Linux Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: "true"
- name: Install build dependencies
run: |
sudo apt-get update && sudo apt-get install -y build-essential cmake
- name: Install just
run: |
curl -sL https://just.systems/install.sh | bash -s -- --to ~/.cargo/bin
echo "$HOME/.cargo/bin" >> $GITHUB_PATH
- name: Cache Cargo
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: linux-cargo-${{ hashFiles('**/Cargo.lock', 'justfile') }}
restore-keys: linux-cargo-
- name: Build Rust and examples/tools
run: |
just ci-check
- name: Upload static library
uses: actions/upload-artifact@v4
with:
name: libidevice-linux-a
path: target/release/libidevice_ffi.a
- name: Upload headers
uses: actions/upload-artifact@v4
with:
name: idevice-headers
path: ffi/idevice.h
- name: Upload C examples/tools
uses: actions/upload-artifact@v4
with:
name: idevice-c-examples-linux
path: ffi/examples/build/bin/*
- name: Upload C++ examples/tools
uses: actions/upload-artifact@v4
with:
name: idevice-cpp-examples-linux
path: cpp/examples/build/bin/*
- name: Stage Rust tools (linux)
shell: bash
run: |
mkdir -p dist
find target/release -maxdepth 1 -type f -exec sh -c '
for f in "$@"; do
if file "$f" | grep -Eq "ELF .* executable"; then
cp "$f" dist/
fi
done
' sh {} +
- name: Upload Rust tools
uses: actions/upload-artifact@v4
with:
name: idevice-tools-linux
path: dist/*
if-no-files-found: error
windows:
name: Windows Build
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
with:
submodules: "true"
# Install Rust (adds cargo/rustc to PATH)
- name: Install Rust (stable)
uses: dtolnay/rust-toolchain@stable
with:
targets: x86_64-pc-windows-msvc
# Use Scoop to install just (reuse your existing scoop setup)
- uses: MinoruSekine/setup-scoop@v4.0.2
with:
buckets: main extras
apps: just doxygen plantuml
# (Paranoid) ensure shims and cargo bin are on PATH for subsequent steps
- name: Ensure tools on PATH
shell: pwsh
run: |
echo "$env:USERPROFILE\scoop\shims" >> $env:GITHUB_PATH
echo "$env:USERPROFILE\.cargo\bin" >> $env:GITHUB_PATH
- name: Cache Cargo
uses: actions/cache@v4
with:
path: |
~\AppData\Local\cargo\registry
~\AppData\Local\cargo\git
target
key: windows-cargo-${{ hashFiles('**/Cargo.lock', 'justfile') }}
restore-keys: windows-cargo-
- name: Build Rust and examples/tools
run: just windows-ci-check
- name: Upload static library
uses: actions/upload-artifact@v4
with:
name: libidevice-windows-a
path: target\release\idevice_ffi.lib
- name: Upload C++ examples/tools
uses: actions/upload-artifact@v4
with:
name: idevice-cpp-examples-windows
path: cpp\examples\build\bin\*
- name: Stage Rust tools (windows)
shell: pwsh
run: |
New-Item -ItemType Directory -Force -Path dist | Out-Null
Get-ChildItem target\release\*.exe -File | Copy-Item -Destination dist
- name: Upload Rust tools
uses: actions/upload-artifact@v4
with:
name: idevice-tools-windows
path: dist\*.exe
if-no-files-found: error

6
.gitignore vendored
View File

@@ -5,6 +5,7 @@ Image.dmg.trustcache
.DS_Store .DS_Store
*.pcap *.pcap
idevice.h idevice.h
plist.h
/ffi/examples/build /ffi/examples/build
/.cache /.cache
/ffi/examples/.cache /ffi/examples/.cache
@@ -16,3 +17,8 @@ bundle.zip
/swift/include/*.h /swift/include/*.h
/swift/.build /swift/.build
/swift/.swiftpm /swift/.swiftpm
xcuserdata
._*
*.vcxproj.user
.vs

3
.gitmodules vendored
View File

@@ -1,3 +1,6 @@
[submodule "ffi/libplist"] [submodule "ffi/libplist"]
path = ffi/libplist path = ffi/libplist
url = https://github.com/libimobiledevice/libplist.git url = https://github.com/libimobiledevice/libplist.git
[submodule "cpp/plist_ffi"]
path = cpp/plist_ffi
url = https://github.com/jkcoxson/plist_ffi.git

1269
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,10 @@
[workspace] [workspace]
resolver = "2" resolver = "2"
members = [ "ffi","idevice", "tools"] members = ["ffi", "idevice", "tools"]
[profile.release]
opt-level = "z"
codegen-units = 1
lto = true
panic = "abort"

View File

@@ -28,20 +28,22 @@ To keep dependency bloat and compile time down, everything is contained in featu
| `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.|
| `diagnostics_relay` | Access device diagnostics information (IORegistry, MobileGestalt, battery, NAND, device control).|
| `dvt` | Access Apple developer tools (e.g. Instruments).| | `dvt` | Access Apple developer tools (e.g. Instruments).|
| `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.| | `springboardservices` | Control SpringBoard (e.g. UI interactions). Partial support.|
| `misagent` | Manage provisioning profiles on the device.| | `misagent` | Manage provisioning profiles on the device.|
| `mobilebackup2` | Manage backups.|
| `mobile_image_mounter` | Manage DDI images.| | `mobile_image_mounter` | Manage DDI images.|
| `location_simulation` | Simulate GPS locations on the device.| | `location_simulation` | Simulate GPS locations on the device.|
| `pair` | Pair the device.| | `pair` | Pair the device.|
| `syslog_relay` | Relay system logs from the device | | `syslog_relay` | Relay system 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 Apples 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. |

32
cpp/.clang-format Normal file
View File

@@ -0,0 +1,32 @@
# .clang-format
BasedOnStyle: LLVM
IndentWidth: 4
TabWidth: 4
UseTab: Never
ColumnLimit: 100
BreakBeforeBraces: Attach
AllowShortFunctionsOnASingleLine: InlineOnly
AllowShortIfStatementsOnASingleLine: Never
AllowShortLoopsOnASingleLine: false
AllowShortCaseLabelsOnASingleLine: false
AlwaysBreakAfterReturnType: None
AlignConsecutiveAssignments: AcrossEmptyLinesAndComments
AlignConsecutiveDeclarations: AcrossEmptyLinesAndComments
AlignTrailingComments: true
SortIncludes: CaseInsensitive
IncludeBlocks: Preserve
SpaceBeforeParens: ControlStatements
SpacesInParentheses: false
SpacesInSquareBrackets: false
SpacesInAngles: Never
SpaceAfterCStyleCast: true
Cpp11BracedListStyle: true
BreakBeforeBinaryOperators: NonAssignment
ReflowComments: true
PointerAlignment: Left
BinPackArguments: false
BinPackParameters: false
AllowShortBlocksOnASingleLine: Never
MacroBlockBegin: "^#define"
MacroBlockEnd: "^#undef"
InsertBraces: true

3
cpp/.clangd Normal file
View File

@@ -0,0 +1,3 @@
CompileFlags:
Add: ["-I/usr/local/include/"]
CompilationDatabase: "examples/build"

119
cpp/CMakeLists.txt Normal file
View File

@@ -0,0 +1,119 @@
cmake_minimum_required(VERSION 3.16)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
# Project
project(idevice++
VERSION 0.1.0
LANGUAGES CXX
)
# ---- Options ---------------------------------------------------------------
# Path to the Rust static library (override on the command line if needed):
# cmake -DIDEVICE_FFI_PATH=/absolute/path/to/libidevice_ffi.a ..
set(IDEVICE_FFI_PATH
"${CMAKE_CURRENT_SOURCE_DIR}/../target/release/libidevice_ffi.a"
CACHE FILEPATH "Path to libidevice_ffi.a produced by Rust build"
)
# Optional extra libraries/frameworks if your Rust/C bridge needs them:
# e.g. -framework CoreFoundation on macOS, etc.
set(IDEVICEPP_EXTRA_LIBS
""
CACHE STRING "Extra libraries to link (space-separated)"
)
# ---- Tooling ---------------------------------------------------------------
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_POSITION_INDEPENDENT_CODE ON) # Helpful for shared libs
# Threads
find_package(Threads REQUIRED)
# On some platforms dl is required for Rust staticlibs that use libdl
include(CheckLibraryExists)
set(HAVE_LIBDL FALSE)
if(UNIX AND NOT APPLE)
check_library_exists(dl dlopen "" HAVE_LIBDL)
endif()
# ---- Imported Rust static library ------------------------------------------
if(NOT EXISTS "${IDEVICE_FFI_PATH}")
message(FATAL_ERROR "IDEVICE_FFI_PATH does not exist: ${IDEVICE_FFI_PATH}")
endif()
add_library(idevice_ffi STATIC IMPORTED GLOBAL)
set_target_properties(idevice_ffi PROPERTIES
IMPORTED_LOCATION "${IDEVICE_FFI_PATH}"
)
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/../ffi/idevice.h" "${CMAKE_CURRENT_SOURCE_DIR}/include" COPYONLY)
# ---- Our C++ library -------------------------------------------------------
# Collect sources (convenience: tracks new files when you re-run CMake)
file(GLOB_RECURSE IDEVICEPP_SOURCES
CONFIGURE_DEPENDS
"${CMAKE_CURRENT_SOURCE_DIR}/src/*.cc"
"${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/src/*.cxx"
)
# Respect BUILD_SHARED_LIBS (OFF=static, ON=shared)
add_library(${PROJECT_NAME})
target_sources(${PROJECT_NAME} PRIVATE ${IDEVICEPP_SOURCES})
# Public headers live under include/, e.g. include/idevice++/Foo.hpp
target_include_directories(${PROJECT_NAME}
PUBLIC
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>"
"$<INSTALL_INTERFACE:include>"
)
# Link dependencies
target_link_libraries(${PROJECT_NAME}
PUBLIC
idevice_ffi
Threads::Threads
)
# Link libdl on Linux if present
if(HAVE_LIBDL)
target_link_libraries(${PROJECT_NAME} PUBLIC dl)
endif()
# Windows winsock (if you ever build there)
if(WIN32)
target_link_libraries(${PROJECT_NAME} PUBLIC ws2_32)
endif()
# Extra user-specified libs/frameworks
if(IDEVICEPP_EXTRA_LIBS)
# Split by spaces and link each one
separate_arguments(_extra_libs NATIVE_COMMAND ${IDEVICEPP_EXTRA_LIBS})
target_link_libraries(${PROJECT_NAME} PUBLIC ${_extra_libs})
endif()
# Visibility / warnings (tweak to taste)
if(MSVC)
target_compile_options(${PROJECT_NAME} PRIVATE /permissive- /W4 /WX-)
else()
target_compile_options(${PROJECT_NAME} PRIVATE -Wall -Wextra -Wpedantic)
endif()
# ---- Examples (optional) ---------------------------------------------------
option(BUILD_EXAMPLES "Build examples in examples/ directory" OFF)
if(BUILD_EXAMPLES)
file(GLOB EXAMPLE_SOURCES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/examples/*.cpp")
foreach(ex_src IN LISTS EXAMPLE_SOURCES)
get_filename_component(exe_name "${ex_src}" NAME_WE)
add_executable(${exe_name} "${ex_src}")
target_link_libraries(${exe_name} PRIVATE ${PROJECT_NAME})
target_include_directories(${exe_name} PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/include")
endforeach()
endif()

107
cpp/examples/CMakeLists.txt Normal file
View File

@@ -0,0 +1,107 @@
# Jackson Coxson
cmake_minimum_required(VERSION 3.15)
project(IdeviceFFI CXX)
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
# Paths
set(IDEVICE_CPP_INCLUDE_DIR ${CMAKE_SOURCE_DIR}/../include) # public C++ headers
set(IDEVICE_CPP_SRC_DIR ${CMAKE_SOURCE_DIR}/../src) # C++ .cpp files
set(IDEVICE_FFI_INCLUDE_DIR ${CMAKE_SOURCE_DIR}/../../ffi) # Rust FFI headers (idevice.h)
set(PLIST_CPP_INCLUDE_DIR ${CMAKE_SOURCE_DIR}/../plist_ffi/cpp/include)
set(PLIST_CPP_SRC_DIR ${CMAKE_SOURCE_DIR}/../plist_ffi/cpp/src)
if (MSVC)
set(STATIC_LIB ${CMAKE_SOURCE_DIR}/../../target/release/idevice_ffi.lib)
else()
set(STATIC_LIB ${CMAKE_SOURCE_DIR}/../../target/release/libidevice_ffi.a)
endif()
set(EXAMPLES_DIR ${CMAKE_SOURCE_DIR}/../examples)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
# Warnings
if (MSVC)
add_compile_options(/W4 /permissive- /EHsc)
else()
add_compile_options(-Wall -Wextra -Wpedantic)
endif()
# ---- Build the C++ wrapper library -----------------------------------------
# Collect your .cpps
file(GLOB IDEVICE_CPP_SOURCES
${IDEVICE_CPP_SRC_DIR}/*.cpp
)
file(GLOB PLIST_CPP_SOURCES
${PLIST_CPP_SRC_DIR}/*.cpp
)
add_library(idevice_cpp STATIC ${IDEVICE_CPP_SOURCES} ${PLIST_CPP_SOURCES})
target_include_directories(idevice_cpp
PUBLIC
${IDEVICE_CPP_INCLUDE_DIR}
${PLIST_CPP_INCLUDE_DIR}
PRIVATE
${IDEVICE_FFI_INCLUDE_DIR}
${PLIST_CPP_INCLUDE_DIR}
)
# Link to the Rust static lib (+ system libs/frameworks). Mark as PUBLIC so dependents inherit.
target_link_libraries(idevice_cpp
PUBLIC
${STATIC_LIB}
)
# Unix-y extras frequently required by Rust static libs
if (UNIX AND NOT APPLE)
find_package(Threads REQUIRED)
target_link_libraries(idevice_cpp PUBLIC Threads::Threads dl m)
endif()
# Apple frameworks (propagate to dependents)
if (APPLE)
target_link_libraries(idevice_cpp PUBLIC
"-framework CoreFoundation"
"-framework Security"
"-framework SystemConfiguration"
"-framework CoreServices"
"-framework IOKit"
"-framework CFNetwork"
)
endif()
if (WIN32)
target_link_libraries(idevice_cpp PUBLIC
ws2_32
userenv
ntdll
bcrypt
)
endif()
set_target_properties(idevice_cpp PROPERTIES POSITION_INDEPENDENT_CODE ON)
# ---- Build each example and link against idevice_cpp ------------------------
file(GLOB EXAMPLE_SOURCES ${EXAMPLES_DIR}/*.cpp)
foreach(EXAMPLE_FILE ${EXAMPLE_SOURCES})
get_filename_component(EXAMPLE_NAME ${EXAMPLE_FILE} NAME_WE)
add_executable(${EXAMPLE_NAME} ${EXAMPLE_FILE})
# Examples include public headers and (if they directly include FFI headers) the FFI dir.
target_include_directories(${EXAMPLE_NAME} PRIVATE
${IDEVICE_CPP_INCLUDE_DIR}
${IDEVICE_FFI_INCLUDE_DIR}
${PLIST_CPP_INCLUDE_DIR}
)
# Link to your C++ wrapper (inherits Rust lib + frameworks/system libs)
target_link_libraries(${EXAMPLE_NAME} PRIVATE idevice_cpp)
endforeach()

View File

@@ -0,0 +1,211 @@
// Jackson Coxson
#include <cstdlib>
#include <fstream>
#include <iostream>
#include <string>
#include <vector>
#include <idevice++/app_service.hpp>
#include <idevice++/core_device_proxy.hpp>
#include <idevice++/ffi.hpp>
#include <idevice++/option.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 << "(" << e.code << ")\n";
std::exit(1);
}
static void usage(const char* argv0) {
std::cerr << "Usage:\n"
<< " " << argv0 << " list\n"
<< " " << argv0 << " launch <bundle_id>\n"
<< " " << argv0 << " processes\n"
<< " " << argv0 << " uninstall <bundle_id>\n"
<< " " << argv0 << " signal <pid> <signal>\n"
<< " " << argv0 << " icon <bundle_id> <save_path> [hw=1.0] [scale=1.0]\n";
}
int main(int argc, char** argv) {
if (argc < 2) {
usage(argv[0]);
return 2;
}
std::string cmd = argv[1];
FfiError err;
// 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 = "app_service-jkcoxson";
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) AppService over RSD (borrows adapter + handshake)
auto app = AppService::connect_rsd(adapter, rsd).unwrap_or_else([&](FfiError e) -> AppService {
die("failed to connect AppService", e); // never returns
});
// 8) Commands
if (cmd == "list") {
auto apps = app.list_apps(/*app_clips*/ true,
/*removable*/ true,
/*hidden*/ true,
/*internal*/ true,
/*default_apps*/ true)
.unwrap_or_else(
[](FfiError e) -> std::vector<AppInfo> { die("list_apps failed", e); });
for (const auto& a : apps) {
std::cout << "- " << a.bundle_identifier << " | name=" << a.name << " | version="
<< (a.version.is_some() ? a.version.unwrap() : std::string("<none>"))
<< " | dev=" << (a.is_developer_app ? "y" : "n")
<< " | hidden=" << (a.is_hidden ? "y" : "n") << "\n";
}
return 0;
} else if (cmd == "launch") {
if (argc < 3) {
std::cerr << "No bundle ID passed\n";
return 2;
}
std::string bundle_id = argv[2];
std::vector<std::string> args; // empty in this example
auto resp =
app.launch(bundle_id,
args,
/*kill_existing*/ false,
/*start_suspended*/ false)
.unwrap_or_else([](FfiError e) -> LaunchResponse { die("launch failed", e); });
std::cout << "Launched pid=" << resp.pid << " exe=" << resp.executable_url
<< " piv=" << resp.process_identifier_version
<< " audit_token_len=" << resp.audit_token.size() << "\n";
return 0;
} else if (cmd == "processes") {
auto procs = app.list_processes().unwrap_or_else(
[](FfiError e) -> std::vector<ProcessToken> { die("list_processes failed", e); });
for (const auto& p : procs) {
std::cout << p.pid << " : "
<< (p.executable_url.is_some() ? p.executable_url.unwrap()
: std::string("<none>"))
<< "\n";
}
return 0;
} else if (cmd == "uninstall") {
if (argc < 3) {
std::cerr << "No bundle ID passed\n";
return 2;
}
std::string bundle_id = argv[2];
app.uninstall(bundle_id).expect("Uninstall failed");
std::cout << "Uninstalled " << bundle_id << "\n";
return 0;
} else if (cmd == "signal") {
if (argc < 4) {
std::cerr << "Usage: signal <pid> <signal>\n";
return 2;
}
uint32_t pid = static_cast<uint32_t>(std::stoul(argv[2]));
uint32_t signal = static_cast<uint32_t>(std::stoul(argv[3]));
auto res = app.send_signal(pid, signal).unwrap_or_else([](FfiError e) -> SignalResponse {
die("send_signal failed", e);
});
std::cout << "Signaled pid=" << res.pid << " signal=" << res.signal
<< " ts_ms=" << res.device_timestamp_ms << " exe="
<< (res.executable_url.is_some() ? res.executable_url.unwrap()
: std::string("<none>"))
<< "\n";
return 0;
} else if (cmd == "icon") {
if (argc < 4) {
std::cerr << "Usage: icon <bundle_id> <save_path> [hw=1.0] [scale=1.0]\n";
return 2;
}
std::string bundle_id = argv[2];
std::string save_path = argv[3];
float hw = (argc >= 5) ? std::stof(argv[4]) : 1.0f;
float scale = (argc >= 6) ? std::stof(argv[5]) : 1.0f;
auto icon =
app.fetch_icon(bundle_id, hw, hw, scale, /*allow_placeholder*/ true)
.unwrap_or_else([](FfiError e) -> IconData { die("fetch_app_icon failed", e); });
std::ofstream out(save_path, std::ios::binary);
if (!out) {
std::cerr << "Failed to open " << save_path << " for writing\n";
return 1;
}
out.write(reinterpret_cast<const char*>(icon.data.data()),
static_cast<std::streamsize>(icon.data.size()));
out.close();
std::cout << "Saved icon to " << save_path << " (" << icon.data.size() << " bytes, "
<< icon.icon_width << "x" << icon.icon_height << ", min " << icon.minimum_width
<< "x" << icon.minimum_height << ")\n";
return 0;
} else {
usage(argv[0]);
return 2;
}
}

View File

@@ -0,0 +1,128 @@
// Jackson Coxson
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
#include <idevice++/core_device_proxy.hpp>
#include <idevice++/debug_proxy.hpp>
#include <idevice++/ffi.hpp>
#include <idevice++/option.hpp>
#include <idevice++/provider.hpp>
#include <idevice++/rsd.hpp>
#include <idevice++/usbmuxd.hpp>
using namespace IdeviceFFI;
[[noreturn]]
static void die(const char* msg, const IdeviceFFI::FfiError& e) {
std::cerr << msg << ": " << e.message << "\n";
std::exit(1);
}
static std::vector<std::string> split_args(const std::string& line) {
std::istringstream iss(line);
std::vector<std::string> toks;
std::string tok;
while (iss >> tok) {
toks.push_back(tok);
}
return toks;
}
int main() {
IdeviceFFI::FfiError err;
// 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 = "app_service-jkcoxson";
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");
// 6) DebugProxy over RSD
auto dbg =
IdeviceFFI::DebugProxy::connect_rsd(adapter, rsd).expect("failed to connect DebugProxy");
std::cout << "Shell connected! Type 'exit' to quit.\n";
for (;;) {
std::cout << "> " << std::flush;
std::string line;
if (!std::getline(std::cin, line)) {
break;
}
// trim
auto first = line.find_first_not_of(" \t\r\n");
if (first == std::string::npos) {
continue;
}
auto last = line.find_last_not_of(" \t\r\n");
line = line.substr(first, last - first + 1);
if (line == "exit") {
break;
}
// Interpret: first token = command name, rest = argv
auto toks = split_args(line);
if (toks.empty()) {
continue;
}
std::string name = toks.front();
std::vector<std::string> argv(toks.begin() + 1, toks.end());
auto res = dbg.send_command(name, argv);
match_result(
res,
ok_value,
{ if_let_some(ok_value, some_value, { std::cout << some_value << "\n"; }); },
err_value,
{ std::cerr << "send_command failed: " << err_value.message << "\n"; });
}
return 0;
}

View File

@@ -0,0 +1,127 @@
// Jackson Coxson
#include <cstdint>
#include <fstream>
#include <iostream>
#include <string>
#include <idevice++/bindings.hpp>
#include <idevice++/core_device_proxy.hpp>
#include <idevice++/diagnosticsservice.hpp>
#include <idevice++/ffi.hpp>
#include <idevice++/provider.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;
if (e) {
std::cerr << ": " << e.message;
}
std::cerr << "\n";
std::exit(1);
}
int main() {
idevice_init_logger(Debug, Disabled, NULL);
FfiError err;
// 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 = "app_service-jkcoxson";
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) DebugProxy over RSD
auto diag =
DiagnosticsService::connect_rsd(adapter, rsd).expect("failed to connect DebugProxy");
std::cout << "Getting sysdiagnose, this takes a while! iOS is slow...\n";
auto cap = diag.capture_sysdiagnose(/*dry_run=*/false).expect("capture_sysdiagnose failed");
std::cout << "Got sysdiagnose! Saving to file: " << cap.preferred_filename << "\n";
// 7) Stream to file with progress
std::ofstream out(cap.preferred_filename, std::ios::binary);
if (!out) {
std::cerr << "failed to open output file\n";
return 1;
}
std::size_t written = 0;
const std::size_t total = cap.expected_length;
for (;;) {
auto chunk = cap.stream.next_chunk();
match_result(
chunk,
res,
{
if_let_some(res, chunk_res, {
out.write(reinterpret_cast<const char*>(chunk_res.data()),
static_cast<std::streamsize>(chunk_res.size()));
if (!out) {
std::cerr << "write failed\n";
return 1;
}
written += chunk_res.size();
});
if (res.is_none()) {
break;
}
},
err,
{ die("stream error", err); });
std::cout << "wrote " << written << "/" << total << " bytes\r" << std::flush;
}
out.flush();
std::cout << "\nDone! Saved to " << cap.preferred_filename << "\n";
return 0;
}

View File

@@ -0,0 +1,23 @@
// Jackson Coxson
#include <idevice++/usbmuxd.hpp>
#include <iostream>
int main() {
auto u = IdeviceFFI::UsbmuxdConnection::default_new(0).expect("failed to connect to usbmuxd");
auto devices = u.get_devices().expect("failed to get devices from usbmuxd");
for (IdeviceFFI::UsbmuxdDevice& d : devices) {
auto udid = d.get_udid();
if (udid.is_none()) {
std::cerr << "failed to get udid";
continue;
}
auto connection_type = d.get_connection_type();
if (connection_type.is_none()) {
std::cerr << "failed to get connection type";
continue;
}
std::cout << udid.unwrap() << " (" << connection_type.unwrap().to_string() << ")" << "\n";
}
}

View File

@@ -0,0 +1,58 @@
// Jackson Coxson
#include <idevice++/lockdown.hpp>
#include <idevice++/provider.hpp>
#include <idevice++/usbmuxd.hpp>
#include <iostream>
#include <plist/plist++.h>
int main() {
idevice_init_logger(Debug, Disabled, NULL);
auto u = IdeviceFFI::UsbmuxdConnection::default_new(0).expect("failed to connect to usbmuxd");
auto devices = u.get_devices().expect("failed to get devices from usbmuxd");
if (devices.empty()) {
std::cerr << "no devices connected";
return 1;
}
auto& dev = (devices)[0];
auto udid = dev.get_udid();
if (udid.is_none()) {
std::cerr << "no udid\n";
return 1;
}
auto id = dev.get_id();
if (id.is_none()) {
std::cerr << "no id\n";
return 1;
}
IdeviceFFI::UsbmuxdAddr addr = IdeviceFFI::UsbmuxdAddr::default_new();
auto prov = IdeviceFFI::Provider::usbmuxd_new(
std::move(addr), /*tag*/ 0, udid.unwrap(), id.unwrap(), "reeeeeeeee")
.expect("Failed to create usbmuxd provider");
auto client = IdeviceFFI::Lockdown::connect(prov).expect("lockdown connect failed");
auto pf = prov.get_pairing_file().expect("failed to get pairing file");
client.start_session(pf).expect("failed to start session");
auto values = client.get_value(NULL, NULL);
match_result(
values,
ok_val,
{
PList::Dictionary res = PList::Dictionary(ok_val);
std::cout << res.ToXml();
},
e,
{
std::cerr << "get values failed: " << e.message << "\n";
return 1;
});
}

View File

@@ -0,0 +1,114 @@
// Jackson Coxson
#include <cstdlib>
#include <iostream>
#include <string>
#include <thread>
#include <idevice++/core_device_proxy.hpp>
#include <idevice++/ffi.hpp>
#include <idevice++/location_simulation.hpp>
#include <idevice++/provider.hpp>
#include <idevice++/readwrite.hpp>
#include <idevice++/remote_server.hpp>
#include <idevice++/rsd.hpp>
#include <idevice++/usbmuxd.hpp>
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) {
// Usage:
// simulate_location clear
// simulate_location set <lat> <lon>
bool do_clear = false;
Option<double> lat, lon;
if (argc == 2 && std::string(argv[1]) == "clear") {
do_clear = true;
} else if (argc == 4 && std::string(argv[1]) == "set") {
lat = std::stod(argv[2]);
lon = std::stod(argv[3]);
} else {
std::cerr << "Usage:\n"
<< " " << argv[0] << " clear\n"
<< " " << argv[0] << " set <latitude> <longitude>\n";
return 2;
}
// 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 = "app_service-jkcoxson";
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");
// 8) RemoteServer over RSD (borrows adapter + handshake)
auto rs = RemoteServer::connect_rsd(adapter, rsd).expect("failed to connect to RemoteServer");
// 9) LocationSimulation client (borrows RemoteServer)
auto sim = LocationSimulation::create(rs).expect("failed to create LocationSimulation client");
if (do_clear) {
sim.clear().expect("clear failed");
std::cout << "Location cleared!\n";
return 0;
}
// set path
sim.set(lat.unwrap(), lon.unwrap()).expect("set failed");
std::cout << "Location set to (" << lat.unwrap() << ", " << lon.unwrap() << ")\n";
std::cout << "Press Ctrl-C to stop\n";
// keep process alive like the Rust example
for (;;) {
sim.set(lat.unwrap(), lon.unwrap()).expect("set failed");
std::this_thread::sleep_for(std::chrono::seconds(3));
}
}

View File

@@ -0,0 +1,521 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 77;
objects = {
/* Begin PBXBuildFile section */
1914C7972E623CC2002EAB6E /* option.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 1914C7962E623CC2002EAB6E /* option.hpp */; };
1914C7992E623CC8002EAB6E /* result.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 1914C7982E623CC8002EAB6E /* result.hpp */; };
198077932E5CA6EF00CB501E /* adapter_stream.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1980776C2E5CA69800CB501E /* adapter_stream.cpp */; };
198077942E5CA6EF00CB501E /* app_service.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1980776D2E5CA69800CB501E /* app_service.cpp */; };
198077952E5CA6EF00CB501E /* core_device.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1980776E2E5CA69800CB501E /* core_device.cpp */; };
198077962E5CA6EF00CB501E /* debug_proxy.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1980776F2E5CA69800CB501E /* debug_proxy.cpp */; };
198077972E5CA6EF00CB501E /* diagnosticsservice.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 198077702E5CA69800CB501E /* diagnosticsservice.cpp */; };
198077982E5CA6EF00CB501E /* ffi.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 198077712E5CA69800CB501E /* ffi.cpp */; };
198077992E5CA6EF00CB501E /* idevice.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 198077722E5CA69800CB501E /* idevice.cpp */; };
1980779A2E5CA6EF00CB501E /* location_simulation.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 198077732E5CA69800CB501E /* location_simulation.cpp */; };
1980779B2E5CA6EF00CB501E /* lockdown.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 198077742E5CA69800CB501E /* lockdown.cpp */; };
1980779C2E5CA6EF00CB501E /* pairing_file.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 198077752E5CA69800CB501E /* pairing_file.cpp */; };
1980779D2E5CA6EF00CB501E /* provider.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 198077762E5CA69800CB501E /* provider.cpp */; };
1980779E2E5CA6EF00CB501E /* remote_server.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 198077772E5CA69800CB501E /* remote_server.cpp */; };
1980779F2E5CA6EF00CB501E /* rsd.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 198077782E5CA69800CB501E /* rsd.cpp */; };
198077A02E5CA6EF00CB501E /* tcp_callback_feeder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 198077792E5CA69800CB501E /* tcp_callback_feeder.cpp */; };
198077A12E5CA6EF00CB501E /* usbmuxd.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1980777A2E5CA69800CB501E /* usbmuxd.cpp */; };
198077B72E5CA6FC00CB501E /* core_device_proxy.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 198077A62E5CA6FC00CB501E /* core_device_proxy.hpp */; };
198077B82E5CA6FC00CB501E /* pairing_file.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 198077AD2E5CA6FC00CB501E /* pairing_file.hpp */; };
198077B92E5CA6FC00CB501E /* bindings.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 198077A52E5CA6FC00CB501E /* bindings.hpp */; };
198077BA2E5CA6FC00CB501E /* diagnosticsservice.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 198077A82E5CA6FC00CB501E /* diagnosticsservice.hpp */; };
198077BB2E5CA6FC00CB501E /* idevice.h in Headers */ = {isa = PBXBuildFile; fileRef = 198077B52E5CA6FC00CB501E /* idevice.h */; };
198077BC2E5CA6FC00CB501E /* debug_proxy.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 198077A72E5CA6FC00CB501E /* debug_proxy.hpp */; };
198077BD2E5CA6FC00CB501E /* ffi.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 198077A92E5CA6FC00CB501E /* ffi.hpp */; };
198077BE2E5CA6FC00CB501E /* rsd.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 198077B12E5CA6FC00CB501E /* rsd.hpp */; };
198077BF2E5CA6FC00CB501E /* tcp_object_stack.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 198077B22E5CA6FC00CB501E /* tcp_object_stack.hpp */; };
198077C02E5CA6FC00CB501E /* remote_server.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 198077B02E5CA6FC00CB501E /* remote_server.hpp */; };
198077C12E5CA6FC00CB501E /* readwrite.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 198077AF2E5CA6FC00CB501E /* readwrite.hpp */; };
198077C22E5CA6FC00CB501E /* location_simulation.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 198077AB2E5CA6FC00CB501E /* location_simulation.hpp */; };
198077C32E5CA6FC00CB501E /* adapter_stream.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 198077A32E5CA6FC00CB501E /* adapter_stream.hpp */; };
198077C42E5CA6FC00CB501E /* lockdown.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 198077AC2E5CA6FC00CB501E /* lockdown.hpp */; };
198077C52E5CA6FC00CB501E /* usbmuxd.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 198077B32E5CA6FC00CB501E /* usbmuxd.hpp */; };
198077C62E5CA6FC00CB501E /* app_service.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 198077A42E5CA6FC00CB501E /* app_service.hpp */; };
198077C72E5CA6FC00CB501E /* idevice.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 198077AA2E5CA6FC00CB501E /* idevice.hpp */; };
198077C82E5CA6FC00CB501E /* provider.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 198077AE2E5CA6FC00CB501E /* provider.hpp */; };
198077DF2E5CCA2900CB501E /* libidevice_ffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 198077DB2E5CC33000CB501E /* libidevice_ffi.a */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
1914C7962E623CC2002EAB6E /* option.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = option.hpp; sourceTree = "<group>"; };
1914C7982E623CC8002EAB6E /* result.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = result.hpp; sourceTree = "<group>"; };
1980776C2E5CA69800CB501E /* adapter_stream.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = adapter_stream.cpp; sourceTree = "<group>"; };
1980776D2E5CA69800CB501E /* app_service.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = app_service.cpp; sourceTree = "<group>"; };
1980776E2E5CA69800CB501E /* core_device.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = core_device.cpp; sourceTree = "<group>"; };
1980776F2E5CA69800CB501E /* debug_proxy.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = debug_proxy.cpp; sourceTree = "<group>"; };
198077702E5CA69800CB501E /* diagnosticsservice.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = diagnosticsservice.cpp; sourceTree = "<group>"; };
198077712E5CA69800CB501E /* ffi.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = ffi.cpp; sourceTree = "<group>"; };
198077722E5CA69800CB501E /* idevice.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = idevice.cpp; sourceTree = "<group>"; };
198077732E5CA69800CB501E /* location_simulation.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = location_simulation.cpp; sourceTree = "<group>"; };
198077742E5CA69800CB501E /* lockdown.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = lockdown.cpp; sourceTree = "<group>"; };
198077752E5CA69800CB501E /* pairing_file.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = pairing_file.cpp; sourceTree = "<group>"; };
198077762E5CA69800CB501E /* provider.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = provider.cpp; sourceTree = "<group>"; };
198077772E5CA69800CB501E /* remote_server.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = remote_server.cpp; sourceTree = "<group>"; };
198077782E5CA69800CB501E /* rsd.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = rsd.cpp; sourceTree = "<group>"; };
198077792E5CA69800CB501E /* tcp_callback_feeder.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = tcp_callback_feeder.cpp; sourceTree = "<group>"; };
1980777A2E5CA69800CB501E /* usbmuxd.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = usbmuxd.cpp; sourceTree = "<group>"; };
1980778F2E5CA6C700CB501E /* libidevice++.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libidevice++.a"; sourceTree = BUILT_PRODUCTS_DIR; };
198077A32E5CA6FC00CB501E /* adapter_stream.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = adapter_stream.hpp; sourceTree = "<group>"; };
198077A42E5CA6FC00CB501E /* app_service.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = app_service.hpp; sourceTree = "<group>"; };
198077A52E5CA6FC00CB501E /* bindings.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = bindings.hpp; sourceTree = "<group>"; };
198077A62E5CA6FC00CB501E /* core_device_proxy.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = core_device_proxy.hpp; sourceTree = "<group>"; };
198077A72E5CA6FC00CB501E /* debug_proxy.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = debug_proxy.hpp; sourceTree = "<group>"; };
198077A82E5CA6FC00CB501E /* diagnosticsservice.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = diagnosticsservice.hpp; sourceTree = "<group>"; };
198077A92E5CA6FC00CB501E /* ffi.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ffi.hpp; sourceTree = "<group>"; };
198077AA2E5CA6FC00CB501E /* idevice.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = idevice.hpp; sourceTree = "<group>"; };
198077AB2E5CA6FC00CB501E /* location_simulation.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = location_simulation.hpp; sourceTree = "<group>"; };
198077AC2E5CA6FC00CB501E /* lockdown.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = lockdown.hpp; sourceTree = "<group>"; };
198077AD2E5CA6FC00CB501E /* pairing_file.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = pairing_file.hpp; sourceTree = "<group>"; };
198077AE2E5CA6FC00CB501E /* provider.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = provider.hpp; sourceTree = "<group>"; };
198077AF2E5CA6FC00CB501E /* readwrite.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = readwrite.hpp; sourceTree = "<group>"; };
198077B02E5CA6FC00CB501E /* remote_server.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = remote_server.hpp; sourceTree = "<group>"; };
198077B12E5CA6FC00CB501E /* rsd.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = rsd.hpp; sourceTree = "<group>"; };
198077B22E5CA6FC00CB501E /* tcp_object_stack.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = tcp_object_stack.hpp; sourceTree = "<group>"; };
198077B32E5CA6FC00CB501E /* usbmuxd.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = usbmuxd.hpp; sourceTree = "<group>"; };
198077B52E5CA6FC00CB501E /* idevice.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = idevice.h; sourceTree = "<group>"; };
198077DB2E5CC33000CB501E /* libidevice_ffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libidevice_ffi.a; path = "${DESTINATION_PATH}/libidevice_ffi.a"; sourceTree = "<group>"; };
198077E02E5CD49200CB501E /* xcode_build_rust.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = xcode_build_rust.sh; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
1980778D2E5CA6C700CB501E /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
198077DF2E5CCA2900CB501E /* libidevice_ffi.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
198077562E5CA62F00CB501E = {
isa = PBXGroup;
children = (
1980777B2E5CA69800CB501E /* src */,
198077612E5CA62F00CB501E /* Products */,
198077B62E5CA6FC00CB501E /* include */,
198077D92E5CC31100CB501E /* Frameworks */,
198077E02E5CD49200CB501E /* xcode_build_rust.sh */,
);
sourceTree = "<group>";
};
198077612E5CA62F00CB501E /* Products */ = {
isa = PBXGroup;
children = (
1980778F2E5CA6C700CB501E /* libidevice++.a */,
);
name = Products;
sourceTree = "<group>";
};
1980777B2E5CA69800CB501E /* src */ = {
isa = PBXGroup;
children = (
1980776C2E5CA69800CB501E /* adapter_stream.cpp */,
1980776D2E5CA69800CB501E /* app_service.cpp */,
1980776E2E5CA69800CB501E /* core_device.cpp */,
1980776F2E5CA69800CB501E /* debug_proxy.cpp */,
198077702E5CA69800CB501E /* diagnosticsservice.cpp */,
198077712E5CA69800CB501E /* ffi.cpp */,
198077722E5CA69800CB501E /* idevice.cpp */,
198077732E5CA69800CB501E /* location_simulation.cpp */,
198077742E5CA69800CB501E /* lockdown.cpp */,
198077752E5CA69800CB501E /* pairing_file.cpp */,
198077762E5CA69800CB501E /* provider.cpp */,
198077772E5CA69800CB501E /* remote_server.cpp */,
198077782E5CA69800CB501E /* rsd.cpp */,
198077792E5CA69800CB501E /* tcp_callback_feeder.cpp */,
1980777A2E5CA69800CB501E /* usbmuxd.cpp */,
);
path = src;
sourceTree = "<group>";
};
198077B42E5CA6FC00CB501E /* idevice++ */ = {
isa = PBXGroup;
children = (
198077A32E5CA6FC00CB501E /* adapter_stream.hpp */,
198077A42E5CA6FC00CB501E /* app_service.hpp */,
198077A52E5CA6FC00CB501E /* bindings.hpp */,
198077A62E5CA6FC00CB501E /* core_device_proxy.hpp */,
198077A72E5CA6FC00CB501E /* debug_proxy.hpp */,
198077A82E5CA6FC00CB501E /* diagnosticsservice.hpp */,
198077A92E5CA6FC00CB501E /* ffi.hpp */,
198077AA2E5CA6FC00CB501E /* idevice.hpp */,
198077AB2E5CA6FC00CB501E /* location_simulation.hpp */,
198077AC2E5CA6FC00CB501E /* lockdown.hpp */,
198077AD2E5CA6FC00CB501E /* pairing_file.hpp */,
198077AE2E5CA6FC00CB501E /* provider.hpp */,
198077AF2E5CA6FC00CB501E /* readwrite.hpp */,
198077B02E5CA6FC00CB501E /* remote_server.hpp */,
198077B12E5CA6FC00CB501E /* rsd.hpp */,
198077B22E5CA6FC00CB501E /* tcp_object_stack.hpp */,
198077B32E5CA6FC00CB501E /* usbmuxd.hpp */,
1914C7962E623CC2002EAB6E /* option.hpp */,
1914C7982E623CC8002EAB6E /* result.hpp */,
);
path = "idevice++";
sourceTree = "<group>";
};
198077B62E5CA6FC00CB501E /* include */ = {
isa = PBXGroup;
children = (
198077B42E5CA6FC00CB501E /* idevice++ */,
198077B52E5CA6FC00CB501E /* idevice.h */,
);
path = include;
sourceTree = "<group>";
};
198077D92E5CC31100CB501E /* Frameworks */ = {
isa = PBXGroup;
children = (
198077DB2E5CC33000CB501E /* libidevice_ffi.a */,
);
name = Frameworks;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXHeadersBuildPhase section */
1980778B2E5CA6C700CB501E /* Headers */ = {
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
198077B72E5CA6FC00CB501E /* core_device_proxy.hpp in Headers */,
198077B82E5CA6FC00CB501E /* pairing_file.hpp in Headers */,
198077B92E5CA6FC00CB501E /* bindings.hpp in Headers */,
198077BA2E5CA6FC00CB501E /* diagnosticsservice.hpp in Headers */,
198077BB2E5CA6FC00CB501E /* idevice.h in Headers */,
198077BC2E5CA6FC00CB501E /* debug_proxy.hpp in Headers */,
198077BD2E5CA6FC00CB501E /* ffi.hpp in Headers */,
198077BE2E5CA6FC00CB501E /* rsd.hpp in Headers */,
198077BF2E5CA6FC00CB501E /* tcp_object_stack.hpp in Headers */,
198077C02E5CA6FC00CB501E /* remote_server.hpp in Headers */,
198077C12E5CA6FC00CB501E /* readwrite.hpp in Headers */,
198077C22E5CA6FC00CB501E /* location_simulation.hpp in Headers */,
198077C32E5CA6FC00CB501E /* adapter_stream.hpp in Headers */,
1914C7972E623CC2002EAB6E /* option.hpp in Headers */,
198077C42E5CA6FC00CB501E /* lockdown.hpp in Headers */,
198077C52E5CA6FC00CB501E /* usbmuxd.hpp in Headers */,
198077C62E5CA6FC00CB501E /* app_service.hpp in Headers */,
198077C72E5CA6FC00CB501E /* idevice.hpp in Headers */,
198077C82E5CA6FC00CB501E /* provider.hpp in Headers */,
1914C7992E623CC8002EAB6E /* result.hpp in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXHeadersBuildPhase section */
/* Begin PBXNativeTarget section */
1980778E2E5CA6C700CB501E /* idevice++ */ = {
isa = PBXNativeTarget;
buildConfigurationList = 198077902E5CA6C700CB501E /* Build configuration list for PBXNativeTarget "idevice++" */;
buildPhases = (
198077E22E5CD4C900CB501E /* ShellScript */,
1980778B2E5CA6C700CB501E /* Headers */,
1980778C2E5CA6C700CB501E /* Sources */,
1980778D2E5CA6C700CB501E /* Frameworks */,
);
buildRules = (
);
dependencies = (
);
name = "idevice++";
packageProductDependencies = (
);
productName = "idevice++";
productReference = 1980778F2E5CA6C700CB501E /* libidevice++.a */;
productType = "com.apple.product-type.library.static";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
198077572E5CA62F00CB501E /* Project object */ = {
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = 1;
LastSwiftUpdateCheck = 1640;
LastUpgradeCheck = 1640;
TargetAttributes = {
1980778E2E5CA6C700CB501E = {
CreatedOnToolsVersion = 16.4;
};
};
};
buildConfigurationList = 1980775A2E5CA62F00CB501E /* Build configuration list for PBXProject "idevice++" */;
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 198077562E5CA62F00CB501E;
minimizedProjectReferenceProxies = 1;
preferredProjectObjectVersion = 77;
productRefGroup = 198077612E5CA62F00CB501E /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
1980778E2E5CA6C700CB501E /* idevice++ */,
);
};
/* End PBXProject section */
/* Begin PBXShellScriptBuildPhase section */
198077E22E5CD4C900CB501E /* ShellScript */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
);
outputFileListPaths = (
);
outputPaths = (
"",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "#!/bin/sh\n./xcode_build_rust.sh\n";
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
1980778C2E5CA6C700CB501E /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
198077932E5CA6EF00CB501E /* adapter_stream.cpp in Sources */,
198077942E5CA6EF00CB501E /* app_service.cpp in Sources */,
198077952E5CA6EF00CB501E /* core_device.cpp in Sources */,
198077962E5CA6EF00CB501E /* debug_proxy.cpp in Sources */,
198077972E5CA6EF00CB501E /* diagnosticsservice.cpp in Sources */,
198077982E5CA6EF00CB501E /* ffi.cpp in Sources */,
198077992E5CA6EF00CB501E /* idevice.cpp in Sources */,
1980779A2E5CA6EF00CB501E /* location_simulation.cpp in Sources */,
1980779B2E5CA6EF00CB501E /* lockdown.cpp in Sources */,
1980779C2E5CA6EF00CB501E /* pairing_file.cpp in Sources */,
1980779D2E5CA6EF00CB501E /* provider.cpp in Sources */,
1980779E2E5CA6EF00CB501E /* remote_server.cpp in Sources */,
1980779F2E5CA6EF00CB501E /* rsd.cpp in Sources */,
198077A02E5CA6EF00CB501E /* tcp_callback_feeder.cpp in Sources */,
198077A12E5CA6EF00CB501E /* usbmuxd.cpp in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
198077672E5CA63000CB501E /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = 4FW3Q8784L;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu17;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = Debug;
};
198077682E5CA63000CB501E /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = 4FW3Q8784L;
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu17;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SWIFT_COMPILATION_MODE = wholemodule;
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = Release;
};
198077912E5CA6C700CB501E /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
CODE_SIGN_STYLE = Automatic;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = 4FW3Q8784L;
EXECUTABLE_PREFIX = lib;
GCC_WARN_INHIBIT_ALL_WARNINGS = NO;
GCC_WARN_SIGN_COMPARE = YES;
GCC_WARN_UNKNOWN_PRAGMAS = YES;
GCC_WARN_UNUSED_LABEL = YES;
GCC_WARN_UNUSED_PARAMETER = YES;
LIBRARY_SEARCH_PATHS = (
"${TARGET_BUILD_DIR}/**",
"$(PROJECT_DIR)/${DESTINATION_PATH}",
);
MACOSX_DEPLOYMENT_TARGET = 15.5;
OTHER_LDFLAGS = "-Wall";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = macosx;
SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator macosx watchos watchsimulator";
SUPPORTS_MACCATALYST = NO;
TARGETED_DEVICE_FAMILY = "1,2,3,4";
};
name = Debug;
};
198077922E5CA6C700CB501E /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
CODE_SIGN_STYLE = Automatic;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = 4FW3Q8784L;
EXECUTABLE_PREFIX = lib;
GCC_WARN_INHIBIT_ALL_WARNINGS = NO;
GCC_WARN_SIGN_COMPARE = YES;
GCC_WARN_UNKNOWN_PRAGMAS = YES;
GCC_WARN_UNUSED_LABEL = YES;
GCC_WARN_UNUSED_PARAMETER = YES;
LIBRARY_SEARCH_PATHS = (
"${TARGET_BUILD_DIR}/**",
"$(PROJECT_DIR)/${DESTINATION_PATH}",
);
MACOSX_DEPLOYMENT_TARGET = 15.5;
OTHER_LDFLAGS = "-Wall";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = macosx;
SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator macosx watchos watchsimulator";
SUPPORTS_MACCATALYST = NO;
TARGETED_DEVICE_FAMILY = "1,2,3,4";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
1980775A2E5CA62F00CB501E /* Build configuration list for PBXProject "idevice++" */ = {
isa = XCConfigurationList;
buildConfigurations = (
198077672E5CA63000CB501E /* Debug */,
198077682E5CA63000CB501E /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
198077902E5CA6C700CB501E /* Build configuration list for PBXNativeTarget "idevice++" */ = {
isa = XCConfigurationList;
buildConfigurations = (
198077912E5CA6C700CB501E /* Debug */,
198077922E5CA6C700CB501E /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 198077572E5CA62F00CB501E /* Project object */;
}

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>

View File

@@ -0,0 +1,67 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1640"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES"
buildArchitectures = "Automatic">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "1980778E2E5CA6C700CB501E"
BuildableName = "libidevice++.a"
BlueprintName = "idevice++"
ReferencedContainer = "container:idevice++.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "1980778E2E5CA6C700CB501E"
BuildableName = "libidevice++.a"
BlueprintName = "idevice++"
ReferencedContainer = "container:idevice++.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@@ -0,0 +1,51 @@
// Jackson Coxson
#pragma once
#include <cstddef>
#include <cstdint>
#include <idevice++/bindings.hpp>
#include <idevice++/ffi.hpp>
#include <idevice++/option.hpp>
#include <idevice++/result.hpp>
#include <vector>
struct IdeviceFfiError;
struct AdapterStreamHandle;
namespace IdeviceFFI {
// Non-owning view over a stream (must call close(); no implicit free provided)
class AdapterStream {
public:
explicit AdapterStream(AdapterStreamHandle* h) noexcept : h_(h) {}
AdapterStream(const AdapterStream&) = delete;
AdapterStream& operator=(const AdapterStream&) = delete;
AdapterStream(AdapterStream&& other) noexcept : h_(other.h_) { other.h_ = nullptr; }
AdapterStream& operator=(AdapterStream&& other) noexcept {
if (this != &other) {
h_ = other.h_;
other.h_ = nullptr;
}
return *this;
}
~AdapterStream() noexcept = default; // no auto-close; caller controls
AdapterStreamHandle* raw() const noexcept { return h_; }
Result<void, FfiError> close();
Result<void, FfiError> send(const uint8_t* data, size_t len);
Result<void, FfiError> send(const std::vector<uint8_t>& buf) {
return send(buf.data(), buf.size());
}
// recv into caller-provided buffer (resizes to actual length)
Result<std::vector<uint8_t>, FfiError> recv(size_t max_hint = 2048);
private:
AdapterStreamHandle* h_{};
};
} // namespace IdeviceFFI

View File

@@ -0,0 +1,108 @@
// Jackson Coxson
#pragma once
#include <cstddef>
#include <cstdint>
#include <idevice++/adapter_stream.hpp>
#include <idevice++/core_device_proxy.hpp>
#include <idevice++/ffi.hpp>
#include <idevice++/readwrite.hpp>
#include <idevice++/rsd.hpp>
#include <memory>
#include <string>
#include <vector>
namespace IdeviceFFI {
using AppServicePtr =
std::unique_ptr<AppServiceHandle, FnDeleter<AppServiceHandle, app_service_free>>;
struct AppInfo {
bool is_removable{};
std::string name;
bool is_first_party{};
std::string path;
std::string bundle_identifier;
bool is_developer_app{};
Option<std::string> bundle_version;
bool is_internal{};
bool is_hidden{};
bool is_app_clip{};
Option<std::string> version;
};
struct LaunchResponse {
uint32_t process_identifier_version{};
uint32_t pid{};
std::string executable_url;
std::vector<uint32_t> audit_token; // raw words
};
struct ProcessToken {
uint32_t pid{};
Option<std::string> executable_url;
};
struct SignalResponse {
uint32_t pid{};
Option<std::string> executable_url;
uint64_t device_timestamp_ms{};
uint32_t signal{};
};
struct IconData {
std::vector<uint8_t> data;
double icon_width{};
double icon_height{};
double minimum_width{};
double minimum_height{};
};
class AppService {
public:
// Factory: connect via RSD (borrows adapter & handshake)
static Result<AppService, FfiError> connect_rsd(Adapter& adapter, RsdHandshake& rsd);
// Factory: from socket Box<dyn ReadWrite> (consumes it).
static Result<AppService, FfiError> from_readwrite_ptr(ReadWriteOpaque* consumed);
// nice ergonomic overload: consume a C++ ReadWrite by releasing it
static Result<AppService, FfiError> from_readwrite(ReadWrite&& rw);
// API
Result<std::vector<AppInfo>, FfiError>
list_apps(bool app_clips, bool removable, bool hidden, bool internal, bool default_apps) const;
Result<LaunchResponse, FfiError> launch(const std::string& bundle_id,
const std::vector<std::string>& argv,
bool kill_existing,
bool start_suspended);
Result<std::vector<ProcessToken>, FfiError> list_processes() const;
Result<void, FfiError> uninstall(const std::string& bundle_id);
Result<SignalResponse, FfiError> send_signal(uint32_t pid, uint32_t signal);
Result<IconData, FfiError> fetch_icon(const std::string& bundle_id,
float width,
float height,
float scale,
bool allow_placeholder);
// RAII / moves
~AppService() noexcept = default;
AppService(AppService&&) noexcept = default;
AppService& operator=(AppService&&) noexcept = default;
AppService(const AppService&) = delete;
AppService& operator=(const AppService&) = delete;
AppServiceHandle* raw() const noexcept { return handle_.get(); }
static AppService adopt(AppServiceHandle* h) noexcept { return AppService(h); }
private:
explicit AppService(AppServiceHandle* h) noexcept : handle_(h) {}
AppServicePtr handle_{};
};
} // namespace IdeviceFFI

View File

@@ -0,0 +1,8 @@
// Jackson Coxson
#ifndef IDEVICE_BINDINGS_H
#define IDEVICE_BINDINGS_H
extern "C" {
#include <idevice.h> // this file is generated by bindgen
}
#endif

View File

@@ -0,0 +1,104 @@
// Jackson Coxson
#ifndef IDEVICE_CORE_DEVICE_PROXY_H
#define IDEVICE_CORE_DEVICE_PROXY_H
#include <idevice++/bindings.hpp>
#include <idevice++/ffi.hpp>
#include <idevice++/option.hpp>
#include <idevice++/provider.hpp>
#include <idevice++/readwrite.hpp>
#include <idevice++/result.hpp>
namespace IdeviceFFI {
using CoreProxyPtr = std::unique_ptr<CoreDeviceProxyHandle,
FnDeleter<CoreDeviceProxyHandle, core_device_proxy_free>>;
using AdapterPtr = std::unique_ptr<AdapterHandle, FnDeleter<AdapterHandle, adapter_free>>;
struct CoreClientParams {
uint16_t mtu{};
std::string address; // freed from Rust side after copy
std::string netmask; // freed from Rust side after copy
};
class Adapter {
public:
~Adapter() noexcept = default;
Adapter(Adapter&&) noexcept = default;
Adapter& operator=(Adapter&&) noexcept = default;
Adapter(const Adapter&) = delete;
Adapter& operator=(const Adapter&) = delete;
static Adapter adopt(AdapterHandle* h) noexcept { return Adapter(h); }
AdapterHandle* raw() const noexcept { return handle_.get(); }
// Enable PCAP
Result<void, FfiError> pcap(const std::string& path) {
FfiError e(::adapter_pcap(handle_.get(), path.c_str()));
if (e) {
return Err(e);
}
return Ok();
}
// Connect to a port, returns a ReadWrite stream (to be consumed by
// RSD/CoreDeviceProxy)
Result<ReadWrite, FfiError> connect(uint16_t port) {
ReadWriteOpaque* s = nullptr;
FfiError e(::adapter_connect(handle_.get(), port, &s));
if (e) {
return Err(e);
}
return Ok(ReadWrite::adopt(s));
}
private:
explicit Adapter(AdapterHandle* h) noexcept : handle_(h) {}
AdapterPtr handle_{};
};
class CoreDeviceProxy {
public:
// Factory: connect using a Provider (NOT consumed on success or error)
static Result<CoreDeviceProxy, FfiError> connect(Provider& provider);
// Factory: from a socket; Rust consumes the socket regardless of result → we
// release before call
static Result<CoreDeviceProxy, FfiError> from_socket(Idevice&& socket);
// Send/recv
Result<void, FfiError> send(const uint8_t* data, size_t len);
Result<void, FfiError> send(const std::vector<uint8_t>& buf) {
return send(buf.data(), buf.size());
}
// recv into a pre-sized buffer; resizes to actual bytes received
Result<void, FfiError> recv(std::vector<uint8_t>& out);
// Handshake info
Result<CoreClientParams, FfiError> get_client_parameters() const;
Result<std::string, FfiError> get_server_address() const;
Result<uint16_t, FfiError> get_server_rsd_port() const;
// Consuming creation of a TCP adapter: Rust consumes the proxy handle
Result<Adapter, FfiError> create_tcp_adapter() &&;
// RAII / moves
~CoreDeviceProxy() noexcept = default;
CoreDeviceProxy(CoreDeviceProxy&&) noexcept = default;
CoreDeviceProxy& operator=(CoreDeviceProxy&&) noexcept = default;
CoreDeviceProxy(const CoreDeviceProxy&) = delete;
CoreDeviceProxy& operator=(const CoreDeviceProxy&) = delete;
CoreDeviceProxyHandle* raw() const noexcept { return handle_.get(); }
CoreDeviceProxyHandle* release() noexcept { return handle_.release(); }
static CoreDeviceProxy adopt(CoreDeviceProxyHandle* h) noexcept { return CoreDeviceProxy(h); }
private:
explicit CoreDeviceProxy(CoreDeviceProxyHandle* h) noexcept : handle_(h) {}
CoreProxyPtr handle_{};
};
} // namespace IdeviceFFI
#endif

View File

@@ -0,0 +1,120 @@
// Jackson Coxson
#pragma once
#include <cstddef>
#include <cstdint>
#include <memory>
#include <string>
#include <vector>
// Bring in the global C ABI (all C structs/functions are global)
#include <idevice++/core_device_proxy.hpp>
#include <idevice++/ffi.hpp>
#include <idevice++/result.hpp>
#include <idevice++/rsd.hpp>
namespace IdeviceFFI {
class DebugProxy {
public:
DebugProxy() = default;
DebugProxy(const DebugProxy&) = delete;
DebugProxy& operator=(const DebugProxy&) = delete;
DebugProxy(DebugProxy&& other) noexcept : handle_(other.handle_) { other.handle_ = nullptr; }
DebugProxy& operator=(DebugProxy&& other) noexcept {
if (this != &other) {
reset();
handle_ = other.handle_;
other.handle_ = nullptr;
}
return *this;
}
~DebugProxy() { reset(); }
// Factory: connect over RSD (borrows adapter & handshake; does not consume
// them)
static Result<DebugProxy, FfiError> connect_rsd(Adapter& adapter, RsdHandshake& rsd);
// Factory: consume a ReadWrite stream (fat pointer)
static Result<DebugProxy, FfiError> from_readwrite_ptr(::ReadWriteOpaque* consumed);
// Convenience: consume a C++ ReadWrite wrapper by releasing it into the ABI
static Result<DebugProxy, FfiError> from_readwrite(ReadWrite&& rw);
// API
Result<Option<std::string>, FfiError> send_command(const std::string& name,
const std::vector<std::string>& argv);
Result<Option<std::string>, FfiError> read_response();
Result<void, FfiError> send_raw(const std::vector<uint8_t>& data);
// Reads up to `len` bytes; ABI returns a heap C string (we treat as bytes →
// string)
Result<Option<std::string>, FfiError> read(std::size_t len);
// Sets argv, returns textual reply (OK/echo/etc)
Result<Option<std::string>, FfiError> set_argv(const std::vector<std::string>& argv);
Result<void, FfiError> send_ack();
Result<void, FfiError> send_nack();
// No error object in ABI; immediate effect
void set_ack_mode(bool enabled) { ::debug_proxy_set_ack_mode(handle_, enabled ? 1 : 0); }
::DebugProxyHandle* raw() const { return handle_; }
private:
explicit DebugProxy(::DebugProxyHandle* h) : handle_(h) {}
void reset() {
if (handle_) {
::debug_proxy_free(handle_);
handle_ = nullptr;
}
}
::DebugProxyHandle* handle_ = nullptr;
};
// Small helper that owns a DebugserverCommandHandle
class DebugCommand {
public:
DebugCommand() = default;
DebugCommand(const DebugCommand&) = delete;
DebugCommand& operator=(const DebugCommand&) = delete;
DebugCommand(DebugCommand&& other) noexcept : handle_(other.handle_) {
other.handle_ = nullptr;
}
DebugCommand& operator=(DebugCommand&& other) noexcept {
if (this != &other) {
reset();
handle_ = other.handle_;
other.handle_ = nullptr;
}
return *this;
}
~DebugCommand() { reset(); }
static Option<DebugCommand> make(const std::string& name, const std::vector<std::string>& argv);
::DebugserverCommandHandle* raw() const { return handle_; }
private:
explicit DebugCommand(::DebugserverCommandHandle* h) : handle_(h) {}
void reset() {
if (handle_) {
::debugserver_command_free(handle_);
handle_ = nullptr;
}
}
::DebugserverCommandHandle* handle_ = nullptr;
};
} // namespace IdeviceFFI

View File

@@ -0,0 +1,109 @@
// Jackson Coxson
#pragma once
#include <cstddef>
#include <cstdint>
#include <string>
#include <vector>
#include <idevice++/bindings.hpp>
#include <idevice++/core_device_proxy.hpp>
#include <idevice++/ffi.hpp>
#include <idevice++/rsd.hpp>
namespace IdeviceFFI {
class SysdiagnoseStream {
public:
SysdiagnoseStream() = default;
SysdiagnoseStream(const SysdiagnoseStream&) = delete;
SysdiagnoseStream& operator=(const SysdiagnoseStream&) = delete;
SysdiagnoseStream(SysdiagnoseStream&& other) noexcept : h_(other.h_) { other.h_ = nullptr; }
SysdiagnoseStream& operator=(SysdiagnoseStream&& other) noexcept {
if (this != &other) {
reset();
h_ = other.h_;
other.h_ = nullptr;
}
return *this;
}
~SysdiagnoseStream() { reset(); }
// Pull next chunk. Returns nullopt on end-of-stream. On error, returns
// nullopt and sets `err`.
Result<Option<std::vector<uint8_t>>, FfiError> next_chunk();
SysdiagnoseStreamHandle* raw() const { return h_; }
private:
friend class DiagnosticsService;
explicit SysdiagnoseStream(::SysdiagnoseStreamHandle* h) : h_(h) {}
void reset() {
if (h_) {
::sysdiagnose_stream_free(h_);
h_ = nullptr;
}
}
::SysdiagnoseStreamHandle* h_ = nullptr;
};
// The result of starting a sysdiagnose capture.
struct SysdiagnoseCapture {
std::string preferred_filename;
std::size_t expected_length = 0;
SysdiagnoseStream stream;
};
// RAII for Diagnostics service client
class DiagnosticsService {
public:
DiagnosticsService() = default;
DiagnosticsService(const DiagnosticsService&) = delete;
DiagnosticsService& operator=(const DiagnosticsService&) = delete;
DiagnosticsService(DiagnosticsService&& other) noexcept : h_(other.h_) { other.h_ = nullptr; }
DiagnosticsService& operator=(DiagnosticsService&& other) noexcept {
if (this != &other) {
reset();
h_ = other.h_;
other.h_ = nullptr;
}
return *this;
}
~DiagnosticsService() { reset(); }
// Connect via RSD (borrows adapter & handshake; does not consume them)
static Result<DiagnosticsService, FfiError> connect_rsd(Adapter& adapter, RsdHandshake& rsd);
// Create from a ReadWrite stream (consumes it)
static Result<DiagnosticsService, FfiError> from_stream_ptr(::ReadWriteOpaque* consumed);
static Result<DiagnosticsService, FfiError> from_stream(ReadWrite&& rw) {
return from_stream_ptr(rw.release());
}
// Start sysdiagnose capture; on success returns filename, length and a byte
// stream
Result<SysdiagnoseCapture, FfiError> capture_sysdiagnose(bool dry_run);
::DiagnosticsServiceHandle* raw() const { return h_; }
private:
explicit DiagnosticsService(::DiagnosticsServiceHandle* h) : h_(h) {}
void reset() {
if (h_) {
::diagnostics_service_free(h_);
h_ = nullptr;
}
}
::DiagnosticsServiceHandle* h_ = nullptr;
};
} // namespace IdeviceFFI

View File

@@ -0,0 +1,24 @@
// Jackson Coxson
#ifndef IDEVICE_FFI
#define IDEVICE_FFI
#include <idevice++/bindings.hpp>
#include <string>
namespace IdeviceFFI {
class FfiError {
public:
int32_t code = 0;
std::string message;
FfiError(const IdeviceFfiError* err);
FfiError();
explicit operator bool() const { return code != 0; }
static FfiError NotConnected();
static FfiError InvalidArgument();
};
} // namespace IdeviceFFI
#endif

View File

@@ -0,0 +1,63 @@
// Jackson Coxson
#ifndef IDEVICE_CPP
#define IDEVICE_CPP
#include <idevice++/ffi.hpp>
#include <idevice++/pairing_file.hpp>
#include <idevice++/result.hpp>
#include <string>
#if defined(_WIN32) && !defined(__MINGW32__)
// MSVC doesn't have BSD u_int* types
using u_int8_t = std::uint8_t;
using u_int16_t = std::uint16_t;
using u_int32_t = std::uint32_t;
using u_int64_t = std::uint64_t;
#endif
namespace IdeviceFFI {
// Generic “bind a free function” deleter
template <class T, void (*FreeFn)(T*)> struct FnDeleter {
void operator()(T* p) const noexcept {
if (p) {
FreeFn(p);
}
}
};
using IdevicePtr = std::unique_ptr<IdeviceHandle, FnDeleter<IdeviceHandle, idevice_free>>;
class Idevice {
public:
static Result<Idevice, FfiError> create(IdeviceSocketHandle* socket, const std::string& label);
static Result<Idevice, FfiError>
create_tcp(const sockaddr* addr, socklen_t addr_len, const std::string& label);
// Methods
Result<std::string, FfiError> get_type() const;
Result<void, FfiError> rsd_checkin();
Result<void, FfiError> start_session(const PairingFile& pairing_file);
// Ownership/RAII
~Idevice() noexcept = default;
Idevice(Idevice&&) noexcept = default;
Idevice& operator=(Idevice&&) noexcept = default;
Idevice(const Idevice&) = delete;
Idevice& operator=(const Idevice&) = delete;
static Idevice adopt(IdeviceHandle* h) noexcept { return Idevice(h); }
// Accessor
IdeviceHandle* raw() const noexcept { return handle_.get(); }
IdeviceHandle* release() noexcept { return handle_.release(); }
private:
explicit Idevice(IdeviceHandle* h) noexcept : handle_(h) {}
IdevicePtr handle_{};
};
} // namespace IdeviceFFI
#endif

View File

@@ -0,0 +1,38 @@
// Jackson Coxson
#pragma once
#include <idevice++/bindings.hpp>
#include <idevice++/remote_server.hpp>
#include <idevice++/result.hpp>
#include <memory>
namespace IdeviceFFI {
using LocSimPtr = std::unique_ptr<LocationSimulationHandle,
FnDeleter<LocationSimulationHandle, location_simulation_free>>;
class LocationSimulation {
public:
// Factory: borrows the RemoteServer; not consumed
static Result<LocationSimulation, FfiError> create(RemoteServer& server);
Result<void, FfiError> clear();
Result<void, FfiError> set(double latitude, double longitude);
~LocationSimulation() noexcept = default;
LocationSimulation(LocationSimulation&&) noexcept = default;
LocationSimulation& operator=(LocationSimulation&&) noexcept = default;
LocationSimulation(const LocationSimulation&) = delete;
LocationSimulation& operator=(const LocationSimulation&) = delete;
LocationSimulationHandle* raw() const noexcept { return handle_.get(); }
static LocationSimulation adopt(LocationSimulationHandle* h) noexcept {
return LocationSimulation(h);
}
private:
explicit LocationSimulation(LocationSimulationHandle* h) noexcept : handle_(h) {}
LocSimPtr handle_{};
};
} // namespace IdeviceFFI

View File

@@ -0,0 +1,43 @@
#pragma once
#include <cstdint>
#include <idevice++/bindings.hpp>
#include <idevice++/ffi.hpp>
#include <idevice++/provider.hpp>
#include <memory>
#include <string>
namespace IdeviceFFI {
using LockdownPtr =
std::unique_ptr<LockdowndClientHandle, FnDeleter<LockdowndClientHandle, lockdownd_client_free>>;
class Lockdown {
public:
// Factory: connect via Provider
static Result<Lockdown, FfiError> connect(Provider& provider);
// Factory: wrap an existing Idevice socket (consumes it on success)
static Result<Lockdown, FfiError> from_socket(Idevice&& socket);
// Ops
Result<void, FfiError> start_session(const PairingFile& pf);
Result<std::pair<uint16_t, bool>, FfiError> start_service(const std::string& identifier);
Result<plist_t, FfiError> get_value(const char* key, const char* domain);
// RAII / moves
~Lockdown() noexcept = default;
Lockdown(Lockdown&&) noexcept = default;
Lockdown& operator=(Lockdown&&) noexcept = default;
Lockdown(const Lockdown&) = delete;
Lockdown& operator=(const Lockdown&) = delete;
LockdowndClientHandle* raw() const noexcept { return handle_.get(); }
static Lockdown adopt(LockdowndClientHandle* h) noexcept { return Lockdown(h); }
private:
explicit Lockdown(LockdowndClientHandle* h) noexcept : handle_(h) {}
LockdownPtr handle_{};
};
} // namespace IdeviceFFI

View File

@@ -0,0 +1,184 @@
// So here's the thing, std::optional and friends weren't added until C++17.
// Some consumers of this codebase aren't on C++17 yet, so this won't work.
// Plus, as a professional Rust evangelist, it's my duty to place as many Rust
// idioms into other languages as possible to give everyone a taste of greatness.
// Required error handling is correct error handling. And they called me a mad man.
// Heavily influced from https://github.com/oktal/result, thank you
#pragma once
#include <stdexcept>
#include <type_traits>
#include <utility>
namespace IdeviceFFI {
struct none_t {};
constexpr none_t None{};
template <typename T> class Option {
bool has_;
typename std::aligned_storage<sizeof(T), alignof(T)>::type storage_;
T* ptr() { return reinterpret_cast<T*>(&storage_); }
const T* ptr() const { return reinterpret_cast<const T*>(&storage_); }
public:
// None
Option() noexcept : has_(false) {}
Option(none_t) noexcept : has_(false) {}
// Some
Option(const T& v) : has_(true) { ::new (ptr()) T(v); }
Option(T&& v) : has_(true) { ::new (ptr()) T(std::move(v)); }
// Copy / move
Option(const Option& o) : has_(o.has_) {
if (has_) {
::new (ptr()) T(*o.ptr());
}
}
Option(Option&& o) noexcept(std::is_nothrow_move_constructible<T>::value) : has_(o.has_) {
if (has_) {
::new (ptr()) T(std::move(*o.ptr()));
o.reset();
}
}
Option& operator=(Option o) noexcept(std::is_nothrow_move_constructible<T>::value
&& std::is_nothrow_move_assignable<T>::value) {
swap(o);
return *this;
}
~Option() { reset(); }
void reset() noexcept {
if (has_) {
ptr()->~T();
has_ = false;
}
}
void swap(Option& other) noexcept(std::is_nothrow_move_constructible<T>::value) {
if (has_ && other.has_) {
using std::swap;
swap(*ptr(), *other.ptr());
} else if (has_ && !other.has_) {
::new (other.ptr()) T(std::move(*ptr()));
other.has_ = true;
reset();
} else if (!has_ && other.has_) {
::new (ptr()) T(std::move(*other.ptr()));
has_ = true;
other.reset();
}
}
// State
bool is_some() const noexcept { return has_; }
bool is_none() const noexcept { return !has_; }
// Unwraps (ref-qualified)
T& unwrap() & {
if (!has_) {
throw std::runtime_error("unwrap on None");
}
return *ptr();
}
const T& unwrap() const& {
if (!has_) {
throw std::runtime_error("unwrap on None");
}
return *ptr();
}
T unwrap() && {
if (!has_) {
throw std::runtime_error("unwrap on None");
}
T tmp = std::move(*ptr());
reset();
return tmp;
}
// unwrap_or / unwrap_or_else
T unwrap_or(T default_value) const& { return has_ ? *ptr() : std::move(default_value); }
T unwrap_or(T default_value) && { return has_ ? std::move(*ptr()) : std::move(default_value); }
T unwrap_or(const T& default_value) const& { return has_ ? *ptr() : default_value; }
T unwrap_or(T&& default_value) const& { return has_ ? *ptr() : std::move(default_value); }
template <typename F> T unwrap_or_else(F&& f) const& {
return has_ ? *ptr() : static_cast<T>(f());
}
template <typename F> T unwrap_or_else(F&& f) && {
return has_ ? std::move(*ptr()) : static_cast<T>(f());
}
// map
template <typename F>
auto map(F&& f) const& -> Option<typename std::decay<decltype(f(*ptr()))>::type> {
using U = typename std::decay<decltype(f(*ptr()))>::type;
if (has_) {
return Option<U>(f(*ptr()));
}
return Option<U>(None);
}
template <typename F>
auto map(F&& f) && -> Option<typename std::decay<decltype(f(std::move(*ptr())))>::type> {
using U = typename std::decay<decltype(f(std::move(*ptr())))>::type;
if (has_) {
// Move the value into the function
return Option<U>(f(std::move(*ptr())));
}
return Option<U>(None);
}
};
// Helpers
template <typename T> inline Option<typename std::decay<T>::type> Some(T&& v) {
return Option<typename std::decay<T>::type>(std::forward<T>(v));
}
inline Option<void> Some() = delete; // no Option<void>
#define match_option(opt, some_name, some_block, none_block) \
/* NOTE: you may return in a block, but not break/continue */ \
do { \
auto&& _option_val = (opt); \
if (_option_val.is_some()) { \
auto&& some_name = _option_val.unwrap(); \
some_block \
} else { \
none_block \
} \
} while (0)
// --- Option helpers: if_let_some / if_let_some_move / if_let_none ---
#define _opt_concat(a, b) a##b
#define _opt_unique(base) _opt_concat(base, __LINE__)
/* Bind a reference to the contained value if Some(...) */
#define if_let_some(expr, name, block) \
/* NOTE: you may return in a block, but not break/continue */ \
do { \
auto _opt_unique(_opt_) = (expr); \
if (_opt_unique(_opt_).is_some()) { \
auto&& name = _opt_unique(_opt_).unwrap(); \
block \
} \
} while (0)
/* Move the contained value out (consumes the Option) if Some(...) */
#define if_let_some_move(expr, name, block) \
/* NOTE: you may return in a block, but not break/continue */ \
do { \
auto _opt_unique(_opt_) = (expr); \
if (_opt_unique(_opt_).is_some()) { \
auto name = std::move(_opt_unique(_opt_)).unwrap(); \
block \
} \
} while (0)
} // namespace IdeviceFFI

View File

@@ -0,0 +1,45 @@
// Jackson Coxson
#ifndef IDEVICE_PAIRING_FILE
#define IDEVICE_PAIRING_FILE
#pragma once
#include <idevice++/ffi.hpp>
#include <idevice++/result.hpp>
#include <memory>
#include <string>
#include <vector>
namespace IdeviceFFI {
struct PairingFileDeleter {
void operator()(IdevicePairingFile* p) const noexcept;
};
using PairingFilePtr = std::unique_ptr<IdevicePairingFile, PairingFileDeleter>;
class PairingFile {
public:
static Result<PairingFile, FfiError> read(const std::string& path);
static Result<PairingFile, FfiError> from_bytes(const uint8_t* data, size_t size);
~PairingFile() noexcept = default; // unique_ptr handles destruction
PairingFile(const PairingFile&) = delete;
PairingFile& operator=(const PairingFile&) = delete;
PairingFile(PairingFile&&) noexcept = default; // move is correct by default
PairingFile& operator=(PairingFile&&) noexcept = default;
Result<std::vector<uint8_t>, FfiError> serialize() const;
explicit PairingFile(IdevicePairingFile* ptr) noexcept : ptr_(ptr) {}
IdevicePairingFile* raw() const noexcept { return ptr_.get(); }
IdevicePairingFile* release() noexcept { return ptr_.release(); }
private:
PairingFilePtr ptr_{}; // owns the handle
};
} // namespace IdeviceFFI
#endif

View File

@@ -0,0 +1,49 @@
// Jackson Coxson
#pragma once
#include <cstdint>
#include <idevice++/bindings.hpp>
#include <idevice++/ffi.hpp>
#include <idevice++/usbmuxd.hpp>
#include <string>
namespace IdeviceFFI {
class FfiError;
class PairingFile; // has: IdevicePairingFile* raw() const; void
// release_on_success();
class UsbmuxdAddr; // has: UsbmuxdAddrHandle* raw() const; void
// release_on_success();
using ProviderPtr =
std::unique_ptr<IdeviceProviderHandle, FnDeleter<IdeviceProviderHandle, idevice_provider_free>>;
class Provider {
public:
static Result<Provider, FfiError>
tcp_new(const idevice_sockaddr* ip, PairingFile&& pairing, const std::string& label);
static Result<Provider, FfiError> usbmuxd_new(UsbmuxdAddr&& addr,
uint32_t tag,
const std::string& udid,
uint32_t device_id,
const std::string& label);
~Provider() noexcept = default;
Provider(Provider&&) noexcept = default;
Provider& operator=(Provider&&) noexcept = default;
Provider(const Provider&) = delete;
Provider& operator=(const Provider&) = delete;
Result<PairingFile, FfiError> get_pairing_file();
IdeviceProviderHandle* raw() const noexcept { return handle_.get(); }
static Provider adopt(IdeviceProviderHandle* h) noexcept { return Provider(h); }
IdeviceProviderHandle* release() noexcept { return handle_.release(); }
private:
explicit Provider(IdeviceProviderHandle* h) noexcept : handle_(h) {}
ProviderPtr handle_{};
};
} // namespace IdeviceFFI

View File

@@ -0,0 +1,45 @@
// Jackson Coxson
#pragma once
#include <idevice++/bindings.hpp>
namespace IdeviceFFI {
// A move-only holder for a fat-pointer stream. It does NOT free on destruction.
// Always pass ownership to an FFI that consumes it by calling release().
class ReadWrite {
public:
ReadWrite() noexcept : ptr_(nullptr) {}
explicit ReadWrite(ReadWriteOpaque* p) noexcept : ptr_(p) {}
ReadWrite(const ReadWrite&) = delete;
ReadWrite& operator=(const ReadWrite&) = delete;
ReadWrite(ReadWrite&& other) noexcept : ptr_(other.ptr_) { other.ptr_ = nullptr; }
ReadWrite& operator=(ReadWrite&& other) noexcept {
if (this != &other) {
ptr_ = other.ptr_;
other.ptr_ = nullptr;
}
return *this;
}
~ReadWrite() noexcept = default; // no dtor Rust consumers own free/drop
ReadWriteOpaque* raw() const noexcept { return ptr_; }
ReadWriteOpaque* release() noexcept {
auto* p = ptr_;
ptr_ = nullptr;
return p;
}
static ReadWrite adopt(ReadWriteOpaque* p) noexcept { return ReadWrite(p); }
explicit operator bool() const noexcept { return ptr_ != nullptr; }
private:
ReadWriteOpaque* ptr_;
};
} // namespace IdeviceFFI

View File

@@ -0,0 +1,42 @@
// Jackson Coxson
#ifndef IDEVICE_REMOTE_SERVER_H
#define IDEVICE_REMOTE_SERVER_H
#pragma once
#include <idevice++/core_device_proxy.hpp>
#include <idevice++/idevice.hpp>
#include <idevice++/readwrite.hpp>
#include <idevice++/rsd.hpp>
#include <memory>
namespace IdeviceFFI {
using RemoteServerPtr =
std::unique_ptr<RemoteServerHandle, FnDeleter<RemoteServerHandle, remote_server_free>>;
class RemoteServer {
public:
// Factory: consumes the ReadWrite stream regardless of result
static Result<RemoteServer, FfiError> from_socket(ReadWrite&& rw);
// Factory: borrows adapter + handshake (neither is consumed)
static Result<RemoteServer, FfiError> connect_rsd(Adapter& adapter, RsdHandshake& rsd);
// RAII / moves
~RemoteServer() noexcept = default;
RemoteServer(RemoteServer&&) noexcept = default;
RemoteServer& operator=(RemoteServer&&) noexcept = default;
RemoteServer(const RemoteServer&) = delete;
RemoteServer& operator=(const RemoteServer&) = delete;
RemoteServerHandle* raw() const noexcept { return handle_.get(); }
static RemoteServer adopt(RemoteServerHandle* h) noexcept { return RemoteServer(h); }
private:
explicit RemoteServer(RemoteServerHandle* h) noexcept : handle_(h) {}
RemoteServerPtr handle_{};
};
} // namespace IdeviceFFI
#endif

View File

@@ -0,0 +1,306 @@
// Jackson Coxson
#pragma once
#include <cstdio>
#include <exception>
#include <type_traits>
#include <utility>
namespace IdeviceFFI {
namespace types {
template <typename T> struct Ok {
T val;
Ok(const T& val) : val(val) {}
Ok(T&& val) : val(std::move(val)) {}
};
template <> struct Ok<void> {};
template <typename E> struct Err {
E val;
Err(const E& val) : val(val) {}
Err(E&& val) : val(std::move(val)) {}
};
} // namespace types
template <typename T> inline types::Ok<typename std::decay<T>::type> Ok(T&& val) {
return types::Ok<typename std::decay<T>::type>(std::forward<T>(val));
}
inline types::Ok<void> Ok() {
return types::Ok<void>();
}
template <typename E> inline types::Err<typename std::decay<E>::type> Err(E&& val) {
return types::Err<typename std::decay<E>::type>(std::forward<E>(val));
}
// =======================
// Result<T, E>
// =======================
template <typename T, typename E> class Result {
bool is_ok_;
union {
T ok_value_;
E err_value_;
};
public:
Result(types::Ok<T> ok_val) : is_ok_(true), ok_value_(std::move(ok_val.val)) {}
Result(types::Err<E> err_val) : is_ok_(false), err_value_(std::move(err_val.val)) {}
Result(const Result& other) : is_ok_(other.is_ok_) {
if (is_ok_) {
new (&ok_value_) T(other.ok_value_);
} else {
new (&err_value_) E(other.err_value_);
}
}
Result(Result&& other) noexcept : is_ok_(other.is_ok_) {
if (is_ok_) {
new (&ok_value_) T(std::move(other.ok_value_));
} else {
new (&err_value_) E(std::move(other.err_value_));
}
}
~Result() {
if (is_ok_) {
ok_value_.~T();
} else {
err_value_.~E();
}
}
// Copy Assignment
Result& operator=(const Result& other) {
// Prevent self-assignment
if (this == &other) {
return *this;
}
// Destroy the current value
if (is_ok_) {
ok_value_.~T();
} else {
err_value_.~E();
}
is_ok_ = other.is_ok_;
// Construct the new value
if (is_ok_) {
new (&ok_value_) T(other.ok_value_);
} else {
new (&err_value_) E(other.err_value_);
}
return *this;
}
// Move Assignment
Result& operator=(Result&& other) noexcept {
if (this == &other) {
return *this;
}
// Destroy the current value
if (is_ok_) {
ok_value_.~T();
} else {
err_value_.~E();
}
is_ok_ = other.is_ok_;
// Construct the new value by moving
if (is_ok_) {
new (&ok_value_) T(std::move(other.ok_value_));
} else {
new (&err_value_) E(std::move(other.err_value_));
}
return *this;
}
bool is_ok() const { return is_ok_; }
bool is_err() const { return !is_ok_; }
// lvalue (mutable)
T& unwrap() & {
if (!is_ok_) {
std::fprintf(stderr, "unwrap on Err\n");
std::terminate();
}
return ok_value_;
}
// lvalue (const)
const T& unwrap() const& {
if (!is_ok_) {
std::fprintf(stderr, "unwrap on Err\n");
std::terminate();
}
return ok_value_;
}
// rvalue (consume/move)
T unwrap() && {
if (!is_ok_) {
std::fprintf(stderr, "unwrap on Err\n");
std::terminate();
}
return std::move(ok_value_);
}
E& unwrap_err() & {
if (is_ok_) {
std::fprintf(stderr, "unwrap_err on Ok\n");
std::terminate();
}
return err_value_;
}
const E& unwrap_err() const& {
if (is_ok_) {
std::fprintf(stderr, "unwrap_err on Ok\n");
std::terminate();
}
return err_value_;
}
E unwrap_err() && {
if (is_ok_) {
std::fprintf(stderr, "unwrap_err on Ok\n");
std::terminate();
}
return std::move(err_value_);
}
T unwrap_or(T&& default_value) const { return is_ok_ ? ok_value_ : std::move(default_value); }
T expect(const char* message) && {
if (is_err()) {
std::fprintf(stderr, "Fatal (expect) error: %s\n", message);
std::terminate();
}
return std::move(ok_value_);
}
// Returns a mutable reference from an lvalue Result
T& expect(const char* message) & {
if (is_err()) {
std::fprintf(stderr, "Fatal (expect) error: %s\n", message);
std::terminate();
}
return ok_value_;
}
// Returns a const reference from a const lvalue Result
const T& expect(const char* message) const& {
if (is_err()) {
std::fprintf(stderr, "Fatal (expect) error: %s\n", message);
std::terminate();
}
return ok_value_;
}
template <typename F> T unwrap_or_else(F&& f) & {
return is_ok_ ? ok_value_ : static_cast<T>(f(err_value_));
}
// const lvalue: returns T by copy
template <typename F> T unwrap_or_else(F&& f) const& {
return is_ok_ ? ok_value_ : static_cast<T>(f(err_value_));
}
// rvalue: moves Ok(T) out; on Err(E), allow the handler to consume/move E
template <typename F> T unwrap_or_else(F&& f) && {
if (is_ok_) {
return std::move(ok_value_);
}
return static_cast<T>(std::forward<F>(f)(std::move(err_value_)));
}
};
// Result<void, E> specialization
template <typename E> class Result<void, E> {
bool is_ok_;
union {
char dummy_;
E err_value_;
};
public:
Result(types::Ok<void>) : is_ok_(true), dummy_() {}
Result(types::Err<E> err_val) : is_ok_(false), err_value_(std::move(err_val.val)) {}
Result(const Result& other) : is_ok_(other.is_ok_) {
if (!is_ok_) {
new (&err_value_) E(other.err_value_);
}
}
Result(Result&& other) noexcept : is_ok_(other.is_ok_) {
if (!is_ok_) {
new (&err_value_) E(std::move(other.err_value_));
}
}
~Result() {
if (!is_ok_) {
err_value_.~E();
}
}
bool is_ok() const { return is_ok_; }
bool is_err() const { return !is_ok_; }
void unwrap() const {
if (!is_ok_) {
std::fprintf(stderr, "Attempted to unwrap an error Result<void, E>\n");
std::terminate();
}
}
const E& unwrap_err() const {
if (is_ok_) {
std::fprintf(stderr, "Attempted to unwrap_err on an ok Result<void, E>\n");
std::terminate();
}
return err_value_;
}
E& unwrap_err() {
if (is_ok_) {
std::fprintf(stderr, "Attempted to unwrap_err on an ok Result<void, E>\n");
std::terminate();
}
return err_value_;
}
void expect(const char* message) const {
if (is_err()) {
std::fprintf(stderr, "Fatal (expect) error: %s\n", message);
std::terminate();
}
}
};
#define match_result(res, ok_name, ok_block, err_name, err_block) \
do { \
auto&& _result_val = (res); \
if (_result_val.is_ok()) { \
auto&& ok_name = _result_val.unwrap(); \
ok_block \
} else { \
auto&& err_name = _result_val.unwrap_err(); \
err_block \
} \
} while (0)
} // namespace IdeviceFFI

View File

@@ -0,0 +1,57 @@
// Jackson Coxson
#ifndef IDEVICE_RSD_H
#define IDEVICE_RSD_H
#include <idevice++/ffi.hpp>
#include <idevice++/idevice.hpp>
#include <idevice++/readwrite.hpp>
#include <vector>
namespace IdeviceFFI {
struct RsdService {
std::string name;
std::string entitlement;
uint16_t port{};
bool uses_remote_xpc{};
std::vector<std::string> features;
int64_t service_version{-1};
};
using RsdPtr =
std::unique_ptr<RsdHandshakeHandle, FnDeleter<RsdHandshakeHandle, rsd_handshake_free>>;
class RsdHandshake {
public:
// Factory: consumes the ReadWrite socket regardless of result
static Result<RsdHandshake, FfiError> from_socket(ReadWrite&& rw);
// Basic info
Result<size_t, FfiError> protocol_version() const;
Result<std::string, FfiError> uuid() const;
// Services
Result<std::vector<RsdService>, FfiError> services() const;
Result<bool, FfiError> service_available(const std::string& name) const;
Result<RsdService, FfiError> service_info(const std::string& name) const;
// RAII / moves
~RsdHandshake() noexcept = default;
RsdHandshake(RsdHandshake&&) noexcept = default;
RsdHandshake& operator=(RsdHandshake&&) noexcept = default;
// Enable Copying
RsdHandshake(const RsdHandshake& other);
RsdHandshake& operator=(const RsdHandshake& other);
RsdHandshakeHandle* raw() const noexcept { return handle_.get(); }
static RsdHandshake adopt(RsdHandshakeHandle* h) noexcept { return RsdHandshake(h); }
private:
explicit RsdHandshake(RsdHandshakeHandle* h) noexcept : handle_(h) {}
RsdPtr handle_{};
};
} // namespace IdeviceFFI
#endif

View File

@@ -0,0 +1,176 @@
// Jackson Coxson
#pragma once
#include <cstddef>
#include <cstdint>
#include <memory>
#include <string>
#include <idevice++/core_device_proxy.hpp>
#include <idevice++/ffi.hpp>
namespace IdeviceFFI {
// ---------------- OwnedBuffer: RAII for zero-copy read buffers
// ----------------
class OwnedBuffer {
public:
OwnedBuffer() noexcept : p_(nullptr), n_(0) {}
OwnedBuffer(const OwnedBuffer&) = delete;
OwnedBuffer& operator=(const OwnedBuffer&) = delete;
OwnedBuffer(OwnedBuffer&& o) noexcept : p_(o.p_), n_(o.n_) {
o.p_ = nullptr;
o.n_ = 0;
}
OwnedBuffer& operator=(OwnedBuffer&& o) noexcept {
if (this != &o) {
reset();
p_ = o.p_;
n_ = o.n_;
o.p_ = nullptr;
o.n_ = 0;
}
return *this;
}
~OwnedBuffer() { reset(); }
const uint8_t* data() const noexcept { return p_; }
uint8_t* data() noexcept { return p_; }
std::size_t size() const noexcept { return n_; }
bool empty() const noexcept { return n_ == 0; }
void reset() noexcept {
if (p_) {
::idevice_data_free(p_, n_);
p_ = nullptr;
n_ = 0;
}
}
private:
friend class TcpObjectStackEater;
void adopt(uint8_t* p, std::size_t n) noexcept {
reset();
p_ = p;
n_ = n;
}
uint8_t* p_;
std::size_t n_;
};
// ---------------- TcpFeeder: push inbound IP packets into the stack ----------
class TcpObjectStackFeeder {
public:
TcpObjectStackFeeder() = default;
TcpObjectStackFeeder(const TcpObjectStackFeeder&) = delete;
TcpObjectStackFeeder& operator=(const TcpObjectStackFeeder&) = delete;
TcpObjectStackFeeder(TcpObjectStackFeeder&& o) noexcept : h_(o.h_) { o.h_ = nullptr; }
TcpObjectStackFeeder& operator=(TcpObjectStackFeeder&& o) noexcept {
if (this != &o) {
reset();
h_ = o.h_;
o.h_ = nullptr;
}
return *this;
}
~TcpObjectStackFeeder() { reset(); }
bool write(const uint8_t* data, std::size_t len, FfiError& err) const;
::TcpFeedObject* raw() const { return h_; }
private:
friend class TcpObjectStack;
explicit TcpObjectStackFeeder(::TcpFeedObject* h) : h_(h) {}
void reset() {
if (h_) {
::idevice_free_tcp_feed_object(h_);
h_ = nullptr;
}
}
::TcpFeedObject* h_ = nullptr;
};
// ---------------- TcpEater: blocking read of outbound packets ----------------
class TcpObjectStackEater {
public:
TcpObjectStackEater() = default;
TcpObjectStackEater(const TcpObjectStackEater&) = delete;
TcpObjectStackEater& operator=(const TcpObjectStackEater&) = delete;
TcpObjectStackEater(TcpObjectStackEater&& o) noexcept : h_(o.h_) { o.h_ = nullptr; }
TcpObjectStackEater& operator=(TcpObjectStackEater&& o) noexcept {
if (this != &o) {
reset();
h_ = o.h_;
o.h_ = nullptr;
}
return *this;
}
~TcpObjectStackEater() { reset(); }
// Blocks until a packet is available. On success, 'out' adopts the buffer
// and you must keep 'out' alive until done (RAII frees via
// idevice_data_free).
bool read(OwnedBuffer& out, FfiError& err) const;
::TcpEatObject* raw() const { return h_; }
private:
friend class TcpObjectStack;
explicit TcpObjectStackEater(::TcpEatObject* h) : h_(h) {}
void reset() {
if (h_) {
::idevice_free_tcp_eat_object(h_);
h_ = nullptr;
}
}
::TcpEatObject* h_ = nullptr;
};
// ---------------- Stack builder: returns feeder + eater + adapter ------------
class TcpObjectStack {
public:
TcpObjectStack() = default;
TcpObjectStack(const TcpObjectStack&) = delete; // no sharing
TcpObjectStack& operator=(const TcpObjectStack&) = delete;
TcpObjectStack(TcpObjectStack&&) noexcept = default; // movable
TcpObjectStack& operator=(TcpObjectStack&&) noexcept = default;
// Build the stack (dual-handle). Name kept to minimize churn.
static Result<TcpObjectStack, FfiError> create(const std::string& our_ip,
const std::string& their_ip);
TcpObjectStackFeeder& feeder();
const TcpObjectStackFeeder& feeder() const;
TcpObjectStackEater& eater();
const TcpObjectStackEater& eater() const;
Adapter& adapter();
const Adapter& adapter() const;
Option<TcpObjectStackFeeder> release_feeder(); // nullptr inside wrapper after call
Option<TcpObjectStackEater> release_eater(); // nullptr inside wrapper after call
Option<Adapter> release_adapter();
private:
struct Impl {
TcpObjectStackFeeder feeder;
TcpObjectStackEater eater;
Option<Adapter> adapter;
};
// Unique ownership so theres a single point of truth to release from
std::unique_ptr<Impl> impl_;
};
} // namespace IdeviceFFI

View File

@@ -0,0 +1,124 @@
// Jackson Coxson
#ifndef IDEVICE_USBMUXD_HPP
#define IDEVICE_USBMUXD_HPP
#include <cstdint>
#include <idevice++/idevice.hpp>
#include <idevice++/option.hpp>
#include <idevice++/pairing_file.hpp>
#include <string>
#include <vector>
#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#else
#include <sys/socket.h>
#endif
namespace IdeviceFFI {
using AddrPtr =
std::unique_ptr<UsbmuxdAddrHandle, FnDeleter<UsbmuxdAddrHandle, idevice_usbmuxd_addr_free>>;
using DevicePtr = std::unique_ptr<UsbmuxdDeviceHandle,
FnDeleter<UsbmuxdDeviceHandle, idevice_usbmuxd_device_free>>;
using ConnectionPtr =
std::unique_ptr<UsbmuxdConnectionHandle,
FnDeleter<UsbmuxdConnectionHandle, idevice_usbmuxd_connection_free>>;
class UsbmuxdAddr {
public:
static Result<UsbmuxdAddr, FfiError> tcp_new(const sockaddr* addr, socklen_t addr_len);
#if defined(__unix__) || defined(__APPLE__)
static Result<UsbmuxdAddr, FfiError> unix_new(const std::string& path);
#endif
static UsbmuxdAddr default_new();
~UsbmuxdAddr() noexcept = default;
UsbmuxdAddr(UsbmuxdAddr&&) noexcept = default;
UsbmuxdAddr& operator=(UsbmuxdAddr&&) noexcept = default;
UsbmuxdAddr(const UsbmuxdAddr&) = delete;
UsbmuxdAddr& operator=(const UsbmuxdAddr&) = delete;
UsbmuxdAddrHandle* raw() const noexcept { return handle_.get(); }
UsbmuxdAddrHandle* release() noexcept { return handle_.release(); }
static UsbmuxdAddr adopt(UsbmuxdAddrHandle* h) noexcept { return UsbmuxdAddr(h); }
private:
explicit UsbmuxdAddr(UsbmuxdAddrHandle* h) noexcept : handle_(h) {}
AddrPtr handle_{};
};
class UsbmuxdConnectionType {
public:
enum class Value : uint8_t { Usb = 1, Network = 2, Unknown = 3 };
explicit UsbmuxdConnectionType(uint8_t v) : _value(static_cast<Value>(v)) {}
std::string to_string() const; // body in .cpp
Value value() const noexcept { return _value; }
bool operator==(Value other) const noexcept { return _value == other; }
private:
Value _value{Value::Unknown};
};
class UsbmuxdDevice {
public:
~UsbmuxdDevice() noexcept = default;
UsbmuxdDevice(UsbmuxdDevice&&) noexcept = default;
UsbmuxdDevice& operator=(UsbmuxdDevice&&) noexcept = default;
UsbmuxdDevice(const UsbmuxdDevice&) = delete;
UsbmuxdDevice& operator=(const UsbmuxdDevice&) = delete;
static UsbmuxdDevice adopt(UsbmuxdDeviceHandle* h) noexcept { return UsbmuxdDevice(h); }
UsbmuxdDeviceHandle* raw() const noexcept { return handle_.get(); }
Option<std::string> get_udid() const;
Option<uint32_t> get_id() const;
Option<UsbmuxdConnectionType> get_connection_type() const;
private:
explicit UsbmuxdDevice(UsbmuxdDeviceHandle* h) noexcept : handle_(h) {}
DevicePtr handle_{};
friend class UsbmuxdConnection;
};
class PairingFile;
class UsbmuxdConnection {
public:
static Result<UsbmuxdConnection, FfiError>
tcp_new(const idevice_sockaddr* addr, idevice_socklen_t addr_len, uint32_t tag);
#if defined(__unix__) || defined(__APPLE__)
static Result<UsbmuxdConnection, FfiError> unix_new(const std::string& path, uint32_t tag);
#endif
static Result<UsbmuxdConnection, FfiError> default_new(uint32_t tag);
~UsbmuxdConnection() noexcept = default;
UsbmuxdConnection(UsbmuxdConnection&&) noexcept = default;
UsbmuxdConnection& operator=(UsbmuxdConnection&&) noexcept = default;
UsbmuxdConnection(const UsbmuxdConnection&) = delete;
UsbmuxdConnection& operator=(const UsbmuxdConnection&) = delete;
Result<std::vector<UsbmuxdDevice>, FfiError> get_devices() const;
Result<std::string, FfiError> get_buid() const;
Result<PairingFile, FfiError> get_pair_record(const std::string& udid);
Result<Idevice, FfiError>
connect_to_device(uint32_t device_id, uint16_t port, const std::string& path) &&;
Result<Idevice, FfiError> connect_to_device(uint32_t, uint16_t, const std::string&) & = delete;
Result<Idevice, FfiError>
connect_to_device(uint32_t, uint16_t, const std::string&) const& = delete;
UsbmuxdConnectionHandle* raw() const noexcept { return handle_.get(); }
private:
explicit UsbmuxdConnection(UsbmuxdConnectionHandle* h) noexcept : handle_(h) {}
ConnectionPtr handle_{};
};
} // namespace IdeviceFFI
#endif

1
cpp/plist_ffi Submodule

Submodule cpp/plist_ffi added at 66e6e6362b

37
cpp/sln/idevice++.sln Normal file
View File

@@ -0,0 +1,37 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.14.36327.8 d17.14
MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "idevice++", "idevice++.vcxproj", "{EBC5A8CF-BC80-454B-95B5-F2D14770A41D}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|ARM64 = Debug|ARM64
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|ARM64 = Release|ARM64
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{EBC5A8CF-BC80-454B-95B5-F2D14770A41D}.Debug|ARM64.ActiveCfg = Debug|ARM64
{EBC5A8CF-BC80-454B-95B5-F2D14770A41D}.Debug|ARM64.Build.0 = Debug|ARM64
{EBC5A8CF-BC80-454B-95B5-F2D14770A41D}.Debug|x64.ActiveCfg = Debug|x64
{EBC5A8CF-BC80-454B-95B5-F2D14770A41D}.Debug|x64.Build.0 = Debug|x64
{EBC5A8CF-BC80-454B-95B5-F2D14770A41D}.Debug|x86.ActiveCfg = Debug|Win32
{EBC5A8CF-BC80-454B-95B5-F2D14770A41D}.Debug|x86.Build.0 = Debug|Win32
{EBC5A8CF-BC80-454B-95B5-F2D14770A41D}.Release|ARM64.ActiveCfg = Release|ARM64
{EBC5A8CF-BC80-454B-95B5-F2D14770A41D}.Release|ARM64.Build.0 = Release|ARM64
{EBC5A8CF-BC80-454B-95B5-F2D14770A41D}.Release|x64.ActiveCfg = Release|x64
{EBC5A8CF-BC80-454B-95B5-F2D14770A41D}.Release|x64.Build.0 = Release|x64
{EBC5A8CF-BC80-454B-95B5-F2D14770A41D}.Release|x86.ActiveCfg = Release|Win32
{EBC5A8CF-BC80-454B-95B5-F2D14770A41D}.Release|x86.Build.0 = Release|Win32
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {9A195DE0-F99B-4101-80B4-C1CFC7BFC06F}
EndGlobalSection
EndGlobal

354
cpp/sln/idevice++.vcxproj Normal file
View File

@@ -0,0 +1,354 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|ARM64">
<Configuration>Debug</Configuration>
<Platform>ARM64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|ARM64">
<Configuration>Release</Configuration>
<Platform>ARM64</Platform>
</ProjectConfiguration>
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\src\adapter_stream.cpp" />
<ClCompile Include="..\src\app_service.cpp" />
<ClCompile Include="..\src\core_device.cpp" />
<ClCompile Include="..\src\debug_proxy.cpp" />
<ClCompile Include="..\src\diagnosticsservice.cpp" />
<ClCompile Include="..\src\ffi.cpp" />
<ClCompile Include="..\src\idevice.cpp" />
<ClCompile Include="..\src\location_simulation.cpp" />
<ClCompile Include="..\src\lockdown.cpp" />
<ClCompile Include="..\src\pairing_file.cpp" />
<ClCompile Include="..\src\provider.cpp" />
<ClCompile Include="..\src\remote_server.cpp" />
<ClCompile Include="..\src\rsd.cpp" />
<ClCompile Include="..\src\tcp_callback_feeder.cpp" />
<ClCompile Include="..\src\usbmuxd.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\include\idevice++\adapter_stream.hpp" />
<ClInclude Include="..\include\idevice++\app_service.hpp" />
<ClInclude Include="..\include\idevice++\bindings.hpp" />
<ClInclude Include="..\include\idevice++\core_device_proxy.hpp" />
<ClInclude Include="..\include\idevice++\debug_proxy.hpp" />
<ClInclude Include="..\include\idevice++\diagnosticsservice.hpp" />
<ClInclude Include="..\include\idevice++\ffi.hpp" />
<ClInclude Include="..\include\idevice++\idevice.hpp" />
<ClInclude Include="..\include\idevice++\location_simulation.hpp" />
<ClInclude Include="..\include\idevice++\lockdown.hpp" />
<ClInclude Include="..\include\idevice++\option.hpp" />
<ClInclude Include="..\include\idevice++\pairing_file.hpp" />
<ClInclude Include="..\include\idevice++\provider.hpp" />
<ClInclude Include="..\include\idevice++\readwrite.hpp" />
<ClInclude Include="..\include\idevice++\remote_server.hpp" />
<ClInclude Include="..\include\idevice++\result.hpp" />
<ClInclude Include="..\include\idevice++\rsd.hpp" />
<ClInclude Include="..\include\idevice++\tcp_object_stack.hpp" />
<ClInclude Include="..\include\idevice++\usbmuxd.hpp" />
<ClInclude Include="..\..\ffi\idevice.h" />
</ItemGroup>
<PropertyGroup Label="Globals">
<VCProjectVersion>17.0</VCProjectVersion>
<Keyword>Win32Proj</Keyword>
<ProjectGuid>{ebc5a8cf-bc80-454b-95b5-f2d14770a41d}</ProjectGuid>
<RootNamespace>idevice</RootNamespace>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ExternalIncludePath>$(VC_IncludePath);$(WindowsSDK_IncludePath);$(ProjectDir)..\include;</ExternalIncludePath>
<IncludePath>$(VC_IncludePath);$(WindowsSDK_IncludePath);$(ProjectDir)..\include;</IncludePath>
<OutDir>$(SolutionDir)Build\64\$(Configuration)\</OutDir>
<IntDir>$(SolutionDir)Build\64\$(Configuration)\Temp\$(ProjectName)\</IntDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ExternalIncludePath>$(VC_IncludePath);$(WindowsSDK_IncludePath);</ExternalIncludePath>
<IncludePath>$(VC_IncludePath);$(WindowsSDK_IncludePath);$(ProjectDir)..\</IncludePath>
<OutDir>$(SolutionDir)Build\64\$(Configuration)\</OutDir>
<IntDir>$(SolutionDir)Build\64\$(Configuration)\Temp\$(ProjectName)\</IntDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">
<IncludePath>$(VC_IncludePath);$(WindowsSDK_IncludePath);$(ProjectDir)..\include;</IncludePath>
<ExternalIncludePath>$(VC_IncludePath);$(WindowsSDK_IncludePath);$(ProjectDir)..\include;</ExternalIncludePath>
<OutDir>$(SolutionDir)Build\aarch64\$(Configuration)\</OutDir>
<IntDir>$(SolutionDir)Build\aarch64\$(Configuration)\Temp\$(ProjectName)\</IntDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">
<IncludePath>$(VC_IncludePath);$(WindowsSDK_IncludePath);$(ProjectDir)..\</IncludePath>
<OutDir>$(SolutionDir)Build\aarch64\$(Configuration)\</OutDir>
<IntDir>$(SolutionDir)Build\aarch64\$(Configuration)\Temp\$(ProjectName)\</IntDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<IncludePath>$(VC_IncludePath);$(WindowsSDK_IncludePath);$(ProjectDir)..\include;</IncludePath>
<ExternalIncludePath>$(VC_IncludePath);$(WindowsSDK_IncludePath);$(ProjectDir)..\include;</ExternalIncludePath>
<OutDir>$(SolutionDir)Build\32\$(Configuration)\</OutDir>
<IntDir>$(SolutionDir)Build\32\$(Configuration)\Temp\$(ProjectName)\</IntDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<IncludePath>$(VC_IncludePath);$(WindowsSDK_IncludePath);$(ProjectDir)..\</IncludePath>
<OutDir>$(SolutionDir)Build\32\$(Configuration)\</OutDir>
<IntDir>$(SolutionDir)Build\32\$(Configuration)\Temp\$(ProjectName)\</IntDir>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<AdditionalIncludeDirectories>$(ProjectDir)..\include;$(ProjectDir)..\..\ffi</AdditionalIncludeDirectories>
<PrecompiledHeader />
<PrecompiledHeaderFile />
<LanguageStandard>Default</LanguageStandard>
</ClCompile>
<Link>
<SubSystem>
</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
<CustomBuildStep>
<Command>call "$(ProjectDir)vs_build_rust.bat" $(Platform) $(OutDir)</Command>
</CustomBuildStep>
<Lib>
<AdditionalDependencies>idevice_ffi.lib</AdditionalDependencies>
<AdditionalLibraryDirectories>$(OutDir)</AdditionalLibraryDirectories>
</Lib>
<PreBuildEvent>
<Command>call "$(ProjectDir)..\vs_build_rust.bat" "$(Platform)" "$(OutDir)"</Command>
</PreBuildEvent>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<PrecompiledHeader>
</PrecompiledHeader>
<PrecompiledHeaderFile>
</PrecompiledHeaderFile>
<AdditionalIncludeDirectories>$(ProjectDir)..\include;$(ProjectDir)..\..\ffi</AdditionalIncludeDirectories>
<LanguageStandard>Default</LanguageStandard>
</ClCompile>
<Link>
<SubSystem>
</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
<CustomBuildStep>
<Command>call "$(ProjectDir)vs_build_rust.bat" $(Platform) $(OutDir)</Command>
</CustomBuildStep>
<Lib>
<AdditionalDependencies>idevice_ffi.lib</AdditionalDependencies>
<AdditionalLibraryDirectories>$(OutDir)</AdditionalLibraryDirectories>
</Lib>
<PreBuildEvent>
<Command>call "$(ProjectDir)..\vs_build_rust.bat" "$(Platform)" "$(OutDir)"</Command>
</PreBuildEvent>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_DEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<PrecompiledHeader>
</PrecompiledHeader>
<PrecompiledHeaderFile>
</PrecompiledHeaderFile>
<AdditionalIncludeDirectories>$(ProjectDir)..\include;$(ProjectDir)..\..\ffi</AdditionalIncludeDirectories>
<LanguageStandard>Default</LanguageStandard>
</ClCompile>
<Link>
<SubSystem>
</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
<PreBuildEvent>
<Command>call "$(ProjectDir)..\vs_build_rust.bat" "$(Platform)" "$(OutDir)"</Command>
</PreBuildEvent>
<CustomBuildStep>
<Command>call "$(ProjectDir)vs_build_rust.bat" $(Platform) $(OutDir)</Command>
</CustomBuildStep>
<Lib>
<AdditionalDependencies>idevice_ffi.lib</AdditionalDependencies>
<AdditionalLibraryDirectories>$(OutDir)</AdditionalLibraryDirectories>
</Lib>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>NDEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<PrecompiledHeader>
</PrecompiledHeader>
<PrecompiledHeaderFile>
</PrecompiledHeaderFile>
<AdditionalIncludeDirectories>$(ProjectDir)..\include;$(ProjectDir)..\..\ffi</AdditionalIncludeDirectories>
<LanguageStandard>Default</LanguageStandard>
</ClCompile>
<Link>
<SubSystem>
</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
<PreBuildEvent>
<Command>call "$(ProjectDir)..\vs_build_rust.bat" "$(Platform)" "$(OutDir)"</Command>
</PreBuildEvent>
<CustomBuildStep>
<Command>call "$(ProjectDir)vs_build_rust.bat" $(Platform) $(OutDir)</Command>
</CustomBuildStep>
<Lib>
<AdditionalDependencies>idevice_ffi.lib</AdditionalDependencies>
<AdditionalLibraryDirectories>$(OutDir)</AdditionalLibraryDirectories>
</Lib>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_DEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<PrecompiledHeader>
</PrecompiledHeader>
<PrecompiledHeaderFile>
</PrecompiledHeaderFile>
<AdditionalIncludeDirectories>$(ProjectDir)..\include;$(ProjectDir)..\..\ffi</AdditionalIncludeDirectories>
<LanguageStandard>Default</LanguageStandard>
</ClCompile>
<Link>
<SubSystem>
</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
<CustomBuildStep>
<Command>call "$(ProjectDir)vs_build_rust.bat" $(Platform) $(OutDir)</Command>
</CustomBuildStep>
<Lib>
<AdditionalDependencies>idevice_ffi.lib</AdditionalDependencies>
<AdditionalLibraryDirectories>$(OutDir)</AdditionalLibraryDirectories>
</Lib>
<PreBuildEvent>
<Command>call "$(ProjectDir)..\vs_build_rust.bat" "$(Platform)" "$(OutDir)"</Command>
</PreBuildEvent>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>NDEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<PrecompiledHeader>
</PrecompiledHeader>
<PrecompiledHeaderFile>
</PrecompiledHeaderFile>
<AdditionalIncludeDirectories>$(ProjectDir)..\include;$(ProjectDir)..\..\ffi</AdditionalIncludeDirectories>
<LanguageStandard>Default</LanguageStandard>
</ClCompile>
<Link>
<SubSystem>
</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
<CustomBuildStep>
<Command>call "$(ProjectDir)vs_build_rust.bat" $(Platform) $(OutDir)</Command>
</CustomBuildStep>
<Lib>
<AdditionalDependencies>idevice_ffi.lib</AdditionalDependencies>
<AdditionalLibraryDirectories>$(OutDir)</AdditionalLibraryDirectories>
</Lib>
<PreBuildEvent>
<Command>call "$(ProjectDir)..\vs_build_rust.bat" "$(Platform)" "$(OutDir)"</Command>
</PreBuildEvent>
</ItemDefinitionGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

View File

@@ -0,0 +1,129 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="Header Files">
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
<Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
</Filter>
<Filter Include="Resource Files">
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
</Filter>
<Filter Include="Header Files\idevice++">
<UniqueIdentifier>{b4e9aee7-7e94-4e5f-b443-09677e8d69c2}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\src\adapter_stream.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\src\app_service.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\src\core_device.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\src\debug_proxy.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\src\diagnosticsservice.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\src\ffi.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\src\idevice.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\src\location_simulation.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\src\lockdown.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\src\pairing_file.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\src\provider.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\src\remote_server.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\src\rsd.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\src\tcp_callback_feeder.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\src\usbmuxd.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\include\idevice++\adapter_stream.hpp">
<Filter>Header Files\idevice++</Filter>
</ClInclude>
<ClInclude Include="..\include\idevice++\app_service.hpp">
<Filter>Header Files\idevice++</Filter>
</ClInclude>
<ClInclude Include="..\include\idevice++\bindings.hpp">
<Filter>Header Files\idevice++</Filter>
</ClInclude>
<ClInclude Include="..\include\idevice++\core_device_proxy.hpp">
<Filter>Header Files\idevice++</Filter>
</ClInclude>
<ClInclude Include="..\include\idevice++\debug_proxy.hpp">
<Filter>Header Files\idevice++</Filter>
</ClInclude>
<ClInclude Include="..\include\idevice++\diagnosticsservice.hpp">
<Filter>Header Files\idevice++</Filter>
</ClInclude>
<ClInclude Include="..\include\idevice++\ffi.hpp">
<Filter>Header Files\idevice++</Filter>
</ClInclude>
<ClInclude Include="..\include\idevice++\idevice.hpp">
<Filter>Header Files\idevice++</Filter>
</ClInclude>
<ClInclude Include="..\include\idevice++\location_simulation.hpp">
<Filter>Header Files\idevice++</Filter>
</ClInclude>
<ClInclude Include="..\include\idevice++\lockdown.hpp">
<Filter>Header Files\idevice++</Filter>
</ClInclude>
<ClInclude Include="..\include\idevice++\pairing_file.hpp">
<Filter>Header Files\idevice++</Filter>
</ClInclude>
<ClInclude Include="..\include\idevice++\provider.hpp">
<Filter>Header Files\idevice++</Filter>
</ClInclude>
<ClInclude Include="..\include\idevice++\readwrite.hpp">
<Filter>Header Files\idevice++</Filter>
</ClInclude>
<ClInclude Include="..\include\idevice++\remote_server.hpp">
<Filter>Header Files\idevice++</Filter>
</ClInclude>
<ClInclude Include="..\include\idevice++\rsd.hpp">
<Filter>Header Files\idevice++</Filter>
</ClInclude>
<ClInclude Include="..\include\idevice++\tcp_object_stack.hpp">
<Filter>Header Files\idevice++</Filter>
</ClInclude>
<ClInclude Include="..\include\idevice++\usbmuxd.hpp">
<Filter>Header Files\idevice++</Filter>
</ClInclude>
<ClInclude Include="..\..\ffi\idevice.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\include\idevice++\option.hpp">
<Filter>Header Files\idevice++</Filter>
</ClInclude>
<ClInclude Include="..\include\idevice++\result.hpp">
<Filter>Header Files\idevice++</Filter>
</ClInclude>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,50 @@
// Jackson Coxson
#include <idevice++/adapter_stream.hpp>
#include <idevice++/option.hpp>
namespace IdeviceFFI {
Result<void, FfiError> AdapterStream::close() {
if (!h_)
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) {
if (!h_)
return Err(FfiError::NotConnected());
FfiError e(::adapter_send(h_, data, len));
if (e) {
return Err(e);
}
return Ok();
}
Result<std::vector<uint8_t>, FfiError> AdapterStream::recv(size_t max_hint) {
if (!h_)
return Err(FfiError::NotConnected());
if (max_hint == 0)
max_hint = 2048;
std::vector<uint8_t> out(max_hint);
size_t actual = 0;
FfiError e(::adapter_recv(h_, out.data(), &actual, out.size()));
if (e) {
return Err(e);
}
out.resize(actual);
return Ok(std::move(out));
}
} // namespace IdeviceFFI

181
cpp/src/app_service.cpp Normal file
View File

@@ -0,0 +1,181 @@
// Jackson Coxson
#include <idevice++/app_service.hpp>
namespace IdeviceFFI {
// ---- Factories ----
Result<AppService, FfiError> AppService::connect_rsd(Adapter &adapter,
RsdHandshake &rsd) {
AppServiceHandle *out = nullptr;
if (IdeviceFfiError *e =
::app_service_connect_rsd(adapter.raw(), rsd.raw(), &out)) {
return Err(FfiError(e));
}
return Ok(AppService::adopt(out));
}
Result<AppService, FfiError>
AppService::from_readwrite_ptr(ReadWriteOpaque *consumed) {
AppServiceHandle *out = nullptr;
if (IdeviceFfiError *e = ::app_service_new(consumed, &out)) {
return Err(FfiError(e));
}
return Ok(AppService::adopt(out));
}
Result<AppService, FfiError> AppService::from_readwrite(ReadWrite &&rw) {
// Rust consumes the stream regardless of result → release BEFORE call
return from_readwrite_ptr(rw.release());
}
// ---- Helpers to copy/free C arrays ----
static std::vector<AppInfo> copy_and_free_app_list(AppListEntryC *arr,
size_t n) {
std::vector<AppInfo> out;
out.reserve(n);
for (size_t i = 0; i < n; ++i) {
const auto &c = arr[i];
AppInfo a;
a.is_removable = c.is_removable != 0;
if (c.name)
a.name = c.name;
a.is_first_party = c.is_first_party != 0;
if (c.path)
a.path = c.path;
if (c.bundle_identifier)
a.bundle_identifier = c.bundle_identifier;
a.is_developer_app = c.is_developer_app != 0;
if (c.bundle_version)
a.bundle_version = std::string(c.bundle_version);
a.is_internal = c.is_internal != 0;
a.is_hidden = c.is_hidden != 0;
a.is_app_clip = c.is_app_clip != 0;
if (c.version)
a.version = std::string(c.version);
out.emplace_back(std::move(a));
}
::app_service_free_app_list(arr, n);
return out;
}
static std::vector<ProcessToken> copy_and_free_process_list(ProcessTokenC *arr,
size_t n) {
std::vector<ProcessToken> out;
out.reserve(n);
for (size_t i = 0; i < n; ++i) {
ProcessToken p;
p.pid = arr[i].pid;
if (arr[i].executable_url)
p.executable_url = std::string(arr[i].executable_url);
out.emplace_back(std::move(p));
}
::app_service_free_process_list(arr, n);
return out;
}
// ---- API impls ----
Result<std::vector<AppInfo>, FfiError>
AppService::list_apps(bool app_clips, bool removable, bool hidden,
bool internal, bool default_apps) const {
AppListEntryC *arr = nullptr;
size_t n = 0;
if (IdeviceFfiError *e = ::app_service_list_apps(
handle_.get(), app_clips ? 1 : 0, removable ? 1 : 0, hidden ? 1 : 0,
internal ? 1 : 0, default_apps ? 1 : 0, &arr, &n)) {
return Err(FfiError(e));
}
return Ok(copy_and_free_app_list(arr, n));
}
Result<LaunchResponse, FfiError>
AppService::launch(const std::string &bundle_id,
const std::vector<std::string> &argv, bool kill_existing,
bool start_suspended) {
std::vector<const char *> c_argv;
c_argv.reserve(argv.size());
for (auto &s : argv)
c_argv.push_back(s.c_str());
LaunchResponseC *resp = nullptr;
if (IdeviceFfiError *e = ::app_service_launch_app(
handle_.get(), bundle_id.c_str(),
c_argv.empty() ? nullptr : c_argv.data(), c_argv.size(),
kill_existing ? 1 : 0, start_suspended ? 1 : 0,
NULL, // TODO: stdio handling
&resp)) {
return Err(FfiError(e));
}
LaunchResponse out;
out.process_identifier_version = resp->process_identifier_version;
out.pid = resp->pid;
if (resp->executable_url)
out.executable_url = resp->executable_url;
if (resp->audit_token && resp->audit_token_len > 0) {
out.audit_token.assign(resp->audit_token,
resp->audit_token + resp->audit_token_len);
}
::app_service_free_launch_response(resp);
return Ok(std::move(out));
}
Result<std::vector<ProcessToken>, FfiError> AppService::list_processes() const {
ProcessTokenC *arr = nullptr;
size_t n = 0;
if (IdeviceFfiError *e =
::app_service_list_processes(handle_.get(), &arr, &n)) {
return Err(FfiError(e));
}
return Ok(copy_and_free_process_list(arr, n));
}
Result<void, FfiError> AppService::uninstall(const std::string &bundle_id) {
if (IdeviceFfiError *e =
::app_service_uninstall_app(handle_.get(), bundle_id.c_str())) {
return Err(FfiError(e));
}
return Ok();
}
Result<SignalResponse, FfiError> AppService::send_signal(uint32_t pid,
uint32_t signal) {
SignalResponseC *c = nullptr;
if (IdeviceFfiError *e =
::app_service_send_signal(handle_.get(), pid, signal, &c)) {
return Err(FfiError(e));
}
SignalResponse out;
out.pid = c->pid;
if (c->executable_url)
out.executable_url = std::string(c->executable_url);
out.device_timestamp_ms = c->device_timestamp;
out.signal = c->signal;
::app_service_free_signal_response(c);
return Ok(std::move(out));
}
Result<IconData, FfiError> AppService::fetch_icon(const std::string &bundle_id,
float width, float height,
float scale,
bool allow_placeholder) {
IconDataC *c = nullptr;
if (IdeviceFfiError *e = ::app_service_fetch_app_icon(
handle_.get(), bundle_id.c_str(), width, height, scale,
allow_placeholder ? 1 : 0, &c)) {
return Err(FfiError(e));
}
IconData out;
if (c->data && c->data_len) {
out.data.assign(c->data, c->data + c->data_len);
}
out.icon_width = c->icon_width;
out.icon_height = c->icon_height;
out.minimum_width = c->minimum_width;
out.minimum_height = c->minimum_height;
::app_service_free_icon_data(c);
return Ok(std::move(out));
}
} // namespace IdeviceFFI

120
cpp/src/core_device.cpp Normal file
View File

@@ -0,0 +1,120 @@
// Jackson Coxson
#include <idevice++/core_device_proxy.hpp>
namespace IdeviceFFI {
// ---- Factories ----
Result<CoreDeviceProxy, FfiError> CoreDeviceProxy::connect(Provider &provider) {
CoreDeviceProxyHandle *out = nullptr;
FfiError e(::core_device_proxy_connect(provider.raw(), &out));
if (e) {
return Err(e);
}
return Ok(CoreDeviceProxy::adopt(out));
}
Result<CoreDeviceProxy, FfiError>
CoreDeviceProxy::from_socket(Idevice &&socket) {
CoreDeviceProxyHandle *out = nullptr;
// Rust consumes the socket regardless of result → release BEFORE call
IdeviceHandle *raw = socket.release();
FfiError e(::core_device_proxy_new(raw, &out));
if (e) {
return Err(e);
}
return Ok(CoreDeviceProxy::adopt(out));
}
// ---- IO ----
Result<void, FfiError> CoreDeviceProxy::send(const uint8_t *data, size_t len) {
FfiError e(::core_device_proxy_send(handle_.get(), data, len));
if (e) {
return Err(e);
}
return Ok();
}
Result<void, FfiError> CoreDeviceProxy::recv(std::vector<uint8_t> &out) {
if (out.empty())
out.resize(4096); // a reasonable default; caller can pre-size
size_t actual = 0;
FfiError e(
::core_device_proxy_recv(handle_.get(), out.data(), &actual, out.size()));
if (e) {
return Err(e);
}
out.resize(actual);
return Ok();
}
// ---- Handshake ----
Result<CoreClientParams, FfiError>
CoreDeviceProxy::get_client_parameters() const {
uint16_t mtu = 0;
char *addr_c = nullptr;
char *mask_c = nullptr;
FfiError e(::core_device_proxy_get_client_parameters(handle_.get(), &mtu,
&addr_c, &mask_c));
if (e) {
return Err(e);
}
CoreClientParams params;
params.mtu = mtu;
if (addr_c) {
params.address = addr_c;
::idevice_string_free(addr_c);
}
if (mask_c) {
params.netmask = mask_c;
::idevice_string_free(mask_c);
}
return Ok(std::move(params));
}
Result<std::string, FfiError> CoreDeviceProxy::get_server_address() const {
char *addr_c = nullptr;
FfiError e(::core_device_proxy_get_server_address(handle_.get(), &addr_c));
if (e) {
return Err(e);
}
std::string s;
if (addr_c) {
s = addr_c;
::idevice_string_free(addr_c);
}
return Ok(s);
}
Result<uint16_t, FfiError> CoreDeviceProxy::get_server_rsd_port() const {
uint16_t port = 0;
FfiError e(::core_device_proxy_get_server_rsd_port(handle_.get(), &port));
if (e) {
return Err(e);
}
return Ok(port);
}
// ---- Adapter creation (consumes *this) ----
Result<Adapter, FfiError> CoreDeviceProxy::create_tcp_adapter() && {
AdapterHandle *out = nullptr;
// Rust consumes the proxy regardless of result → release BEFORE call
CoreDeviceProxyHandle *raw = this->release();
FfiError e(::core_device_proxy_create_tcp_adapter(raw, &out));
if (e) {
return Err(e);
}
return Ok(Adapter::adopt(out));
}
} // namespace IdeviceFFI

143
cpp/src/debug_proxy.cpp Normal file
View File

@@ -0,0 +1,143 @@
// Jackson Coxson
#include <cstring>
#include <idevice++/debug_proxy.hpp>
namespace IdeviceFFI {
// ---- helpers ----
static Option<std::string> take_cstring(char *p) {
if (!p)
return None;
std::string s(p);
::idevice_string_free(p);
return Some(s);
}
// ---- DebugCommand ----
Option<DebugCommand> DebugCommand::make(const std::string &name,
const std::vector<std::string> &argv) {
std::vector<const char *> c_argv;
c_argv.reserve(argv.size());
for (auto &a : argv)
c_argv.push_back(a.c_str());
auto *h = ::debugserver_command_new(
name.c_str(),
c_argv.empty() ? nullptr : const_cast<const char *const *>(c_argv.data()),
c_argv.size());
if (!h)
return None;
return Some(DebugCommand(h));
}
// ---- DebugProxy factories ----
Result<DebugProxy, FfiError> DebugProxy::connect_rsd(Adapter &adapter,
RsdHandshake &rsd) {
::DebugProxyHandle *out = nullptr;
FfiError e(::debug_proxy_connect_rsd(adapter.raw(), rsd.raw(), &out));
if (e) {
return Err(e);
}
return Ok(DebugProxy(out));
}
Result<DebugProxy, FfiError>
DebugProxy::from_readwrite_ptr(::ReadWriteOpaque *consumed) {
::DebugProxyHandle *out = nullptr;
FfiError e(::debug_proxy_new(consumed, &out));
if (e) {
return Err(e);
}
return Ok(DebugProxy(out));
}
Result<DebugProxy, FfiError> DebugProxy::from_readwrite(ReadWrite &&rw) {
// Rust consumes the pointer regardless of outcome; release before calling
return from_readwrite_ptr(rw.release());
}
// ---- DebugProxy API ----
Result<Option<std::string>, FfiError>
DebugProxy::send_command(const std::string &name,
const std::vector<std::string> &argv) {
auto cmdRes = DebugCommand::make(name, argv);
if (cmdRes.is_none()) {
// treat as invalid arg
FfiError err;
err.code = -1;
err.message = "debugserver_command_new failed";
return Err(err);
}
auto cmd = std::move(cmdRes).unwrap();
char *resp_c = nullptr;
FfiError e(::debug_proxy_send_command(handle_, cmd.raw(), &resp_c));
if (e) {
return Err(e);
}
return Ok(take_cstring(resp_c));
}
Result<Option<std::string>, FfiError> DebugProxy::read_response() {
char *resp_c = nullptr;
FfiError e(::debug_proxy_read_response(handle_, &resp_c));
if (e) {
return Err(e);
}
return Ok(take_cstring(resp_c));
}
Result<void, FfiError> DebugProxy::send_raw(const std::vector<uint8_t> &data) {
FfiError e(::debug_proxy_send_raw(handle_, data.data(), data.size()));
if (e) {
return Err(e);
}
return Ok();
}
Result<Option<std::string>, FfiError> DebugProxy::read(std::size_t len) {
char *resp_c = nullptr;
FfiError e(::debug_proxy_read(handle_, len, &resp_c));
if (e) {
return Err(e);
}
return Ok(take_cstring(resp_c));
}
Result<Option<std::string>, FfiError>
DebugProxy::set_argv(const std::vector<std::string> &argv) {
std::vector<const char *> c_argv;
c_argv.reserve(argv.size());
for (auto &a : argv)
c_argv.push_back(a.c_str());
char *resp_c = nullptr;
FfiError e(::debug_proxy_set_argv(
handle_,
c_argv.empty() ? nullptr : const_cast<const char *const *>(c_argv.data()),
c_argv.size(), &resp_c));
if (e) {
return Err(e);
}
return Ok(take_cstring(resp_c));
}
Result<void, FfiError> DebugProxy::send_ack() {
FfiError e(::debug_proxy_send_ack(handle_));
if (e) {
return Err(e);
}
return Ok();
}
Result<void, FfiError> DebugProxy::send_nack() {
FfiError e(::debug_proxy_send_nack(handle_));
if (e) {
return Err(e);
}
return Ok();
}
} // namespace IdeviceFFI

View File

@@ -0,0 +1,93 @@
// Jackson Coxson
#include <cstring>
#include <idevice++/diagnosticsservice.hpp>
#include <idevice++/option.hpp>
namespace IdeviceFFI {
// Local helper: take ownership of a C string and convert to std::string
static Option<std::string> take_cstring(char* p) {
if (!p) {
return None;
}
std::string s(p);
::idevice_string_free(p);
return Some(std::move(s));
}
// -------- SysdiagnoseStream --------
Result<Option<std::vector<uint8_t>>, FfiError> SysdiagnoseStream::next_chunk() {
if (!h_) {
return Err(FfiError::NotConnected());
}
uint8_t* data = nullptr;
std::size_t len = 0;
FfiError e(::sysdiagnose_stream_next(h_, &data, &len));
if (e) {
return Err(e);
}
if (!data || len == 0) {
// End of stream
return Ok(Option<std::vector<uint8_t>>(None));
}
// Copy into a C++ buffer
std::vector<uint8_t> out(len);
std::memcpy(out.data(), data, len);
idevice_data_free(data, len);
return Ok(Some(out));
}
// -------- DiagnosticsService --------
Result<DiagnosticsService, FfiError> DiagnosticsService::connect_rsd(Adapter& adapter,
RsdHandshake& rsd) {
::DiagnosticsServiceHandle* out = nullptr;
FfiError e(::diagnostics_service_connect_rsd(adapter.raw(), rsd.raw(), &out));
if (e) {
return Err(e);
}
return Ok(DiagnosticsService(out));
}
Result<DiagnosticsService, FfiError>
DiagnosticsService::from_stream_ptr(::ReadWriteOpaque* consumed) {
::DiagnosticsServiceHandle* out = nullptr;
FfiError e(::diagnostics_service_new(consumed, &out));
if (e) {
return Err(e);
}
return Ok(DiagnosticsService(out));
}
Result<SysdiagnoseCapture, FfiError> DiagnosticsService::capture_sysdiagnose(bool dry_run) {
if (!h_) {
return Err(FfiError::NotConnected());
}
char* filename_c = nullptr;
std::size_t expected_len = 0;
::SysdiagnoseStreamHandle* stream_h = nullptr;
FfiError e(::diagnostics_service_capture_sysdiagnose(
h_, dry_run ? true : false, &filename_c, &expected_len, &stream_h));
if (e) {
return Err(e);
}
auto fname = take_cstring(filename_c).unwrap_or(std::string{});
SysdiagnoseStream stream(stream_h);
SysdiagnoseCapture cap{/*preferred_filename*/ std::move(fname),
/*expected_length*/ expected_len,
/*stream*/ std::move(stream)};
return Ok(std::move(cap));
}
} // namespace IdeviceFFI

32
cpp/src/ffi.cpp Normal file
View File

@@ -0,0 +1,32 @@
// Jackson Coxson
#include <idevice++/bindings.hpp>
#include <idevice++/ffi.hpp>
#include <idevice.h>
#include <string>
namespace IdeviceFFI {
FfiError::FfiError(const IdeviceFfiError* err)
: code(err ? err->code : 0), message(err && err->message ? err->message : "") {
if (err) {
idevice_error_free(const_cast<IdeviceFfiError*>(err));
}
}
FfiError::FfiError() : code(0), message("") {
}
FfiError FfiError::NotConnected() {
FfiError err;
err.code = -11; // from idevice/lib.rs
err.message = "No established socket connection";
return err;
}
FfiError FfiError::InvalidArgument() {
FfiError err;
err.code = -57; // from idevice/lib.rs
err.message = "No established socket connection";
return err;
}
} // namespace IdeviceFFI

53
cpp/src/idevice.cpp Normal file
View File

@@ -0,0 +1,53 @@
// Jackson Coxson
#include <idevice++/idevice.hpp>
namespace IdeviceFFI {
Result<Idevice, FfiError> Idevice::create(IdeviceSocketHandle* socket, const std::string& label) {
IdeviceHandle* h = nullptr;
FfiError e(idevice_new(socket, label.c_str(), &h));
if (e) {
return Err(e);
}
return Ok(Idevice(h));
}
Result<Idevice, FfiError>
Idevice::create_tcp(const sockaddr* addr, socklen_t addr_len, const std::string& label) {
IdeviceHandle* h = nullptr;
FfiError e(idevice_new_tcp_socket(addr, addr_len, label.c_str(), &h));
if (e) {
return Err(e);
}
return Ok(Idevice(h));
}
Result<std::string, FfiError> Idevice::get_type() const {
char* cstr = nullptr;
FfiError e(idevice_get_type(handle_.get(), &cstr));
if (e) {
return Err(e);
}
std::string out(cstr);
idevice_string_free(cstr);
return Ok(out);
}
Result<void, FfiError> Idevice::rsd_checkin() {
FfiError e(idevice_rsd_checkin(handle_.get()));
if (e) {
return Err(e);
}
return Ok();
}
Result<void, FfiError> Idevice::start_session(const PairingFile& pairing_file) {
FfiError e(idevice_start_session(handle_.get(), pairing_file.raw()));
if (e) {
return Err(e);
}
return Ok();
}
} // namespace IdeviceFFI

View File

@@ -0,0 +1,32 @@
// Jackson Coxson
#include <idevice++/location_simulation.hpp>
namespace IdeviceFFI {
Result<LocationSimulation, FfiError> LocationSimulation::create(RemoteServer& server) {
LocationSimulationHandle* out = nullptr;
FfiError e(::location_simulation_new(server.raw(), &out));
if (e) {
return Err(e);
}
return Ok(LocationSimulation::adopt(out));
}
Result<void, FfiError> LocationSimulation::clear() {
FfiError e(::location_simulation_clear(handle_.get()));
if (e) {
return Err(e);
}
return Ok();
}
Result<void, FfiError> LocationSimulation::set(double latitude, double longitude) {
FfiError e(::location_simulation_set(handle_.get(), latitude, longitude));
if (e) {
return Err(e);
}
return Ok();
}
} // namespace IdeviceFFI

57
cpp/src/lockdown.cpp Normal file
View File

@@ -0,0 +1,57 @@
// Jackson Coxson
#include <idevice++/bindings.hpp>
#include <idevice++/ffi.hpp>
#include <idevice++/lockdown.hpp>
#include <idevice++/provider.hpp>
namespace IdeviceFFI {
Result<Lockdown, FfiError> Lockdown::connect(Provider& provider) {
LockdowndClientHandle* out = nullptr;
FfiError e(::lockdownd_connect(provider.raw(), &out));
if (e) {
provider.release();
return Err(e);
}
return Ok(Lockdown::adopt(out));
}
Result<Lockdown, FfiError> Lockdown::from_socket(Idevice&& socket) {
LockdowndClientHandle* out = nullptr;
FfiError e(::lockdownd_new(socket.raw(), &out));
if (e) {
return Err(e);
}
socket.release();
return Ok(Lockdown::adopt(out));
}
Result<void, FfiError> Lockdown::start_session(const PairingFile& pf) {
FfiError e(::lockdownd_start_session(handle_.get(), pf.raw()));
if (e) {
return Err(e);
}
return Ok();
}
Result<std::pair<uint16_t, bool>, FfiError> Lockdown::start_service(const std::string& identifier) {
uint16_t port = 0;
bool ssl = false;
FfiError e(::lockdownd_start_service(handle_.get(), identifier.c_str(), &port, &ssl));
if (e) {
return Err(e);
}
return Ok(std::make_pair(port, ssl));
}
Result<plist_t, FfiError> Lockdown::get_value(const char* key, const char* domain) {
plist_t out = nullptr;
FfiError e(::lockdownd_get_value(handle_.get(), key, domain, &out));
if (e) {
return Err(e);
}
return Ok(out);
}
} // namespace IdeviceFFI

53
cpp/src/pairing_file.cpp Normal file
View File

@@ -0,0 +1,53 @@
// Jackson Coxson
#include <idevice++/bindings.hpp>
#include <idevice++/ffi.hpp>
#include <idevice++/pairing_file.hpp>
namespace IdeviceFFI {
// Deleter definition (out-of-line)
void PairingFileDeleter::operator()(IdevicePairingFile* p) const noexcept {
if (p) {
idevice_pairing_file_free(p);
}
}
// Static member definitions
Result<PairingFile, FfiError> PairingFile::read(const std::string& path) {
IdevicePairingFile* ptr = nullptr;
FfiError e(idevice_pairing_file_read(path.c_str(), &ptr));
if (e) {
return Err(e);
}
return Ok(PairingFile(ptr));
}
Result<PairingFile, FfiError> PairingFile::from_bytes(const uint8_t* data, size_t size) {
IdevicePairingFile* raw = nullptr;
FfiError e(idevice_pairing_file_from_bytes(data, size, &raw));
if (e) {
return Err(e);
}
return Ok(PairingFile(raw));
}
Result<std::vector<uint8_t>, FfiError> PairingFile::serialize() const {
if (!ptr_) {
return Err(FfiError::InvalidArgument());
}
uint8_t* data = nullptr;
size_t size = 0;
FfiError e(idevice_pairing_file_serialize(ptr_.get(), &data, &size));
if (e) {
return Err(e);
}
std::vector<uint8_t> out(data, data + size);
idevice_data_free(data, size);
return Ok(out);
}
} // namespace IdeviceFFI

58
cpp/src/provider.cpp Normal file
View File

@@ -0,0 +1,58 @@
// Jackson Coxson
#include <idevice++/bindings.hpp>
#include <idevice++/ffi.hpp>
#include <idevice++/provider.hpp>
namespace IdeviceFFI {
Result<Provider, FfiError>
Provider::tcp_new(const idevice_sockaddr* ip, PairingFile&& pairing, const std::string& label) {
IdeviceProviderHandle* out = nullptr;
FfiError e(idevice_tcp_provider_new(
ip, static_cast<IdevicePairingFile*>(pairing.raw()), label.c_str(), &out));
if (e) {
return Err(e);
}
// Success: Rust consumed the pairing file -> abandon our ownership
pairing.release();
return Ok(Provider::adopt(out));
}
Result<Provider, FfiError> Provider::usbmuxd_new(UsbmuxdAddr&& addr,
uint32_t tag,
const std::string& udid,
uint32_t device_id,
const std::string& label) {
IdeviceProviderHandle* out = nullptr;
FfiError e(usbmuxd_provider_new(static_cast<UsbmuxdAddrHandle*>(addr.raw()),
tag,
udid.c_str(),
device_id,
label.c_str(),
&out));
if (e) {
return Err(e);
}
// Success: Rust consumed the addr -> abandon our ownership
addr.release();
return Ok(Provider::adopt(out));
}
Result<PairingFile, FfiError> Provider::get_pairing_file() {
IdevicePairingFile* out = nullptr;
FfiError e(idevice_provider_get_pairing_file(handle_.get(), &out));
if (e) {
return Err(e);
}
return Ok(PairingFile(out));
}
} // namespace IdeviceFFI

29
cpp/src/remote_server.cpp Normal file
View File

@@ -0,0 +1,29 @@
// Jackson Coxson
#include <idevice++/remote_server.hpp>
namespace IdeviceFFI {
Result<RemoteServer, FfiError> RemoteServer::from_socket(ReadWrite&& rw) {
RemoteServerHandle* out = nullptr;
// Rust consumes the stream regardless of result, release BEFORE the call
ReadWriteOpaque* raw = rw.release();
FfiError e(::remote_server_new(raw, &out));
if (e) {
return Err(e);
}
return Ok(RemoteServer::adopt(out));
}
Result<RemoteServer, FfiError> RemoteServer::connect_rsd(Adapter& adapter, RsdHandshake& rsd) {
RemoteServerHandle* out = nullptr;
FfiError e(::remote_server_connect_rsd(adapter.raw(), rsd.raw(), &out));
if (e) {
return Err(e);
}
return Ok(RemoteServer::adopt(out));
}
} // namespace IdeviceFFI

151
cpp/src/rsd.cpp Normal file
View File

@@ -0,0 +1,151 @@
// Jackson Coxson
#include <idevice++/rsd.hpp>
namespace IdeviceFFI {
// ---------- helpers to copy/free CRsdService ----------
static RsdService to_cpp_and_free(CRsdService* c) {
RsdService s;
if (c->name) {
s.name = c->name;
}
if (c->entitlement) {
s.entitlement = c->entitlement;
}
s.port = c->port;
s.uses_remote_xpc = c->uses_remote_xpc;
s.service_version = c->service_version;
// features
if (c->features && c->features_count > 0) {
auto** arr = c->features;
s.features.reserve(c->features_count);
for (size_t i = 0; i < c->features_count; ++i) {
if (arr[i]) {
s.features.emplace_back(arr[i]);
}
}
}
// release the C allocation now that we've copied
rsd_free_service(c);
return s;
}
static std::vector<RsdService> to_cpp_and_free(CRsdServiceArray* arr) {
std::vector<RsdService> out;
if (!arr || !arr->services || arr->count == 0) {
if (arr) {
rsd_free_services(arr);
}
return out;
}
out.reserve(arr->count);
auto* begin = arr->services;
for (size_t i = 0; i < arr->count; ++i) {
out.emplace_back(RsdService{begin[i].name ? begin[i].name : "",
begin[i].entitlement ? begin[i].entitlement : "",
begin[i].port,
begin[i].uses_remote_xpc,
{}, // features, fill below
begin[i].service_version});
// features for this service
if (begin[i].features && begin[i].features_count > 0) {
auto** feats = begin[i].features;
out.back().features.reserve(begin[i].features_count);
for (size_t j = 0; j < begin[i].features_count; ++j) {
if (feats[j]) {
out.back().features.emplace_back(feats[j]);
}
}
}
}
// free the array + nested C strings now that we've copied
rsd_free_services(arr);
return out;
}
RsdHandshake::RsdHandshake(const RsdHandshake& other) {
if (other.handle_) {
// Call the Rust FFI to clone the underlying handle
handle_.reset(rsd_handshake_clone(other.handle_.get()));
}
// If other.handle_ is null, our new handle_ will also be null, which is correct.
}
RsdHandshake& RsdHandshake::operator=(const RsdHandshake& other) {
// Check for self-assignment
if (this != &other) {
// Create a temporary copy, then swap ownership
RsdHandshake temp(other);
std::swap(handle_, temp.handle_);
}
return *this;
}
// ---------- factory ----------
Result<RsdHandshake, FfiError> RsdHandshake::from_socket(ReadWrite&& rw) {
RsdHandshakeHandle* out = nullptr;
// Rust consumes the socket regardless of result ⇒ release BEFORE call.
ReadWriteOpaque* raw = rw.release();
FfiError e(rsd_handshake_new(raw, &out));
if (e) {
return Err(e);
}
return Ok(RsdHandshake::adopt(out));
}
// ---------- queries ----------
Result<size_t, FfiError> RsdHandshake::protocol_version() const {
size_t v = 0;
FfiError e(rsd_get_protocol_version(handle_.get(), &v));
if (e) {
return Err(e);
}
return Ok(v);
}
Result<std::string, FfiError> RsdHandshake::uuid() const {
char* c = nullptr;
FfiError e(rsd_get_uuid(handle_.get(), &c));
if (e) {
return Err(e);
}
std::string out;
if (c) {
out = c;
rsd_free_string(c);
}
return Ok(out);
}
Result<std::vector<RsdService>, FfiError> RsdHandshake::services() const {
CRsdServiceArray* arr = nullptr;
FfiError e(rsd_get_services(handle_.get(), &arr));
if (e) {
return Err(e);
}
return Ok(to_cpp_and_free(arr));
}
Result<bool, FfiError> RsdHandshake::service_available(const std::string& name) const {
bool avail = false;
FfiError e(rsd_service_available(handle_.get(), name.c_str(), &avail));
if (e) {
return Err(e);
}
return Ok(avail);
}
Result<RsdService, FfiError> RsdHandshake::service_info(const std::string& name) const {
CRsdService* svc = nullptr;
FfiError e(rsd_get_service_info(handle_.get(), name.c_str(), &svc));
if (e) {
return Err(e);
}
return Ok(to_cpp_and_free(svc));
}
} // namespace IdeviceFFI

View File

@@ -0,0 +1,118 @@
// Jackson Coxson
#include <idevice++/bindings.hpp>
#include <idevice++/tcp_object_stack.hpp>
namespace IdeviceFFI {
// ---------- TcpFeeder ----------
bool TcpObjectStackFeeder::write(const uint8_t* data, std::size_t len, FfiError& err) const {
if (IdeviceFfiError* e = ::idevice_tcp_feed_object_write(h_, data, len)) {
err = FfiError(e);
return false;
}
return true;
}
// ---------- TcpEater ----------
bool TcpObjectStackEater::read(OwnedBuffer& out, FfiError& err) const {
uint8_t* ptr = nullptr;
std::size_t len = 0;
if (IdeviceFfiError* e = ::idevice_tcp_eat_object_read(h_, &ptr, &len)) {
err = FfiError(e);
return false;
}
// Success: adopt the buffer (freed via idevice_data_free in OwnedBuffer dtor)
out.adopt(ptr, len);
return true;
}
// ---------- TcpStackFromCallback ----------
Result<TcpObjectStack, FfiError> TcpObjectStack::create(const std::string& our_ip,
const std::string& their_ip) {
::TcpFeedObject* feeder_h = nullptr;
::TcpEatObject* eater_h = nullptr;
::AdapterHandle* adapter_h = nullptr;
FfiError e(::idevice_tcp_stack_into_sync_objects(
our_ip.c_str(), their_ip.c_str(), &feeder_h, &eater_h, &adapter_h));
if (e) {
return Err(e);
}
auto impl = std::make_unique<Impl>();
impl->feeder = TcpObjectStackFeeder(feeder_h);
impl->eater = TcpObjectStackEater(eater_h);
impl->adapter = Adapter::adopt(adapter_h);
TcpObjectStack out;
out.impl_ = std::move(impl);
return Ok(std::move(out));
}
TcpObjectStackFeeder& TcpObjectStack::feeder() {
return impl_->feeder;
}
const TcpObjectStackFeeder& TcpObjectStack::feeder() const {
return impl_->feeder;
}
TcpObjectStackEater& TcpObjectStack::eater() {
return impl_->eater;
}
const TcpObjectStackEater& TcpObjectStack::eater() const {
return impl_->eater;
}
Adapter& TcpObjectStack::adapter() {
if (!impl_ || impl_->adapter.is_some()) {
static Adapter* never = nullptr;
return *never;
}
return (impl_->adapter.unwrap());
}
const Adapter& TcpObjectStack::adapter() const {
if (!impl_ || impl_->adapter.is_none()) {
static Adapter* never = nullptr;
return *never;
}
return (impl_->adapter.unwrap());
}
// ---------- Release APIs ----------
Option<TcpObjectStackFeeder> TcpObjectStack::release_feeder() {
if (!impl_) {
return None;
}
auto has = impl_->feeder.raw() != nullptr;
if (!has) {
return None;
}
TcpObjectStackFeeder out = std::move(impl_->feeder);
// impl_->feeder is now empty (h_ == nullptr) thanks to move
return Some(std::move(out));
}
Option<TcpObjectStackEater> TcpObjectStack::release_eater() {
if (!impl_) {
return None;
}
auto has = impl_->eater.raw() != nullptr;
if (!has) {
return None;
}
TcpObjectStackEater out = std::move(impl_->eater);
return Some(std::move(out));
}
Option<Adapter> TcpObjectStack::release_adapter() {
if (!impl_ || impl_->adapter.is_none()) {
return None;
}
// Move out and clear our optional
auto out = std::move((impl_->adapter.unwrap()));
impl_->adapter.reset();
return Some(std::move(out));
}
} // namespace IdeviceFFI

155
cpp/src/usbmuxd.cpp Normal file
View File

@@ -0,0 +1,155 @@
// Jackson Coxson
#include <idevice++/ffi.hpp>
#include <idevice++/usbmuxd.hpp>
namespace IdeviceFFI {
// ---------- UsbmuxdAddr ----------
Result<UsbmuxdAddr, FfiError> UsbmuxdAddr::tcp_new(const sockaddr* addr, socklen_t addr_len) {
UsbmuxdAddrHandle* h = nullptr;
FfiError e(idevice_usbmuxd_tcp_addr_new(addr, addr_len, &h));
if (e) {
return Err(e);
}
return Ok(UsbmuxdAddr(h));
}
#if defined(__unix__) || defined(__APPLE__)
Result<UsbmuxdAddr, FfiError> UsbmuxdAddr::unix_new(const std::string& path) {
UsbmuxdAddrHandle* h = nullptr;
FfiError e(idevice_usbmuxd_unix_addr_new(path.c_str(), &h));
if (e) {
return Err(e);
}
return Ok(UsbmuxdAddr(h));
}
#endif
UsbmuxdAddr UsbmuxdAddr::default_new() {
UsbmuxdAddrHandle* h = nullptr;
idevice_usbmuxd_default_addr_new(&h);
return UsbmuxdAddr::adopt(h);
}
// ---------- UsbmuxdConnectionType ----------
std::string UsbmuxdConnectionType::to_string() const {
switch (_value) {
case Value::Usb:
return "USB";
case Value::Network:
return "Network";
case Value::Unknown:
return "Unknown";
default:
return "UnknownEnumValue";
}
}
// ---------- UsbmuxdDevice ----------
Option<std::string> UsbmuxdDevice::get_udid() const {
char* c = idevice_usbmuxd_device_get_udid(handle_.get());
if (!c) {
return None;
}
std::string out(c);
idevice_string_free(c);
return Some(out);
}
Option<uint32_t> UsbmuxdDevice::get_id() const {
uint32_t id = idevice_usbmuxd_device_get_device_id(handle_.get());
if (id == 0) {
return None;
}
return Some(id);
}
Option<UsbmuxdConnectionType> UsbmuxdDevice::get_connection_type() const {
uint8_t t = idevice_usbmuxd_device_get_connection_type(handle_.get());
if (t == 0) {
return None;
}
return Some(UsbmuxdConnectionType(t));
}
// ---------- UsbmuxdConnection ----------
Result<UsbmuxdConnection, FfiError>
UsbmuxdConnection::tcp_new(const idevice_sockaddr* addr, idevice_socklen_t addr_len, uint32_t tag) {
UsbmuxdConnectionHandle* h = nullptr;
FfiError e(idevice_usbmuxd_new_tcp_connection(addr, addr_len, tag, &h));
if (e) {
return Err(e);
}
return Ok(UsbmuxdConnection(h));
}
#if defined(__unix__) || defined(__APPLE__)
Result<UsbmuxdConnection, FfiError> UsbmuxdConnection::unix_new(const std::string& path,
uint32_t tag) {
UsbmuxdConnectionHandle* h = nullptr;
FfiError e(idevice_usbmuxd_new_unix_socket_connection(path.c_str(), tag, &h));
if (e) {
return Err(e);
}
return Ok(UsbmuxdConnection(h));
}
#endif
Result<UsbmuxdConnection, FfiError> UsbmuxdConnection::default_new(uint32_t tag) {
UsbmuxdConnectionHandle* h = nullptr;
FfiError e(idevice_usbmuxd_new_default_connection(tag, &h));
if (e) {
return Err(e);
}
return Ok(UsbmuxdConnection(h));
}
Result<std::vector<UsbmuxdDevice>, FfiError> UsbmuxdConnection::get_devices() const {
UsbmuxdDeviceHandle** list = nullptr;
int count = 0;
FfiError e(idevice_usbmuxd_get_devices(handle_.get(), &list, &count));
if (e) {
return Err(e);
}
std::vector<UsbmuxdDevice> out;
out.reserve(count);
for (int i = 0; i < count; ++i) {
out.emplace_back(UsbmuxdDevice::adopt(list[i]));
}
return Ok(std::move(out));
}
Result<std::string, FfiError> UsbmuxdConnection::get_buid() const {
char* c = nullptr;
FfiError e(idevice_usbmuxd_get_buid(handle_.get(), &c));
if (e) {
return Err(e);
}
std::string out(c);
idevice_string_free(c);
return Ok(out);
}
Result<PairingFile, FfiError> UsbmuxdConnection::get_pair_record(const std::string& udid) {
IdevicePairingFile* pf = nullptr;
FfiError e(idevice_usbmuxd_get_pair_record(handle_.get(), udid.c_str(), &pf));
if (e) {
return Err(e);
}
return Ok(PairingFile(pf));
}
Result<Idevice, FfiError> UsbmuxdConnection::connect_to_device(uint32_t device_id,
uint16_t port,
const std::string& path) && {
UsbmuxdConnectionHandle* raw = handle_.release();
IdeviceHandle* out = nullptr;
FfiError e(idevice_usbmuxd_connect_to_device(raw, device_id, port, path.c_str(), &out));
if (e) {
return Err(e);
}
return Ok(Idevice::adopt(out));
}
} // namespace IdeviceFFI

65
cpp/vs_build_rust.bat Normal file
View File

@@ -0,0 +1,65 @@
@echo off
setlocal
REM --- Configuration ---
SET "CRATE_NAME=idevice_ffi"
SET "RUST_PROJECT_PATH=%~dp0..\ffi"
echo "--- Rust Build Script Started ---"
echo "Rust Project Path: %RUST_PROJECT_PATH%"
echo "Visual Studio Platform: %1"
REM --- Header File Copy ---
xcopy /Y "%RUST_PROJECT_PATH%\idevice.h" "%~dp0\include\"
REM --- Locate Cargo ---
REM Check if cargo is in the PATH.
where cargo >nul 2>nul
if %errorlevel% neq 0 (
echo Error: cargo.exe not found in PATH.
echo Please ensure the Rust toolchain is installed and configured.
exit /b 1
)
REM --- Determine Rust Target ---
SET "RUST_TARGET="
IF /I "%~1" == "x64" (
SET "RUST_TARGET=x86_64-pc-windows-msvc"
)
IF /I "%~1" == "ARM64" (
SET "RUST_TARGET=aarch64-pc-windows-msvc"
)
IF /I "%~1" == "Win32" (
SET "RUST_TARGET=i686-pc-windows-msvc"
)
IF NOT DEFINED RUST_TARGET (
echo Error: Unsupported Visual Studio platform '%~1'.
echo This script supports 'x64' and 'ARM64'.
exit /b 1
)
echo "Building for Rust target: %RUST_TARGET%"
REM --- Run Cargo Build ---
SET "STATIC_LIB_NAME=%CRATE_NAME%.lib"
SET "BUILT_LIB_PATH=%RUST_PROJECT_PATH%\..\target\%RUST_TARGET%\release\%STATIC_LIB_NAME%"
REM Change to the Rust project directory and run the build.
pushd "%RUST_PROJECT_PATH%"
cargo build --release --target %RUST_TARGET% --features ring,full --no-default-features
if %errorlevel% neq 0 (
echo Error: Cargo build failed.
popd
exit /b 1
)
popd
echo "Cargo build successful."
REM --- Copy Artifacts ---
echo "Copying '%BUILT_LIB_PATH%' to '%2'"
xcopy /Y "%BUILT_LIB_PATH%" "%2"
echo "--- Rust Build Script Finished ---"
exit /b 0

123
cpp/xcode_build_rust.sh Executable file
View File

@@ -0,0 +1,123 @@
#!/bin/sh
cp ../ffi/idevice.h include/
# This script builds a Rust library for use in an Xcode project.
# It's designed to be used as a "Run Script" build phase on a native Xcode target.
# It handles multiple architectures by building for each and combining them with `lipo`.
# --- Configuration ---
# The name of your Rust crate (the name in Cargo.toml)
CRATE_NAME="idevice_ffi"
# The path to your Rust project's root directory (containing Cargo.toml)
RUST_PROJECT_PATH="${PROJECT_DIR}/../ffi"
# --- Environment Setup ---
# Augment the PATH to include common locations for build tools like cmake and go.
export PATH="/opt/homebrew/bin:/usr/local/bin:/usr/local/go/bin:$PATH"
# --- Locate Cargo ---
# Xcode's build environment often has a minimal PATH, so we need to find cargo explicitly.
if [ -x "${HOME}/.cargo/bin/cargo" ]; then
CARGO="${HOME}/.cargo/bin/cargo"
else
CARGO=$(command -v cargo)
if [ -z "$CARGO" ]; then
echo "Error: cargo executable not found." >&2
echo "Please ensure Rust is installed and cargo is in your PATH or at ~/.cargo/bin/" >&2
exit 1
fi
fi
# --- Script Logic ---
# Exit immediately if a command exits with a non-zero status.
set -e
# --- Platform & SDK Configuration ---
# In a "Run Script" phase on a native target, PLATFORM_NAME is reliable.
# We use it to determine the correct SDK and build parameters.
PLATFORM_SUFFIX=""
SDK_NAME=""
if [ "$PLATFORM_NAME" = "iphoneos" ]; then
PLATFORM_SUFFIX="-iphoneos"
SDK_NAME="iphoneos"
elif [ "$PLATFORM_NAME" = "iphonesimulator" ]; then
PLATFORM_SUFFIX="-iphonesimulator"
SDK_NAME="iphonesimulator"
elif [ "$PLATFORM_NAME" = "macosx" ]; then
PLATFORM_SUFFIX=""
SDK_NAME="macosx"
else
echo "Error: Unsupported platform '$PLATFORM_NAME'" >&2
exit 1
fi
# Get the SDK path. This is crucial for cross-compilation.
SDK_PATH=$(xcrun --sdk ${SDK_NAME} --show-sdk-path)
echo "Configured for cross-compilation with SDK: ${SDK_PATH}"
# Export variables needed by crates like `bindgen` to find the correct headers.
export BINDGEN_EXTRA_CLANG_ARGS="--sysroot=${SDK_PATH}"
export SDKROOT="${SDK_PATH}" # Also respected by some build scripts.
STATIC_LIB_NAME="lib$(echo $CRATE_NAME | sed 's/-/_/g').a"
LIPO_INPUT_FILES=""
# Loop through each architecture specified by Xcode.
for ARCH in $ARCHS; do
# Determine the Rust target triple based on the architecture and platform.
if [ "$PLATFORM_NAME" = "macosx" ]; then
if [ "$ARCH" = "arm64" ]; then
RUST_TARGET="aarch64-apple-darwin"
else
RUST_TARGET="x86_64-apple-darwin"
fi
elif [ "$PLATFORM_NAME" = "iphoneos" ]; then
RUST_TARGET="aarch64-apple-ios"
elif [ "$PLATFORM_NAME" = "iphonesimulator" ]; then
if [ "$ARCH" = "arm64" ]; then
RUST_TARGET="aarch64-apple-ios-sim"
else
RUST_TARGET="x86_64-apple-ios"
fi
fi
echo "Building for arch: ${ARCH}, Rust target: ${RUST_TARGET}"
# --- Configure Linker for Cargo ---
# Use RUSTFLAGS to pass linker arguments directly to rustc. This is the most
# reliable way to ensure the linker finds system libraries in the correct SDK.
export RUSTFLAGS="-C link-arg=-L${SDK_PATH}/usr/lib"
# export PATH="${SDK_PATH}:$PATH"
# Run the cargo build command. It will inherit the exported RUSTFLAGS.
(cd "$RUST_PROJECT_PATH" && ${CARGO} build --release --target ${RUST_TARGET})
BUILT_LIB_PATH="${RUST_PROJECT_PATH}/../target/${RUST_TARGET}/release/${STATIC_LIB_NAME}"
# Add the path of the built library to our list for `lipo`.
LIPO_INPUT_FILES="${LIPO_INPUT_FILES} ${BUILT_LIB_PATH}"
done
# --- Universal Library Creation ---
# Construct the correct, platform-specific destination directory.
DESTINATION_DIR="${BUILT_PRODUCTS_DIR}"
mkdir -p "${DESTINATION_DIR}"
DESTINATION_PATH="${DESTINATION_DIR}/${STATIC_LIB_NAME}"
echo "Creating universal library for architectures: $ARCHS"
echo "Input files: ${LIPO_INPUT_FILES}"
echo "Output path: ${DESTINATION_PATH}"
# Use `lipo` to combine the individual architecture libraries into one universal library.
lipo -create ${LIPO_INPUT_FILES} -output "${DESTINATION_PATH}"
echo "Universal library created successfully."
# Verify the architectures in the final library.
lipo -info "${DESTINATION_PATH}"
echo "Rust build script finished successfully."

View File

@@ -5,19 +5,28 @@ edition = "2024"
[dependencies] [dependencies]
idevice = { path = "../idevice" } idevice = { path = "../idevice", default-features = false }
futures = { version = "0.3", optional = true }
log = "0.4.26" log = "0.4.26"
simplelog = "0.12.2" simplelog = "0.12.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"
plist = "1.7.1" plist = "1.7.1"
plist_plus = { version = "0.2.6", features = ["dynamic"] } plist_ffi = { version = "0.1.5" }
uuid = { version = "1.12", features = ["v4"], optional = true }
[target.'cfg(windows)'.dependencies]
windows-sys = { version = "0.60", features = ["Win32_Networking_WinSock"] }
[features] [features]
aws-lc = ["idevice/aws-lc"]
ring = ["idevice/ring"]
afc = ["idevice/afc"] afc = ["idevice/afc"]
amfi = ["idevice/amfi"] amfi = ["idevice/amfi"]
core_device = ["idevice/core_device"] 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"]
@@ -64,10 +73,11 @@ full = [
"springboardservices", "springboardservices",
"syslog_relay", "syslog_relay",
] ]
default = ["full"] default = ["full", "aws-lc"]
[build-dependencies] [build-dependencies]
cbindgen = "0.28.0" cbindgen = "0.29.0"
ureq = "3"
[lib] [lib]
crate-type = ["staticlib"] crate-type = ["staticlib", "cdylib"]

View File

@@ -1,21 +1,50 @@
// Jackson Coxson // Jackson Coxson
use std::env; use std::{env, fs::OpenOptions, io::Write};
const HEADER: &str = r#"// Jackson Coxson
// Bindings to idevice - https://github.com/jkcoxson/idevice
#ifdef _WIN32
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <winsock2.h>
#include <ws2tcpip.h>
typedef int idevice_socklen_t;
typedef struct sockaddr idevice_sockaddr;
#else
#include <sys/types.h>
#include <sys/socket.h>
typedef socklen_t idevice_socklen_t;
typedef struct sockaddr idevice_sockaddr;
#endif
"#;
fn main() { fn main() {
let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
cbindgen::Builder::new() cbindgen::Builder::new()
.with_crate(crate_dir) .with_crate(crate_dir)
.with_header( .with_header(HEADER)
"// Jackson Coxson\n// Bindings to idevice - https://github.com/jkcoxson/idevice",
)
.with_language(cbindgen::Language::C) .with_language(cbindgen::Language::C)
.with_sys_include("sys/socket.h") .with_include_guard("IDEVICE_H")
.with_sys_include("plist/plist.h") .exclude_item("idevice_socklen_t")
.exclude_item("idevice_sockaddr")
.generate() .generate()
.expect("Unable to generate bindings") .expect("Unable to generate bindings")
.write_to_file("idevice.h"); .write_to_file("idevice.h");
println!("cargo:rustc-link-arg=-lplist-2.0"); // download plist.h
let h = ureq::get("https://raw.githubusercontent.com/libimobiledevice/libplist/refs/heads/master/include/plist/plist.h")
.call()
.expect("failed to download plist.h");
let h = h
.into_body()
.read_to_string()
.expect("failed to get string content");
let mut f = OpenOptions::new().append(true).open("idevice.h").unwrap();
f.write_all(b"\n\n\n").unwrap();
f.write_all(&h.into_bytes())
.expect("failed to append plist.h");
} }

View File

@@ -8,10 +8,13 @@ project(IdeviceFFI C)
set(HEADER_FILE ${CMAKE_SOURCE_DIR}/../idevice.h) set(HEADER_FILE ${CMAKE_SOURCE_DIR}/../idevice.h)
set(STATIC_LIB ${CMAKE_SOURCE_DIR}/../../target/release/libidevice_ffi.a) set(STATIC_LIB ${CMAKE_SOURCE_DIR}/../../target/release/libidevice_ffi.a)
set(EXAMPLES_DIR ${CMAKE_SOURCE_DIR}/../examples) set(EXAMPLES_DIR ${CMAKE_SOURCE_DIR}/../examples)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
# Find all C example files # Find all C example files
file(GLOB EXAMPLE_SOURCES ${EXAMPLES_DIR}/*.c) file(GLOB EXAMPLE_SOURCES ${EXAMPLES_DIR}/*.c)
find_package(PkgConfig REQUIRED)
# Create an executable for each example file # Create an executable for each example file
foreach(EXAMPLE_FILE ${EXAMPLE_SOURCES}) foreach(EXAMPLE_FILE ${EXAMPLE_SOURCES})
# Extract the filename without the path # Extract the filename without the path
@@ -26,29 +29,9 @@ foreach(EXAMPLE_FILE ${EXAMPLE_SOURCES})
# Link the static Rust library # Link the static Rust library
target_link_libraries(${EXAMPLE_NAME} PRIVATE ${STATIC_LIB}) target_link_libraries(${EXAMPLE_NAME} PRIVATE ${STATIC_LIB})
# libplist if(UNIX AND NOT APPLE)
target_link_libraries(${EXAMPLE_NAME} PRIVATE m)
if( APPLE )
# use static linking
find_library( LIBPLIST libplist-2.0.a REQUIRED )
message( STATUS "(Static linking) LIBPLIST " ${LIBPLIST} )
target_link_libraries ( ${EXAMPLE_NAME} PRIVATE ${LIBPLIST} )
elseif( WIN32)
pkg_search_module(PLIST REQUIRED libplist-2.0)
find_library( LIBPLIST ${PLIST_LIBRARIES} PATH ${PLIST_LIBDIR} )
target_link_libraries ( ${EXAMPLE_NAME} PRIVATE ${LIBPLIST} )
else ()
pkg_search_module(PLIST libplist>=2.0)
if(NOT PLIST_FOUND)
pkg_search_module(PLIST REQUIRED libplist-2.0)
endif() endif()
find_library( LIBPLIST ${PLIST_LIBRARIES} PATH ${PLIST_LIBDIR} )
target_link_libraries ( ${EXAMPLE_NAME} PUBLIC ${LIBPLIST} )
endif()
if ( PLIST_FOUND )
message( STATUS "found libplist-${PLIST_VERSION}" )
endif()
target_include_directories( ${EXAMPLE_NAME} PRIVATE ${PLIST_INCLUDE_DIRS} )
# Bulk-link common macOS system frameworks # Bulk-link common macOS system frameworks
if(APPLE) if(APPLE)

View File

@@ -5,7 +5,6 @@
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <sys/_types/_u_int64_t.h>
int main() { int main() {
// Initialize logger // Initialize logger

View File

@@ -1,7 +1,6 @@
// Jackson Coxson // Jackson Coxson
#include "idevice.h" #include "idevice.h"
#include "plist/plist.h"
#include <arpa/inet.h> #include <arpa/inet.h>
#include <stdint.h> #include <stdint.h>
#include <stdio.h> #include <stdio.h>
@@ -122,7 +121,7 @@ int main() {
// Get all values // Get all values
plist_t all_values = NULL; plist_t all_values = NULL;
err = lockdownd_get_all_values(client, &all_values); err = lockdownd_get_value(client, NULL, NULL, &all_values);
if (err != NULL) { if (err != NULL) {
fprintf(stderr, "Failed to get all values: [%d] %s", err->code, fprintf(stderr, "Failed to get all values: [%d] %s", err->code,
err->message); err->message);

View File

@@ -1,7 +1,6 @@
// Jackson Coxson // Jackson Coxson
#include "idevice.h" #include "idevice.h"
#include "plist/plist.h"
#include <arpa/inet.h> #include <arpa/inet.h>
#include <stdint.h> #include <stdint.h>
#include <stdio.h> #include <stdio.h>

Submodule ffi/libplist deleted from cf5897a71e

View File

@@ -3,12 +3,13 @@
use std::ffi::{CStr, c_char}; use std::ffi::{CStr, c_char};
use std::ptr::null_mut; use std::ptr::null_mut;
use idevice::tcp::stream::AdapterStream; use idevice::tcp::handle::StreamHandle;
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, RUNTIME, ReadWriteOpaque, ffi_err};
pub struct AdapterStreamHandle<'a>(pub AdapterStream<'a>); pub struct AdapterStreamHandle(pub StreamHandle);
/// Connects the adapter to a specific port /// Connects the adapter to a specific port
/// ///
@@ -35,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 { AdapterStream::connect(adapter, port).await }); let res = RUNTIME.block_on(async move { adapter.connect(port).await });
match res { match res {
Ok(r) => { Ok(r) => {
@@ -46,7 +47,7 @@ pub unsafe extern "C" fn adapter_connect(
null_mut() null_mut()
} }
Err(e) => { Err(e) => {
log::error!("Adapter connect failed: {}", e); log::error!("Adapter connect failed: {e}");
ffi_err!(e) ffi_err!(e)
} }
} }
@@ -85,13 +86,13 @@ pub unsafe extern "C" fn adapter_pcap(
match res { match res {
Ok(_) => null_mut(), Ok(_) => null_mut(),
Err(e) => { Err(e) => {
log::error!("Adapter pcap failed: {}", e); log::error!("Adapter pcap failed: {e}");
ffi_err!(e) ffi_err!(e)
} }
} }
} }
/// Closes the adapter connection /// Closes the adapter stream connection
/// ///
/// # Arguments /// # Arguments
/// * [`handle`] - The adapter stream handle /// * [`handle`] - The adapter stream handle
@@ -108,21 +109,15 @@ pub unsafe extern "C" fn adapter_close(handle: *mut AdapterStreamHandle) -> *mut
} }
let adapter = unsafe { &mut (*handle).0 }; let adapter = unsafe { &mut (*handle).0 };
let res = RUNTIME.block_on(async move { adapter.close().await }); RUNTIME.block_on(async move { adapter.close() });
match res { null_mut()
Ok(_) => null_mut(),
Err(e) => {
log::error!("Adapter close failed: {}", e);
ffi_err!(e)
}
}
} }
/// Sends data through the adapter /// Sends data through the adapter stream
/// ///
/// # Arguments /// # Arguments
/// * [`handle`] - The adapter handle /// * [`handle`] - The adapter stream handle
/// * [`data`] - The data to send /// * [`data`] - The data to send
/// * [`length`] - The length of the data /// * [`length`] - The length of the data
/// ///
@@ -145,21 +140,21 @@ 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.psh(data_slice).await }); let res = RUNTIME.block_on(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); log::error!("Adapter send failed: {e}");
ffi_err!(e) ffi_err!(e)
} }
} }
} }
/// Receives data from the adapter /// Receives data from the adapter stream
/// ///
/// # Arguments /// # Arguments
/// * [`handle`] - The adapter handle /// * [`handle`] - The adapter stream handle
/// * [`data`] - Pointer to a buffer where the received data will be stored /// * [`data`] - Pointer to a buffer where the received data will be stored
/// * [`length`] - Pointer to store the actual length of received data /// * [`length`] - Pointer to store the actual length of received data
/// * [`max_length`] - Maximum number of bytes that can be stored in `data` /// * [`max_length`] - Maximum number of bytes that can be stored in `data`
@@ -183,7 +178,11 @@ pub unsafe extern "C" fn adapter_recv(
} }
let adapter = unsafe { &mut (*handle).0 }; let adapter = unsafe { &mut (*handle).0 };
let res = RUNTIME.block_on(async move { adapter.recv().await }); let res: Result<Vec<u8>, std::io::Error> = RUNTIME.block_on(async move {
let mut buf = [0; 2048];
let res = adapter.read(&mut buf).await?;
Ok(buf[..res].to_vec())
});
match res { match res {
Ok(received_data) => { Ok(received_data) => {
@@ -200,7 +199,7 @@ pub unsafe extern "C" fn adapter_recv(
null_mut() null_mut()
} }
Err(e) => { Err(e) => {
log::error!("Adapter recv failed: {}", e); log::error!("Adapter recv failed: {e}");
ffi_err!(e) ffi_err!(e)
} }
} }

View File

@@ -5,12 +5,11 @@ use std::os::raw::{c_float, c_int};
use std::ptr::{self, null_mut}; use std::ptr::{self, null_mut};
use idevice::core_device::AppServiceClient; use idevice::core_device::AppServiceClient;
use idevice::tcp::stream::AdapterStream;
use idevice::{IdeviceError, ReadWrite, RsdService}; 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, ffi_err}; use crate::{IdeviceFfiError, RUNTIME, ReadWriteOpaque, ffi_err};
/// 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 +90,8 @@ pub unsafe extern "C" fn app_service_connect_rsd(
return ffi_err!(IdeviceError::FfiInvalidArg); return ffi_err!(IdeviceError::FfiInvalidArg);
} }
let res: Result<AppServiceClient<AdapterStream>, IdeviceError> = RUNTIME.block_on(async move { let res: Result<AppServiceClient<Box<dyn ReadWrite>>, IdeviceError> =
RUNTIME.block_on(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 };
@@ -100,7 +100,7 @@ pub unsafe extern "C" fn app_service_connect_rsd(
match res { match res {
Ok(client) => { Ok(client) => {
let boxed = Box::new(AppServiceHandle(client.box_inner())); let boxed = Box::new(AppServiceHandle(client));
unsafe { *handle = Box::into_raw(boxed) }; unsafe { *handle = Box::into_raw(boxed) };
null_mut() null_mut()
} }
@@ -122,7 +122,7 @@ pub unsafe extern "C" fn app_service_connect_rsd(
/// `handle` must be a valid pointer to a location where the handle will be stored /// `handle` must be a valid pointer to a location where the handle will be stored
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
pub unsafe extern "C" fn app_service_new( pub unsafe extern "C" fn app_service_new(
socket: *mut Box<dyn ReadWrite>, socket: *mut ReadWriteOpaque,
handle: *mut *mut AppServiceHandle, handle: *mut *mut AppServiceHandle,
) -> *mut IdeviceFfiError { ) -> *mut IdeviceFfiError {
if socket.is_null() || handle.is_null() { if socket.is_null() || handle.is_null() {
@@ -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).await }); let res = RUNTIME.block_on(async move { AppServiceClient::new(socket.inner.unwrap()).await });
match res { match res {
Ok(client) => { Ok(client) => {
@@ -299,6 +299,7 @@ pub unsafe extern "C" fn app_service_free_app_list(apps: *mut AppListEntryC, cou
/// * [`argc`] - Number of arguments /// * [`argc`] - Number of arguments
/// * [`kill_existing`] - Whether to kill existing instances /// * [`kill_existing`] - Whether to kill existing instances
/// * [`start_suspended`] - Whether to start suspended /// * [`start_suspended`] - Whether to start suspended
/// * [`stdio_uuid`] - The UUID received from openstdiosocket, null for none
/// * [`response`] - Pointer to store the launch response (caller must free) /// * [`response`] - Pointer to store the launch response (caller must free)
/// ///
/// # Returns /// # Returns
@@ -314,6 +315,7 @@ pub unsafe extern "C" fn app_service_launch_app(
argc: usize, argc: usize,
kill_existing: c_int, kill_existing: c_int,
start_suspended: c_int, start_suspended: c_int,
stdio_uuid: *const u8,
response: *mut *mut LaunchResponseC, response: *mut *mut LaunchResponseC,
) -> *mut IdeviceFfiError { ) -> *mut IdeviceFfiError {
if handle.is_null() || bundle_id.is_null() || response.is_null() { if handle.is_null() || bundle_id.is_null() || response.is_null() {
@@ -329,13 +331,20 @@ pub unsafe extern "C" fn app_service_launch_app(
if !argv.is_null() && argc > 0 { if !argv.is_null() && argc > 0 {
let argv_slice = unsafe { std::slice::from_raw_parts(argv, argc) }; let argv_slice = unsafe { std::slice::from_raw_parts(argv, argc) };
for &arg in argv_slice { for &arg in argv_slice {
if !arg.is_null() { if !arg.is_null()
if let Ok(arg_str) = unsafe { CStr::from_ptr(arg) }.to_str() { && let Ok(arg_str) = unsafe { CStr::from_ptr(arg) }.to_str()
{
args.push(arg_str); args.push(arg_str);
} }
} }
} }
}
let stdio_uuid = if stdio_uuid.is_null() {
None
} else {
let stdio_uuid = unsafe { std::slice::from_raw_parts(stdio_uuid, 16) };
Some(uuid::Uuid::from_bytes(stdio_uuid.try_into().unwrap()))
};
let client = unsafe { &mut (*handle).0 }; let client = unsafe { &mut (*handle).0 };
let res = RUNTIME.block_on(async move { let res = RUNTIME.block_on(async move {
@@ -347,6 +356,7 @@ pub unsafe extern "C" fn app_service_launch_app(
start_suspended != 0, start_suspended != 0,
None, // environment None, // environment
None, // platform_options None, // platform_options
stdio_uuid,
) )
.await .await
}); });

View File

@@ -0,0 +1,215 @@
// Jackson Coxson
use std::ffi::{CString, c_char};
use std::pin::Pin;
use std::ptr::null_mut;
use futures::{Stream, StreamExt};
use idevice::core_device::DiagnostisServiceClient;
use idevice::{IdeviceError, ReadWrite, RsdService};
use log::debug;
use crate::core_device_proxy::AdapterHandle;
use crate::rsd::RsdHandshakeHandle;
use crate::{IdeviceFfiError, RUNTIME, ReadWriteOpaque, ffi_err};
/// Opaque handle to an AppServiceClient
pub struct DiagnosticsServiceHandle(pub DiagnostisServiceClient<Box<dyn ReadWrite>>);
pub struct SysdiagnoseStreamHandle<'a>(
pub Pin<Box<dyn Stream<Item = Result<Vec<u8>, IdeviceError>> + 'a>>,
);
/// Creates a new DiagnosticsServiceClient using RSD connection
///
/// # Arguments
/// * [`provider`] - An adapter created by this library
/// * [`handshake`] - An RSD handshake from the same provider
/// * [`handle`] - Pointer to store the newly created handle
///
/// # Returns
/// An IdeviceFfiError on error, null on success
///
/// # Safety
/// `provider` and `handshake` must be valid pointers to handles allocated by this library
/// `handle` must be a valid pointer to a location where the handle will be stored
#[unsafe(no_mangle)]
pub unsafe extern "C" fn diagnostics_service_connect_rsd(
provider: *mut AdapterHandle,
handshake: *mut RsdHandshakeHandle,
handle: *mut *mut DiagnosticsServiceHandle,
) -> *mut IdeviceFfiError {
if provider.is_null() || handshake.is_null() || handle.is_null() {
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let res: Result<DiagnostisServiceClient<Box<dyn ReadWrite>>, IdeviceError> =
RUNTIME.block_on(async move {
let provider_ref = unsafe { &mut (*provider).0 };
let handshake_ref = unsafe { &mut (*handshake).0 };
debug!(
"Connecting to DiagnosticsService: provider {provider_ref:?}, handshake: {:?}",
handshake_ref.uuid
);
DiagnostisServiceClient::connect_rsd(provider_ref, handshake_ref).await
});
match res {
Ok(client) => {
debug!("Connected to DiagnosticsService");
let boxed = Box::new(DiagnosticsServiceHandle(client));
unsafe { *handle = Box::into_raw(boxed) };
null_mut()
}
Err(e) => ffi_err!(e),
}
}
/// Creates a new DiagnostisServiceClient from a socket
///
/// # Arguments
/// * [`socket`] - The socket to use for communication
/// * [`handle`] - Pointer to store the newly created handle
///
/// # Returns
/// An IdeviceFfiError on error, null on success
///
/// # Safety
/// `socket` must be a valid pointer to a handle allocated by this library
/// `handle` must be a valid pointer to a location where the handle will be stored
#[unsafe(no_mangle)]
pub unsafe extern "C" fn diagnostics_service_new(
socket: *mut ReadWriteOpaque,
handle: *mut *mut DiagnosticsServiceHandle,
) -> *mut IdeviceFfiError {
if socket.is_null() || handle.is_null() {
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let socket = unsafe { Box::from_raw(socket) };
let res = RUNTIME
.block_on(async move { DiagnostisServiceClient::from_stream(socket.inner.unwrap()).await });
match res {
Ok(client) => {
let new_handle = DiagnosticsServiceHandle(client);
unsafe { *handle = Box::into_raw(Box::new(new_handle)) };
null_mut()
}
Err(e) => ffi_err!(e),
}
}
/// Captures a sysdiagnose from the device.
/// Note that this will take a LONG time to return while the device collects enough information to
/// return to the service. This function returns a stream that can be called on to get the next
/// chunk of data. A typical sysdiagnose is roughly 1-2 GB.
///
/// # Arguments
/// * [`handle`] - The handle to the client
/// * [`dry_run`] - Whether or not to do a dry run with a simple .txt file from the device
/// * [`preferred_filename`] - The name the device wants to save the sysdaignose as
/// * [`expected_length`] - The size in bytes of the sysdiagnose
/// * [`stream_handle`] - The handle that will be set to capture bytes for
///
/// # Returns
/// An IdeviceFfiError on error, null on success
///
/// # Safety
/// Pointers must be all valid. Handle must be allocated by this library. Preferred filename must
/// be freed `idevice_string_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn diagnostics_service_capture_sysdiagnose(
handle: *mut DiagnosticsServiceHandle,
dry_run: bool,
preferred_filename: *mut *mut c_char,
expected_length: *mut usize,
stream_handle: *mut *mut SysdiagnoseStreamHandle,
) -> *mut IdeviceFfiError {
if handle.is_null()
|| preferred_filename.is_null()
|| expected_length.is_null()
|| stream_handle.is_null()
{
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let handle = unsafe { &mut *handle };
let res = RUNTIME.block_on(async move { handle.0.capture_sysdiagnose(dry_run).await });
match res {
Ok(res) => {
let filename = CString::new(res.preferred_filename).unwrap();
unsafe {
*preferred_filename = filename.into_raw();
*expected_length = res.expected_length;
*stream_handle = Box::into_raw(Box::new(SysdiagnoseStreamHandle(res.stream)));
}
null_mut()
}
Err(e) => ffi_err!(e),
}
}
/// Gets the next packet from the stream.
/// Data will be set to 0 when there is no more data to get from the stream.
///
/// # Arguments
/// * [`handle`] - The handle to the stream
/// * [`data`] - A pointer to the bytes
/// * [`len`] - The length of the bytes written
///
/// # Returns
/// An IdeviceFfiError on error, null on success
///
/// # Safety
/// Pass valid pointers. The handle must be allocated by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn sysdiagnose_stream_next(
handle: *mut SysdiagnoseStreamHandle,
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 handle = unsafe { &mut *handle };
let res = RUNTIME.block_on(async move { handle.0.next().await });
match res {
Some(Ok(res)) => {
let mut res = res.into_boxed_slice();
unsafe {
*len = res.len();
*data = res.as_mut_ptr();
}
std::mem::forget(res);
null_mut()
}
Some(Err(e)) => ffi_err!(e),
None => {
// we're empty
unsafe { *data = null_mut() };
null_mut()
}
}
}
/// Frees a DiagnostisServiceClient handle
///
/// # Safety
/// `handle` must be a valid pointer to a handle allocated by this library or NULL
#[unsafe(no_mangle)]
pub unsafe extern "C" fn diagnostics_service_free(handle: *mut DiagnosticsServiceHandle) {
if !handle.is_null() {
let _ = unsafe { Box::from_raw(handle) };
}
}
/// Frees a SysdiagnoseStreamHandle handle
///
/// # Safety
/// `handle` must be a valid pointer to a handle allocated by this library or NULL
#[unsafe(no_mangle)]
pub unsafe extern "C" fn sysdiagnose_stream_free(handle: *mut SysdiagnoseStreamHandle) {
if !handle.is_null() {
let _ = unsafe { Box::from_raw(handle) };
}
}

View File

@@ -1,3 +1,4 @@
// Jackson Coxson // Jackson Coxson
pub mod app_service; pub mod app_service;
pub mod diagnosticsservice;

View File

@@ -7,13 +7,12 @@ use std::{
use idevice::{ use idevice::{
IdeviceError, IdeviceService, core_device_proxy::CoreDeviceProxy, provider::IdeviceProvider, IdeviceError, IdeviceService, core_device_proxy::CoreDeviceProxy, provider::IdeviceProvider,
tcp::adapter::Adapter,
}; };
use crate::{IdeviceFfiError, IdeviceHandle, RUNTIME, ffi_err, provider::IdeviceProviderHandle}; use crate::{IdeviceFfiError, IdeviceHandle, RUNTIME, ffi_err, provider::IdeviceProviderHandle};
pub struct CoreDeviceProxyHandle(pub CoreDeviceProxy); pub struct CoreDeviceProxyHandle(pub CoreDeviceProxy);
pub struct AdapterHandle(pub Adapter); pub struct AdapterHandle(pub idevice::tcp::handle::AdapterHandle);
/// Automatically creates and connects to Core Device Proxy, returning a client handle /// Automatically creates and connects to Core Device Proxy, returning a client handle
/// ///
@@ -312,7 +311,10 @@ pub unsafe extern "C" fn core_device_proxy_create_tcp_adapter(
match result { match result {
Ok(adapter_obj) => { Ok(adapter_obj) => {
let boxed = Box::new(AdapterHandle(adapter_obj)); // We have to run this in the RUNTIME since we're spawning a new thread
let adapter_handle = RUNTIME.block_on(async move { adapter_obj.to_async_handle() });
let boxed = Box::new(AdapterHandle(adapter_handle));
unsafe { *adapter = Box::into_raw(boxed) }; unsafe { *adapter = Box::into_raw(boxed) };
null_mut() null_mut()
} }

View File

@@ -5,12 +5,11 @@ use std::os::raw::c_int;
use std::ptr::{self, null_mut}; use std::ptr::{self, null_mut};
use idevice::debug_proxy::{DebugProxyClient, DebugserverCommand}; use idevice::debug_proxy::{DebugProxyClient, DebugserverCommand};
use idevice::tcp::stream::AdapterStream;
use idevice::{IdeviceError, ReadWrite, RsdService}; 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, ffi_err}; use crate::{IdeviceFfiError, RUNTIME, ReadWriteOpaque, ffi_err};
/// 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 +135,8 @@ pub unsafe extern "C" fn debug_proxy_connect_rsd(
if provider.is_null() || handshake.is_null() || handshake.is_null() { if provider.is_null() || handshake.is_null() || handshake.is_null() {
return ffi_err!(IdeviceError::FfiInvalidArg); return ffi_err!(IdeviceError::FfiInvalidArg);
} }
let res: Result<DebugProxyClient<AdapterStream>, IdeviceError> = RUNTIME.block_on(async move { let res: Result<DebugProxyClient<Box<dyn ReadWrite>>, IdeviceError> =
RUNTIME.block_on(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 };
@@ -170,7 +170,7 @@ pub unsafe extern "C" fn debug_proxy_connect_rsd(
/// `handle` must be a valid pointer to a location where the handle will be stored /// `handle` must be a valid pointer to a location where the handle will be stored
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
pub unsafe extern "C" fn debug_proxy_new( pub unsafe extern "C" fn debug_proxy_new(
socket: *mut Box<dyn ReadWrite>, socket: *mut ReadWriteOpaque,
handle: *mut *mut DebugProxyHandle, handle: *mut *mut DebugProxyHandle,
) -> *mut IdeviceFfiError { ) -> *mut IdeviceFfiError {
if socket.is_null() || handle.is_null() { if socket.is_null() || handle.is_null() {
@@ -178,7 +178,7 @@ pub unsafe extern "C" fn debug_proxy_new(
} }
let socket = unsafe { Box::from_raw(socket) }; let socket = unsafe { Box::from_raw(socket) };
let client = DebugProxyClient::new(*socket); let client = DebugProxyClient::new(socket.inner.unwrap());
let new_handle = DebugProxyHandle(client); let new_handle = DebugProxyHandle(client);
unsafe { *handle = Box::into_raw(Box::new(new_handle)) }; unsafe { *handle = Box::into_raw(Box::new(new_handle)) };

View File

@@ -6,10 +6,9 @@ use idevice::{
IdeviceError, IdeviceService, installation_proxy::InstallationProxyClient, IdeviceError, IdeviceService, installation_proxy::InstallationProxyClient,
provider::IdeviceProvider, provider::IdeviceProvider,
}; };
use plist_ffi::{PlistWrapper, plist_t};
use crate::{ use crate::{IdeviceFfiError, IdeviceHandle, RUNTIME, ffi_err, provider::IdeviceProviderHandle};
IdeviceFfiError, IdeviceHandle, RUNTIME, ffi_err, provider::IdeviceProviderHandle, util,
};
pub struct InstallationProxyClientHandle(pub InstallationProxyClient); pub struct InstallationProxyClientHandle(pub InstallationProxyClient);
@@ -110,11 +109,14 @@ pub unsafe extern "C" fn installation_proxy_get_apps(
let app_type = if application_type.is_null() { let app_type = if application_type.is_null() {
None None
} else { } else {
Some(unsafe { Some(
std::ffi::CStr::from_ptr(application_type) match unsafe { std::ffi::CStr::from_ptr(application_type) }.to_str() {
.to_string_lossy() Ok(a) => a,
.into_owned() Err(_) => {
}) return ffi_err!(IdeviceError::InvalidCString);
}
},
)
}; };
let bundle_ids = if bundle_identifiers.is_null() { let bundle_ids = if bundle_identifiers.is_null() {
@@ -126,16 +128,16 @@ pub unsafe extern "C" fn installation_proxy_get_apps(
.map(|&s| { .map(|&s| {
unsafe { std::ffi::CStr::from_ptr(s) } unsafe { std::ffi::CStr::from_ptr(s) }
.to_string_lossy() .to_string_lossy()
.into_owned() .to_string()
}) })
.collect(), .collect::<Vec<String>>(),
) )
}; };
let res: Result<Vec<*mut c_void>, IdeviceError> = RUNTIME.block_on(async { let res: Result<Vec<plist_t>, IdeviceError> = RUNTIME.block_on(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| util::plist_to_libplist(&v)) .map(|v| PlistWrapper::new_node(v).into_ptr())
.collect() .collect()
}) })
}); });
@@ -192,7 +194,7 @@ pub unsafe extern "C" fn installation_proxy_client_free(
pub unsafe extern "C" fn installation_proxy_install( pub unsafe extern "C" fn installation_proxy_install(
client: *mut InstallationProxyClientHandle, client: *mut InstallationProxyClientHandle,
package_path: *const libc::c_char, package_path: *const libc::c_char,
options: *mut c_void, options: plist_t,
) -> *mut IdeviceFfiError { ) -> *mut IdeviceFfiError {
if client.is_null() || package_path.is_null() { if client.is_null() || package_path.is_null() {
return ffi_err!(IdeviceError::FfiInvalidArg); return ffi_err!(IdeviceError::FfiInvalidArg);
@@ -204,8 +206,9 @@ pub unsafe extern "C" fn installation_proxy_install(
let options = if options.is_null() { let options = if options.is_null() {
None None
} else { } else {
Some(util::libplist_to_plist(options)) Some(unsafe { &mut *options })
}; }
.map(|x| x.borrow_self().clone());
let res = RUNTIME.block_on(async { let res = RUNTIME.block_on(async {
unsafe { &mut *client } unsafe { &mut *client }
@@ -240,7 +243,7 @@ pub unsafe extern "C" fn installation_proxy_install(
pub unsafe extern "C" fn installation_proxy_install_with_callback( pub unsafe extern "C" fn installation_proxy_install_with_callback(
client: *mut InstallationProxyClientHandle, client: *mut InstallationProxyClientHandle,
package_path: *const libc::c_char, package_path: *const libc::c_char,
options: *mut c_void, options: plist_t,
callback: extern "C" fn(progress: u64, context: *mut c_void), callback: extern "C" fn(progress: u64, context: *mut c_void),
context: *mut c_void, context: *mut c_void,
) -> *mut IdeviceFfiError { ) -> *mut IdeviceFfiError {
@@ -254,8 +257,9 @@ pub unsafe extern "C" fn installation_proxy_install_with_callback(
let options = if options.is_null() { let options = if options.is_null() {
None None
} else { } else {
Some(util::libplist_to_plist(options)) Some(unsafe { &mut *options })
}; }
.map(|x| x.borrow_self().clone());
let res = RUNTIME.block_on(async { let res = RUNTIME.block_on(async {
let callback_wrapper = |(progress, context)| async move { let callback_wrapper = |(progress, context)| async move {
@@ -292,7 +296,7 @@ pub unsafe extern "C" fn installation_proxy_install_with_callback(
pub unsafe extern "C" fn installation_proxy_upgrade( pub unsafe extern "C" fn installation_proxy_upgrade(
client: *mut InstallationProxyClientHandle, client: *mut InstallationProxyClientHandle,
package_path: *const libc::c_char, package_path: *const libc::c_char,
options: *mut c_void, options: plist_t,
) -> *mut IdeviceFfiError { ) -> *mut IdeviceFfiError {
if client.is_null() || package_path.is_null() { if client.is_null() || package_path.is_null() {
return ffi_err!(IdeviceError::FfiInvalidArg); return ffi_err!(IdeviceError::FfiInvalidArg);
@@ -304,8 +308,9 @@ pub unsafe extern "C" fn installation_proxy_upgrade(
let options = if options.is_null() { let options = if options.is_null() {
None None
} else { } else {
Some(util::libplist_to_plist(options)) Some(unsafe { &mut *options })
}; }
.map(|x| x.borrow_self().clone());
let res = RUNTIME.block_on(async { let res = RUNTIME.block_on(async {
unsafe { &mut *client } unsafe { &mut *client }
@@ -340,7 +345,7 @@ pub unsafe extern "C" fn installation_proxy_upgrade(
pub unsafe extern "C" fn installation_proxy_upgrade_with_callback( pub unsafe extern "C" fn installation_proxy_upgrade_with_callback(
client: *mut InstallationProxyClientHandle, client: *mut InstallationProxyClientHandle,
package_path: *const libc::c_char, package_path: *const libc::c_char,
options: *mut c_void, options: plist_t,
callback: extern "C" fn(progress: u64, context: *mut c_void), callback: extern "C" fn(progress: u64, context: *mut c_void),
context: *mut c_void, context: *mut c_void,
) -> *mut IdeviceFfiError { ) -> *mut IdeviceFfiError {
@@ -354,8 +359,9 @@ pub unsafe extern "C" fn installation_proxy_upgrade_with_callback(
let options = if options.is_null() { let options = if options.is_null() {
None None
} else { } else {
Some(util::libplist_to_plist(options)) Some(unsafe { &mut *options })
}; }
.map(|x| x.borrow_self().clone());
let res = RUNTIME.block_on(async { let res = RUNTIME.block_on(async {
let callback_wrapper = |(progress, context)| async move { let callback_wrapper = |(progress, context)| async move {
@@ -392,7 +398,7 @@ pub unsafe extern "C" fn installation_proxy_upgrade_with_callback(
pub unsafe extern "C" fn installation_proxy_uninstall( pub unsafe extern "C" fn installation_proxy_uninstall(
client: *mut InstallationProxyClientHandle, client: *mut InstallationProxyClientHandle,
bundle_id: *const libc::c_char, bundle_id: *const libc::c_char,
options: *mut c_void, options: plist_t,
) -> *mut IdeviceFfiError { ) -> *mut IdeviceFfiError {
if client.is_null() || bundle_id.is_null() { if client.is_null() || bundle_id.is_null() {
return ffi_err!(IdeviceError::FfiInvalidArg); return ffi_err!(IdeviceError::FfiInvalidArg);
@@ -404,8 +410,9 @@ pub unsafe extern "C" fn installation_proxy_uninstall(
let options = if options.is_null() { let options = if options.is_null() {
None None
} else { } else {
Some(util::libplist_to_plist(options)) Some(unsafe { &mut *options })
}; }
.map(|x| x.borrow_self().clone());
let res = RUNTIME.block_on(async { let res = RUNTIME.block_on(async {
unsafe { &mut *client } unsafe { &mut *client }
@@ -440,7 +447,7 @@ pub unsafe extern "C" fn installation_proxy_uninstall(
pub unsafe extern "C" fn installation_proxy_uninstall_with_callback( pub unsafe extern "C" fn installation_proxy_uninstall_with_callback(
client: *mut InstallationProxyClientHandle, client: *mut InstallationProxyClientHandle,
bundle_id: *const libc::c_char, bundle_id: *const libc::c_char,
options: *mut c_void, options: plist_t,
callback: extern "C" fn(progress: u64, context: *mut c_void), callback: extern "C" fn(progress: u64, context: *mut c_void),
context: *mut c_void, context: *mut c_void,
) -> *mut IdeviceFfiError { ) -> *mut IdeviceFfiError {
@@ -454,8 +461,9 @@ pub unsafe extern "C" fn installation_proxy_uninstall_with_callback(
let options = if options.is_null() { let options = if options.is_null() {
None None
} else { } else {
Some(util::libplist_to_plist(options)) Some(unsafe { &mut *options })
}; }
.map(|x| x.borrow_self().clone());
let res = RUNTIME.block_on(async { let res = RUNTIME.block_on(async {
let callback_wrapper = |(progress, context)| async move { let callback_wrapper = |(progress, context)| async move {
@@ -494,9 +502,9 @@ pub unsafe extern "C" fn installation_proxy_uninstall_with_callback(
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
pub unsafe extern "C" fn installation_proxy_check_capabilities_match( pub unsafe extern "C" fn installation_proxy_check_capabilities_match(
client: *mut InstallationProxyClientHandle, client: *mut InstallationProxyClientHandle,
capabilities: *const *mut c_void, capabilities: *const plist_t,
capabilities_len: libc::size_t, capabilities_len: libc::size_t,
options: *mut c_void, options: plist_t,
out_result: *mut bool, out_result: *mut bool,
) -> *mut IdeviceFfiError { ) -> *mut IdeviceFfiError {
if client.is_null() || out_result.is_null() { if client.is_null() || out_result.is_null() {
@@ -508,15 +516,16 @@ pub unsafe extern "C" fn installation_proxy_check_capabilities_match(
} else { } else {
unsafe { std::slice::from_raw_parts(capabilities, capabilities_len) } unsafe { std::slice::from_raw_parts(capabilities, capabilities_len) }
.iter() .iter()
.map(|&ptr| util::libplist_to_plist(ptr)) .map(|ptr| unsafe { &mut **ptr }.borrow_self().clone())
.collect() .collect()
}; };
let options = if options.is_null() { let options = if options.is_null() {
None None
} else { } else {
Some(util::libplist_to_plist(options)) Some(unsafe { &mut *options })
}; }
.map(|x| x.borrow_self().clone());
let res = RUNTIME.block_on(async { let res = RUNTIME.block_on(async {
unsafe { &mut *client } unsafe { &mut *client }
@@ -553,8 +562,8 @@ pub unsafe extern "C" fn installation_proxy_check_capabilities_match(
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
pub unsafe extern "C" fn installation_proxy_browse( pub unsafe extern "C" fn installation_proxy_browse(
client: *mut InstallationProxyClientHandle, client: *mut InstallationProxyClientHandle,
options: *mut c_void, options: plist_t,
out_result: *mut *mut c_void, out_result: *mut *mut plist_t,
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() {
@@ -564,25 +573,27 @@ pub unsafe extern "C" fn installation_proxy_browse(
let options = if options.is_null() { let options = if options.is_null() {
None None
} else { } else {
Some(util::libplist_to_plist(options)) Some(unsafe { &mut *options })
}; }
.map(|x| x.borrow_self().clone());
let res: Result<Vec<*mut c_void>, IdeviceError> = RUNTIME.block_on(async { let res: Result<Vec<plist_t>, IdeviceError> = RUNTIME.block_on(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| util::plist_to_libplist(&v)) .map(|v| PlistWrapper::new_node(v).into_ptr())
.collect() .collect()
}) })
}); });
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);
unsafe { unsafe {
*out_result = ptr as *mut c_void; *out_result = ptr;
*out_result_len = len; *out_result_len = len;
} }
null_mut() null_mut()

View File

@@ -39,6 +39,8 @@ pub mod rsd;
pub mod springboardservices; pub mod springboardservices;
#[cfg(feature = "syslog_relay")] #[cfg(feature = "syslog_relay")]
pub mod syslog_relay; pub mod syslog_relay;
#[cfg(feature = "tunnel_tcp_stack")]
pub mod tcp_object_stack;
#[cfg(feature = "usbmuxd")] #[cfg(feature = "usbmuxd")]
pub mod usbmuxd; pub mod usbmuxd;
pub mod util; pub mod util;
@@ -54,6 +56,9 @@ use std::{
}; };
use tokio::runtime::{self, Runtime}; use tokio::runtime::{self, Runtime};
#[cfg(unix)]
use crate::util::{idevice_sockaddr, idevice_socklen_t};
static RUNTIME: Lazy<Runtime> = Lazy::new(|| { static RUNTIME: Lazy<Runtime> = Lazy::new(|| {
runtime::Builder::new_multi_thread() runtime::Builder::new_multi_thread()
.enable_io() .enable_io()
@@ -73,6 +78,10 @@ pub struct ReadWriteOpaque {
pub struct IdeviceHandle(pub Idevice); pub struct IdeviceHandle(pub Idevice);
pub struct IdeviceSocketHandle(IdeviceSocket); pub struct IdeviceSocketHandle(IdeviceSocket);
/// Stub to avoid header problems
#[allow(non_camel_case_types)]
pub type plist_t = *mut std::ffi::c_void;
// https://github.com/mozilla/cbindgen/issues/539 // https://github.com/mozilla/cbindgen/issues/539
#[allow(non_camel_case_types, unused)] #[allow(non_camel_case_types, unused)]
struct sockaddr; struct sockaddr;
@@ -132,22 +141,25 @@ pub unsafe extern "C" fn idevice_new(
/// `addr` must be a valid sockaddr /// `addr` must be a valid sockaddr
/// `label` must be a valid null-terminated C string /// `label` must be a valid null-terminated C string
/// `idevice` must be a valid, non-null pointer to a location where the handle will be stored /// `idevice` must be a valid, non-null pointer to a location where the handle will be stored
#[cfg(unix)]
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
pub unsafe extern "C" fn idevice_new_tcp_socket( pub unsafe extern "C" fn idevice_new_tcp_socket(
addr: *const libc::sockaddr, addr: *const idevice_sockaddr,
addr_len: libc::socklen_t, addr_len: idevice_socklen_t,
label: *const c_char, label: *const c_char,
idevice: *mut *mut IdeviceHandle, idevice: *mut *mut IdeviceHandle,
) -> *mut IdeviceFfiError { ) -> *mut IdeviceFfiError {
if addr.is_null() { use crate::util::SockAddr;
log::error!("socket addr null pointer");
if addr.is_null() || label.is_null() || idevice.is_null() {
log::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;
// Convert C string to Rust string
let label = match unsafe { CStr::from_ptr(label).to_str() } { let label = match unsafe { CStr::from_ptr(label).to_str() } {
Ok(s) => s, Ok(s) => s,
Err(_) => return ffi_err!(IdeviceError::FfiInvalidArg), Err(_) => return ffi_err!(IdeviceError::FfiInvalidString),
}; };
let addr = match util::c_socket_to_rust(addr, addr_len) { let addr = match util::c_socket_to_rust(addr, addr_len) {
@@ -155,9 +167,10 @@ pub unsafe extern "C" fn idevice_new_tcp_socket(
Err(e) => return ffi_err!(e), Err(e) => return ffi_err!(e),
}; };
let device: Result<idevice::Idevice, idevice::IdeviceError> = RUNTIME.block_on(async move { let device = RUNTIME.block_on(async move {
Ok(idevice::Idevice::new( let stream = tokio::net::TcpStream::connect(addr).await?;
Box::new(tokio::net::TcpStream::connect(addr).await?), Ok::<idevice::Idevice, idevice::IdeviceError>(idevice::Idevice::new(
Box::new(stream),
label, label,
)) ))
}); });
@@ -166,7 +179,7 @@ pub unsafe extern "C" fn idevice_new_tcp_socket(
Ok(dev) => { Ok(dev) => {
let boxed = Box::new(IdeviceHandle(dev)); let boxed = Box::new(IdeviceHandle(dev));
unsafe { *idevice = Box::into_raw(boxed) }; unsafe { *idevice = Box::into_raw(boxed) };
null_mut() std::ptr::null_mut()
} }
Err(e) => ffi_err!(e), Err(e) => ffi_err!(e),
} }
@@ -304,3 +317,18 @@ pub unsafe extern "C" fn idevice_string_free(string: *mut c_char) {
let _ = unsafe { CString::from_raw(string) }; let _ = unsafe { CString::from_raw(string) };
} }
} }
/// Frees data allocated by this library
///
/// # Arguments
/// * [`data`] - The data to free
///
/// # Safety
/// `data` must be a valid pointer to data that was allocated by this library,
/// or NULL (in which case this function does nothing)
#[unsafe(no_mangle)]
pub unsafe extern "C" fn idevice_data_free(data: *mut u8, len: usize) {
if !data.is_null() {
let _ = unsafe { std::slice::from_raw_parts(data, len) };
}
}

View File

@@ -1,8 +1,9 @@
// Jackson Coxson // Jackson Coxson
use std::{ffi::c_void, ptr::null_mut}; use std::ptr::null_mut;
use idevice::{IdeviceError, IdeviceService, lockdown::LockdownClient, provider::IdeviceProvider}; use idevice::{IdeviceError, IdeviceService, lockdown::LockdownClient, provider::IdeviceProvider};
use plist_ffi::plist_t;
use crate::{ use crate::{
IdeviceFfiError, IdeviceHandle, IdevicePairingFile, RUNTIME, ffi_err, IdeviceFfiError, IdeviceHandle, IdevicePairingFile, RUNTIME, ffi_err,
@@ -11,7 +12,7 @@ use crate::{
pub struct LockdowndClientHandle(pub LockdownClient); pub struct LockdowndClientHandle(pub LockdownClient);
/// Connects to lockdownd service using TCP provider /// Connects to lockdownd service using provider
/// ///
/// # Arguments /// # Arguments
/// * [`provider`] - An IdeviceProvider /// * [`provider`] - An IdeviceProvider
@@ -176,24 +177,32 @@ pub unsafe extern "C" fn lockdownd_get_value(
client: *mut LockdowndClientHandle, client: *mut LockdowndClientHandle,
key: *const libc::c_char, key: *const libc::c_char,
domain: *const libc::c_char, domain: *const libc::c_char,
out_plist: *mut *mut c_void, out_plist: *mut plist_t,
) -> *mut IdeviceFfiError { ) -> *mut IdeviceFfiError {
if key.is_null() || out_plist.is_null() { if out_plist.is_null() {
return ffi_err!(IdeviceError::FfiInvalidArg); return ffi_err!(IdeviceError::FfiInvalidArg);
} }
let value = unsafe { std::ffi::CStr::from_ptr(key) } let value = if key.is_null() {
.to_string_lossy() None
.into_owned(); } else {
Some(match unsafe { std::ffi::CStr::from_ptr(key) }.to_str() {
Ok(v) => v,
Err(_) => {
return ffi_err!(IdeviceError::InvalidCString);
}
})
};
let domain = if domain.is_null() { let domain = if domain.is_null() {
None None
} else { } else {
Some( Some(match unsafe { std::ffi::CStr::from_ptr(domain) }.to_str() {
unsafe { std::ffi::CStr::from_ptr(domain) } Ok(v) => v,
.to_string_lossy() Err(_) => {
.into_owned(), return ffi_err!(IdeviceError::InvalidCString);
) }
})
}; };
let res: Result<plist::Value, IdeviceError> = RUNTIME.block_on(async move { let res: Result<plist::Value, IdeviceError> = RUNTIME.block_on(async move {
@@ -204,55 +213,7 @@ pub unsafe extern "C" fn lockdownd_get_value(
match res { match res {
Ok(value) => { Ok(value) => {
unsafe { unsafe {
*out_plist = crate::util::plist_to_libplist(&value); *out_plist = plist_ffi::PlistWrapper::new_node(value).into_ptr();
}
null_mut()
}
Err(e) => ffi_err!(e),
}
}
/// Gets all values from lockdownd
///
/// # Arguments
/// * `client` - A valid LockdowndClient handle
/// * `out_plist` - Pointer to store the returned plist dictionary
///
/// # Returns
/// An IdeviceFfiError on error, null on success
///
/// # Safety
/// `client` must be a valid pointer to a handle allocated by this library
/// `out_plist` must be a valid pointer to store the plist
#[unsafe(no_mangle)]
pub unsafe extern "C" fn lockdownd_get_all_values(
client: *mut LockdowndClientHandle,
domain: *const libc::c_char,
out_plist: *mut *mut c_void,
) -> *mut IdeviceFfiError {
if out_plist.is_null() {
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let domain = if domain.is_null() {
None
} else {
Some(
unsafe { std::ffi::CStr::from_ptr(domain) }
.to_string_lossy()
.into_owned(),
)
};
let res: Result<plist::Dictionary, IdeviceError> = RUNTIME.block_on(async move {
let client_ref = unsafe { &mut (*client).0 };
client_ref.get_all_values(domain).await
});
match res {
Ok(dict) => {
unsafe {
*out_plist = crate::util::plist_to_libplist(&plist::Value::Dictionary(dict));
} }
null_mut() null_mut()
} }

View File

@@ -6,10 +6,9 @@ use idevice::{
IdeviceError, IdeviceService, mobile_image_mounter::ImageMounter, provider::IdeviceProvider, IdeviceError, IdeviceService, mobile_image_mounter::ImageMounter, provider::IdeviceProvider,
}; };
use plist::Value; use plist::Value;
use plist_ffi::{PlistWrapper, plist_t};
use crate::{ use crate::{IdeviceFfiError, IdeviceHandle, RUNTIME, ffi_err, provider::IdeviceProviderHandle};
IdeviceFfiError, IdeviceHandle, RUNTIME, ffi_err, provider::IdeviceProviderHandle, util,
};
pub struct ImageMounterHandle(pub ImageMounter); pub struct ImageMounterHandle(pub ImageMounter);
@@ -112,7 +111,7 @@ pub unsafe extern "C" fn image_mounter_free(handle: *mut ImageMounterHandle) {
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
pub unsafe extern "C" fn image_mounter_copy_devices( pub unsafe extern "C" fn image_mounter_copy_devices(
client: *mut ImageMounterHandle, client: *mut ImageMounterHandle,
devices: *mut *mut c_void, 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> = RUNTIME.block_on(async move {
@@ -124,14 +123,14 @@ pub unsafe extern "C" fn image_mounter_copy_devices(
Ok(devices_list) => { Ok(devices_list) => {
let devices_list = devices_list let devices_list = devices_list
.into_iter() .into_iter()
.map(|x| util::plist_to_libplist(&x)) .map(|x| plist_ffi::PlistWrapper::new_node(x).into_ptr())
.collect::<Vec<*mut std::ffi::c_void>>(); .collect::<Vec<plist_t>>();
let len = devices_list.len(); let len = devices_list.len();
let boxed_slice = devices_list.into_boxed_slice(); let boxed_slice = devices_list.into_boxed_slice();
let ptr = Box::leak(boxed_slice).as_mut_ptr(); let ptr = Box::leak(boxed_slice).as_mut_ptr();
unsafe { unsafe {
*devices = ptr as *mut c_void; *devices = ptr as *mut plist_t;
*devices_len = len; *devices_len = len;
} }
null_mut() null_mut()
@@ -515,7 +514,7 @@ pub unsafe extern "C" fn image_mounter_query_nonce(
let image_type = if !personalized_image_type.is_null() { let image_type = if !personalized_image_type.is_null() {
let image_type_cstr = unsafe { std::ffi::CStr::from_ptr(personalized_image_type) }; let image_type_cstr = unsafe { std::ffi::CStr::from_ptr(personalized_image_type) };
match image_type_cstr.to_str() { match image_type_cstr.to_str() {
Ok(s) => Some(s.to_string()), Ok(s) => Some(s),
Err(_) => return ffi_err!(IdeviceError::FfiInvalidArg), Err(_) => return ffi_err!(IdeviceError::FfiInvalidArg),
} }
} else { } else {
@@ -558,7 +557,7 @@ pub unsafe extern "C" fn image_mounter_query_nonce(
pub unsafe extern "C" fn image_mounter_query_personalization_identifiers( pub unsafe extern "C" fn image_mounter_query_personalization_identifiers(
client: *mut ImageMounterHandle, client: *mut ImageMounterHandle,
image_type: *const libc::c_char, image_type: *const libc::c_char,
identifiers: *mut *mut c_void, identifiers: *mut plist_t,
) -> *mut IdeviceFfiError { ) -> *mut IdeviceFfiError {
if identifiers.is_null() { if identifiers.is_null() {
return ffi_err!(IdeviceError::FfiInvalidArg); return ffi_err!(IdeviceError::FfiInvalidArg);
@@ -567,7 +566,7 @@ pub unsafe extern "C" fn image_mounter_query_personalization_identifiers(
let image_type = if !image_type.is_null() { let image_type = if !image_type.is_null() {
let image_type_cstr = unsafe { std::ffi::CStr::from_ptr(image_type) }; let image_type_cstr = unsafe { std::ffi::CStr::from_ptr(image_type) };
match image_type_cstr.to_str() { match image_type_cstr.to_str() {
Ok(s) => Some(s.to_string()), Ok(s) => Some(s),
Err(_) => return ffi_err!(IdeviceError::FfiInvalidArg), Err(_) => return ffi_err!(IdeviceError::FfiInvalidArg),
} }
} else { } else {
@@ -583,7 +582,7 @@ pub unsafe extern "C" fn image_mounter_query_personalization_identifiers(
match res { match res {
Ok(id) => { Ok(id) => {
let plist = util::plist_to_libplist(&plist::Value::Dictionary(id)); let plist = PlistWrapper::new_node(Value::Dictionary(id)).into_ptr();
unsafe { *identifiers = plist }; unsafe { *identifiers = plist };
null_mut() null_mut()
} }

View File

@@ -105,14 +105,14 @@ pub unsafe extern "C" fn process_control_launch_app(
for &env_var in env_vars_slice { for &env_var in env_vars_slice {
if !env_var.is_null() { if !env_var.is_null() {
let env_var = unsafe { CStr::from_ptr(env_var) }; let env_var = unsafe { CStr::from_ptr(env_var) };
if let Ok(env_var) = env_var.to_str() { if let Ok(env_var) = env_var.to_str()
if let Some((key, value)) = env_var.split_once('=') { && let Some((key, value)) = env_var.split_once('=')
{
env_dict.insert(key.to_string(), Value::String(value.to_string())); env_dict.insert(key.to_string(), Value::String(value.to_string()));
} }
} }
} }
} }
}
let mut args_dict = Dictionary::new(); let mut args_dict = Dictionary::new();
if !arguments.is_null() { if !arguments.is_null() {

View File

@@ -1,10 +1,13 @@
// Jackson Coxson // Jackson Coxson
use idevice::provider::{IdeviceProvider, TcpProvider, UsbmuxdProvider}; use idevice::provider::{IdeviceProvider, TcpProvider, UsbmuxdProvider};
use std::net::IpAddr;
use std::os::raw::c_char; use std::os::raw::c_char;
use std::{ffi::CStr, ptr::null_mut}; use std::{ffi::CStr, ptr::null_mut};
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};
pub struct IdeviceProviderHandle(pub Box<dyn IdeviceProvider>); pub struct IdeviceProviderHandle(pub Box<dyn IdeviceProvider>);
@@ -24,33 +27,27 @@ pub struct IdeviceProviderHandle(pub Box<dyn IdeviceProvider>);
/// `pairing_file` is consumed must never be used again /// `pairing_file` is consumed must never be used again
/// `label` must be a valid Cstr /// `label` must be a valid Cstr
/// `provider` must be a valid, non-null pointer to a location where the handle will be stored /// `provider` must be a valid, non-null pointer to a location where the handle will be stored
#[cfg(feature = "tcp")]
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
pub unsafe extern "C" fn idevice_tcp_provider_new( pub unsafe extern "C" fn idevice_tcp_provider_new(
ip: *const libc::sockaddr, ip: *const idevice_sockaddr,
pairing_file: *mut crate::pairing_file::IdevicePairingFile, pairing_file: *mut crate::pairing_file::IdevicePairingFile,
label: *const c_char, label: *const c_char,
provider: *mut *mut IdeviceProviderHandle, provider: *mut *mut IdeviceProviderHandle,
) -> *mut IdeviceFfiError { ) -> *mut IdeviceFfiError {
if ip.is_null() || label.is_null() || provider.is_null() { let ip = ip as *const SockAddr;
return ffi_err!(IdeviceError::FfiInvalidArg); let addr: IpAddr = match util::c_addr_to_rust(ip) {
}
let addr = match util::c_addr_to_rust(ip) {
Ok(i) => i, Ok(i) => i,
Err(e) => { Err(e) => return ffi_err!(e),
return ffi_err!(e);
}
};
let label = match unsafe { CStr::from_ptr(label) }.to_str() {
Ok(l) => l.to_string(),
Err(e) => {
log::error!("Invalid label string: {e:?}");
return ffi_err!(IdeviceError::FfiInvalidString);
}
}; };
let label = match unsafe { CStr::from_ptr(label).to_str() } {
Ok(s) => s.to_string(),
Err(_) => return ffi_err!(IdeviceError::FfiInvalidString),
};
// consume the pairing file on success
let pairing_file = unsafe { Box::from_raw(pairing_file) }; let pairing_file = unsafe { Box::from_raw(pairing_file) };
let t = TcpProvider { let t = TcpProvider {
addr, addr,
pairing_file: pairing_file.0, pairing_file: pairing_file.0,
@@ -59,7 +56,7 @@ pub unsafe extern "C" fn idevice_tcp_provider_new(
let boxed = Box::new(IdeviceProviderHandle(Box::new(t))); let boxed = Box::new(IdeviceProviderHandle(Box::new(t)));
unsafe { *provider = Box::into_raw(boxed) }; unsafe { *provider = Box::into_raw(boxed) };
null_mut() std::ptr::null_mut()
} }
/// Frees an IdeviceProvider handle /// Frees an IdeviceProvider handle
@@ -140,3 +137,32 @@ pub unsafe extern "C" fn usbmuxd_provider_new(
null_mut() null_mut()
} }
/// Gets the pairing file for the device
///
/// # Arguments
/// * [`provider`] - A pointer to the provider
/// * [`pairing_file`] - A pointer to the newly allocated pairing file
///
/// # Returns
/// An IdeviceFfiError on error, null on success
///
/// # Safety
/// `provider` must be a valid, non-null pointer to the provider
#[unsafe(no_mangle)]
pub unsafe extern "C" fn idevice_provider_get_pairing_file(
provider: *mut IdeviceProviderHandle,
pairing_file: *mut *mut IdevicePairingFile,
) -> *mut IdeviceFfiError {
let provider = unsafe { &mut *provider };
let res = RUNTIME.block_on(async move { provider.0.get_pairing_file().await });
match res {
Ok(pf) => {
let pf = Box::new(IdevicePairingFile(pf));
unsafe { *pairing_file = Box::into_raw(pf) };
null_mut()
}
Err(e) => ffi_err!(e),
}
}

View File

@@ -6,7 +6,6 @@ use crate::core_device_proxy::AdapterHandle;
use crate::rsd::RsdHandshakeHandle; use crate::rsd::RsdHandshakeHandle;
use crate::{IdeviceFfiError, RUNTIME, ReadWriteOpaque, ffi_err}; use crate::{IdeviceFfiError, RUNTIME, ReadWriteOpaque, ffi_err};
use idevice::dvt::remote_server::RemoteServerClient; use idevice::dvt::remote_server::RemoteServerClient;
use idevice::tcp::stream::AdapterStream;
use idevice::{IdeviceError, ReadWrite, RsdService}; use idevice::{IdeviceError, ReadWrite, RsdService};
/// Opaque handle to a RemoteServerClient /// Opaque handle to a RemoteServerClient
@@ -77,7 +76,7 @@ pub unsafe extern "C" fn remote_server_connect_rsd(
if provider.is_null() || handshake.is_null() || handshake.is_null() { if provider.is_null() || handshake.is_null() || handshake.is_null() {
return ffi_err!(IdeviceError::FfiInvalidArg); return ffi_err!(IdeviceError::FfiInvalidArg);
} }
let res: Result<RemoteServerClient<AdapterStream>, IdeviceError> = let res: Result<RemoteServerClient<Box<dyn ReadWrite>>, IdeviceError> =
RUNTIME.block_on(async move { RUNTIME.block_on(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 };

View File

@@ -10,6 +10,7 @@ use idevice::rsd::RsdHandshake;
use crate::{IdeviceFfiError, RUNTIME, ReadWriteOpaque, ffi_err}; use crate::{IdeviceFfiError, RUNTIME, ReadWriteOpaque, ffi_err};
/// Opaque handle to an RsdHandshake /// Opaque handle to an RsdHandshake
#[derive(Clone)]
pub struct RsdHandshakeHandle(pub RsdHandshake); pub struct RsdHandshakeHandle(pub RsdHandshake);
/// C-compatible representation of an RSD service /// C-compatible representation of an RSD service
@@ -370,6 +371,22 @@ pub unsafe extern "C" fn rsd_get_service_info(
null_mut() null_mut()
} }
/// Clones an RSD handshake
///
/// # Safety
/// Pass a valid pointer allocated by this library
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rsd_handshake_clone(
handshake: *mut RsdHandshakeHandle,
) -> *mut RsdHandshakeHandle {
if handshake.is_null() {
return null_mut();
}
let handshake = unsafe { &mut *handshake };
let new_handshake = handshake.clone();
Box::into_raw(Box::new(new_handshake))
}
/// Frees a string returned by RSD functions /// Frees a string returned by RSD functions
/// ///
/// # Arguments /// # Arguments

215
ffi/src/tcp_object_stack.rs Normal file
View File

@@ -0,0 +1,215 @@
//! Just to make things more complicated, some setups need an IP input from FFI. Or maybe a packet
//! input that is sync only. This is a stupid simple shim between callbacks and an input for the
//! legendary idevice TCP stack.
use std::{
ffi::{CStr, c_char, c_void},
ptr::null_mut,
sync::Arc,
};
use tokio::sync::Mutex;
use tokio::{
io::AsyncWriteExt,
net::tcp::{OwnedReadHalf, OwnedWriteHalf},
};
use crate::{IdeviceFfiError, RUNTIME, core_device_proxy::AdapterHandle, ffi_err};
pub struct TcpFeedObject {
sender: Arc<Mutex<OwnedWriteHalf>>,
}
pub struct TcpEatObject {
receiver: Arc<Mutex<OwnedReadHalf>>,
}
#[repr(transparent)]
#[derive(Clone)]
pub struct UserContext(*mut c_void);
unsafe impl Send for UserContext {}
unsafe impl Sync for UserContext {}
/// # Safety
/// Pass valid pointers.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn idevice_tcp_stack_into_sync_objects(
our_ip: *const c_char,
their_ip: *const c_char,
feeder: *mut *mut TcpFeedObject, // feed the TCP stack with IP packets
tcp_receiver: *mut *mut TcpEatObject,
adapter_handle: *mut *mut AdapterHandle, // this object can be used throughout the rest of the
// idevice ecosystem
) -> *mut IdeviceFfiError {
if our_ip.is_null() || their_ip.is_null() || feeder.is_null() || adapter_handle.is_null() {
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let our_ip = unsafe { CStr::from_ptr(our_ip) }
.to_string_lossy()
.to_string();
let our_ip = match our_ip.parse::<std::net::IpAddr>() {
Ok(o) => o,
Err(_) => {
return ffi_err!(IdeviceError::FfiInvalidArg);
}
};
let their_ip = unsafe { CStr::from_ptr(their_ip) }
.to_string_lossy()
.to_string();
let their_ip = match their_ip.parse::<std::net::IpAddr>() {
Ok(o) => o,
Err(_) => {
return ffi_err!(IdeviceError::FfiInvalidArg);
}
};
let res = RUNTIME.block_on(async {
let mut port = 4000;
loop {
if port > 4050 {
return None;
}
let listener = match tokio::net::TcpListener::bind(format!("127.0.0.1:{port}")).await {
Ok(l) => l,
Err(_) => {
port += 1;
continue;
}
};
let stream = tokio::net::TcpStream::connect(format!("127.0.0.1:{port}"))
.await
.ok()?;
stream.set_nodelay(true).ok()?;
let (stream2, _) = listener.accept().await.ok()?;
stream2.set_nodelay(true).ok()?;
break Some((stream, stream2));
}
});
let (stream, stream2) = match res {
Some(x) => x,
None => {
return ffi_err!(IdeviceError::NoEstablishedConnection);
}
};
let (r, w) = stream2.into_split();
let w = Arc::new(Mutex::new(w));
let r = Arc::new(Mutex::new(r));
// let w = Arc::new(Mutex::new(stream2));
// let r = w.clone();
let feed_object = TcpFeedObject { sender: w };
let eat_object = TcpEatObject { receiver: r };
// we must be inside the runtime for the inner function to spawn threads
let new_adapter = RUNTIME.block_on(async {
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
unsafe {
*feeder = Box::into_raw(Box::new(feed_object));
*tcp_receiver = Box::into_raw(Box::new(eat_object));
*adapter_handle = Box::into_raw(Box::new(AdapterHandle(new_adapter)));
}
null_mut()
}
/// Feed the TCP stack with data
/// # Safety
/// Pass valid pointers. Data is cloned out of slice.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn idevice_tcp_feed_object_write(
object: *mut TcpFeedObject,
data: *const u8,
len: usize,
) -> *mut IdeviceFfiError {
if object.is_null() || data.is_null() {
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let object = unsafe { &mut *object };
let data = unsafe { std::slice::from_raw_parts(data, len) };
RUNTIME.block_on(async move {
let mut lock = object.sender.lock().await;
match lock.write_all(data).await {
Ok(_) => {
lock.flush().await.ok();
null_mut()
}
Err(e) => {
ffi_err!(IdeviceError::Socket(std::io::Error::new(
std::io::ErrorKind::BrokenPipe,
format!("could not send: {e:?}")
)))
}
}
})
}
/// Block on getting a block of data to write to the underlying stream.
/// Write this to the stream as is, and free the data with idevice_data_free
///
/// # Safety
/// Pass valid pointers
#[unsafe(no_mangle)]
pub unsafe extern "C" fn idevice_tcp_eat_object_read(
object: *mut TcpEatObject,
data: *mut *mut u8,
len: *mut usize,
) -> *mut IdeviceFfiError {
let object = unsafe { &mut *object };
let mut buf = [0; 2048];
RUNTIME.block_on(async {
let lock = object.receiver.lock().await;
match lock.try_read(&mut buf) {
Ok(size) => {
let bytes = buf[..size].to_vec();
let mut res = bytes.into_boxed_slice();
unsafe {
*len = res.len();
*data = res.as_mut_ptr();
}
std::mem::forget(res);
std::ptr::null_mut()
}
Err(e) => match e.kind() {
std::io::ErrorKind::WouldBlock => {
unsafe {
*len = 0;
}
std::ptr::null_mut()
}
_ => {
ffi_err!(IdeviceError::Socket(std::io::Error::new(
std::io::ErrorKind::BrokenPipe,
"channel closed"
)))
}
},
}
})
}
/// # Safety
/// Pass a valid pointer allocated by this library
#[unsafe(no_mangle)]
pub unsafe extern "C" fn idevice_free_tcp_feed_object(object: *mut TcpFeedObject) {
if object.is_null() {
return;
}
let _ = unsafe { Box::from_raw(object) };
}
/// # Safety
/// Pass a valid pointer allocated by this library
#[unsafe(no_mangle)]
pub unsafe extern "C" fn idevice_free_tcp_eat_object(object: *mut TcpEatObject) {
if object.is_null() {
return;
}
let _ = unsafe { Box::from_raw(object) };
}

View File

@@ -1,18 +1,23 @@
// Jackson Coxson // Jackson Coxson
use std::{ use std::{
ffi::{CStr, c_char}, ffi::{CStr, CString, c_char},
ptr::null_mut, ptr::null_mut,
}; };
use crate::{IdeviceFfiError, RUNTIME, ffi_err, util::c_socket_to_rust}; use crate::{
IdeviceFfiError, IdeviceHandle, IdevicePairingFile, RUNTIME, ffi_err,
util::{SockAddr, c_socket_to_rust, idevice_sockaddr, idevice_socklen_t},
};
use idevice::{ use idevice::{
IdeviceError, IdeviceError,
usbmuxd::{UsbmuxdAddr, UsbmuxdConnection}, usbmuxd::{UsbmuxdAddr, UsbmuxdConnection, UsbmuxdDevice},
}; };
use log::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);
/// Connects to a usbmuxd instance over TCP /// Connects to a usbmuxd instance over TCP
/// ///
@@ -30,26 +35,32 @@ pub struct UsbmuxdAddrHandle(pub UsbmuxdAddr);
/// `usbmuxd_connection` must be a valid, non-null pointer to a location where the handle will be stored /// `usbmuxd_connection` must be a valid, non-null pointer to a location where the handle will be stored
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
pub unsafe extern "C" fn idevice_usbmuxd_new_tcp_connection( pub unsafe extern "C" fn idevice_usbmuxd_new_tcp_connection(
addr: *const libc::sockaddr, addr: *const idevice_sockaddr,
addr_len: libc::socklen_t, addr_len: idevice_socklen_t,
tag: u32, tag: u32,
usbmuxd_connection: *mut *mut UsbmuxdConnectionHandle, out: *mut *mut UsbmuxdConnectionHandle,
) -> *mut IdeviceFfiError { ) -> *mut IdeviceFfiError {
let addr = match c_socket_to_rust(addr, addr_len) { if addr.is_null() || out.is_null() {
return ffi_err!(IdeviceError::FfiInvalidArg);
}
// Reinterpret as the real platform sockaddr for parsing
let addr = addr as *const SockAddr;
let addr = match c_socket_to_rust(addr, addr_len as _) {
Ok(a) => a, Ok(a) => a,
Err(e) => return ffi_err!(e), Err(e) => return ffi_err!(e),
}; };
let res: Result<UsbmuxdConnection, IdeviceError> = RUNTIME.block_on(async move { let res = RUNTIME.block_on(async move {
let stream = tokio::net::TcpStream::connect(addr).await?; let stream = tokio::net::TcpStream::connect(addr).await?;
Ok(UsbmuxdConnection::new(Box::new(stream), tag)) Ok::<_, IdeviceError>(UsbmuxdConnection::new(Box::new(stream), tag))
}); });
match res { match res {
Ok(r) => { Ok(conn) => {
let boxed = Box::new(UsbmuxdConnectionHandle(r)); unsafe { *out = Box::into_raw(Box::new(UsbmuxdConnectionHandle(conn))) };
unsafe { *usbmuxd_connection = Box::into_raw(boxed) }; std::ptr::null_mut()
null_mut()
} }
Err(e) => ffi_err!(e), Err(e) => ffi_err!(e),
} }
@@ -108,6 +119,7 @@ pub unsafe extern "C" fn idevice_usbmuxd_new_unix_socket_connection(
/// # Safety /// # Safety
/// `addr` must be a valid CStr /// `addr` must be a valid CStr
/// `usbmuxd_connection` must be a valid, non-null pointer to a location where the handle will be stored /// `usbmuxd_connection` must be a valid, non-null pointer to a location where the handle will be stored
#[unsafe(no_mangle)]
pub unsafe extern "C" fn idevice_usbmuxd_new_default_connection( pub unsafe extern "C" fn idevice_usbmuxd_new_default_connection(
tag: u32, tag: u32,
usbmuxd_connection: *mut *mut UsbmuxdConnectionHandle, usbmuxd_connection: *mut *mut UsbmuxdConnectionHandle,
@@ -133,6 +145,203 @@ pub unsafe extern "C" fn idevice_usbmuxd_new_default_connection(
} }
} }
/// Gets a list of connected devices from usbmuxd.
///
/// The returned list must be freed with `idevice_usbmuxd_device_list_free`.
///
/// # Arguments
/// * `usbmuxd_conn` - A valid connection to usbmuxd.
/// * `devices` - A pointer to a C-style array of `UsbmuxdDeviceHandle` pointers. On success, this will be filled.
/// * `count` - A pointer to an integer. On success, this will be filled with the number of devices found.
///
/// # Returns
/// An `IdeviceFfiError` on error, `null` on success.
///
/// # Safety
/// * `usbmuxd_conn` must be a valid pointer.
/// * `devices` and `count` must be valid, non-null pointers.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn idevice_usbmuxd_get_devices(
usbmuxd_conn: *mut UsbmuxdConnectionHandle,
devices: *mut *mut *mut UsbmuxdDeviceHandle,
count: *mut libc::c_int,
) -> *mut IdeviceFfiError {
if usbmuxd_conn.is_null() {
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let conn = unsafe { &mut (*usbmuxd_conn).0 };
let res = RUNTIME.block_on(async { conn.get_devices().await });
match res {
Ok(device_vec) => {
unsafe {
*count = device_vec.len() as libc::c_int;
}
let mut c_arr = Vec::with_capacity(device_vec.len());
for device in device_vec {
let handle = Box::new(UsbmuxdDeviceHandle(device));
c_arr.push(Box::into_raw(handle));
}
let mut c_arr = c_arr.into_boxed_slice();
unsafe {
*devices = c_arr.as_mut_ptr();
}
std::mem::forget(c_arr); // Prevent deallocation of the slice's buffer
null_mut()
}
Err(e) => ffi_err!(e),
}
}
/// Connects to a service on a given device.
///
/// This function consumes the `UsbmuxdConnectionHandle`. The handle will be invalid after this call
/// and must not be used again. The caller is NOT responsible for freeing it.
/// A new `IdeviceHandle` is returned on success, which must be freed by the caller.
///
/// # Arguments
/// * `usbmuxd_connection` - The connection to use. It will be consumed.
/// * `device_id` - The ID of the device to connect to.
/// * `port` - The TCP port on the device to connect to.
/// * `idevice` - On success, points to the new device connection handle.
///
/// # Returns
/// An `IdeviceFfiError` on error, `null` on success.
///
/// # Safety
/// * `usbmuxd_connection` must be a valid pointer allocated by this library and never used again.
/// The value is consumed.
/// * `idevice` must be a valid, non-null pointer.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn idevice_usbmuxd_connect_to_device(
usbmuxd_connection: *mut UsbmuxdConnectionHandle,
device_id: u32,
port: u16,
label: *const c_char,
idevice: *mut *mut IdeviceHandle,
) -> *mut IdeviceFfiError {
if usbmuxd_connection.is_null() {
return ffi_err!(IdeviceError::FfiInvalidArg);
}
// Take ownership of the connection handle
let conn = unsafe {
let conn = std::ptr::read(&(*usbmuxd_connection).0); // move the inner connection
drop(Box::from_raw(usbmuxd_connection)); // free the wrapper
conn
};
let label = unsafe {
match CStr::from_ptr(label).to_str() {
Ok(s) => s,
Err(_) => return ffi_err!(IdeviceError::FfiInvalidArg),
}
};
let res = RUNTIME.block_on(async move { conn.connect_to_device(device_id, port, label).await });
match res {
Ok(device_conn) => {
let boxed = Box::new(IdeviceHandle(device_conn));
unsafe {
*idevice = Box::into_raw(boxed);
}
null_mut()
}
Err(e) => ffi_err!(e),
}
}
/// Reads the pairing record for a given device UDID.
///
/// The returned `PairingFileHandle` must be freed with `idevice_pair_record_free`.
///
/// # Arguments
/// * `usbmuxd_conn` - A valid connection to usbmuxd.
/// * `udid` - The UDID of the device.
/// * `pair_record` - On success, points to the new pairing file handle.
///
/// # 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_get_pair_record(
usbmuxd_conn: *mut UsbmuxdConnectionHandle,
udid: *const c_char,
pair_record: *mut *mut IdevicePairingFile,
) -> *mut IdeviceFfiError {
if usbmuxd_conn.is_null() {
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let conn = unsafe { &mut (*usbmuxd_conn).0 };
let udid_str = unsafe {
match CStr::from_ptr(udid).to_str() {
Ok(s) => s,
Err(_) => return ffi_err!(IdeviceError::FfiInvalidArg),
}
};
let res = RUNTIME.block_on(async { conn.get_pair_record(udid_str).await });
match res {
Ok(pf) => {
let boxed = Box::new(IdevicePairingFile(pf));
unsafe {
*pair_record = Box::into_raw(boxed);
}
null_mut()
}
Err(e) => ffi_err!(e),
}
}
/// Reads the BUID (Boot-Unique ID) from usbmuxd.
///
/// The returned string must be freed with `idevice_string_free`.
///
/// # Arguments
/// * `usbmuxd_conn` - A valid connection to usbmuxd.
/// * `buid` - On success, points to a newly allocated, null-terminated C string.
///
/// # Returns
/// An `IdeviceFfiError` on error, `null` on success.
///
/// # Safety
/// * `usbmuxd_conn` must be a valid pointer.
/// * `buid` must be a valid, non-null pointer.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn idevice_usbmuxd_get_buid(
usbmuxd_conn: *mut UsbmuxdConnectionHandle,
buid: *mut *mut c_char,
) -> *mut IdeviceFfiError {
if usbmuxd_conn.is_null() {
return ffi_err!(IdeviceError::FfiInvalidArg);
}
let conn = unsafe { &mut (*usbmuxd_conn).0 };
let res = RUNTIME.block_on(async { conn.get_buid().await });
match res {
Ok(buid_str) => match CString::new(buid_str) {
Ok(c_str) => {
unsafe { *buid = c_str.into_raw() };
null_mut()
}
Err(e) => {
error!("Unable to convert BUID string to CString: {e:?}. Null interior byte.");
ffi_err!(IdeviceError::UnexpectedResponse)
}
},
Err(e) => ffi_err!(e),
}
}
/// Frees a UsbmuxdConnection handle /// Frees a UsbmuxdConnection handle
/// ///
/// # Arguments /// # Arguments
@@ -165,20 +374,28 @@ pub unsafe extern "C" fn idevice_usbmuxd_connection_free(
/// `usbmuxd_Addr` must be a valid, non-null pointer to a location where the handle will be stored /// `usbmuxd_Addr` must be a valid, non-null pointer to a location where the handle will be stored
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
pub unsafe extern "C" fn idevice_usbmuxd_tcp_addr_new( pub unsafe extern "C" fn idevice_usbmuxd_tcp_addr_new(
addr: *const libc::sockaddr, addr: *const idevice_sockaddr, // <- portable
addr_len: libc::socklen_t, addr_len: idevice_socklen_t,
usbmuxd_addr: *mut *mut UsbmuxdAddrHandle, usbmuxd_addr: *mut *mut UsbmuxdAddrHandle,
) -> *mut IdeviceFfiError { ) -> *mut IdeviceFfiError {
let addr = match c_socket_to_rust(addr, addr_len) { if addr.is_null() || usbmuxd_addr.is_null() {
return ffi_err!(IdeviceError::FfiInvalidArg);
}
// Reinterpret as the real platform sockaddr for parsing
let addr = addr as *const SockAddr;
let addr = match c_socket_to_rust(addr, addr_len as _) {
Ok(a) => a, Ok(a) => a,
Err(e) => return ffi_err!(e), Err(e) => return ffi_err!(e),
}; };
let u = UsbmuxdAddr::TcpSocket(addr); let u = UsbmuxdAddr::TcpSocket(addr);
let boxed = Box::new(UsbmuxdAddrHandle(u)); let boxed = Box::new(UsbmuxdAddrHandle(u));
unsafe { *usbmuxd_addr = Box::into_raw(boxed) }; unsafe {
null_mut() *usbmuxd_addr = Box::into_raw(boxed);
}
std::ptr::null_mut()
} }
/// Creates a new UsbmuxdAddr struct with a unix socket /// Creates a new UsbmuxdAddr struct with a unix socket
@@ -211,6 +428,26 @@ pub unsafe extern "C" fn idevice_usbmuxd_unix_addr_new(
null_mut() null_mut()
} }
/// Creates a default UsbmuxdAddr struct for the platform
///
/// # Arguments
/// * [`usbmuxd_addr`] - On success, will be set to point to a newly allocated UsbmuxdAddr handle
///
/// # Returns
/// An IdeviceFfiError on error, null on success
///
/// # Safety
/// `usbmuxd_addr` must be a valid, non-null pointer to a location where the handle will be stored
#[unsafe(no_mangle)]
pub unsafe extern "C" fn idevice_usbmuxd_default_addr_new(
usbmuxd_addr: *mut *mut UsbmuxdAddrHandle,
) -> *mut IdeviceFfiError {
let addr = UsbmuxdAddr::default();
let boxed = Box::new(UsbmuxdAddrHandle(addr));
unsafe { *usbmuxd_addr = Box::into_raw(boxed) };
null_mut()
}
/// Frees a UsbmuxdAddr handle /// Frees a UsbmuxdAddr handle
/// ///
/// # Arguments /// # Arguments
@@ -225,3 +462,108 @@ pub unsafe extern "C" fn idevice_usbmuxd_addr_free(usbmuxd_addr: *mut UsbmuxdAdd
let _ = unsafe { Box::from_raw(usbmuxd_addr) }; let _ = unsafe { Box::from_raw(usbmuxd_addr) };
} }
} }
/// Frees a list of devices returned by `idevice_usbmuxd_get_devices`.
///
/// # Arguments
/// * `devices` - The array of device handles to free.
/// * `count` - The number of elements in the array.
///
/// # Safety
/// `devices` must be a valid pointer to an array of `count` device handles
/// allocated by this library, or NULL.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn idevice_usbmuxd_device_list_free(
devices: *mut *mut UsbmuxdDeviceHandle,
count: libc::c_int,
) {
if devices.is_null() {
return;
}
let slice = unsafe { std::slice::from_raw_parts_mut(devices, count as usize) };
for &mut ptr in slice {
if !ptr.is_null() {
let _ = unsafe { Box::from_raw(ptr) };
}
}
}
/// Frees a usbmuxd device
///
/// # Arguments
/// * `device` - The device handle to free.
///
/// # Safety
/// `device` must be a valid pointer to the device handle
/// allocated by this library, or NULL.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn idevice_usbmuxd_device_free(device: *mut UsbmuxdDeviceHandle) {
if device.is_null() {
return;
}
let _ = unsafe { Box::from_raw(device) };
}
/// Gets the UDID from a device handle.
/// The returned string must be freed by the caller using `idevice_string_free`.
///
/// # Safety
/// `device` must be a valid pointer to a `UsbmuxdDeviceHandle`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn idevice_usbmuxd_device_get_udid(
device: *const UsbmuxdDeviceHandle,
) -> *mut c_char {
if device.is_null() {
return null_mut();
}
let device = unsafe { &(*device).0 };
match CString::new(device.udid.as_str()) {
Ok(s) => s.into_raw(),
Err(_) => null_mut(),
}
}
/// Gets the device ID from a device handle.
///
/// # Safety
/// `device` must be a valid pointer to a `UsbmuxdDeviceHandle`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn idevice_usbmuxd_device_get_device_id(
device: *const UsbmuxdDeviceHandle,
) -> u32 {
if device.is_null() {
return 0;
}
unsafe { (*device).0.device_id }
}
#[repr(C)]
enum UsbmuxdConnectionType {
Usb = 1,
Network = 2,
Unknown = 3,
}
/// Gets the connection type (UsbmuxdConnectionType) from a device handle.
///
/// # Returns
/// The enum value of the connection type, or 0 for null device handles
///
/// # Safety
/// `device` must be a valid pointer to a `UsbmuxdDeviceHandle`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn idevice_usbmuxd_device_get_connection_type(
device: *const UsbmuxdDeviceHandle,
) -> u8 {
if device.is_null() {
return 0;
}
let ct = unsafe { &(*device).0.connection_type };
let ct = match ct {
idevice::usbmuxd::Connection::Usb => UsbmuxdConnectionType::Usb,
idevice::usbmuxd::Connection::Network(_) => UsbmuxdConnectionType::Network,
idevice::usbmuxd::Connection::Unknown(_) => UsbmuxdConnectionType::Unknown,
};
ct as u8
}

View File

@@ -1,98 +1,186 @@
// Jackson Coxson // Jackson Coxson
use std::{ use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
ffi::c_int,
net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr},
os::raw::c_void,
};
use idevice::IdeviceError; use idevice::IdeviceError;
use libc::{sockaddr_in, sockaddr_in6};
use plist::Value; // portable FFI-facing types (only used in signatures)
#[allow(non_camel_case_types)]
#[repr(C)]
pub struct idevice_sockaddr {
_priv: [u8; 0], // opaque; acts as "struct sockaddr" placeholder
}
#[cfg(unix)]
#[allow(non_camel_case_types)]
pub type idevice_socklen_t = libc::socklen_t;
#[cfg(windows)]
#[allow(non_camel_case_types)]
pub type idevice_socklen_t = i32;
// platform sockaddr aliases for implementation
#[cfg(unix)]
pub(crate) type SockAddr = libc::sockaddr;
#[cfg(windows)]
use windows_sys::Win32::Networking::WinSock as winsock;
#[cfg(windows)]
pub(crate) type SockAddr = winsock::SOCKADDR;
#[cfg(unix)]
use libc::{self, sockaddr_in, sockaddr_in6};
#[cfg(windows)]
use windows_sys::Win32::Networking::WinSock::{
AF_INET, AF_INET6, SOCKADDR_IN as sockaddr_in, SOCKADDR_IN6 as sockaddr_in6,
};
#[cfg(unix)]
type SockLen = libc::socklen_t;
#[cfg(windows)]
type SockLen = i32; // socklen_t is an int on Windows
#[inline]
fn invalid_arg<T>() -> Result<T, IdeviceError> {
Err(IdeviceError::FfiInvalidArg)
}
pub(crate) fn c_socket_to_rust( pub(crate) fn c_socket_to_rust(
addr: *const libc::sockaddr, addr: *const SockAddr,
addr_len: libc::socklen_t, addr_len: SockLen,
) -> Result<SocketAddr, IdeviceError> { ) -> Result<SocketAddr, IdeviceError> {
Ok(unsafe { if addr.is_null() {
match (*addr).sa_family as c_int { log::error!("null sockaddr");
return invalid_arg();
}
unsafe {
let family = (*addr).sa_family;
#[cfg(unix)]
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"); log::error!("Invalid sockaddr_in size");
return Err(IdeviceError::FfiInvalidArg); return invalid_arg();
} }
let addr_in = *(addr as *const sockaddr_in); let a = &*(addr as *const sockaddr_in);
let ip = std::net::Ipv4Addr::from(u32::from_be(addr_in.sin_addr.s_addr)); let ip = Ipv4Addr::from(u32::from_be(a.sin_addr.s_addr));
let port = u16::from_be(addr_in.sin_port); let port = u16::from_be(a.sin_port);
std::net::SocketAddr::V4(std::net::SocketAddrV4::new(ip, port)) Ok(SocketAddr::V4(std::net::SocketAddrV4::new(ip, port)))
} }
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>() {
let addr_in6 = *(addr as *const sockaddr_in6); log::error!("Invalid sockaddr_in6 size");
let ip = std::net::Ipv6Addr::from(addr_in6.sin6_addr.s6_addr); return invalid_arg();
let port = u16::from_be(addr_in6.sin6_port); }
std::net::SocketAddr::V6(std::net::SocketAddrV6::new( let a = &*(addr as *const sockaddr_in6);
let ip = Ipv6Addr::from(a.sin6_addr.s6_addr);
let port = u16::from_be(a.sin6_port);
Ok(SocketAddr::V6(std::net::SocketAddrV6::new(
ip, ip,
port, port,
addr_in6.sin6_flowinfo, a.sin6_flowinfo,
addr_in6.sin6_scope_id, a.sin6_scope_id,
)) )))
} else {
log::error!("Invalid sockaddr_in6 size");
return Err(IdeviceError::FfiInvalidArg);
} }
_ => {
log::error!(
"Unsupported socket address family: {}",
(*addr).sa_family as i32
);
invalid_arg()
}
}
#[cfg(windows)]
match family {
AF_INET => {
if (addr_len as usize) < std::mem::size_of::<sockaddr_in>() {
log::error!("Invalid SOCKADDR_IN size");
return invalid_arg();
}
let a = &*(addr as *const sockaddr_in);
// IN_ADDR is a union; use S_un.S_addr (network byte order)
let ip_be = a.sin_addr.S_un.S_addr;
let ip = Ipv4Addr::from(u32::from_be(ip_be));
let port = u16::from_be(a.sin_port);
Ok(SocketAddr::V4(std::net::SocketAddrV4::new(ip, port)))
}
AF_INET6 => {
if (addr_len as usize) < std::mem::size_of::<sockaddr_in6>() {
log::error!("Invalid SOCKADDR_IN6 size");
return invalid_arg();
}
let a = &*(addr as *const sockaddr_in6);
// IN6_ADDR is a union; read the 16 Byte array
let bytes: [u8; 16] = a.sin6_addr.u.Byte;
let ip = Ipv6Addr::from(bytes);
let port = u16::from_be(a.sin6_port);
let scope_id = a.Anonymous.sin6_scope_id;
Ok(SocketAddr::V6(std::net::SocketAddrV6::new(
ip,
port,
a.sin6_flowinfo,
scope_id,
)))
} }
_ => { _ => {
log::error!("Unsupported socket address family: {}", (*addr).sa_family); log::error!("Unsupported socket address family: {}", (*addr).sa_family);
return Err(IdeviceError::FfiInvalidArg); invalid_arg()
}
} }
} }
})
} }
pub(crate) fn c_addr_to_rust(addr: *const libc::sockaddr) -> Result<IpAddr, IdeviceError> { pub(crate) fn c_addr_to_rust(addr: *const SockAddr) -> Result<IpAddr, IdeviceError> {
if addr.is_null() {
log::error!("null sockaddr");
return invalid_arg();
}
unsafe { unsafe {
// Check the address family #[cfg(unix)]
match (*addr).sa_family as c_int { let family = (*addr).sa_family as i32;
#[cfg(windows)]
let family = (*addr).sa_family;
#[cfg(unix)]
match family {
libc::AF_INET => { libc::AF_INET => {
// Convert sockaddr_in (IPv4) to IpAddr let a = &*(addr as *const sockaddr_in);
let sockaddr_in = addr as *const sockaddr_in; let octets = u32::from_be(a.sin_addr.s_addr).to_be_bytes();
let ip = (*sockaddr_in).sin_addr.s_addr;
let octets = u32::from_be(ip).to_be_bytes();
Ok(IpAddr::V4(Ipv4Addr::new( Ok(IpAddr::V4(Ipv4Addr::new(
octets[0], octets[1], octets[2], octets[3], octets[0], octets[1], octets[2], octets[3],
))) )))
} }
libc::AF_INET6 => { libc::AF_INET6 => {
// Convert sockaddr_in6 (IPv6) to IpAddr let a = &*(addr as *const sockaddr_in6);
let sockaddr_in6 = addr as *const sockaddr_in6; Ok(IpAddr::V6(Ipv6Addr::from(a.sin6_addr.s6_addr)))
let ip = (*sockaddr_in6).sin6_addr.s6_addr; }
Ok(IpAddr::V6(Ipv6Addr::from(ip))) _ => {
log::error!(
"Unsupported socket address family: {}",
(*addr).sa_family as i32
);
invalid_arg()
}
}
#[cfg(windows)]
match family {
AF_INET => {
let a = &*(addr as *const sockaddr_in);
let ip_be = a.sin_addr.S_un.S_addr;
Ok(IpAddr::V4(Ipv4Addr::from(u32::from_be(ip_be))))
}
AF_INET6 => {
let a = &*(addr as *const sockaddr_in6);
let bytes: [u8; 16] = a.sin6_addr.u.Byte;
Ok(IpAddr::V6(Ipv6Addr::from(bytes)))
} }
_ => { _ => {
log::error!("Unsupported socket address family: {}", (*addr).sa_family); log::error!("Unsupported socket address family: {}", (*addr).sa_family);
Err(IdeviceError::FfiInvalidArg) invalid_arg()
} }
} }
} }
} }
pub(crate) fn plist_to_libplist(v: &Value) -> *mut libc::c_void {
let buf = Vec::new();
let mut writer = std::io::BufWriter::new(buf);
plist::to_writer_xml(&mut writer, v).unwrap();
let buf = String::from_utf8(writer.into_inner().unwrap()).unwrap();
let p = plist_plus::Plist::from_xml(buf).unwrap();
let ptr = p.get_pointer();
p.false_drop();
ptr
}
pub(crate) fn libplist_to_plist(v: *mut c_void) -> Value {
let v: plist_plus::Plist = v.into();
let v_string = v.to_string();
v.false_drop();
let reader = std::io::Cursor::new(v_string.as_bytes());
plist::from_reader(reader).unwrap()
}

View File

@@ -2,8 +2,8 @@
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.35" version = "0.1.41"
edition = "2021" edition = "2024"
license = "MIT" license = "MIT"
documentation = "https://docs.rs/idevice" documentation = "https://docs.rs/idevice"
repository = "https://github.com/jkcoxson/idevice" repository = "https://github.com/jkcoxson/idevice"
@@ -12,8 +12,12 @@ keywords = ["lockdownd", "ios"]
[dependencies] [dependencies]
tokio = { version = "1.43", features = ["io-util"] } tokio = { version = "1.43", features = ["io-util"] }
tokio-rustls = "0.26" tokio-rustls = { version = "0.26", default-features = false }
rustls = "0.23" rustls = { version = "0.23", default-features = false, features = [
"std",
"tls12",
] }
crossfire = { version = "2.0", optional = true } # TODO: update to 2.1 when it comes out
plist = { version = "1.7" } plist = { version = "1.7" }
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
@@ -26,7 +30,9 @@ base64 = { version = "0.22" }
indexmap = { version = "2.7", features = ["serde"], optional = true } indexmap = { version = "2.7", features = ["serde"], optional = true }
uuid = { version = "1.12", features = ["serde", "v4"], optional = true } uuid = { version = "1.12", features = ["serde", "v4"], optional = true }
chrono = { version = "0.4.40", optional = true, default-features = false } chrono = { version = "0.4.40", optional = true, default-features = false, features = [
"serde",
] }
serde_json = { version = "1", optional = true } serde_json = { version = "1", optional = true }
json = { version = "0.12", optional = true } json = { version = "0.12", optional = true }
@@ -35,10 +41,10 @@ bytes = { version = "1.10", optional = true }
reqwest = { version = "0.12", features = [ reqwest = { version = "0.12", features = [
"json", "json",
"rustls-tls",
], optional = true, default-features = false } ], optional = true, default-features = false }
rand = { version = "0.9", optional = true } rand = { version = "0.9", optional = true }
futures = { version = "0.3", optional = true } futures = { version = "0.3", optional = true }
async-stream = { version = "0.3.6", optional = true }
sha2 = { version = "0.10", optional = true, features = ["oid"] } sha2 = { version = "0.10", optional = true, features = ["oid"] }
@@ -60,12 +66,19 @@ tun-rs = { version = "2.0.8", features = ["async_tokio"] }
bytes = "1.10.1" bytes = "1.10.1"
[features] [features]
default = ["aws-lc"]
aws-lc = ["rustls/aws-lc-rs", "tokio-rustls/aws-lc-rs"]
ring = ["rustls/ring", "tokio-rustls/ring"]
afc = ["dep:chrono"] afc = ["dep:chrono"]
amfi = [] amfi = []
bt_packet_logger = []
companion_proxy = []
core_device = ["xpc", "dep:uuid"] core_device = ["xpc", "dep:uuid"]
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 = []
diagnostics_relay = []
dvt = ["dep:byteorder", "dep:ns-keyed-archive"] dvt = ["dep:byteorder", "dep:ns-keyed-archive"]
heartbeat = ["tokio/macros", "tokio/time"] heartbeat = ["tokio/macros", "tokio/time"]
house_arrest = ["afc"] house_arrest = ["afc"]
@@ -73,8 +86,11 @@ installation_proxy = []
springboardservices = [] springboardservices = []
misagent = [] misagent = []
mobile_image_mounter = ["dep:sha2"] mobile_image_mounter = ["dep:sha2"]
mobilebackup2 = []
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 = []
preboard_service = []
obfuscate = ["dep:obfstr"] obfuscate = ["dep:obfstr"]
restore_service = [] restore_service = []
remote_pairing = [ remote_pairing = [
@@ -88,18 +104,27 @@ remote_pairing = [
rsd = ["xpc"] rsd = ["xpc"]
syslog_relay = ["dep:bytes"] syslog_relay = ["dep:bytes"]
tcp = ["tokio/net"] tcp = ["tokio/net"]
tunnel_tcp_stack = ["dep:rand", "dep:futures", "tokio/fs", "tokio/sync"] tunnel_tcp_stack = [
"dep:rand",
"dep:futures",
"tokio/fs",
"tokio/sync",
"dep:crossfire",
]
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"]
xpc = ["dep:indexmap", "dep:uuid"] xpc = ["dep:indexmap", "dep:uuid", "dep:async-stream"]
full = [ full = [
"afc", "afc",
"amfi", "amfi",
"bt_packet_logger",
"companion_proxy",
"core_device", "core_device",
"core_device_proxy", "core_device_proxy",
"crashreportcopymobile", "crashreportcopymobile",
"debug_proxy", "debug_proxy",
"diagnostics_relay",
"dvt", "dvt",
"heartbeat", "heartbeat",
"house_arrest", "house_arrest",
@@ -107,7 +132,10 @@ full = [
"location_simulation", "location_simulation",
"misagent", "misagent",
"mobile_image_mounter", "mobile_image_mounter",
"mobilebackup2",
"pair", "pair",
"pcapd",
"preboard_service",
"restore_service", "restore_service",
"usbmuxd", "usbmuxd",
"xpc", "xpc",

View File

@@ -4,19 +4,19 @@
use std::str::FromStr; use std::str::FromStr;
use rsa::{ use rsa::{
RsaPrivateKey, RsaPublicKey,
pkcs1::DecodeRsaPublicKey, pkcs1::DecodeRsaPublicKey,
pkcs1v15::SigningKey, pkcs1v15::SigningKey,
pkcs8::{EncodePrivateKey, LineEnding, SubjectPublicKeyInfo}, pkcs8::{EncodePrivateKey, LineEnding, SubjectPublicKeyInfo},
RsaPrivateKey, RsaPublicKey,
}; };
use sha2::Sha256; use sha2::Sha256;
use x509_cert::{ use x509_cert::{
Certificate,
builder::{Builder, CertificateBuilder, Profile}, builder::{Builder, CertificateBuilder, Profile},
der::EncodePem, der::EncodePem,
name::Name, name::Name,
serial_number::SerialNumber, serial_number::SerialNumber,
time::Validity, time::Validity,
Certificate,
}; };
#[derive(Clone, Debug)] #[derive(Clone, Debug)]

View File

@@ -16,6 +16,7 @@ pub mod tunneld;
#[cfg(feature = "usbmuxd")] #[cfg(feature = "usbmuxd")]
pub mod usbmuxd; pub mod usbmuxd;
mod util; mod util;
pub mod utils;
#[cfg(feature = "xpc")] #[cfg(feature = "xpc")]
pub mod xpc; pub mod xpc;
@@ -37,6 +38,8 @@ use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
pub use util::{pretty_print_dictionary, pretty_print_plist}; pub use util::{pretty_print_dictionary, pretty_print_plist};
use crate::services::lockdown::LockdownClient;
/// A trait combining all required characteristics for a device communication socket /// A trait combining all required characteristics for a device communication socket
/// ///
/// This serves as a convenience trait for any type that can be used as an asynchronous /// This serves as a convenience trait for any type that can be used as an asynchronous
@@ -61,29 +64,58 @@ pub trait IdeviceService: Sized {
/// ///
/// # Arguments /// # Arguments
/// * `provider` - The device provider that can supply connections /// * `provider` - The device provider that can supply connections
fn connect( ///
provider: &dyn IdeviceProvider, // From the docs
) -> impl std::future::Future<Output = Result<Self, IdeviceError>> + Send; // │ │ ├╴ use of `async fn` in public traits is discouraged as auto trait bounds cannot be specified
// │ │ │ you can suppress this lint if you plan to use the trait only in your own code, or do not care about auto traits like `Send` on the `Future`
// │ │ │ `#[warn(async_fn_in_trait)]` on by default rustc (async_fn_in_trait) [66, 5]
#[allow(async_fn_in_trait)]
async fn connect(provider: &dyn IdeviceProvider) -> Result<Self, IdeviceError> {
let mut lockdown = LockdownClient::connect(provider).await?;
lockdown
.start_session(&provider.get_pairing_file().await?)
.await?;
// Best-effort fetch UDID for downstream defaults (e.g., MobileBackup2 Target/Source identifiers)
let udid_value = match lockdown.get_value(Some("UniqueDeviceID"), None).await {
Ok(v) => v.as_string().map(|s| s.to_string()),
Err(_) => None,
};
let (port, ssl) = lockdown.start_service(Self::service_name()).await?;
let mut idevice = provider.connect(port).await?;
if ssl {
idevice
.start_session(&provider.get_pairing_file().await?)
.await?;
}
if let Some(udid) = udid_value {
idevice.set_udid(udid);
}
Self::from_stream(idevice).await
}
#[allow(async_fn_in_trait)]
async fn from_stream(idevice: Idevice) -> Result<Self, IdeviceError>;
} }
#[cfg(feature = "rsd")] #[cfg(feature = "rsd")]
pub trait RsdService: Sized { pub trait RsdService: Sized {
fn rsd_service_name() -> std::borrow::Cow<'static, str>; fn rsd_service_name() -> std::borrow::Cow<'static, str>;
fn from_stream( fn from_stream(
stream: Self::Stream, stream: Box<dyn ReadWrite>,
) -> impl std::future::Future<Output = Result<Self, IdeviceError>> + Send; ) -> impl std::future::Future<Output = Result<Self, IdeviceError>> + Send;
fn connect_rsd<'a, S>( fn connect_rsd(
provider: &'a mut impl RsdProvider<'a, Stream = S>, provider: &mut impl RsdProvider,
handshake: &mut rsd::RsdHandshake, handshake: &mut rsd::RsdHandshake,
) -> impl std::future::Future<Output = Result<Self, IdeviceError>> ) -> impl std::future::Future<Output = Result<Self, IdeviceError>>
where where
Self: crate::RsdService<Stream = S>, Self: crate::RsdService,
S: ReadWrite,
{ {
handshake.connect(provider) handshake.connect(provider)
} }
type Stream: ReadWrite;
} }
/// Type alias for boxed device connection sockets /// Type alias for boxed device connection sockets
@@ -101,6 +133,8 @@ pub struct Idevice {
socket: Option<Box<dyn ReadWrite>>, socket: Option<Box<dyn ReadWrite>>,
/// Unique label identifying this connection /// Unique label identifying this connection
label: String, label: String,
/// Cached device UDID for convenience in higher-level protocols
udid: Option<String>,
} }
impl Idevice { impl Idevice {
@@ -113,9 +147,24 @@ impl Idevice {
Self { Self {
socket: Some(socket), socket: Some(socket),
label: label.into(), label: label.into(),
udid: None,
} }
} }
pub fn get_socket(self) -> Option<Box<dyn ReadWrite>> {
self.socket
}
/// Sets cached UDID
pub fn set_udid(&mut self, udid: impl Into<String>) {
self.udid = Some(udid.into());
}
/// Returns cached UDID if available
pub fn udid(&self) -> Option<&str> {
self.udid.as_deref()
}
/// Queries the device type /// Queries the device type
/// ///
/// Sends a QueryType request and parses the response /// Sends a QueryType request and parses the response
@@ -126,11 +175,12 @@ 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 mut req = plist::Dictionary::new(); let req = crate::plist!({
req.insert("Label".into(), self.label.clone().into()); "Label": self.label.clone(),
req.insert("Request".into(), "QueryType".into()); "Request": "QueryType",
let message = plist::to_value(&req)?; });
self.send_plist(message).await?; self.send_plist(req).await?;
let message: plist::Dictionary = self.read_plist().await?; let message: plist::Dictionary = self.read_plist().await?;
match message.get("Type") { match message.get("Type") {
Some(m) => Ok(plist::from_value(m)?), Some(m) => Ok(plist::from_value(m)?),
@@ -145,11 +195,13 @@ 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 mut req = plist::Dictionary::new(); let req = crate::plist!({
req.insert("Label".into(), self.label.clone().into()); "Label": self.label.clone(),
req.insert("ProtocolVersion".into(), "2".into()); "ProtocolVersion": "2",
req.insert("Request".into(), "RSDCheckin".into()); "Request": "RSDCheckin",
self.send_plist(plist::to_value(&req).unwrap()).await?; });
self.send_plist(req).await?;
let res = self.read_plist().await?; let res = self.read_plist().await?;
match res.get("Request").and_then(|x| x.as_string()) { match res.get("Request").and_then(|x| x.as_string()) {
Some(r) => { Some(r) => {
@@ -322,6 +374,35 @@ impl Idevice {
/// # Errors /// # Errors
/// Returns `IdeviceError` if reading, parsing fails, or device reports an error /// Returns `IdeviceError` if reading, parsing fails, or device reports an error
async fn read_plist(&mut self) -> Result<plist::Dictionary, IdeviceError> { async fn read_plist(&mut self) -> Result<plist::Dictionary, IdeviceError> {
let res = self.read_plist_value().await?;
let res: plist::Dictionary = plist::from_value(&res)?;
debug!("Received plist: {}", pretty_print_dictionary(&res));
if let Some(e) = res.get("Error") {
let e = match e {
plist::Value::String(e) => e.to_string(),
plist::Value::Integer(e) => {
if let Some(error_string) = res.get("ErrorString").and_then(|x| x.as_string()) {
error_string.to_string()
} else {
e.to_string()
}
}
_ => {
log::error!("Error is not a string or integer from read_plist: {e:?}");
return Err(IdeviceError::UnexpectedResponse);
}
};
if let Some(e) = IdeviceError::from_device_error_type(e.as_str(), &res) {
return Err(e);
} else {
return Err(IdeviceError::UnknownErrorType(e));
}
}
Ok(res)
}
async fn read_plist_value(&mut self) -> Result<plist::Value, IdeviceError> {
if let Some(socket) = &mut self.socket { if let Some(socket) = &mut self.socket {
debug!("Reading response size"); debug!("Reading response size");
let mut buf = [0u8; 4]; let mut buf = [0u8; 4];
@@ -329,17 +410,7 @@ impl Idevice {
let len = u32::from_be_bytes(buf); let len = u32::from_be_bytes(buf);
let mut buf = vec![0; len as usize]; let mut buf = vec![0; len as usize];
socket.read_exact(&mut buf).await?; socket.read_exact(&mut buf).await?;
let res: plist::Dictionary = plist::from_bytes(&buf)?; let res: plist::Value = plist::from_bytes(&buf)?;
debug!("Received plist: {}", pretty_print_dictionary(&res));
if let Some(e) = res.get("Error") {
let e: String = plist::from_value(e)?;
if let Some(e) = IdeviceError::from_device_error_type(e.as_str(), &res) {
return Err(e);
} else {
return Err(IdeviceError::UnknownErrorType(e));
}
}
Ok(res) Ok(res)
} else { } else {
Err(IdeviceError::NoEstablishedConnection) Err(IdeviceError::NoEstablishedConnection)
@@ -390,9 +461,39 @@ impl Idevice {
pairing_file: &pairing_file::PairingFile, pairing_file: &pairing_file::PairingFile,
) -> Result<(), IdeviceError> { ) -> Result<(), IdeviceError> {
if CryptoProvider::get_default().is_none() { if CryptoProvider::get_default().is_none() {
if let Err(e) = // rust-analyzer will choke on this block, don't worry about it
CryptoProvider::install_default(rustls::crypto::aws_lc_rs::default_provider()) 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");
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 // For whatever reason, getting the default provider will return None on iOS at
// random. Installing the default provider a second time will return an error, so // random. Installing the default provider a second time will return an error, so
// we will log it but not propogate it. An issue should be opened with rustls. // we will log it but not propogate it. An issue should be opened with rustls.
@@ -600,22 +701,22 @@ pub enum IdeviceError {
#[cfg(feature = "remote_pairing")] #[cfg(feature = "remote_pairing")]
#[error("could not parse as JSON")] #[error("could not parse as JSON")]
JsonParseFailed(#[from] json::Error) = -63, JsonParseFailed(#[from] json::Error) = -67,
#[cfg(feature = "remote_pairing")] #[cfg(feature = "remote_pairing")]
#[error("unknown TLV type: {0}")] #[error("unknown TLV type: {0}")]
UnknownTlv(u8) = -64, UnknownTlv(u8) = -68,
#[cfg(feature = "remote_pairing")] #[cfg(feature = "remote_pairing")]
#[error("malformed TLV")] #[error("malformed TLV")]
MalformedTlv = -65, MalformedTlv = -69,
#[error("failed to decode base64 string")] #[error("failed to decode base64 string")]
Base64Decode(#[from] base64::DecodeError) = -66, Base64Decode(#[from] base64::DecodeError) = -70,
#[cfg(feature = "remote_pairing")] #[cfg(feature = "remote_pairing")]
#[error("pair verify failed")] #[error("pair verify failed")]
PairVerifyFailed = -67, PairVerifyFailed = -71,
#[error("invalid arguments were passed")] #[error("invalid arguments were passed")]
FfiInvalidArg = -60, FfiInvalidArg = -60,
@@ -623,6 +724,14 @@ pub enum IdeviceError {
FfiInvalidString = -61, FfiInvalidString = -61,
#[error("buffer passed is too small - needs {0}, got {1}")] #[error("buffer passed is too small - needs {0}, got {1}")]
FfiBufferTooSmall(usize, usize) = -62, FfiBufferTooSmall(usize, usize) = -62,
#[error("unsupported watch key")]
UnsupportedWatchKey = -63,
#[error("malformed command")]
MalformedCommand = -64,
#[error("integer overflow")]
IntegerOverflow = -65,
#[error("canceled by user")]
CanceledByUser = -66,
} }
impl IdeviceError { impl IdeviceError {
@@ -635,6 +744,9 @@ impl IdeviceError {
/// # Returns /// # Returns
/// Some(IdeviceError) if the string maps to a known error type, None otherwise /// Some(IdeviceError) if the string maps to a known error type, None otherwise
fn from_device_error_type(e: &str, context: &plist::Dictionary) -> Option<Self> { fn from_device_error_type(e: &str, context: &plist::Dictionary) -> Option<Self> {
if e.contains("NSDebugDescription=Canceled by user.") {
return Some(Self::CanceledByUser);
}
match e { match e {
"GetProhibited" => Some(Self::GetProhibited), "GetProhibited" => Some(Self::GetProhibited),
"InvalidHostID" => Some(Self::InvalidHostID), "InvalidHostID" => Some(Self::InvalidHostID),
@@ -646,6 +758,8 @@ impl IdeviceError {
"UserDeniedPairing" => Some(Self::UserDeniedPairing), "UserDeniedPairing" => Some(Self::UserDeniedPairing),
#[cfg(feature = "pair")] #[cfg(feature = "pair")]
"PasswordProtected" => Some(Self::PasswordProtected), "PasswordProtected" => Some(Self::PasswordProtected),
"UnsupportedWatchKey" => Some(Self::UnsupportedWatchKey),
"MalformedCommand" => Some(Self::MalformedCommand),
"InternalError" => { "InternalError" => {
let detailed_error = context let detailed_error = context
.get("DetailedError") .get("DetailedError")
@@ -769,16 +883,20 @@ impl IdeviceError {
IdeviceError::FfiInvalidArg => -60, IdeviceError::FfiInvalidArg => -60,
IdeviceError::FfiInvalidString => -61, IdeviceError::FfiInvalidString => -61,
IdeviceError::FfiBufferTooSmall(_, _) => -62, IdeviceError::FfiBufferTooSmall(_, _) => -62,
IdeviceError::UnsupportedWatchKey => -63,
IdeviceError::MalformedCommand => -64,
IdeviceError::IntegerOverflow => -65,
IdeviceError::CanceledByUser => -66,
#[cfg(feature = "remote_pairing")] #[cfg(feature = "remote_pairing")]
IdeviceError::JsonParseFailed(_) => -63, IdeviceError::JsonParseFailed(_) => -67,
#[cfg(feature = "remote_pairing")] #[cfg(feature = "remote_pairing")]
IdeviceError::UnknownTlv(_) => -64, IdeviceError::UnknownTlv(_) => -68,
#[cfg(feature = "remote_pairing")] #[cfg(feature = "remote_pairing")]
IdeviceError::MalformedTlv => -65, IdeviceError::MalformedTlv => -69,
IdeviceError::Base64Decode(_) => -66, IdeviceError::Base64Decode(_) => -70,
#[cfg(feature = "remote_pairing")] #[cfg(feature = "remote_pairing")]
IdeviceError::PairVerifyFailed => -67, IdeviceError::PairVerifyFailed => -71,
} }
} }
} }

View File

@@ -7,7 +7,7 @@ use std::path::Path;
use log::warn; use log::warn;
use plist::Data; use plist::Data;
use rustls::pki_types::{pem::PemObject, CertificateDer}; use rustls::pki_types::{CertificateDer, pem::PemObject};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
/// Represents a complete iOS device pairing record /// Represents a complete iOS device pairing record
@@ -219,7 +219,7 @@ fn ensure_pem_headers(data: &[u8], pem_type: &str) -> Vec<u8> {
let mut result = Vec::new(); let mut result = Vec::new();
// Add header // Add header
let header = format!("-----BEGIN {}-----\n", pem_type); let header = format!("-----BEGIN {pem_type}-----\n");
result.extend_from_slice(header.as_bytes()); result.extend_from_slice(header.as_bytes());
// Add base64 content with line breaks every 64 characters // Add base64 content with line breaks every 64 characters
@@ -244,7 +244,7 @@ fn ensure_pem_headers(data: &[u8], pem_type: &str) -> Vec<u8> {
result.push(b'\n'); result.push(b'\n');
// Add footer // Add footer
let footer = format!("-----END {}-----", pem_type); let footer = format!("-----END {pem_type}-----");
result.extend_from_slice(footer.as_bytes()); result.extend_from_slice(footer.as_bytes());
result result

View File

@@ -52,6 +52,23 @@
/// ``` /// ```
#[macro_export] #[macro_export]
macro_rules! plist { 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. // Hide distracting implementation details from the generated rustdoc.
($($plist:tt)+) => { ($($plist:tt)+) => {
$crate::plist_internal!($($plist)+) $crate::plist_internal!($($plist)+)
@@ -123,6 +140,21 @@ macro_rules! plist_internal {
$crate::plist_unexpected!($unexpected) $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 // TT muncher for parsing the inside of an object {...}. Each entry is
// inserted into the given map variable. // inserted into the given map variable.
@@ -177,6 +209,62 @@ macro_rules! plist_internal {
$crate::plist_internal!(@object $object [$($key)+] ($crate::plist_internal!({$($map)*})) $($rest)*); $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. // Next value is an expression followed by comma.
(@object $object:ident ($($key:tt)+) (: $value:expr , $($rest:tt)*) $copy:tt) => { (@object $object:ident ($($key:tt)+) (: $value:expr , $($rest:tt)*) $copy:tt) => {
$crate::plist_internal!(@object $object [$($key)+] ($crate::plist_internal!($value)) , $($rest)*); $crate::plist_internal!(@object $object [$($key)+] ($crate::plist_internal!($value)) , $($rest)*);
@@ -315,6 +403,12 @@ impl PlistConvertible for &str {
} }
} }
impl PlistConvertible for i16 {
fn to_plist_value(self) -> plist::Value {
plist::Value::Integer(self.into())
}
}
impl PlistConvertible for i32 { impl PlistConvertible for i32 {
fn to_plist_value(self) -> plist::Value { fn to_plist_value(self) -> plist::Value {
plist::Value::Integer(self.into()) plist::Value::Integer(self.into())
@@ -327,6 +421,12 @@ impl PlistConvertible for i64 {
} }
} }
impl PlistConvertible for u16 {
fn to_plist_value(self) -> plist::Value {
plist::Value::Integer((self as i64).into())
}
}
impl PlistConvertible for u32 { impl PlistConvertible for u32 {
fn to_plist_value(self) -> plist::Value { fn to_plist_value(self) -> plist::Value {
plist::Value::Integer((self as i64).into()) plist::Value::Integer((self as i64).into())
@@ -357,6 +457,27 @@ impl PlistConvertible for bool {
} }
} }
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> { impl<T: PlistConvertible> PlistConvertible for Vec<T> {
fn to_plist_value(self) -> plist::Value { fn to_plist_value(self) -> plist::Value {
plist::Value::Array(self.into_iter().map(|item| item.to_plist_value()).collect()) plist::Value::Array(self.into_iter().map(|item| item.to_plist_value()).collect())
@@ -423,15 +544,96 @@ where
} }
} }
impl<T: PlistConvertible> PlistConvertible for Option<T> { // Treat plain T as Some(T) and Option<T> as-is.
fn to_plist_value(self) -> plist::Value { 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 { match self {
Some(value) => value.to_plist_value(), plist::Value::Dictionary(d) => d,
None => plist::Value::String("".to_string()), // or however you want to handle None 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)] #[cfg(test)]
mod tests { mod tests {
#[test] #[test]
@@ -440,7 +642,7 @@ mod tests {
"name": "test", "name": "test",
"count": 42, "count": 42,
"active": true, "active": true,
"items": ["a", "b", "c"] "items": ["a", ?"b", "c"]
}); });
if let plist::Value::Dictionary(dict) = value { if let plist::Value::Dictionary(dict) = value {
@@ -460,11 +662,24 @@ mod tests {
let name = "dynamic"; let name = "dynamic";
let count = 100; let count = 100;
let items = vec!["x", "y"]; 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!({ let value = plist!({
"name": name, "name": name,
"count": count, "count": count,
"items": items "items": items,
"omit me":? none,
"keep me":? Some(123),
"replace me": 1,
:< to_merge,
:<? maybe_merge
}); });
if let plist::Value::Dictionary(dict) = value { if let plist::Value::Dictionary(dict) = value {
@@ -473,6 +688,25 @@ mod tests {
Some(&plist::Value::String("dynamic".to_string())) Some(&plist::Value::String("dynamic".to_string()))
); );
assert_eq!(dict.get("count"), Some(&plist::Value::Integer(100.into()))); 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 { } else {
panic!("Expected dictionary"); panic!("Expected dictionary");
} }

View File

@@ -8,7 +8,7 @@ use std::{future::Future, pin::Pin};
#[cfg(feature = "tcp")] #[cfg(feature = "tcp")]
use tokio::net::TcpStream; use tokio::net::TcpStream;
use crate::{pairing_file::PairingFile, Idevice, IdeviceError, ReadWrite}; use crate::{Idevice, IdeviceError, ReadWrite, pairing_file::PairingFile};
#[cfg(feature = "usbmuxd")] #[cfg(feature = "usbmuxd")]
use crate::usbmuxd::UsbmuxdAddr; use crate::usbmuxd::UsbmuxdAddr;
@@ -42,12 +42,11 @@ pub trait IdeviceProvider: Unpin + Send + Sync + std::fmt::Debug {
) -> Pin<Box<dyn Future<Output = Result<PairingFile, IdeviceError>> + Send>>; ) -> Pin<Box<dyn Future<Output = Result<PairingFile, IdeviceError>> + Send>>;
} }
pub trait RsdProvider<'a>: Unpin + Send + Sync + std::fmt::Debug { pub trait RsdProvider: Unpin + Send + Sync + std::fmt::Debug {
fn connect_to_service_port( fn connect_to_service_port(
&'a mut self, &mut self,
port: u16, port: u16,
) -> impl std::future::Future<Output = Result<Self::Stream, IdeviceError>> + Send; ) -> impl std::future::Future<Output = Result<Box<dyn ReadWrite>, IdeviceError>> + Send;
type Stream: ReadWrite;
} }
/// TCP-based device connection provider /// TCP-based device connection provider
@@ -159,13 +158,13 @@ impl IdeviceProvider for UsbmuxdProvider {
} }
#[cfg(feature = "tcp")] #[cfg(feature = "tcp")]
impl<'a> RsdProvider<'a> for std::net::IpAddr { impl RsdProvider for std::net::IpAddr {
async fn connect_to_service_port( async fn connect_to_service_port(
&'a mut self, &mut self,
port: u16, port: u16,
) -> Result<Self::Stream, IdeviceError> { ) -> Result<Box<dyn ReadWrite>, IdeviceError> {
Ok(tokio::net::TcpStream::connect((*self, port)).await?) Ok(Box::new(
tokio::net::TcpStream::connect((*self, port)).await?,
))
} }
type Stream = tokio::net::TcpStream;
} }

View File

@@ -66,7 +66,7 @@ impl std::fmt::Display for AfcError {
AfcError::NotEnoughData => "Not enough data", AfcError::NotEnoughData => "Not enough data",
AfcError::DirNotEmpty => "Directory not empty", AfcError::DirNotEmpty => "Directory not empty",
}; };
write!(f, "{}", description) write!(f, "{description}")
} }
} }

View File

@@ -11,7 +11,7 @@ use log::warn;
use opcode::{AfcFopenMode, AfcOpcode}; use opcode::{AfcFopenMode, AfcOpcode};
use packet::{AfcPacket, AfcPacketHeader}; use packet::{AfcPacket, AfcPacketHeader};
use crate::{lockdown::LockdownClient, obf, Idevice, IdeviceError, IdeviceService}; use crate::{Idevice, IdeviceError, IdeviceService, obf};
pub mod errors; pub mod errors;
pub mod file; pub mod file;
@@ -65,30 +65,7 @@ impl IdeviceService for AfcClient {
obf!("com.apple.afc") obf!("com.apple.afc")
} }
/// Connects to the AFC service on the device async fn from_stream(idevice: Idevice) -> Result<Self, IdeviceError> {
///
/// # Arguments
/// * `provider` - The iDevice provider to use for the connection
///
/// # Returns
/// A new `AfcClient` instance on success
async fn connect(
provider: &dyn crate::provider::IdeviceProvider,
) -> Result<Self, IdeviceError> {
let mut lockdown = LockdownClient::connect(provider).await?;
lockdown
.start_session(&provider.get_pairing_file().await?)
.await?;
let (port, ssl) = lockdown.start_service(Self::service_name()).await?;
let mut idevice = provider.connect(port).await?;
if ssl {
idevice
.start_session(&provider.get_pairing_file().await?)
.await?;
}
Ok(Self { Ok(Self {
idevice, idevice,
package_number: 0, package_number: 0,
@@ -399,11 +376,11 @@ impl AfcClient {
/// ///
/// # Returns /// # Returns
/// A `FileDescriptor` struct for the opened file /// A `FileDescriptor` struct for the opened file
pub async fn open( pub async fn open<'f>(
&mut self, &'f mut self,
path: impl Into<String>, path: impl Into<String>,
mode: AfcFopenMode, mode: AfcFopenMode,
) -> Result<FileDescriptor, IdeviceError> { ) -> Result<FileDescriptor<'f>, IdeviceError> {
let path = path.into(); let path = path.into();
let mut header_payload = (mode as u64).to_le_bytes().to_vec(); let mut header_payload = (mode as u64).to_le_bytes().to_vec();
header_payload.extend(path.as_bytes()); header_payload.extend(path.as_bytes());

View File

@@ -1,8 +1,6 @@
//! Abstraction for Apple Mobile File Integrity //! Abstraction for Apple Mobile File Integrity
use plist::Dictionary; use crate::{Idevice, IdeviceError, IdeviceService, obf};
use crate::{lockdown::LockdownClient, obf, Idevice, IdeviceError, IdeviceService};
/// Client for interacting with the AMFI service on the device /// Client for interacting with the AMFI service on the device
pub struct AmfiClient { pub struct AmfiClient {
@@ -16,41 +14,8 @@ impl IdeviceService for AmfiClient {
obf!("com.apple.amfi.lockdown") obf!("com.apple.amfi.lockdown")
} }
/// Establishes a connection to the amfi service async fn from_stream(idevice: Idevice) -> Result<Self, crate::IdeviceError> {
/// Ok(Self::new(idevice))
/// # Arguments
/// * `provider` - Device connection provider
///
/// # Returns
/// A connected `AmfiClient` instance
///
/// # Errors
/// Returns `IdeviceError` if any step of the connection process fails
///
/// # Process
/// 1. Connects to lockdownd service
/// 2. Starts a lockdown session
/// 3. Requests the amfi service port
/// 4. Establishes connection to the amfi port
/// 5. Optionally starts TLS if required by service
async fn connect(
provider: &dyn crate::provider::IdeviceProvider,
) -> Result<Self, IdeviceError> {
let mut lockdown = LockdownClient::connect(provider).await?;
lockdown
.start_session(&provider.get_pairing_file().await?)
.await?;
let (port, ssl) = lockdown.start_service(Self::service_name()).await?;
let mut idevice = provider.connect(port).await?;
if ssl {
idevice
.start_session(&provider.get_pairing_file().await?)
.await?;
}
Ok(Self { idevice })
} }
} }
@@ -66,11 +31,10 @@ impl AmfiClient {
/// Shows the developer mode option in settings in iOS 18+ /// Shows the developer mode option in settings in iOS 18+
/// Settings -> Privacy & Security -> Developer Mode /// Settings -> Privacy & Security -> Developer Mode
pub async fn reveal_developer_mode_option_in_ui(&mut self) -> Result<(), IdeviceError> { pub async fn reveal_developer_mode_option_in_ui(&mut self) -> Result<(), IdeviceError> {
let mut request = Dictionary::new(); let request = crate::plist!({
request.insert("action".into(), 0.into()); "action": 0,
self.idevice });
.send_plist(plist::Value::Dictionary(request)) self.idevice.send_plist(request).await?;
.await?;
let res = self.idevice.read_plist().await?; let res = self.idevice.read_plist().await?;
if res.get("success").is_some() { if res.get("success").is_some() {
@@ -82,11 +46,10 @@ impl AmfiClient {
/// Enables developer mode, triggering a reboot on iOS 18+ /// Enables developer mode, triggering a reboot on iOS 18+
pub async fn enable_developer_mode(&mut self) -> Result<(), IdeviceError> { pub async fn enable_developer_mode(&mut self) -> Result<(), IdeviceError> {
let mut request = Dictionary::new(); let request = crate::plist!({
request.insert("action".into(), 1.into()); "action": 1,
self.idevice });
.send_plist(plist::Value::Dictionary(request)) self.idevice.send_plist(request).await?;
.await?;
let res = self.idevice.read_plist().await?; let res = self.idevice.read_plist().await?;
if res.get("success").is_some() { if res.get("success").is_some() {
@@ -98,11 +61,10 @@ impl AmfiClient {
/// Shows the accept dialogue for enabling developer mode /// Shows the accept dialogue for enabling developer mode
pub async fn accept_developer_mode(&mut self) -> Result<(), IdeviceError> { pub async fn accept_developer_mode(&mut self) -> Result<(), IdeviceError> {
let mut request = Dictionary::new(); let request = crate::plist!({
request.insert("action".into(), 2.into()); "action": 2,
self.idevice });
.send_plist(plist::Value::Dictionary(request)) self.idevice.send_plist(request).await?;
.await?;
let res = self.idevice.read_plist().await?; let res = self.idevice.read_plist().await?;
if res.get("success").is_some() { if res.get("success").is_some() {
@@ -114,11 +76,10 @@ impl AmfiClient {
/// Gets the developer mode status /// Gets the developer mode status
pub async fn get_developer_mode_status(&mut self) -> Result<bool, IdeviceError> { pub async fn get_developer_mode_status(&mut self) -> Result<bool, IdeviceError> {
let mut request = Dictionary::new(); let request = crate::plist!({
request.insert("action".into(), 3.into()); "action": 3,
self.idevice });
.send_plist(plist::Value::Dictionary(request)) self.idevice.send_plist(request).await?;
.await?;
let res = self.idevice.read_plist().await?; let res = self.idevice.read_plist().await?;
match res.get("success").and_then(|x| x.as_boolean()) { match res.get("success").and_then(|x| x.as_boolean()) {
@@ -137,15 +98,12 @@ impl AmfiClient {
&mut self, &mut self,
uuid: impl Into<String>, uuid: impl Into<String>,
) -> Result<bool, IdeviceError> { ) -> Result<bool, IdeviceError> {
let mut request = Dictionary::new(); let request = crate::plist!({
request.insert("action".into(), 4.into()); "action": 4,
request.insert( "input_profile_uuid": uuid.into(),
"input_profile_uuid".into(), });
plist::Value::String(uuid.into()),
); self.idevice.send_plist(request).await?;
self.idevice
.send_plist(plist::Value::Dictionary(request))
.await?;
let res = self.idevice.read_plist().await?; let res = self.idevice.read_plist().await?;
match res.get("success").and_then(|x| x.as_boolean()) { match res.get("success").and_then(|x| x.as_boolean()) {

View File

@@ -0,0 +1,203 @@
//! Abstraction for BTPacketLogger
//! You must have the Bluetooth profile installed, or you'll get no data.
//! https://developer.apple.com/bug-reporting/profiles-and-logs/?name=bluetooth
use std::pin::Pin;
use futures::Stream;
use log::{debug, warn};
use crate::{Idevice, IdeviceError, IdeviceService, obf};
/// Client for interacting with the BTPacketLogger service on the device.
/// You must have the Bluetooth profile installed, or you'll get no data.
///
/// ``https://developer.apple.com/bug-reporting/profiles-and-logs/?name=bluetooth``
pub struct BtPacketLoggerClient {
/// The underlying device connection with established logger service
pub idevice: Idevice,
}
#[derive(Debug, Clone)]
pub struct BtFrame {
pub hdr: BtHeader,
pub kind: BtPacketKind,
/// H4-ready payload (first byte is H4 type: 0x01 cmd, 0x02 ACL, 0x03 SCO, 0x04 evt)
pub h4: Vec<u8>,
}
#[derive(Debug, Clone, Copy)]
pub struct BtHeader {
/// Advisory length for [kind + payload]; may not equal actual frame len - 12
pub length: u32, // BE on the wire
pub ts_secs: u32, // BE
pub ts_usecs: u32, // BE
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BtPacketKind {
HciCmd, // 0x00
HciEvt, // 0x01
AclSent, // 0x02
AclRecv, // 0x03
ScoSent, // 0x08
ScoRecv, // 0x09
Other(u8),
}
impl BtPacketKind {
fn from_byte(b: u8) -> Self {
match b {
0x00 => BtPacketKind::HciCmd,
0x01 => BtPacketKind::HciEvt,
0x02 => BtPacketKind::AclSent,
0x03 => BtPacketKind::AclRecv,
0x08 => BtPacketKind::ScoSent,
0x09 => BtPacketKind::ScoRecv,
x => BtPacketKind::Other(x),
}
}
fn h4_type(self) -> Option<u8> {
match self {
BtPacketKind::HciCmd => Some(0x01),
BtPacketKind::AclSent | BtPacketKind::AclRecv => Some(0x02),
BtPacketKind::ScoSent | BtPacketKind::ScoRecv => Some(0x03),
BtPacketKind::HciEvt => Some(0x04),
BtPacketKind::Other(_) => None,
}
}
}
impl IdeviceService for BtPacketLoggerClient {
fn service_name() -> std::borrow::Cow<'static, str> {
obf!("com.apple.bluetooth.BTPacketLogger")
}
async fn from_stream(idevice: Idevice) -> Result<Self, crate::IdeviceError> {
Ok(Self::new(idevice))
}
}
impl BtPacketLoggerClient {
pub fn new(idevice: Idevice) -> Self {
Self { idevice }
}
/// Read a single *outer* frame and return one parsed record from it.
/// (This service typically delivers one record per frame.)
pub async fn next_packet(
&mut self,
) -> Result<Option<(BtHeader, BtPacketKind, Vec<u8>)>, IdeviceError> {
// 2-byte outer length is **little-endian**
let len = self.idevice.read_raw(2).await?;
if len.len() != 2 {
return Ok(None); // EOF
}
let frame_len = u16::from_le_bytes([len[0], len[1]]) as usize;
if !(13..=64 * 1024).contains(&frame_len) {
return Err(IdeviceError::UnexpectedResponse);
}
let frame = self.idevice.read_raw(frame_len).await?;
if frame.len() != frame_len {
return Err(IdeviceError::NotEnoughBytes(frame.len(), frame_len));
}
// Parse header at fixed offsets (BE u32s)
let (hdr, off) = BtHeader::parse(&frame).ok_or(IdeviceError::UnexpectedResponse)?;
// packet_type at byte 12, payload starts at 13
let kind = BtPacketKind::from_byte(frame[off]);
let payload = &frame[off + 1..]; // whatever remains
// Optional soft check of advisory header.length
let advisory = hdr.length as usize;
let actual = 1 + payload.len(); // kind + payload
if advisory != actual {
debug!(
"BTPacketLogger advisory length {} != actual {}, proceeding",
advisory, actual
);
}
// Build H4 buffer (prepend type byte)
let mut h4 = Vec::with_capacity(1 + payload.len());
if let Some(t) = kind.h4_type() {
h4.push(t);
} else {
return Ok(None);
}
h4.extend_from_slice(payload);
Ok(Some((hdr, kind, h4)))
}
/// Continuous stream of parsed frames.
pub fn into_stream(
mut self,
) -> Pin<Box<dyn Stream<Item = Result<BtFrame, IdeviceError>> + Send>> {
Box::pin(async_stream::try_stream! {
loop {
// outer length (LE)
let len = self.idevice.read_raw(2).await?;
if len.len() != 2 { break; }
let frame_len = u16::from_le_bytes([len[0], len[1]]) as usize;
if !(13..=64 * 1024).contains(&frame_len) {
warn!("invalid frame_len {}", frame_len);
continue;
}
// frame bytes
let frame = self.idevice.read_raw(frame_len).await?;
if frame.len() != frame_len {
Err(IdeviceError::NotEnoughBytes(frame.len(), frame_len))?;
}
// header + kind + payload
let (hdr, off) = BtHeader::parse(&frame).ok_or(IdeviceError::UnexpectedResponse)?;
let kind = BtPacketKind::from_byte(frame[off]);
let payload = &frame[off + 1..];
// soft advisory check
let advisory = hdr.length as usize;
let actual = 1 + payload.len();
if advisory != actual {
debug!("BTPacketLogger advisory length {} != actual {}", advisory, actual);
}
// make H4 buffer
let mut h4 = Vec::with_capacity(1 + payload.len());
if let Some(t) = kind.h4_type() {
h4.push(t);
} else {
// unknown kind
continue;
}
h4.extend_from_slice(payload);
yield BtFrame { hdr, kind, h4 };
}
})
}
}
impl BtHeader {
/// Parse 12-byte header at the start of a frame.
/// Returns (header, next_offset) where next_offset == 12 (start of packet_type).
fn parse(buf: &[u8]) -> Option<(Self, usize)> {
if buf.len() < 12 {
return None;
}
let length = u32::from_be_bytes(buf[0..4].try_into().ok()?);
let ts_secs = u32::from_be_bytes(buf[4..8].try_into().ok()?);
let ts_usecs = u32::from_be_bytes(buf[8..12].try_into().ok()?);
Some((
BtHeader {
length,
ts_secs,
ts_usecs,
},
12,
))
}
}

View File

@@ -0,0 +1,150 @@
//! Companion Proxy is Apple's bridge to connect to the Apple Watch
use log::warn;
use crate::{Idevice, IdeviceError, IdeviceService, RsdService, obf};
pub struct CompanionProxy {
idevice: Idevice,
}
pub struct CompanionProxyStream {
proxy: CompanionProxy,
}
impl IdeviceService for CompanionProxy {
fn service_name() -> std::borrow::Cow<'static, str> {
obf!("com.apple.companion_proxy")
}
async fn from_stream(idevice: Idevice) -> Result<Self, crate::IdeviceError> {
Ok(Self::new(idevice))
}
}
impl RsdService for CompanionProxy {
fn rsd_service_name() -> std::borrow::Cow<'static, str> {
obf!("com.apple.companion_proxy.shim.remote")
}
async fn from_stream(stream: Box<dyn crate::ReadWrite>) -> Result<Self, crate::IdeviceError> {
let mut idevice = Idevice::new(stream, "");
idevice.rsd_checkin().await?;
Ok(Self::new(idevice))
}
}
impl CompanionProxy {
pub fn new(idevice: Idevice) -> Self {
Self { idevice }
}
pub async fn get_device_registry(&mut self) -> Result<Vec<String>, IdeviceError> {
let command = crate::plist!({
"Command": "GetDeviceRegistry"
});
self.idevice.send_plist(command).await?;
let res = self.idevice.read_plist().await?;
let list = match res.get("PairedDevicesArray").and_then(|x| x.as_array()) {
Some(l) => l,
None => {
warn!("Didn't get PairedDevicesArray array");
return Err(IdeviceError::UnexpectedResponse);
}
};
let mut res = Vec::new();
for l in list {
if let plist::Value::String(l) = l {
res.push(l.to_owned());
}
}
Ok(res)
}
pub async fn listen_for_devices(mut self) -> Result<CompanionProxyStream, IdeviceError> {
let command = crate::plist!({
"Command": "StartListeningForDevices"
});
self.idevice.send_plist(command).await?;
Ok(CompanionProxyStream { proxy: self })
}
pub async fn get_value(
&mut self,
udid: impl Into<String>,
key: impl Into<String>,
) -> Result<plist::Value, IdeviceError> {
let udid = udid.into();
let key = key.into();
let command = crate::plist!({
"Command": "GetValueFromRegistry",
"GetValueGizmoUDIDKey": udid,
"GetValueKeyKey": key.clone()
});
self.idevice.send_plist(command).await?;
let mut res = self.idevice.read_plist().await?;
if let Some(v) = res
.remove("RetrievedValueDictionary")
.and_then(|x| x.into_dictionary())
.and_then(|mut x| x.remove(&key))
{
Ok(v)
} else {
Err(IdeviceError::NotFound)
}
}
pub async fn start_forwarding_service_port(
&mut self,
port: u16,
service_name: Option<&str>,
options: Option<plist::Dictionary>,
) -> Result<u16, IdeviceError> {
let command = crate::plist!({
"Command": "StartForwardingServicePort",
"GizmoRemotePortNumber": port,
"IsServiceLowPriority": false,
"PreferWifi": false,
"ForwardedServiceName":? service_name,
:<? options,
});
self.idevice.send_plist(command).await?;
let res = self.idevice.read_plist().await?;
if let Some(p) = res
.get("CompanionProxyServicePort")
.and_then(|x| x.as_unsigned_integer())
{
Ok(p as u16)
} else {
Err(IdeviceError::UnexpectedResponse)
}
}
pub async fn stop_forwarding_service_port(&mut self, port: u16) -> Result<(), IdeviceError> {
let command = crate::plist!({
"Command": "StopForwardingServicePort",
"GizmoRemotePortNumber": port
});
self.idevice.send_plist(command).await?;
let res = self.idevice.read_plist().await?;
if let Some(c) = res.get("Command").and_then(|x| x.as_string())
&& (c == "ComandSuccess" || c == "CommandSuccess")
// Apple you spelled this wrong, adding the right spelling just in case you fix it smh
{
Ok(())
} else {
Err(IdeviceError::UnexpectedResponse)
}
}
}
impl CompanionProxyStream {
pub async fn next(&mut self) -> Result<plist::Dictionary, IdeviceError> {
self.proxy.idevice.read_plist().await
}
}

View File

@@ -3,22 +3,20 @@
use log::warn; use log::warn;
use serde::Deserialize; use serde::Deserialize;
use crate::{obf, IdeviceError, ReadWrite, RsdService}; use crate::{IdeviceError, ReadWrite, RsdService, obf, xpc::XPCObject};
use super::CoreDeviceServiceClient; use super::CoreDeviceServiceClient;
impl<R: ReadWrite> RsdService for AppServiceClient<R> { impl RsdService for AppServiceClient<Box<dyn ReadWrite>> {
fn rsd_service_name() -> std::borrow::Cow<'static, str> { fn rsd_service_name() -> std::borrow::Cow<'static, str> {
obf!("com.apple.coredevice.appservice") obf!("com.apple.coredevice.appservice")
} }
async fn from_stream(stream: R) -> Result<Self, IdeviceError> { async fn from_stream(stream: Box<dyn ReadWrite>) -> Result<Self, IdeviceError> {
Ok(Self { Ok(Self {
inner: CoreDeviceServiceClient::new(stream).await?, inner: CoreDeviceServiceClient::new(stream).await?,
}) })
} }
type Stream = R;
} }
pub struct AppServiceClient<R: ReadWrite> { pub struct AppServiceClient<R: ReadWrite> {
@@ -130,19 +128,13 @@ pub struct IconUuid {
pub classes: Vec<String>, pub classes: Vec<String>,
} }
impl<'a, R: ReadWrite + 'a> AppServiceClient<R> { impl<R: ReadWrite> AppServiceClient<R> {
pub async fn new(stream: R) -> Result<Self, IdeviceError> { pub async fn new(stream: R) -> Result<Self, IdeviceError> {
Ok(Self { Ok(Self {
inner: CoreDeviceServiceClient::new(stream).await?, inner: CoreDeviceServiceClient::new(stream).await?,
}) })
} }
pub fn box_inner(self) -> AppServiceClient<Box<dyn ReadWrite + 'a>> {
AppServiceClient {
inner: self.inner.box_inner(),
}
}
pub async fn list_apps( pub async fn list_apps(
&mut self, &mut self,
app_clips: bool, app_clips: bool,
@@ -151,15 +143,16 @@ impl<'a, R: ReadWrite + 'a> AppServiceClient<R> {
internal_apps: bool, internal_apps: bool,
default_apps: bool, default_apps: bool,
) -> Result<Vec<AppListEntry>, IdeviceError> { ) -> Result<Vec<AppListEntry>, IdeviceError> {
let mut options = plist::Dictionary::new(); let options = crate::plist!(dict {
options.insert("includeAppClips".into(), app_clips.into()); "includeAppClips": app_clips,
options.insert("includeRemovableApps".into(), removable_apps.into()); "includeRemovableApps": removable_apps,
options.insert("includeHiddenApps".into(), hidden_apps.into()); "includeHiddenApps": hidden_apps,
options.insert("includeInternalApps".into(), internal_apps.into()); "includeInternalApps": internal_apps,
options.insert("includeDefaultApps".into(), default_apps.into()); "includeDefaultApps": default_apps,
});
let res = self let res = self
.inner .inner
.invoke("com.apple.coredevice.feature.listapps", Some(options)) .invoke_with_plist("com.apple.coredevice.feature.listapps", options)
.await?; .await?;
let res = match res.as_array() { let res = match res.as_array() {
@@ -185,6 +178,16 @@ impl<'a, R: ReadWrite + 'a> AppServiceClient<R> {
Ok(desd) Ok(desd)
} }
/// Launches an application by a bundle ID.
///
/// # Notes
/// * `start_suspended` - If set to true, you will need to attach a debugger using
/// `DebugServer` to continue.
///
/// * `stdio_uuid` - Create a new ``OpenStdioSocketClient``, read the UUID, and pass it to this
/// function. Note that if the process already has another stdio UUID, this parameter is ignored by
/// iOS. Either make sure the proccess isn't running, or pass ``kill_existing: true``
#[allow(clippy::too_many_arguments)] // still didn't ask
pub async fn launch_application( pub async fn launch_application(
&mut self, &mut self,
bundle_id: impl Into<String>, bundle_id: impl Into<String>,
@@ -193,6 +196,7 @@ impl<'a, R: ReadWrite + 'a> AppServiceClient<R> {
start_suspended: bool, start_suspended: bool,
environment: Option<plist::Dictionary>, environment: Option<plist::Dictionary>,
platform_options: Option<plist::Dictionary>, platform_options: Option<plist::Dictionary>,
stdio_uuid: Option<uuid::Uuid>,
) -> Result<LaunchResponse, IdeviceError> { ) -> Result<LaunchResponse, IdeviceError> {
let bundle_id = bundle_id.into(); let bundle_id = bundle_id.into();
@@ -203,20 +207,34 @@ impl<'a, R: ReadWrite + 'a> AppServiceClient<R> {
} }
}, },
"options": { "options": {
"arguments": arguments, // Now this will work directly "arguments": arguments,
"environmentVariables": environment.unwrap_or_default(), "environmentVariables": environment.unwrap_or_default(),
"standardIOUsesPseudoterminals": true, "standardIOUsesPseudoterminals": true,
"startStopped": start_suspended, "startStopped": start_suspended,
"terminateExisting": kill_existing, "terminateExisting": kill_existing,
"user": { "user": {
"shortName": "mobile" "active": true,
}, },
"platformSpecificOptions": plist::Value::Data(crate::util::plist_to_xml_bytes(&platform_options.unwrap_or_default())), "platformSpecificOptions": plist::Value::Data(crate::util::plist_to_xml_bytes(&platform_options.unwrap_or_default())),
}, },
"standardIOIdentifiers": {} });
})
.into_dictionary() let req: XPCObject = req.into();
.unwrap(); let mut req = req.to_dictionary().unwrap();
req.insert(
"standardIOIdentifiers".into(),
match stdio_uuid {
Some(u) => {
let u = XPCObject::Uuid(u);
let mut d = crate::xpc::Dictionary::new();
d.insert("standardInput".into(), u.clone());
d.insert("standardOutput".into(), u.clone());
d.insert("standardError".into(), u.clone());
d.into()
}
None => crate::xpc::Dictionary::new().into(),
},
);
let res = self let res = self
.inner .inner
@@ -266,13 +284,11 @@ impl<'a, R: ReadWrite + 'a> AppServiceClient<R> {
) -> Result<(), IdeviceError> { ) -> Result<(), IdeviceError> {
let bundle_id = bundle_id.into(); let bundle_id = bundle_id.into();
self.inner self.inner
.invoke( .invoke_with_plist(
"com.apple.coredevice.feature.uninstallapp", "com.apple.coredevice.feature.uninstallapp",
Some(
crate::plist!({"bundleIdentifier": bundle_id}) crate::plist!({"bundleIdentifier": bundle_id})
.into_dictionary() .into_dictionary()
.unwrap(), .unwrap(),
),
) )
.await?; .await?;
@@ -286,16 +302,14 @@ impl<'a, R: ReadWrite + 'a> AppServiceClient<R> {
) -> Result<SignalResponse, IdeviceError> { ) -> Result<SignalResponse, IdeviceError> {
let res = self let res = self
.inner .inner
.invoke( .invoke_with_plist(
"com.apple.coredevice.feature.sendsignaltoprocess", "com.apple.coredevice.feature.sendsignaltoprocess",
Some(
crate::plist!({ crate::plist!({
"process": { "processIdentifier": pid as i64}, "process": { "processIdentifier": pid as i64},
"signal": signal as i64, "signal": signal as i64,
}) })
.into_dictionary() .into_dictionary()
.unwrap(), .unwrap(),
),
) )
.await?; .await?;
@@ -321,9 +335,8 @@ impl<'a, R: ReadWrite + 'a> AppServiceClient<R> {
let bundle_id = bundle_id.into(); let bundle_id = bundle_id.into();
let res = self let res = self
.inner .inner
.invoke( .invoke_with_plist(
"com.apple.coredevice.feature.fetchappicons", "com.apple.coredevice.feature.fetchappicons",
Some(
crate::plist!({ crate::plist!({
"width": width, "width": width,
"height": height, "height": height,
@@ -333,7 +346,6 @@ impl<'a, R: ReadWrite + 'a> AppServiceClient<R> {
}) })
.into_dictionary() .into_dictionary()
.unwrap(), .unwrap(),
),
) )
.await?; .await?;

View File

@@ -0,0 +1,72 @@
// Jackson Coxson
use std::pin::Pin;
use futures::Stream;
use log::warn;
use crate::{IdeviceError, ReadWrite, RsdService, obf};
impl RsdService for DiagnostisServiceClient<Box<dyn ReadWrite>> {
fn rsd_service_name() -> std::borrow::Cow<'static, str> {
obf!("com.apple.coredevice.diagnosticsservice")
}
async fn from_stream(stream: Box<dyn ReadWrite>) -> Result<Self, IdeviceError> {
Ok(Self {
inner: super::CoreDeviceServiceClient::new(stream).await?,
})
}
}
pub struct DiagnostisServiceClient<R: ReadWrite> {
inner: super::CoreDeviceServiceClient<R>,
}
pub struct SysdiagnoseResponse<'a> {
pub preferred_filename: String,
pub stream: Pin<Box<dyn Stream<Item = Result<Vec<u8>, IdeviceError>> + 'a>>,
pub expected_length: usize,
}
impl<R: ReadWrite> DiagnostisServiceClient<R> {
pub async fn capture_sysdiagnose<'a>(
&'a mut self,
dry_run: bool,
) -> Result<SysdiagnoseResponse<'a>, IdeviceError> {
let req = crate::plist!({
"options": {
"collectFullLogs": true
},
"isDryRun": dry_run
})
.into_dictionary()
.unwrap();
let res = self
.inner
.invoke_with_plist("com.apple.coredevice.feature.capturesysdiagnose", req)
.await?;
if let Some(len) = res
.as_dictionary()
.and_then(|x| x.get("fileTransfer"))
.and_then(|x| x.as_dictionary())
.and_then(|x| x.get("expectedLength"))
.and_then(|x| x.as_unsigned_integer())
&& let Some(name) = res
.as_dictionary()
.and_then(|x| x.get("preferredFilename"))
.and_then(|x| x.as_string())
{
Ok(SysdiagnoseResponse {
stream: Box::pin(self.inner.inner.iter_file_chunks(len as usize, 0)),
preferred_filename: name.to_string(),
expected_length: len as usize,
})
} else {
warn!("Did not get expected responses from RemoteXPC");
Err(IdeviceError::UnexpectedResponse)
}
}
}

View File

@@ -4,12 +4,16 @@
use log::warn; use log::warn;
use crate::{ use crate::{
xpc::{self, XPCObject},
IdeviceError, ReadWrite, RemoteXpcClient, IdeviceError, ReadWrite, RemoteXpcClient,
xpc::{self, XPCObject},
}; };
mod app_service; mod app_service;
mod diagnosticsservice;
mod openstdiosocket;
pub use app_service::*; pub use app_service::*;
pub use diagnosticsservice::*;
pub use openstdiosocket::*;
const CORE_SERVICE_VERSION: &str = "443.18"; const CORE_SERVICE_VERSION: &str = "443.18";
@@ -17,26 +21,33 @@ pub struct CoreDeviceServiceClient<R: ReadWrite> {
inner: RemoteXpcClient<R>, inner: RemoteXpcClient<R>,
} }
impl<'a, R: ReadWrite + 'a> CoreDeviceServiceClient<R> { impl<R: ReadWrite> CoreDeviceServiceClient<R> {
pub async fn new(inner: R) -> Result<Self, IdeviceError> { pub async fn new(inner: R) -> Result<Self, IdeviceError> {
let mut client = RemoteXpcClient::new(inner).await?; let mut client = RemoteXpcClient::new(inner).await?;
client.do_handshake().await?; client.do_handshake().await?;
Ok(Self { inner: client }) Ok(Self { inner: client })
} }
pub fn box_inner(self) -> CoreDeviceServiceClient<Box<dyn ReadWrite + 'a>> { pub async fn invoke_with_plist(
CoreDeviceServiceClient { &mut self,
inner: self.inner.box_inner(), feature: impl Into<String>,
} input: plist::Dictionary,
) -> Result<plist::Value, IdeviceError> {
let input: XPCObject = plist::Value::Dictionary(input).into();
let input = input.to_dictionary().unwrap();
self.invoke(feature, Some(input)).await
} }
pub async fn invoke( pub async fn invoke(
&mut self, &mut self,
feature: impl Into<String>, feature: impl Into<String>,
input: Option<plist::Dictionary>, input: Option<crate::xpc::Dictionary>,
) -> Result<plist::Value, IdeviceError> { ) -> Result<plist::Value, IdeviceError> {
let feature = feature.into(); let feature = feature.into();
let input = input.unwrap_or_default(); let input: crate::xpc::XPCObject = match input {
Some(i) => i.into(),
None => crate::xpc::Dictionary::new().into(),
};
let mut req = xpc::Dictionary::new(); let mut req = xpc::Dictionary::new();
req.insert( req.insert(
@@ -56,10 +67,7 @@ impl<'a, R: ReadWrite + 'a> CoreDeviceServiceClient<R> {
"CoreDevice.featureIdentifier".into(), "CoreDevice.featureIdentifier".into(),
XPCObject::String(feature), XPCObject::String(feature),
); );
req.insert( req.insert("CoreDevice.input".into(), input);
"CoreDevice.input".into(),
plist::Value::Dictionary(input).into(),
);
req.insert( req.insert(
"CoreDevice.invocationIdentifier".into(), "CoreDevice.invocationIdentifier".into(),
XPCObject::String(uuid::Uuid::new_v4().to_string()), XPCObject::String(uuid::Uuid::new_v4().to_string()),

View File

@@ -0,0 +1,33 @@
// Jackson Coxson
use tokio::io::AsyncReadExt;
use crate::{IdeviceError, ReadWrite, RsdService, obf};
impl RsdService for OpenStdioSocketClient {
fn rsd_service_name() -> std::borrow::Cow<'static, str> {
obf!("com.apple.coredevice.openstdiosocket")
}
async fn from_stream(stream: Box<dyn ReadWrite>) -> Result<Self, IdeviceError> {
Ok(Self { inner: stream })
}
}
/// 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
pub struct OpenStdioSocketClient {
pub inner: Box<dyn ReadWrite>,
}
impl OpenStdioSocketClient {
/// iOS assigns a UUID to a newly opened stream. That UUID is then passed to the launch
/// parameters of app service to start a stream.
pub async fn read_uuid(&mut self) -> Result<uuid::Uuid, IdeviceError> {
let mut buf = [0u8; 16];
self.inner.read_exact(&mut buf).await?;
let res = uuid::Uuid::from_bytes(buf);
Ok(res)
}
}

View File

@@ -13,7 +13,7 @@
//! # Features //! # Features
//! - `tunnel_tcp_stack`: Enables software TCP/IP tunnel creation using a virtual adapter. See the tcp moduel. //! - `tunnel_tcp_stack`: Enables software TCP/IP tunnel creation using a virtual adapter. See the tcp moduel.
use crate::{lockdown::LockdownClient, obf, Idevice, IdeviceError, IdeviceService}; use crate::{Idevice, IdeviceError, IdeviceService, obf};
use byteorder::{BigEndian, WriteBytesExt}; use byteorder::{BigEndian, WriteBytesExt};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@@ -97,34 +97,7 @@ impl IdeviceService for CoreDeviceProxy {
obf!("com.apple.internal.devicecompute.CoreDeviceProxy") obf!("com.apple.internal.devicecompute.CoreDeviceProxy")
} }
/// Connects to the CoreDeviceProxy service async fn from_stream(idevice: Idevice) -> Result<Self, crate::IdeviceError> {
///
/// # Arguments
///
/// * `provider` - An implementation of `IdeviceProvider` that supplies
/// pairing data and socket connections.
///
/// # Returns
///
/// * `Ok(CoreDeviceProxy)` if connection and handshake succeed.
/// * `Err(IdeviceError)` if any step fails.
async fn connect(
provider: &dyn crate::provider::IdeviceProvider,
) -> Result<Self, IdeviceError> {
let mut lockdown = LockdownClient::connect(provider).await?;
lockdown
.start_session(&provider.get_pairing_file().await?)
.await?;
let (port, ssl) = lockdown.start_service(Self::service_name()).await?;
let mut idevice = provider.connect(port).await?;
if ssl {
idevice
.start_session(&provider.get_pairing_file().await?)
.await?;
}
Self::new(idevice).await Self::new(idevice).await
} }
} }

View File

@@ -9,7 +9,7 @@
use log::{debug, warn}; use log::{debug, warn};
use crate::{afc::AfcClient, lockdown::LockdownClient, obf, Idevice, IdeviceError, IdeviceService}; use crate::{Idevice, IdeviceError, IdeviceService, afc::AfcClient, lockdown::LockdownClient, obf};
/// Client for managing crash logs on an iOS device. /// Client for managing crash logs on an iOS device.
/// ///
@@ -26,43 +26,8 @@ impl IdeviceService for CrashReportCopyMobileClient {
obf!("com.apple.crashreportcopymobile") obf!("com.apple.crashreportcopymobile")
} }
/// Connects to the CrashReportCopyMobile service on the device. async fn from_stream(idevice: Idevice) -> Result<Self, crate::IdeviceError> {
/// Ok(Self::new(idevice))
/// # Arguments
/// * `provider` - The provider used to access the device and pairing info.
///
/// # Returns
/// A connected `CrashReportCopyMobileClient`.
///
/// # Errors
/// Returns `IdeviceError` if the connection fails at any stage.
///
/// # Process
/// 1. Connects to the lockdownd service.
/// 2. Starts a lockdown session.
/// 3. Requests the CrashReportCopyMobile service.
/// 4. Establishes a connection to the service.
/// 5. Performs SSL handshake if required.
async fn connect(
provider: &dyn crate::provider::IdeviceProvider,
) -> Result<Self, IdeviceError> {
let mut lockdown = LockdownClient::connect(provider).await?;
lockdown
.start_session(&provider.get_pairing_file().await?)
.await?;
let (port, ssl) = lockdown.start_service(Self::service_name()).await?;
let mut idevice = provider.connect(port).await?;
if ssl {
idevice
.start_session(&provider.get_pairing_file().await?)
.await?;
}
Ok(Self {
afc_client: AfcClient::new(idevice),
})
} }
} }

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