mirror of
https://github.com/jkcoxson/idevice.git
synced 2026-03-02 14:36:16 +01:00
Merge branch 'master' into rppairing
a
This commit is contained in:
241
.github/workflows/ci.yml
vendored
Normal file
241
.github/workflows/ci.yml
vendored
Normal 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
6
.gitignore
vendored
@@ -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
3
.gitmodules
vendored
@@ -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
1269
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -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"
|
||||||
|
|||||||
@@ -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 Apple’s TSS servers. Partial support.|
|
| `tss` | Make requests to Apple's TSS servers. Partial support.|
|
||||||
| `tunneld` | Interface with [pymobiledevice3](https://github.com/doronz88/pymobiledevice3)’s tunneld. |
|
| `tunneld` | Interface with [pymobiledevice3](https://github.com/doronz88/pymobiledevice3)'s tunneld. |
|
||||||
| `usbmuxd` | Connect using the usbmuxd daemon.|
|
| `usbmuxd` | Connect using the usbmuxd daemon.|
|
||||||
| `xpc` | Access protected services via XPC over RSD. |
|
| `xpc` | Access protected services via XPC over RSD. |
|
||||||
|
|
||||||
|
|||||||
32
cpp/.clang-format
Normal file
32
cpp/.clang-format
Normal 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
3
cpp/.clangd
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
CompileFlags:
|
||||||
|
Add: ["-I/usr/local/include/"]
|
||||||
|
CompilationDatabase: "examples/build"
|
||||||
119
cpp/CMakeLists.txt
Normal file
119
cpp/CMakeLists.txt
Normal 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
107
cpp/examples/CMakeLists.txt
Normal 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()
|
||||||
|
|
||||||
211
cpp/examples/app_service.cpp
Normal file
211
cpp/examples/app_service.cpp
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
128
cpp/examples/debug_proxy.cpp
Normal file
128
cpp/examples/debug_proxy.cpp
Normal 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;
|
||||||
|
}
|
||||||
127
cpp/examples/diagnosticsservice.cpp
Normal file
127
cpp/examples/diagnosticsservice.cpp
Normal 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;
|
||||||
|
}
|
||||||
23
cpp/examples/idevice_id.cpp
Normal file
23
cpp/examples/idevice_id.cpp
Normal 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";
|
||||||
|
}
|
||||||
|
}
|
||||||
58
cpp/examples/ideviceinfo.cpp
Normal file
58
cpp/examples/ideviceinfo.cpp
Normal 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;
|
||||||
|
});
|
||||||
|
}
|
||||||
114
cpp/examples/location_simulation.cpp
Normal file
114
cpp/examples/location_simulation.cpp
Normal 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));
|
||||||
|
}
|
||||||
|
}
|
||||||
521
cpp/idevice++.xcodeproj/project.pbxproj
Normal file
521
cpp/idevice++.xcodeproj/project.pbxproj
Normal 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 */;
|
||||||
|
}
|
||||||
7
cpp/idevice++.xcodeproj/project.xcworkspace/contents.xcworkspacedata
generated
Normal file
7
cpp/idevice++.xcodeproj/project.xcworkspace/contents.xcworkspacedata
generated
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Workspace
|
||||||
|
version = "1.0">
|
||||||
|
<FileRef
|
||||||
|
location = "self:">
|
||||||
|
</FileRef>
|
||||||
|
</Workspace>
|
||||||
@@ -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>
|
||||||
51
cpp/include/idevice++/adapter_stream.hpp
Normal file
51
cpp/include/idevice++/adapter_stream.hpp
Normal 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
|
||||||
108
cpp/include/idevice++/app_service.hpp
Normal file
108
cpp/include/idevice++/app_service.hpp
Normal 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
|
||||||
8
cpp/include/idevice++/bindings.hpp
Normal file
8
cpp/include/idevice++/bindings.hpp
Normal 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
|
||||||
104
cpp/include/idevice++/core_device_proxy.hpp
Normal file
104
cpp/include/idevice++/core_device_proxy.hpp
Normal 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
|
||||||
120
cpp/include/idevice++/debug_proxy.hpp
Normal file
120
cpp/include/idevice++/debug_proxy.hpp
Normal 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
|
||||||
109
cpp/include/idevice++/diagnosticsservice.hpp
Normal file
109
cpp/include/idevice++/diagnosticsservice.hpp
Normal 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
|
||||||
24
cpp/include/idevice++/ffi.hpp
Normal file
24
cpp/include/idevice++/ffi.hpp
Normal 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
|
||||||
63
cpp/include/idevice++/idevice.hpp
Normal file
63
cpp/include/idevice++/idevice.hpp
Normal 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
|
||||||
38
cpp/include/idevice++/location_simulation.hpp
Normal file
38
cpp/include/idevice++/location_simulation.hpp
Normal 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
|
||||||
43
cpp/include/idevice++/lockdown.hpp
Normal file
43
cpp/include/idevice++/lockdown.hpp
Normal 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
|
||||||
184
cpp/include/idevice++/option.hpp
Normal file
184
cpp/include/idevice++/option.hpp
Normal 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
|
||||||
45
cpp/include/idevice++/pairing_file.hpp
Normal file
45
cpp/include/idevice++/pairing_file.hpp
Normal 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
|
||||||
49
cpp/include/idevice++/provider.hpp
Normal file
49
cpp/include/idevice++/provider.hpp
Normal 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
|
||||||
45
cpp/include/idevice++/readwrite.hpp
Normal file
45
cpp/include/idevice++/readwrite.hpp
Normal 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
|
||||||
42
cpp/include/idevice++/remote_server.hpp
Normal file
42
cpp/include/idevice++/remote_server.hpp
Normal 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
|
||||||
306
cpp/include/idevice++/result.hpp
Normal file
306
cpp/include/idevice++/result.hpp
Normal 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
|
||||||
57
cpp/include/idevice++/rsd.hpp
Normal file
57
cpp/include/idevice++/rsd.hpp
Normal 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
|
||||||
176
cpp/include/idevice++/tcp_object_stack.hpp
Normal file
176
cpp/include/idevice++/tcp_object_stack.hpp
Normal 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 there’s a single point of truth to release from
|
||||||
|
std::unique_ptr<Impl> impl_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace IdeviceFFI
|
||||||
124
cpp/include/idevice++/usbmuxd.hpp
Normal file
124
cpp/include/idevice++/usbmuxd.hpp
Normal 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
1
cpp/plist_ffi
Submodule
Submodule cpp/plist_ffi added at 66e6e6362b
37
cpp/sln/idevice++.sln
Normal file
37
cpp/sln/idevice++.sln
Normal 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
354
cpp/sln/idevice++.vcxproj
Normal 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>
|
||||||
129
cpp/sln/idevice++.vcxproj.filters
Normal file
129
cpp/sln/idevice++.vcxproj.filters
Normal 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>
|
||||||
50
cpp/src/adapter_stream.cpp
Normal file
50
cpp/src/adapter_stream.cpp
Normal 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
181
cpp/src/app_service.cpp
Normal 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
120
cpp/src/core_device.cpp
Normal 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
143
cpp/src/debug_proxy.cpp
Normal 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
|
||||||
93
cpp/src/diagnosticsservice.cpp
Normal file
93
cpp/src/diagnosticsservice.cpp
Normal 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
32
cpp/src/ffi.cpp
Normal 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
53
cpp/src/idevice.cpp
Normal 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
|
||||||
32
cpp/src/location_simulation.cpp
Normal file
32
cpp/src/location_simulation.cpp
Normal 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
57
cpp/src/lockdown.cpp
Normal 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
53
cpp/src/pairing_file.cpp
Normal 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
58
cpp/src/provider.cpp
Normal 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
29
cpp/src/remote_server.cpp
Normal 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
151
cpp/src/rsd.cpp
Normal 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
|
||||||
118
cpp/src/tcp_callback_feeder.cpp
Normal file
118
cpp/src/tcp_callback_feeder.cpp
Normal 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
155
cpp/src/usbmuxd.cpp
Normal 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
65
cpp/vs_build_rust.bat
Normal 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
123
cpp/xcode_build_rust.sh
Executable 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."
|
||||||
@@ -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"]
|
||||||
|
|||||||
43
ffi/build.rs
43
ffi/build.rs
@@ -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");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
|
||||||
find_library( LIBPLIST ${PLIST_LIBRARIES} PATH ${PLIST_LIBDIR} )
|
|
||||||
target_link_libraries ( ${EXAMPLE_NAME} PUBLIC ${LIBPLIST} )
|
|
||||||
endif()
|
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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,16 +90,17 @@ 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> =
|
||||||
let provider_ref = unsafe { &mut (*provider).0 };
|
RUNTIME.block_on(async move {
|
||||||
let handshake_ref = unsafe { &mut (*handshake).0 };
|
let provider_ref = unsafe { &mut (*provider).0 };
|
||||||
|
let handshake_ref = unsafe { &mut (*handshake).0 };
|
||||||
|
|
||||||
AppServiceClient::connect_rsd(provider_ref, handshake_ref).await
|
AppServiceClient::connect_rsd(provider_ref, handshake_ref).await
|
||||||
});
|
});
|
||||||
|
|
||||||
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,14 +331,21 @@ 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 {
|
||||||
client
|
client
|
||||||
@@ -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
|
||||||
});
|
});
|
||||||
|
|||||||
215
ffi/src/core_device/diagnosticsservice.rs
Normal file
215
ffi/src/core_device/diagnosticsservice.rs
Normal 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) };
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
// Jackson Coxson
|
// Jackson Coxson
|
||||||
|
|
||||||
pub mod app_service;
|
pub mod app_service;
|
||||||
|
pub mod diagnosticsservice;
|
||||||
|
|||||||
@@ -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()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,13 +135,14 @@ 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> =
|
||||||
let provider_ref = unsafe { &mut (*provider).0 };
|
RUNTIME.block_on(async move {
|
||||||
let handshake_ref = unsafe { &mut (*handshake).0 };
|
let provider_ref = unsafe { &mut (*provider).0 };
|
||||||
|
let handshake_ref = unsafe { &mut (*handshake).0 };
|
||||||
|
|
||||||
// Connect using the reference
|
// Connect using the reference
|
||||||
DebugProxyClient::connect_rsd(provider_ref, handshake_ref).await
|
DebugProxyClient::connect_rsd(provider_ref, handshake_ref).await
|
||||||
});
|
});
|
||||||
|
|
||||||
match res {
|
match res {
|
||||||
Ok(d) => {
|
Ok(d) => {
|
||||||
@@ -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)) };
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -105,10 +105,10 @@ 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()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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 };
|
||||||
|
|||||||
@@ -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
215
ffi/src/tcp_object_stack.rs
Normal 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) };
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
|||||||
228
ffi/src/util.rs
228
ffi/src/util.rs
@@ -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);
|
|
||||||
let ip = std::net::Ipv6Addr::from(addr_in6.sin6_addr.s6_addr);
|
|
||||||
let port = u16::from_be(addr_in6.sin6_port);
|
|
||||||
std::net::SocketAddr::V6(std::net::SocketAddrV6::new(
|
|
||||||
ip,
|
|
||||||
port,
|
|
||||||
addr_in6.sin6_flowinfo,
|
|
||||||
addr_in6.sin6_scope_id,
|
|
||||||
))
|
|
||||||
} else {
|
|
||||||
log::error!("Invalid sockaddr_in6 size");
|
log::error!("Invalid sockaddr_in6 size");
|
||||||
return Err(IdeviceError::FfiInvalidArg);
|
return invalid_arg();
|
||||||
}
|
}
|
||||||
}
|
let a = &*(addr as *const sockaddr_in6);
|
||||||
_ => {
|
let ip = Ipv6Addr::from(a.sin6_addr.s6_addr);
|
||||||
log::error!("Unsupported socket address family: {}", (*addr).sa_family);
|
let port = u16::from_be(a.sin6_port);
|
||||||
return Err(IdeviceError::FfiInvalidArg);
|
Ok(SocketAddr::V6(std::net::SocketAddrV6::new(
|
||||||
}
|
ip,
|
||||||
}
|
port,
|
||||||
})
|
a.sin6_flowinfo,
|
||||||
}
|
a.sin6_scope_id,
|
||||||
|
|
||||||
pub(crate) fn c_addr_to_rust(addr: *const libc::sockaddr) -> Result<IpAddr, IdeviceError> {
|
|
||||||
unsafe {
|
|
||||||
// Check the address family
|
|
||||||
match (*addr).sa_family as c_int {
|
|
||||||
libc::AF_INET => {
|
|
||||||
// Convert sockaddr_in (IPv4) to IpAddr
|
|
||||||
let sockaddr_in = addr as *const sockaddr_in;
|
|
||||||
let ip = (*sockaddr_in).sin_addr.s_addr;
|
|
||||||
let octets = u32::from_be(ip).to_be_bytes();
|
|
||||||
Ok(IpAddr::V4(Ipv4Addr::new(
|
|
||||||
octets[0], octets[1], octets[2], octets[3],
|
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
libc::AF_INET6 => {
|
_ => {
|
||||||
// Convert sockaddr_in6 (IPv6) to IpAddr
|
log::error!(
|
||||||
let sockaddr_in6 = addr as *const sockaddr_in6;
|
"Unsupported socket address family: {}",
|
||||||
let ip = (*sockaddr_in6).sin6_addr.s6_addr;
|
(*addr).sa_family as i32
|
||||||
Ok(IpAddr::V6(Ipv6Addr::from(ip)))
|
);
|
||||||
|
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);
|
||||||
Err(IdeviceError::FfiInvalidArg)
|
invalid_arg()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn plist_to_libplist(v: &Value) -> *mut libc::c_void {
|
pub(crate) fn c_addr_to_rust(addr: *const SockAddr) -> Result<IpAddr, IdeviceError> {
|
||||||
let buf = Vec::new();
|
if addr.is_null() {
|
||||||
let mut writer = std::io::BufWriter::new(buf);
|
log::error!("null sockaddr");
|
||||||
plist::to_writer_xml(&mut writer, v).unwrap();
|
return invalid_arg();
|
||||||
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 {
|
unsafe {
|
||||||
let v: plist_plus::Plist = v.into();
|
#[cfg(unix)]
|
||||||
let v_string = v.to_string();
|
let family = (*addr).sa_family as i32;
|
||||||
v.false_drop();
|
#[cfg(windows)]
|
||||||
|
let family = (*addr).sa_family;
|
||||||
|
|
||||||
let reader = std::io::Cursor::new(v_string.as_bytes());
|
#[cfg(unix)]
|
||||||
plist::from_reader(reader).unwrap()
|
match family {
|
||||||
|
libc::AF_INET => {
|
||||||
|
let a = &*(addr as *const sockaddr_in);
|
||||||
|
let octets = u32::from_be(a.sin_addr.s_addr).to_be_bytes();
|
||||||
|
Ok(IpAddr::V4(Ipv4Addr::new(
|
||||||
|
octets[0], octets[1], octets[2], octets[3],
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
libc::AF_INET6 => {
|
||||||
|
let a = &*(addr as *const sockaddr_in6);
|
||||||
|
Ok(IpAddr::V6(Ipv6Addr::from(a.sin6_addr.s6_addr)))
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
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);
|
||||||
|
invalid_arg()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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)]
|
||||||
|
|||||||
@@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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());
|
||||||
|
|||||||
@@ -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()) {
|
||||||
|
|||||||
203
idevice/src/services/bt_packet_logger.rs
Normal file
203
idevice/src/services/bt_packet_logger.rs
Normal 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,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
150
idevice/src/services/companion_proxy.rs
Normal file
150
idevice/src/services/companion_proxy.rs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,19 +335,17 @@ 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,
|
"scale": scale,
|
||||||
"scale": scale,
|
"allowPlaceholder": allow_placeholder,
|
||||||
"allowPlaceholder": allow_placeholder,
|
"bundleIdentifier": bundle_id
|
||||||
"bundleIdentifier": bundle_id
|
})
|
||||||
})
|
.into_dictionary()
|
||||||
.into_dictionary()
|
.unwrap(),
|
||||||
.unwrap(),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
|||||||
72
idevice/src/services/core_device/diagnosticsservice.rs
Normal file
72
idevice/src/services/core_device/diagnosticsservice.rs
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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()),
|
||||||
|
|||||||
33
idevice/src/services/core_device/openstdiosocket.rs
Normal file
33
idevice/src/services/core_device/openstdiosocket.rs
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
Reference in New Issue
Block a user