mirror of
https://github.com/jkcoxson/idevice.git
synced 2026-03-02 06:26:15 +01:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c752ee92c5 | ||
|
|
76d847664b | ||
|
|
1f7924b773 | ||
|
|
b459eebe9d | ||
|
|
c246362f54 | ||
|
|
bfe44e16e4 | ||
|
|
54439b85dd |
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -35,6 +35,8 @@ jobs:
|
|||||||
rustup target add aarch64-apple-ios-sim && \
|
rustup target add aarch64-apple-ios-sim && \
|
||||||
rustup target add aarch64-apple-darwin && \
|
rustup target add aarch64-apple-darwin && \
|
||||||
rustup target add x86_64-apple-darwin && \
|
rustup target add x86_64-apple-darwin && \
|
||||||
|
rustup target add aarch64-apple-ios-macabi && \
|
||||||
|
rustup target add x86_64-apple-ios-macabi && \
|
||||||
cargo install --force --locked bindgen-cli
|
cargo install --force --locked bindgen-cli
|
||||||
|
|
||||||
- name: Build all Apple targets and examples/tools
|
- name: Build all Apple targets and examples/tools
|
||||||
|
|||||||
28
README.md
28
README.md
@@ -47,6 +47,8 @@ To keep dependency bloat and compile time down, everything is contained in featu
|
|||||||
|------------------------|-----------------------------------------------------------------------------|
|
|------------------------|-----------------------------------------------------------------------------|
|
||||||
| `afc` | Apple File Conduit for file system access.|
|
| `afc` | Apple File Conduit for file system access.|
|
||||||
| `amfi` | Apple mobile file integrity service |
|
| `amfi` | Apple mobile file integrity service |
|
||||||
|
| `bt_packet_logger` | Capture Bluetooth packets. |
|
||||||
|
| `companion_proxy` | Manage paired Apple Watches. |
|
||||||
| `core_device_proxy` | Start a secure tunnel to access protected services. |
|
| `core_device_proxy` | Start a secure tunnel to access protected services. |
|
||||||
| `crashreportcopymobile`| Copy crash reports.|
|
| `crashreportcopymobile`| Copy crash reports.|
|
||||||
| `debug_proxy` | Send GDB commands to the device.|
|
| `debug_proxy` | Send GDB commands to the device.|
|
||||||
@@ -55,34 +57,36 @@ To keep dependency bloat and compile time down, everything is contained in featu
|
|||||||
| `heartbeat` | Maintain a heartbeat connection.|
|
| `heartbeat` | Maintain a heartbeat connection.|
|
||||||
| `house_arrest` | Manage files in app containers |
|
| `house_arrest` | Manage files in app containers |
|
||||||
| `installation_proxy` | Manage app installation and uninstallation.|
|
| `installation_proxy` | Manage app installation and uninstallation.|
|
||||||
| `springboardservices` | Control SpringBoard (e.g. UI interactions). Partial support.|
|
| `installcoordination_proxy` | Manage app installation coordination.|
|
||||||
| `misagent` | Manage provisioning profiles on the device.|
|
|
||||||
| `mobilebackup2` | Manage backups.|
|
|
||||||
| `mobile_image_mounter` | Manage DDI images.|
|
|
||||||
| `location_simulation` | Simulate GPS locations on the device.|
|
| `location_simulation` | Simulate GPS locations on the device.|
|
||||||
|
| `misagent` | Manage provisioning profiles on the device.|
|
||||||
|
| `mobile_image_mounter` | Manage DDI images.|
|
||||||
|
| `mobileactivationd` | Activate/Deactivate device.|
|
||||||
|
| `mobilebackup2` | Manage backups.|
|
||||||
| `pair` | Pair the device.|
|
| `pair` | Pair the device.|
|
||||||
| `syslog_relay` | Relay system logs from the device |
|
| `pcapd` | Capture network packets.|
|
||||||
|
| `preboard_service` | Interface with Preboard.|
|
||||||
|
| `restore_service` | Restore service (recovery/reboot).|
|
||||||
|
| `screenshotr` | Take screenshots.|
|
||||||
|
| `springboardservices` | Control SpringBoard (icons, wallpaper, orientation, etc.).|
|
||||||
|
| `syslog_relay` | Relay system logs and OS trace logs from the device. |
|
||||||
| `tcp` | Connect to devices over TCP.|
|
| `tcp` | Connect to devices over TCP.|
|
||||||
| `tunnel_tcp_stack` | Naive in-process TCP stack for `core_device_proxy`.|
|
| `tunnel_tcp_stack` | Naive in-process TCP stack for `core_device_proxy`.|
|
||||||
| `tss` | Make requests to Apple's TSS servers. Partial support.|
|
| `tss` | Make requests to Apple's TSS servers. Partial support.|
|
||||||
| `tunneld` | Interface with [pymobiledevice3](https://github.com/doronz88/pymobiledevice3)'s tunneld. |
|
| `tunneld` | Interface with [pymobiledevice3](https://github.com/doronz88/pymobiledevice3)'s tunneld. |
|
||||||
| `usbmuxd` | Connect using the usbmuxd daemon.|
|
| `usbmuxd` | Connect using the usbmuxd daemon.|
|
||||||
| `xpc` | Access protected services via XPC over RSD. |
|
| `xpc` | Access protected services via XPC over RSD. |
|
||||||
|
| `notification_proxy` | Post and observe iOS notifications. |
|
||||||
|
|
||||||
### Planned/TODO
|
### Planned/TODO
|
||||||
|
|
||||||
Finish the following:
|
Finish the following:
|
||||||
|
|
||||||
- springboard
|
- webinspector
|
||||||
|
|
||||||
Implement the following:
|
Implement the following:
|
||||||
|
|
||||||
- companion_proxy
|
- file_relay
|
||||||
- diagnostics
|
|
||||||
- mobilebackup2
|
|
||||||
- notification_proxy
|
|
||||||
- screenshot
|
|
||||||
- webinspector
|
|
||||||
|
|
||||||
As this project is done in my free time within my busy schedule, there
|
As this project is done in my free time within my busy schedule, there
|
||||||
is no ETA for any of these. Feel free to contribute or donate!
|
is no ETA for any of these. Feel free to contribute or donate!
|
||||||
|
|||||||
50
cpp/include/idevice++/crashreportcopymobile.hpp
Normal file
50
cpp/include/idevice++/crashreportcopymobile.hpp
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
// Jackson Coxson
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <idevice++/bindings.hpp>
|
||||||
|
#include <idevice++/ffi.hpp>
|
||||||
|
#include <idevice++/provider.hpp>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace IdeviceFFI {
|
||||||
|
|
||||||
|
using CrashReportCopyMobilePtr =
|
||||||
|
std::unique_ptr<CrashReportCopyMobileHandle,
|
||||||
|
FnDeleter<CrashReportCopyMobileHandle, crash_report_client_free>>;
|
||||||
|
|
||||||
|
class CrashReportCopyMobile {
|
||||||
|
public:
|
||||||
|
// Factory: connect via Provider
|
||||||
|
static Result<CrashReportCopyMobile, FfiError> connect(Provider& provider);
|
||||||
|
|
||||||
|
// Factory: wrap an existing Idevice socket (consumes it on success)
|
||||||
|
static Result<CrashReportCopyMobile, FfiError> from_socket(Idevice&& socket);
|
||||||
|
|
||||||
|
// Static: flush crash reports from system storage
|
||||||
|
static Result<void, FfiError> flush(Provider& provider);
|
||||||
|
|
||||||
|
// Ops
|
||||||
|
Result<std::vector<std::string>, FfiError> ls(const char* dir_path = nullptr);
|
||||||
|
Result<std::vector<char>, FfiError> pull(const std::string& log_name);
|
||||||
|
Result<void, FfiError> remove(const std::string& log_name);
|
||||||
|
|
||||||
|
// RAII / moves
|
||||||
|
~CrashReportCopyMobile() noexcept = default;
|
||||||
|
CrashReportCopyMobile(CrashReportCopyMobile&&) noexcept = default;
|
||||||
|
CrashReportCopyMobile& operator=(CrashReportCopyMobile&&) noexcept = default;
|
||||||
|
CrashReportCopyMobile(const CrashReportCopyMobile&) = delete;
|
||||||
|
CrashReportCopyMobile& operator=(const CrashReportCopyMobile&) = delete;
|
||||||
|
|
||||||
|
CrashReportCopyMobileHandle* raw() const noexcept { return handle_.get(); }
|
||||||
|
static CrashReportCopyMobile adopt(CrashReportCopyMobileHandle* h) noexcept {
|
||||||
|
return CrashReportCopyMobile(h);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
explicit CrashReportCopyMobile(CrashReportCopyMobileHandle* h) noexcept : handle_(h) {}
|
||||||
|
CrashReportCopyMobilePtr handle_{};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace IdeviceFFI
|
||||||
46
cpp/include/idevice++/notification_proxy.hpp
Normal file
46
cpp/include/idevice++/notification_proxy.hpp
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <idevice++/bindings.hpp>
|
||||||
|
#include <idevice++/ffi.hpp>
|
||||||
|
#include <idevice++/provider.hpp>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace IdeviceFFI {
|
||||||
|
|
||||||
|
using NotificationProxyPtr = std::unique_ptr<NotificationProxyClientHandle,
|
||||||
|
FnDeleter<NotificationProxyClientHandle, notification_proxy_client_free>>;
|
||||||
|
|
||||||
|
class NotificationProxy {
|
||||||
|
public:
|
||||||
|
// Factory: connect via Provider
|
||||||
|
static Result<NotificationProxy, FfiError> connect(Provider& provider);
|
||||||
|
|
||||||
|
// Factory: wrap an existing Idevice socket (consumes it on success)
|
||||||
|
static Result<NotificationProxy, FfiError> from_socket(Idevice&& socket);
|
||||||
|
|
||||||
|
// Ops
|
||||||
|
Result<void, FfiError> post_notification(const std::string& name);
|
||||||
|
Result<void, FfiError> observe_notification(const std::string& name);
|
||||||
|
Result<void, FfiError> observe_notifications(const std::vector<std::string>& names);
|
||||||
|
Result<std::string, FfiError> receive_notification();
|
||||||
|
Result<std::string, FfiError> receive_notification_with_timeout(u_int64_t interval);
|
||||||
|
|
||||||
|
// RAII / moves
|
||||||
|
~NotificationProxy() noexcept = default;
|
||||||
|
NotificationProxy(NotificationProxy&&) noexcept = default;
|
||||||
|
NotificationProxy& operator=(NotificationProxy&&) noexcept = default;
|
||||||
|
NotificationProxy(const NotificationProxy&) = delete;
|
||||||
|
NotificationProxy& operator=(const NotificationProxy&) = delete;
|
||||||
|
|
||||||
|
NotificationProxyClientHandle* raw() const noexcept { return handle_.get(); }
|
||||||
|
static NotificationProxy adopt(NotificationProxyClientHandle* h) noexcept {
|
||||||
|
return NotificationProxy(h);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
explicit NotificationProxy(NotificationProxyClientHandle* h) noexcept : handle_(h) {}
|
||||||
|
NotificationProxyPtr handle_{};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace IdeviceFFI
|
||||||
94
cpp/src/crashreportcopymobile.cpp
Normal file
94
cpp/src/crashreportcopymobile.cpp
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
// Jackson Coxson
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
#include <idevice++/bindings.hpp>
|
||||||
|
#include <idevice++/crashreportcopymobile.hpp>
|
||||||
|
#include <idevice++/ffi.hpp>
|
||||||
|
#include <idevice++/provider.hpp>
|
||||||
|
|
||||||
|
namespace IdeviceFFI {
|
||||||
|
|
||||||
|
// -------- Factory Methods --------
|
||||||
|
|
||||||
|
Result<CrashReportCopyMobile, FfiError> CrashReportCopyMobile::connect(Provider& provider) {
|
||||||
|
CrashReportCopyMobileHandle* out = nullptr;
|
||||||
|
FfiError e(::crash_report_client_connect(provider.raw(), &out));
|
||||||
|
if (e) {
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
return Ok(CrashReportCopyMobile::adopt(out));
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<CrashReportCopyMobile, FfiError> CrashReportCopyMobile::from_socket(Idevice&& socket) {
|
||||||
|
CrashReportCopyMobileHandle* out = nullptr;
|
||||||
|
FfiError e(::crash_report_client_new(socket.raw(), &out));
|
||||||
|
if (e) {
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
socket.release();
|
||||||
|
return Ok(CrashReportCopyMobile::adopt(out));
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<void, FfiError> CrashReportCopyMobile::flush(Provider& provider) {
|
||||||
|
FfiError e(::crash_report_flush(provider.raw()));
|
||||||
|
if (e) {
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------- Ops --------
|
||||||
|
|
||||||
|
Result<std::vector<std::string>, FfiError>
|
||||||
|
CrashReportCopyMobile::ls(const char* dir_path) {
|
||||||
|
char** entries_raw = nullptr;
|
||||||
|
size_t count = 0;
|
||||||
|
|
||||||
|
FfiError e(::crash_report_client_ls(handle_.get(), dir_path, &entries_raw, &count));
|
||||||
|
if (e) {
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> result;
|
||||||
|
if (entries_raw) {
|
||||||
|
result.reserve(count);
|
||||||
|
for (size_t i = 0; i < count; ++i) {
|
||||||
|
if (entries_raw[i]) {
|
||||||
|
result.emplace_back(entries_raw[i]);
|
||||||
|
::idevice_string_free(entries_raw[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::free(entries_raw);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(std::move(result));
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<std::vector<char>, FfiError>
|
||||||
|
CrashReportCopyMobile::pull(const std::string& log_name) {
|
||||||
|
uint8_t* data = nullptr;
|
||||||
|
size_t length = 0;
|
||||||
|
|
||||||
|
FfiError e(::crash_report_client_pull(handle_.get(), log_name.c_str(), &data, &length));
|
||||||
|
if (e) {
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<char> result;
|
||||||
|
if (data && length > 0) {
|
||||||
|
result.assign(reinterpret_cast<char*>(data), reinterpret_cast<char*>(data) + length);
|
||||||
|
::idevice_data_free(data, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(std::move(result));
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<void, FfiError> CrashReportCopyMobile::remove(const std::string& log_name) {
|
||||||
|
FfiError e(::crash_report_client_remove(handle_.get(), log_name.c_str()));
|
||||||
|
if (e) {
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace IdeviceFFI
|
||||||
82
cpp/src/notification_proxy.cpp
Normal file
82
cpp/src/notification_proxy.cpp
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
// Jackson Coxson
|
||||||
|
|
||||||
|
#include <idevice++/bindings.hpp>
|
||||||
|
#include <idevice++/ffi.hpp>
|
||||||
|
#include <idevice++/notification_proxy.hpp>
|
||||||
|
#include <idevice++/provider.hpp>
|
||||||
|
|
||||||
|
namespace IdeviceFFI {
|
||||||
|
|
||||||
|
Result<NotificationProxy, FfiError> NotificationProxy::connect(Provider& provider) {
|
||||||
|
NotificationProxyClientHandle* out = nullptr;
|
||||||
|
FfiError e(::notification_proxy_connect(provider.raw(), &out));
|
||||||
|
if (e) {
|
||||||
|
provider.release();
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
return Ok(NotificationProxy::adopt(out));
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<NotificationProxy, FfiError> NotificationProxy::from_socket(Idevice&& socket) {
|
||||||
|
NotificationProxyClientHandle* out = nullptr;
|
||||||
|
FfiError e(::notification_proxy_new(socket.raw(), &out));
|
||||||
|
if (e) {
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
socket.release();
|
||||||
|
return Ok(NotificationProxy::adopt(out));
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<void, FfiError> NotificationProxy::post_notification(const std::string& name) {
|
||||||
|
FfiError e(::notification_proxy_post(handle_.get(), name.c_str()));
|
||||||
|
if (e) {
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<void, FfiError> NotificationProxy::observe_notification(const std::string& name) {
|
||||||
|
FfiError e(::notification_proxy_observe(handle_.get(), name.c_str()));
|
||||||
|
if (e) {
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<void, FfiError> NotificationProxy::observe_notifications(const std::vector<std::string>& names) {
|
||||||
|
std::vector<const char*> ptrs;
|
||||||
|
ptrs.reserve(names.size() + 1);
|
||||||
|
for (const auto& n : names) {
|
||||||
|
ptrs.push_back(n.c_str());
|
||||||
|
}
|
||||||
|
ptrs.push_back(nullptr);
|
||||||
|
FfiError e(::notification_proxy_observe_multiple(handle_.get(), ptrs.data()));
|
||||||
|
if (e) {
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<std::string, FfiError> NotificationProxy::receive_notification() {
|
||||||
|
char* name_ptr = nullptr;
|
||||||
|
FfiError e(::notification_proxy_receive(handle_.get(), &name_ptr));
|
||||||
|
if (e) {
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
std::string name(name_ptr);
|
||||||
|
::notification_proxy_free_string(name_ptr);
|
||||||
|
return Ok(std::move(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<std::string, FfiError> NotificationProxy::receive_notification_with_timeout(u_int64_t interval) {
|
||||||
|
char* name_ptr = nullptr;
|
||||||
|
FfiError e(::notification_proxy_receive_with_timeout(handle_.get(), interval, &name_ptr));
|
||||||
|
if (e) {
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
std::string name(name_ptr);
|
||||||
|
::notification_proxy_free_string(name_ptr);
|
||||||
|
return Ok(std::move(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace IdeviceFFI
|
||||||
@@ -34,6 +34,7 @@ debug_proxy = ["idevice/debug_proxy"]
|
|||||||
diagnostics_relay = ["idevice/diagnostics_relay"]
|
diagnostics_relay = ["idevice/diagnostics_relay"]
|
||||||
dvt = ["idevice/dvt"]
|
dvt = ["idevice/dvt"]
|
||||||
heartbeat = ["idevice/heartbeat"]
|
heartbeat = ["idevice/heartbeat"]
|
||||||
|
notification_proxy = ["idevice/notification_proxy"]
|
||||||
house_arrest = ["idevice/house_arrest"]
|
house_arrest = ["idevice/house_arrest"]
|
||||||
installation_proxy = ["idevice/installation_proxy"]
|
installation_proxy = ["idevice/installation_proxy"]
|
||||||
springboardservices = ["idevice/springboardservices"]
|
springboardservices = ["idevice/springboardservices"]
|
||||||
@@ -61,6 +62,7 @@ full = [
|
|||||||
"diagnostics_relay",
|
"diagnostics_relay",
|
||||||
"dvt",
|
"dvt",
|
||||||
"heartbeat",
|
"heartbeat",
|
||||||
|
"notification_proxy",
|
||||||
"house_arrest",
|
"house_arrest",
|
||||||
"installation_proxy",
|
"installation_proxy",
|
||||||
"misagent",
|
"misagent",
|
||||||
|
|||||||
325
ffi/src/crashreportcopymobile.rs
Normal file
325
ffi/src/crashreportcopymobile.rs
Normal file
@@ -0,0 +1,325 @@
|
|||||||
|
// Jackson Coxson
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
ffi::{CStr, c_char},
|
||||||
|
ptr::null_mut,
|
||||||
|
};
|
||||||
|
|
||||||
|
use idevice::{
|
||||||
|
IdeviceError, IdeviceService,
|
||||||
|
provider::IdeviceProvider,
|
||||||
|
services::crashreportcopymobile::{CrashReportCopyMobileClient, flush_reports},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
IdeviceFfiError, IdeviceHandle, afc::AfcClientHandle, ffi_err, provider::IdeviceProviderHandle,
|
||||||
|
run_sync_local,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct CrashReportCopyMobileHandle(pub CrashReportCopyMobileClient);
|
||||||
|
|
||||||
|
/// Automatically creates and connects to the crash report copy mobile service,
|
||||||
|
/// returning a client handle
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * [`provider`] - An IdeviceProvider
|
||||||
|
/// * [`client`] - On success, will be set to point to a newly allocated handle
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// An IdeviceFfiError on error, null on success
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// `provider` must be a valid pointer to a handle allocated by this library
|
||||||
|
/// `client` must be a valid, non-null pointer to a location where the handle will be stored
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub unsafe extern "C" fn crash_report_client_connect(
|
||||||
|
provider: *mut IdeviceProviderHandle,
|
||||||
|
client: *mut *mut CrashReportCopyMobileHandle,
|
||||||
|
) -> *mut IdeviceFfiError {
|
||||||
|
if provider.is_null() || client.is_null() {
|
||||||
|
tracing::error!("Null pointer provided");
|
||||||
|
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||||
|
}
|
||||||
|
|
||||||
|
let res: Result<CrashReportCopyMobileClient, IdeviceError> = run_sync_local(async move {
|
||||||
|
let provider_ref: &dyn IdeviceProvider = unsafe { &*(*provider).0 };
|
||||||
|
CrashReportCopyMobileClient::connect(provider_ref).await
|
||||||
|
});
|
||||||
|
|
||||||
|
match res {
|
||||||
|
Ok(r) => {
|
||||||
|
let boxed = Box::new(CrashReportCopyMobileHandle(r));
|
||||||
|
unsafe { *client = Box::into_raw(boxed) };
|
||||||
|
null_mut()
|
||||||
|
}
|
||||||
|
Err(e) => ffi_err!(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new CrashReportCopyMobile client from an existing Idevice connection
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * [`socket`] - An IdeviceSocket handle
|
||||||
|
/// * [`client`] - On success, will be set to point to a newly allocated handle
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// An IdeviceFfiError on error, null on success
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// `socket` must be a valid pointer to a handle allocated by this library
|
||||||
|
/// `client` must be a valid, non-null pointer to a location where the handle will be stored
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub unsafe extern "C" fn crash_report_client_new(
|
||||||
|
socket: *mut IdeviceHandle,
|
||||||
|
client: *mut *mut CrashReportCopyMobileHandle,
|
||||||
|
) -> *mut IdeviceFfiError {
|
||||||
|
if socket.is_null() || client.is_null() {
|
||||||
|
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||||
|
}
|
||||||
|
let socket = unsafe { Box::from_raw(socket) }.0;
|
||||||
|
let r = CrashReportCopyMobileClient::new(socket);
|
||||||
|
let boxed = Box::new(CrashReportCopyMobileHandle(r));
|
||||||
|
unsafe { *client = Box::into_raw(boxed) };
|
||||||
|
null_mut()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Lists crash report files in the specified directory
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * [`client`] - A valid CrashReportCopyMobile handle
|
||||||
|
/// * [`dir_path`] - Optional directory path (NULL for root "/")
|
||||||
|
/// * [`entries`] - Will be set to point to an array of C strings
|
||||||
|
/// * [`count`] - Will be set to the number of entries
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// An IdeviceFfiError on error, null on success
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// All pointers must be valid and non-null
|
||||||
|
/// `dir_path` may be NULL (defaults to root)
|
||||||
|
/// Caller must free the returned array with `afc_free_directory_entries`
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub unsafe extern "C" fn crash_report_client_ls(
|
||||||
|
client: *mut CrashReportCopyMobileHandle,
|
||||||
|
dir_path: *const c_char,
|
||||||
|
entries: *mut *mut *mut c_char,
|
||||||
|
count: *mut libc::size_t,
|
||||||
|
) -> *mut IdeviceFfiError {
|
||||||
|
if client.is_null() || entries.is_null() || count.is_null() {
|
||||||
|
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||||
|
}
|
||||||
|
|
||||||
|
let path = if dir_path.is_null() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
match unsafe { CStr::from_ptr(dir_path) }.to_str() {
|
||||||
|
Ok(s) => Some(s),
|
||||||
|
Err(_) => return ffi_err!(IdeviceError::FfiInvalidString),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let res: Result<Vec<String>, IdeviceError> = run_sync_local(async {
|
||||||
|
let client_ref = unsafe { &mut (*client).0 };
|
||||||
|
client_ref.ls(path).await
|
||||||
|
});
|
||||||
|
|
||||||
|
match res {
|
||||||
|
Ok(items) => {
|
||||||
|
let c_strings = items
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|s| std::ffi::CString::new(s).ok())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let string_count = c_strings.len();
|
||||||
|
|
||||||
|
// Allocate array for char pointers (with NULL terminator)
|
||||||
|
let layout = std::alloc::Layout::array::<*mut c_char>(string_count + 1).unwrap();
|
||||||
|
let ptr = unsafe { std::alloc::alloc(layout) as *mut *mut c_char };
|
||||||
|
if ptr.is_null() {
|
||||||
|
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i, cstring) in c_strings.into_iter().enumerate() {
|
||||||
|
let string_ptr = cstring.into_raw();
|
||||||
|
unsafe { *ptr.add(i) = string_ptr };
|
||||||
|
}
|
||||||
|
|
||||||
|
// NULL terminator
|
||||||
|
unsafe { *ptr.add(string_count) = std::ptr::null_mut() };
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
*entries = ptr;
|
||||||
|
*count = string_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
null_mut()
|
||||||
|
}
|
||||||
|
Err(e) => ffi_err!(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Downloads a crash report file from the device
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * [`client`] - A valid CrashReportCopyMobile handle
|
||||||
|
/// * [`log_name`] - Name of the log file to download (C string)
|
||||||
|
/// * [`data`] - Will be set to point to the file contents
|
||||||
|
/// * [`length`] - Will be set to the size of the data
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// An IdeviceFfiError on error, null on success
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// All pointers must be valid and non-null
|
||||||
|
/// `log_name` must be a valid C string
|
||||||
|
/// Caller must free the returned data with `idevice_data_free`
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub unsafe extern "C" fn crash_report_client_pull(
|
||||||
|
client: *mut CrashReportCopyMobileHandle,
|
||||||
|
log_name: *const c_char,
|
||||||
|
data: *mut *mut u8,
|
||||||
|
length: *mut libc::size_t,
|
||||||
|
) -> *mut IdeviceFfiError {
|
||||||
|
if client.is_null() || log_name.is_null() || data.is_null() || length.is_null() {
|
||||||
|
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||||
|
}
|
||||||
|
|
||||||
|
let name = match unsafe { CStr::from_ptr(log_name) }.to_str() {
|
||||||
|
Ok(s) => s.to_string(),
|
||||||
|
Err(_) => return ffi_err!(IdeviceError::FfiInvalidString),
|
||||||
|
};
|
||||||
|
|
||||||
|
let res: Result<Vec<u8>, IdeviceError> = run_sync_local(async {
|
||||||
|
let client_ref = unsafe { &mut (*client).0 };
|
||||||
|
client_ref.pull(name).await
|
||||||
|
});
|
||||||
|
|
||||||
|
match res {
|
||||||
|
Ok(file_data) => {
|
||||||
|
let len = file_data.len();
|
||||||
|
let mut boxed = file_data.into_boxed_slice();
|
||||||
|
unsafe {
|
||||||
|
*data = boxed.as_mut_ptr();
|
||||||
|
*length = len;
|
||||||
|
}
|
||||||
|
std::mem::forget(boxed);
|
||||||
|
null_mut()
|
||||||
|
}
|
||||||
|
Err(e) => ffi_err!(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes a crash report file from the device
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * [`client`] - A valid CrashReportCopyMobile handle
|
||||||
|
/// * [`log_name`] - Name of the log file to remove (C string)
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// An IdeviceFfiError on error, null on success
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// `client` must be a valid pointer to a handle allocated by this library
|
||||||
|
/// `log_name` must be a valid C string
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub unsafe extern "C" fn crash_report_client_remove(
|
||||||
|
client: *mut CrashReportCopyMobileHandle,
|
||||||
|
log_name: *const c_char,
|
||||||
|
) -> *mut IdeviceFfiError {
|
||||||
|
if client.is_null() || log_name.is_null() {
|
||||||
|
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||||
|
}
|
||||||
|
|
||||||
|
let name = match unsafe { CStr::from_ptr(log_name) }.to_str() {
|
||||||
|
Ok(s) => s.to_string(),
|
||||||
|
Err(_) => return ffi_err!(IdeviceError::FfiInvalidString),
|
||||||
|
};
|
||||||
|
|
||||||
|
let res = run_sync_local(async {
|
||||||
|
let client_ref = unsafe { &mut (*client).0 };
|
||||||
|
client_ref.remove(name).await
|
||||||
|
});
|
||||||
|
|
||||||
|
match res {
|
||||||
|
Ok(_) => null_mut(),
|
||||||
|
Err(e) => ffi_err!(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts this client to an AFC client for advanced file operations
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * [`client`] - A valid CrashReportCopyMobile handle (will be consumed)
|
||||||
|
/// * [`afc_client`] - On success, will be set to an AFC client handle
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// An IdeviceFfiError on error, null on success
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// `client` must be a valid pointer (will be freed after this call)
|
||||||
|
/// `afc_client` must be a valid, non-null pointer where the new AFC client will be stored
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub unsafe extern "C" fn crash_report_client_to_afc(
|
||||||
|
client: *mut CrashReportCopyMobileHandle,
|
||||||
|
afc_client: *mut *mut AfcClientHandle,
|
||||||
|
) -> *mut IdeviceFfiError {
|
||||||
|
if client.is_null() || afc_client.is_null() {
|
||||||
|
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||||
|
}
|
||||||
|
|
||||||
|
let crash_client = unsafe { Box::from_raw(client) }.0;
|
||||||
|
let afc = crash_client.to_afc_client();
|
||||||
|
|
||||||
|
let a = Box::into_raw(Box::new(AfcClientHandle(afc)));
|
||||||
|
unsafe { *afc_client = a };
|
||||||
|
|
||||||
|
null_mut()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Triggers a flush of crash logs from system storage
|
||||||
|
///
|
||||||
|
/// This connects to the crashreportmover service to move crash logs
|
||||||
|
/// into the AFC-accessible directory. Should be called before listing logs.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * [`provider`] - An IdeviceProvider
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// An IdeviceFfiError on error, null on success
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// `provider` must be a valid pointer to a handle allocated by this library
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub unsafe extern "C" fn crash_report_flush(
|
||||||
|
provider: *mut IdeviceProviderHandle,
|
||||||
|
) -> *mut IdeviceFfiError {
|
||||||
|
if provider.is_null() {
|
||||||
|
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||||
|
}
|
||||||
|
|
||||||
|
let res = run_sync_local(async {
|
||||||
|
let provider_ref: &dyn IdeviceProvider = unsafe { &*(*provider).0 };
|
||||||
|
flush_reports(provider_ref).await
|
||||||
|
});
|
||||||
|
|
||||||
|
match res {
|
||||||
|
Ok(_) => null_mut(),
|
||||||
|
Err(e) => ffi_err!(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Frees a CrashReportCopyMobile client handle
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * [`handle`] - The handle to free
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// `handle` must be a valid pointer to the handle that was allocated by this library,
|
||||||
|
/// or NULL (in which case this function does nothing)
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub unsafe extern "C" fn crash_report_client_free(handle: *mut CrashReportCopyMobileHandle) {
|
||||||
|
if !handle.is_null() {
|
||||||
|
tracing::debug!("Freeing crash_report_client");
|
||||||
|
let _ = unsafe { Box::from_raw(handle) };
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,6 +10,8 @@ pub mod amfi;
|
|||||||
pub mod core_device;
|
pub mod core_device;
|
||||||
#[cfg(feature = "core_device_proxy")]
|
#[cfg(feature = "core_device_proxy")]
|
||||||
pub mod core_device_proxy;
|
pub mod core_device_proxy;
|
||||||
|
#[cfg(feature = "crashreportcopymobile")]
|
||||||
|
pub mod crashreportcopymobile;
|
||||||
#[cfg(feature = "debug_proxy")]
|
#[cfg(feature = "debug_proxy")]
|
||||||
pub mod debug_proxy;
|
pub mod debug_proxy;
|
||||||
#[cfg(feature = "diagnostics_relay")]
|
#[cfg(feature = "diagnostics_relay")]
|
||||||
@@ -29,6 +31,8 @@ pub mod logging;
|
|||||||
pub mod misagent;
|
pub mod misagent;
|
||||||
#[cfg(feature = "mobile_image_mounter")]
|
#[cfg(feature = "mobile_image_mounter")]
|
||||||
pub mod mobile_image_mounter;
|
pub mod mobile_image_mounter;
|
||||||
|
#[cfg(feature = "notification_proxy")]
|
||||||
|
pub mod notification_proxy;
|
||||||
#[cfg(feature = "syslog_relay")]
|
#[cfg(feature = "syslog_relay")]
|
||||||
pub mod os_trace_relay;
|
pub mod os_trace_relay;
|
||||||
mod pairing_file;
|
mod pairing_file;
|
||||||
|
|||||||
@@ -157,6 +157,74 @@ pub unsafe extern "C" fn lockdownd_start_service(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Pairs with the device using lockdownd
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `client` - A valid LockdowndClient handle
|
||||||
|
/// * `host_id` - The host ID (null-terminated string)
|
||||||
|
/// * `system_buid` - The system BUID (null-terminated string)
|
||||||
|
/// * `pairing_file` - On success, will be set to point to a newly allocated IdevicePairingFile handle
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// An IdeviceFfiError on error, null on success
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// `client` must be a valid pointer to a handle allocated by this library
|
||||||
|
/// `host_id` must be a valid null-terminated string
|
||||||
|
/// `system_buid` must be a valid null-terminated string
|
||||||
|
/// `pairing_file` must be a valid, non-null pointer to a location where the handle will be stored
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub unsafe extern "C" fn lockdownd_pair(
|
||||||
|
client: *mut LockdowndClientHandle,
|
||||||
|
host_id: *const libc::c_char,
|
||||||
|
system_buid: *const libc::c_char,
|
||||||
|
host_name: *const libc::c_char,
|
||||||
|
pairing_file: *mut *mut IdevicePairingFile,
|
||||||
|
) -> *mut IdeviceFfiError {
|
||||||
|
if client.is_null() || host_id.is_null() || system_buid.is_null() {
|
||||||
|
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||||
|
}
|
||||||
|
|
||||||
|
let host_id = unsafe {
|
||||||
|
std::ffi::CStr::from_ptr(host_id)
|
||||||
|
.to_string_lossy()
|
||||||
|
.into_owned()
|
||||||
|
};
|
||||||
|
let system_buid = unsafe {
|
||||||
|
std::ffi::CStr::from_ptr(system_buid)
|
||||||
|
.to_string_lossy()
|
||||||
|
.into_owned()
|
||||||
|
};
|
||||||
|
|
||||||
|
let host_name = if host_name.is_null() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(
|
||||||
|
match unsafe { std::ffi::CStr::from_ptr(host_name) }.to_str() {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(_) => {
|
||||||
|
return ffi_err!(IdeviceError::InvalidCString);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
let res = run_sync_local(async move {
|
||||||
|
let client_ref = unsafe { &mut (*client).0 };
|
||||||
|
|
||||||
|
client_ref.pair(host_id, system_buid, host_name).await
|
||||||
|
});
|
||||||
|
|
||||||
|
match res {
|
||||||
|
Ok(pairing_file_res) => {
|
||||||
|
let boxed_pairing_file = Box::new(IdevicePairingFile(pairing_file_res));
|
||||||
|
unsafe { *pairing_file = Box::into_raw(boxed_pairing_file) };
|
||||||
|
null_mut()
|
||||||
|
}
|
||||||
|
Err(e) => ffi_err!(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Gets a value from lockdownd
|
/// Gets a value from lockdownd
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
|
|||||||
311
ffi/src/notification_proxy.rs
Normal file
311
ffi/src/notification_proxy.rs
Normal file
@@ -0,0 +1,311 @@
|
|||||||
|
// Jackson Coxson
|
||||||
|
|
||||||
|
use std::ffi::{CStr, CString, c_char};
|
||||||
|
use std::ptr::null_mut;
|
||||||
|
|
||||||
|
use idevice::{
|
||||||
|
IdeviceError, IdeviceService, notification_proxy::NotificationProxyClient,
|
||||||
|
provider::IdeviceProvider,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
IdeviceFfiError, IdeviceHandle, ffi_err, provider::IdeviceProviderHandle, run_sync_local,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct NotificationProxyClientHandle(pub NotificationProxyClient);
|
||||||
|
|
||||||
|
/// Automatically creates and connects to Notification Proxy, returning a client handle
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * [`provider`] - An IdeviceProvider
|
||||||
|
/// * [`client`] - On success, will be set to point to a newly allocated NotificationProxyClient handle
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// An IdeviceFfiError on error, null on success
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// `provider` must be a valid pointer to a handle allocated by this library
|
||||||
|
/// `client` must be a valid, non-null pointer to a location where the handle will be stored
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub unsafe extern "C" fn notification_proxy_connect(
|
||||||
|
provider: *mut IdeviceProviderHandle,
|
||||||
|
client: *mut *mut NotificationProxyClientHandle,
|
||||||
|
) -> *mut IdeviceFfiError {
|
||||||
|
if provider.is_null() || client.is_null() {
|
||||||
|
tracing::error!("Null pointer provided");
|
||||||
|
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||||
|
}
|
||||||
|
|
||||||
|
let res: Result<NotificationProxyClient, IdeviceError> = run_sync_local(async move {
|
||||||
|
let provider_ref: &dyn IdeviceProvider = unsafe { &*(*provider).0 };
|
||||||
|
NotificationProxyClient::connect(provider_ref).await
|
||||||
|
});
|
||||||
|
|
||||||
|
match res {
|
||||||
|
Ok(r) => {
|
||||||
|
let boxed = Box::new(NotificationProxyClientHandle(r));
|
||||||
|
unsafe { *client = Box::into_raw(boxed) };
|
||||||
|
null_mut()
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
ffi_err!(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new NotificationProxyClient from an existing Idevice connection
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * [`socket`] - An IdeviceSocket handle
|
||||||
|
/// * [`client`] - On success, will be set to point to a newly allocated NotificationProxyClient handle
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// An IdeviceFfiError on error, null on success
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// `socket` must be a valid pointer to a handle allocated by this library. The socket is consumed,
|
||||||
|
/// and may not be used again.
|
||||||
|
/// `client` must be a valid, non-null pointer to a location where the handle will be stored
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub unsafe extern "C" fn notification_proxy_new(
|
||||||
|
socket: *mut IdeviceHandle,
|
||||||
|
client: *mut *mut NotificationProxyClientHandle,
|
||||||
|
) -> *mut IdeviceFfiError {
|
||||||
|
if socket.is_null() || client.is_null() {
|
||||||
|
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||||
|
}
|
||||||
|
let socket = unsafe { Box::from_raw(socket) }.0;
|
||||||
|
let r = NotificationProxyClient::new(socket);
|
||||||
|
let boxed = Box::new(NotificationProxyClientHandle(r));
|
||||||
|
unsafe { *client = Box::into_raw(boxed) };
|
||||||
|
null_mut()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Posts a notification to the device
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `client` - A valid NotificationProxyClient handle
|
||||||
|
/// * `name` - C string containing the notification name
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// An IdeviceFfiError on error, null on success
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// `client` must be a valid pointer to a handle allocated by this library
|
||||||
|
/// `name` must be a valid null-terminated C string
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub unsafe extern "C" fn notification_proxy_post(
|
||||||
|
client: *mut NotificationProxyClientHandle,
|
||||||
|
name: *const c_char,
|
||||||
|
) -> *mut IdeviceFfiError {
|
||||||
|
if client.is_null() || name.is_null() {
|
||||||
|
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||||
|
}
|
||||||
|
|
||||||
|
let name_str = match unsafe { CStr::from_ptr(name) }.to_str() {
|
||||||
|
Ok(s) => s.to_string(),
|
||||||
|
Err(_) => return ffi_err!(IdeviceError::FfiInvalidString),
|
||||||
|
};
|
||||||
|
|
||||||
|
let res: Result<(), IdeviceError> = run_sync_local(async move {
|
||||||
|
let client_ref = unsafe { &mut (*client).0 };
|
||||||
|
client_ref.post_notification(name_str).await
|
||||||
|
});
|
||||||
|
|
||||||
|
match res {
|
||||||
|
Ok(_) => null_mut(),
|
||||||
|
Err(e) => ffi_err!(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Observes a specific notification
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `client` - A valid NotificationProxyClient handle
|
||||||
|
/// * `name` - C string containing the notification name to observe
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// An IdeviceFfiError on error, null on success
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// `client` must be a valid pointer to a handle allocated by this library
|
||||||
|
/// `name` must be a valid null-terminated C string
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub unsafe extern "C" fn notification_proxy_observe(
|
||||||
|
client: *mut NotificationProxyClientHandle,
|
||||||
|
name: *const c_char,
|
||||||
|
) -> *mut IdeviceFfiError {
|
||||||
|
if client.is_null() || name.is_null() {
|
||||||
|
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||||
|
}
|
||||||
|
|
||||||
|
let name_str = match unsafe { CStr::from_ptr(name) }.to_str() {
|
||||||
|
Ok(s) => s.to_string(),
|
||||||
|
Err(_) => return ffi_err!(IdeviceError::FfiInvalidString),
|
||||||
|
};
|
||||||
|
|
||||||
|
let res: Result<(), IdeviceError> = run_sync_local(async move {
|
||||||
|
let client_ref = unsafe { &mut (*client).0 };
|
||||||
|
client_ref.observe_notification(name_str).await
|
||||||
|
});
|
||||||
|
|
||||||
|
match res {
|
||||||
|
Ok(_) => null_mut(),
|
||||||
|
Err(e) => ffi_err!(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Observes multiple notifications at once
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `client` - A valid NotificationProxyClient handle
|
||||||
|
/// * `names` - A null-terminated array of C strings containing notification names
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// An IdeviceFfiError on error, null on success
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// `client` must be a valid pointer to a handle allocated by this library
|
||||||
|
/// `names` must be a valid pointer to a null-terminated array of null-terminated C strings
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub unsafe extern "C" fn notification_proxy_observe_multiple(
|
||||||
|
client: *mut NotificationProxyClientHandle,
|
||||||
|
names: *const *const c_char,
|
||||||
|
) -> *mut IdeviceFfiError {
|
||||||
|
if client.is_null() || names.is_null() {
|
||||||
|
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut notification_names: Vec<String> = Vec::new();
|
||||||
|
let mut i = 0;
|
||||||
|
loop {
|
||||||
|
let ptr = unsafe { *names.add(i) };
|
||||||
|
if ptr.is_null() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
match unsafe { CStr::from_ptr(ptr) }.to_str() {
|
||||||
|
Ok(s) => notification_names.push(s.to_string()),
|
||||||
|
Err(_) => return ffi_err!(IdeviceError::FfiInvalidString),
|
||||||
|
}
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
let refs: Vec<&str> = notification_names.iter().map(|s| s.as_str()).collect();
|
||||||
|
|
||||||
|
let res: Result<(), IdeviceError> = run_sync_local(async move {
|
||||||
|
let client_ref = unsafe { &mut (*client).0 };
|
||||||
|
client_ref.observe_notifications(&refs).await
|
||||||
|
});
|
||||||
|
|
||||||
|
match res {
|
||||||
|
Ok(_) => null_mut(),
|
||||||
|
Err(e) => ffi_err!(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Receives the next notification from the device
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `client` - A valid NotificationProxyClient handle
|
||||||
|
/// * `name_out` - On success, will be set to a newly allocated C string containing the notification name
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// An IdeviceFfiError on error, null on success
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// `client` must be a valid pointer to a handle allocated by this library
|
||||||
|
/// `name_out` must be a valid pointer. The returned string must be freed with `notification_proxy_free_string`
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub unsafe extern "C" fn notification_proxy_receive(
|
||||||
|
client: *mut NotificationProxyClientHandle,
|
||||||
|
name_out: *mut *mut c_char,
|
||||||
|
) -> *mut IdeviceFfiError {
|
||||||
|
if client.is_null() || name_out.is_null() {
|
||||||
|
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||||
|
}
|
||||||
|
|
||||||
|
let res: Result<String, IdeviceError> = run_sync_local(async move {
|
||||||
|
let client_ref = unsafe { &mut (*client).0 };
|
||||||
|
client_ref.receive_notification().await
|
||||||
|
});
|
||||||
|
|
||||||
|
match res {
|
||||||
|
Ok(name) => match CString::new(name) {
|
||||||
|
Ok(c_string) => {
|
||||||
|
unsafe { *name_out = c_string.into_raw() };
|
||||||
|
null_mut()
|
||||||
|
}
|
||||||
|
Err(_) => ffi_err!(IdeviceError::FfiInvalidString),
|
||||||
|
},
|
||||||
|
Err(e) => ffi_err!(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Receives the next notification with a timeout
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `client` - A valid NotificationProxyClient handle
|
||||||
|
/// * `interval` - Timeout in seconds to wait for a notification
|
||||||
|
/// * `name_out` - On success, will be set to a newly allocated C string containing the notification name
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// An IdeviceFfiError on error, null on success
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// `client` must be a valid pointer to a handle allocated by this library
|
||||||
|
/// `name_out` must be a valid pointer. The returned string must be freed with `notification_proxy_free_string`
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub unsafe extern "C" fn notification_proxy_receive_with_timeout(
|
||||||
|
client: *mut NotificationProxyClientHandle,
|
||||||
|
interval: u64,
|
||||||
|
name_out: *mut *mut c_char,
|
||||||
|
) -> *mut IdeviceFfiError {
|
||||||
|
if client.is_null() || name_out.is_null() {
|
||||||
|
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||||
|
}
|
||||||
|
|
||||||
|
let res: Result<String, IdeviceError> = run_sync_local(async move {
|
||||||
|
let client_ref = unsafe { &mut (*client).0 };
|
||||||
|
client_ref.receive_notification_with_timeout(interval).await
|
||||||
|
});
|
||||||
|
|
||||||
|
match res {
|
||||||
|
Ok(name) => match CString::new(name) {
|
||||||
|
Ok(c_string) => {
|
||||||
|
unsafe { *name_out = c_string.into_raw() };
|
||||||
|
null_mut()
|
||||||
|
}
|
||||||
|
Err(_) => ffi_err!(IdeviceError::FfiInvalidString),
|
||||||
|
},
|
||||||
|
Err(e) => ffi_err!(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Frees a string returned by notification_proxy_receive
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// `s` must be a valid pointer returned from `notification_proxy_receive`
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub unsafe extern "C" fn notification_proxy_free_string(s: *mut c_char) {
|
||||||
|
if !s.is_null() {
|
||||||
|
let _ = unsafe { CString::from_raw(s) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Frees a handle
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * [`handle`] - The handle to free
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// `handle` must be a valid pointer to the handle that was allocated by this library,
|
||||||
|
/// or NULL (in which case this function does nothing)
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub unsafe extern "C" fn notification_proxy_client_free(
|
||||||
|
handle: *mut NotificationProxyClientHandle,
|
||||||
|
) {
|
||||||
|
if !handle.is_null() {
|
||||||
|
tracing::debug!("Freeing notification_proxy_client");
|
||||||
|
let _ = unsafe { Box::from_raw(handle) };
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ use idevice::{
|
|||||||
IdeviceError, IdeviceService, provider::IdeviceProvider,
|
IdeviceError, IdeviceService, provider::IdeviceProvider,
|
||||||
springboardservices::SpringBoardServicesClient,
|
springboardservices::SpringBoardServicesClient,
|
||||||
};
|
};
|
||||||
|
use plist_ffi::plist_t;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
IdeviceFfiError, IdeviceHandle, ffi_err, provider::IdeviceProviderHandle, run_sync,
|
IdeviceFfiError, IdeviceHandle, ffi_err, provider::IdeviceProviderHandle, run_sync,
|
||||||
@@ -263,6 +264,43 @@ pub unsafe extern "C" fn springboard_services_get_interface_orientation(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets the home screen icon layout metrics
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `client` - A valid SpringBoardServicesClient handle
|
||||||
|
/// * `res` - On success, will point to a plist dictionary node containing the metrics
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// An IdeviceFfiError on error, null on success
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// `client` must be a valid pointer to a handle allocated by this library
|
||||||
|
/// `res` must be a valid, non-null pointer
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub unsafe extern "C" fn springboard_services_get_homescreen_icon_metrics(
|
||||||
|
client: *mut SpringBoardServicesClientHandle,
|
||||||
|
res: *mut plist_t,
|
||||||
|
) -> *mut IdeviceFfiError {
|
||||||
|
if client.is_null() || res.is_null() {
|
||||||
|
tracing::error!("Invalid arguments: {client:?}, {res:?}");
|
||||||
|
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||||
|
}
|
||||||
|
let client = unsafe { &mut *client };
|
||||||
|
|
||||||
|
let output = run_sync(async { client.0.get_homescreen_icon_metrics().await });
|
||||||
|
|
||||||
|
match output {
|
||||||
|
Ok(metrics) => {
|
||||||
|
unsafe {
|
||||||
|
*res =
|
||||||
|
plist_ffi::PlistWrapper::new_node(plist::Value::Dictionary(metrics)).into_ptr();
|
||||||
|
}
|
||||||
|
null_mut()
|
||||||
|
}
|
||||||
|
Err(e) => ffi_err!(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Frees an SpringBoardServicesClient handle
|
/// Frees an SpringBoardServicesClient handle
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
|
|||||||
@@ -97,6 +97,7 @@ misagent = []
|
|||||||
mobile_image_mounter = ["dep:sha2"]
|
mobile_image_mounter = ["dep:sha2"]
|
||||||
mobileactivationd = ["dep:reqwest"]
|
mobileactivationd = ["dep:reqwest"]
|
||||||
mobilebackup2 = []
|
mobilebackup2 = []
|
||||||
|
notification_proxy = ["tokio/macros", "tokio/time", "dep:async-stream", "dep:futures"]
|
||||||
location_simulation = []
|
location_simulation = []
|
||||||
pair = ["chrono/default", "tokio/time", "dep:sha2", "dep:rsa", "dep:x509-cert"]
|
pair = ["chrono/default", "tokio/time", "dep:sha2", "dep:rsa", "dep:x509-cert"]
|
||||||
pcapd = []
|
pcapd = []
|
||||||
@@ -138,6 +139,7 @@ full = [
|
|||||||
"mobile_image_mounter",
|
"mobile_image_mounter",
|
||||||
"mobileactivationd",
|
"mobileactivationd",
|
||||||
"mobilebackup2",
|
"mobilebackup2",
|
||||||
|
"notification_proxy",
|
||||||
"pair",
|
"pair",
|
||||||
"pcapd",
|
"pcapd",
|
||||||
"preboard_service",
|
"preboard_service",
|
||||||
|
|||||||
@@ -865,6 +865,14 @@ pub enum IdeviceError {
|
|||||||
|
|
||||||
#[error("Developer mode is not enabled")]
|
#[error("Developer mode is not enabled")]
|
||||||
DeveloperModeNotEnabled = -68,
|
DeveloperModeNotEnabled = -68,
|
||||||
|
|
||||||
|
#[cfg(feature = "notification_proxy")]
|
||||||
|
#[error("notification proxy died")]
|
||||||
|
NotificationProxyDeath = -69,
|
||||||
|
|
||||||
|
#[cfg(feature = "installation_proxy")]
|
||||||
|
#[error("Application verification failed: {0}")]
|
||||||
|
ApplicationVerificationFailed(String) = -70,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IdeviceError {
|
impl IdeviceError {
|
||||||
@@ -908,6 +916,15 @@ impl IdeviceError {
|
|||||||
Some(Self::InternalError(detailed_error))
|
Some(Self::InternalError(detailed_error))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#[cfg(feature = "installation_proxy")]
|
||||||
|
"ApplicationVerificationFailed" => {
|
||||||
|
let msg = context
|
||||||
|
.get("ErrorDescription")
|
||||||
|
.and_then(|x| x.as_string())
|
||||||
|
.unwrap_or("No context")
|
||||||
|
.to_string();
|
||||||
|
Some(Self::ApplicationVerificationFailed(msg))
|
||||||
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1030,6 +1047,12 @@ impl IdeviceError {
|
|||||||
#[cfg(feature = "installation_proxy")]
|
#[cfg(feature = "installation_proxy")]
|
||||||
IdeviceError::MalformedPackageArchive(_) => -67,
|
IdeviceError::MalformedPackageArchive(_) => -67,
|
||||||
IdeviceError::DeveloperModeNotEnabled => -68,
|
IdeviceError::DeveloperModeNotEnabled => -68,
|
||||||
|
|
||||||
|
#[cfg(feature = "notification_proxy")]
|
||||||
|
IdeviceError::NotificationProxyDeath => -69,
|
||||||
|
|
||||||
|
#[cfg(feature = "installation_proxy")]
|
||||||
|
IdeviceError::ApplicationVerificationFailed(_) => -70,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,6 +35,8 @@ pub mod mobile_image_mounter;
|
|||||||
pub mod mobileactivationd;
|
pub mod mobileactivationd;
|
||||||
#[cfg(feature = "mobilebackup2")]
|
#[cfg(feature = "mobilebackup2")]
|
||||||
pub mod mobilebackup2;
|
pub mod mobilebackup2;
|
||||||
|
#[cfg(feature = "notification_proxy")]
|
||||||
|
pub mod notification_proxy;
|
||||||
#[cfg(feature = "syslog_relay")]
|
#[cfg(feature = "syslog_relay")]
|
||||||
pub mod os_trace_relay;
|
pub mod os_trace_relay;
|
||||||
#[cfg(feature = "pcapd")]
|
#[cfg(feature = "pcapd")]
|
||||||
|
|||||||
212
idevice/src/services/notification_proxy.rs
Normal file
212
idevice/src/services/notification_proxy.rs
Normal file
@@ -0,0 +1,212 @@
|
|||||||
|
//! iOS Device Notification Proxy Service
|
||||||
|
//!
|
||||||
|
//! Based on libimobiledevice's notification_proxy implementation
|
||||||
|
//!
|
||||||
|
//! Common notification identifiers:
|
||||||
|
//! Full list: include/libimobiledevice/notification_proxy.h
|
||||||
|
//!
|
||||||
|
//! - Notifications that can be sent (PostNotification):
|
||||||
|
//! - `com.apple.itunes-mobdev.syncWillStart` - Sync will start
|
||||||
|
//! - `com.apple.itunes-mobdev.syncDidStart` - Sync started
|
||||||
|
//! - `com.apple.itunes-mobdev.syncDidFinish` - Sync finished
|
||||||
|
//! - `com.apple.itunes-mobdev.syncLockRequest` - Request sync lock
|
||||||
|
//!
|
||||||
|
//! - Notifications that can be observed (ObserveNotification):
|
||||||
|
//! - `com.apple.itunes-client.syncCancelRequest` - Cancel sync request
|
||||||
|
//! - `com.apple.itunes-client.syncSuspendRequest` - Suspend sync
|
||||||
|
//! - `com.apple.itunes-client.syncResumeRequest` - Resume sync
|
||||||
|
//! - `com.apple.mobile.lockdown.phone_number_changed` - Phone number changed
|
||||||
|
//! - `com.apple.mobile.lockdown.device_name_changed` - Device name changed
|
||||||
|
//! - `com.apple.mobile.lockdown.timezone_changed` - Timezone changed
|
||||||
|
//! - `com.apple.mobile.lockdown.trusted_host_attached` - Trusted host attached
|
||||||
|
//! - `com.apple.mobile.lockdown.host_detached` - Host detached
|
||||||
|
//! - `com.apple.mobile.lockdown.host_attached` - Host attached
|
||||||
|
//! - `com.apple.mobile.lockdown.registration_failed` - Registration failed
|
||||||
|
//! - `com.apple.mobile.lockdown.activation_state` - Activation state
|
||||||
|
//! - `com.apple.mobile.lockdown.brick_state` - Brick state
|
||||||
|
//! - `com.apple.mobile.lockdown.disk_usage_changed` - Disk usage (iOS 4.0+)
|
||||||
|
//! - `com.apple.mobile.data_sync.domain_changed` - Data sync domain changed
|
||||||
|
//! - `com.apple.mobile.application_installed` - App installed
|
||||||
|
//! - `com.apple.mobile.application_uninstalled` - App uninstalled
|
||||||
|
|
||||||
|
use std::pin::Pin;
|
||||||
|
|
||||||
|
use futures::Stream;
|
||||||
|
use tracing::warn;
|
||||||
|
|
||||||
|
use crate::{Idevice, IdeviceError, IdeviceService, obf};
|
||||||
|
|
||||||
|
/// Client for interacting with the iOS notification proxy service
|
||||||
|
///
|
||||||
|
/// The notification proxy service provides a mechanism to observe and post
|
||||||
|
/// system notifications.
|
||||||
|
///
|
||||||
|
/// Use `observe_notification` to register for events, then `receive_notification`
|
||||||
|
/// to wait for them.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct NotificationProxyClient {
|
||||||
|
/// The underlying device connection with established notification_proxy service
|
||||||
|
pub idevice: Idevice,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IdeviceService for NotificationProxyClient {
|
||||||
|
/// Returns the notification proxy service name as registered with lockdownd
|
||||||
|
fn service_name() -> std::borrow::Cow<'static, str> {
|
||||||
|
obf!("com.apple.mobile.notification_proxy")
|
||||||
|
}
|
||||||
|
async fn from_stream(idevice: Idevice) -> Result<Self, crate::IdeviceError> {
|
||||||
|
Ok(Self::new(idevice))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NotificationProxyClient {
|
||||||
|
/// Creates a new notification proxy client from an existing device connection
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `idevice` - Pre-established device connection
|
||||||
|
pub fn new(idevice: Idevice) -> Self {
|
||||||
|
Self { idevice }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Posts a notification to the device
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `notification_name` - Name of the notification to post
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// Returns `IdeviceError` if the notification fails to send
|
||||||
|
pub async fn post_notification(
|
||||||
|
&mut self,
|
||||||
|
notification_name: impl Into<String>,
|
||||||
|
) -> Result<(), IdeviceError> {
|
||||||
|
let request = crate::plist!({
|
||||||
|
"Command": "PostNotification",
|
||||||
|
"Name": notification_name.into()
|
||||||
|
});
|
||||||
|
self.idevice.send_plist(request).await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Registers to observe a specific notification
|
||||||
|
///
|
||||||
|
/// After calling this, use `receive_notification` to wait for events.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `notification_name` - Name of the notification to observe
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// Returns `IdeviceError` if the registration fails
|
||||||
|
pub async fn observe_notification(
|
||||||
|
&mut self,
|
||||||
|
notification_name: impl Into<String>,
|
||||||
|
) -> Result<(), IdeviceError> {
|
||||||
|
let request = crate::plist!({
|
||||||
|
"Command": "ObserveNotification",
|
||||||
|
"Name": notification_name.into()
|
||||||
|
});
|
||||||
|
self.idevice.send_plist(request).await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Registers to observe multiple notifications at once
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `notification_names` - Slice of notification names to observe
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// Returns `IdeviceError` if any registration fails
|
||||||
|
pub async fn observe_notifications(
|
||||||
|
&mut self,
|
||||||
|
notification_names: &[&str],
|
||||||
|
) -> Result<(), IdeviceError> {
|
||||||
|
for name in notification_names {
|
||||||
|
self.observe_notification(*name).await?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Waits for and receives the next notification from the device
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// The name of the received notification
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// - `NotificationProxyDeath` if the proxy connection died
|
||||||
|
/// - `UnexpectedResponse` if the response format is invalid
|
||||||
|
pub async fn receive_notification(&mut self) -> Result<String, IdeviceError> {
|
||||||
|
let response = self.idevice.read_plist().await?;
|
||||||
|
|
||||||
|
match response.get("Command").and_then(|c| c.as_string()) {
|
||||||
|
Some("RelayNotification") => match response.get("Name").and_then(|n| n.as_string()) {
|
||||||
|
Some(name) => Ok(name.to_string()),
|
||||||
|
None => Err(IdeviceError::UnexpectedResponse),
|
||||||
|
},
|
||||||
|
Some("ProxyDeath") => {
|
||||||
|
warn!("NotificationProxy died!");
|
||||||
|
Err(IdeviceError::NotificationProxyDeath)
|
||||||
|
}
|
||||||
|
_ => Err(IdeviceError::UnexpectedResponse),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Waits for a notification with a timeout
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `interval` - Timeout in seconds to wait for a notification
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// The name of the received notification
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// - `NotificationProxyDeath` if the proxy connection died
|
||||||
|
/// - `UnexpectedResponse` if the response format is invalid
|
||||||
|
/// - `HeartbeatTimeout` if no notification received before interval
|
||||||
|
pub async fn receive_notification_with_timeout(
|
||||||
|
&mut self,
|
||||||
|
interval: u64,
|
||||||
|
) -> Result<String, IdeviceError> {
|
||||||
|
tokio::select! {
|
||||||
|
result = self.receive_notification() => result,
|
||||||
|
_ = tokio::time::sleep(tokio::time::Duration::from_secs(interval)) => {
|
||||||
|
Err(IdeviceError::HeartbeatTimeout)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Continuous stream of notifications.
|
||||||
|
pub fn into_stream(
|
||||||
|
mut self,
|
||||||
|
) -> Pin<Box<dyn Stream<Item = Result<String, IdeviceError>> + Send>> {
|
||||||
|
Box::pin(async_stream::try_stream! {
|
||||||
|
loop {
|
||||||
|
let response = self.idevice.read_plist().await?;
|
||||||
|
|
||||||
|
match response.get("Command").and_then(|c| c.as_string()) {
|
||||||
|
Some("RelayNotification") => {
|
||||||
|
match response.get("Name").and_then(|n| n.as_string()) {
|
||||||
|
Some(name) => yield name.to_string(),
|
||||||
|
None => Err(IdeviceError::UnexpectedResponse)?,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some("ProxyDeath") => {
|
||||||
|
warn!("NotificationProxy died!");
|
||||||
|
Err(IdeviceError::NotificationProxyDeath)?;
|
||||||
|
}
|
||||||
|
_ => Err(IdeviceError::UnexpectedResponse)?,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Shuts down the notification proxy connection
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// Returns `IdeviceError` if the shutdown command fails to send
|
||||||
|
pub async fn shutdown(&mut self) -> Result<(), IdeviceError> {
|
||||||
|
let request = crate::plist!({
|
||||||
|
"Command": "Shutdown"
|
||||||
|
});
|
||||||
|
self.idevice.send_plist(request).await?;
|
||||||
|
// Best-effort: wait for ProxyDeath ack
|
||||||
|
let _ = self.idevice.read_plist().await;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -325,4 +325,31 @@ impl SpringBoardServicesClient {
|
|||||||
|
|
||||||
Ok(orientation)
|
Ok(orientation)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets the home screen icon layout metrics
|
||||||
|
///
|
||||||
|
/// Returns icon spacing, size, and positioning information
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// A `plist::Dictionary` containing the icon layout metrics
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// Returns `IdeviceError` if:
|
||||||
|
/// - Communication fails
|
||||||
|
/// - The response is malformed
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```rust
|
||||||
|
/// let metrics = client.get_homescreen_icon_metrics().await?;
|
||||||
|
/// println!("{:?}", metrics);
|
||||||
|
/// ```
|
||||||
|
pub async fn get_homescreen_icon_metrics(&mut self) -> Result<plist::Dictionary, IdeviceError> {
|
||||||
|
let req = crate::plist!({
|
||||||
|
"command": "getHomeScreenIconMetrics",
|
||||||
|
});
|
||||||
|
self.idevice.send_plist(req).await?;
|
||||||
|
|
||||||
|
let res = self.idevice.read_plist().await?;
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
17
justfile
17
justfile
@@ -40,6 +40,9 @@ xcframework: apple-build
|
|||||||
lipo -create -output swift/libs/idevice-ios-sim.a \
|
lipo -create -output swift/libs/idevice-ios-sim.a \
|
||||||
target/aarch64-apple-ios-sim/release/libidevice_ffi.a \
|
target/aarch64-apple-ios-sim/release/libidevice_ffi.a \
|
||||||
target/x86_64-apple-ios/release/libidevice_ffi.a
|
target/x86_64-apple-ios/release/libidevice_ffi.a
|
||||||
|
lipo -create -output swift/libs/idevice-maccatalyst.a \
|
||||||
|
target/aarch64-apple-ios-macabi/release/libidevice_ffi.a \
|
||||||
|
target/x86_64-apple-ios-macabi/release/libidevice_ffi.a
|
||||||
lipo -create -output swift/libs/idevice-macos.a \
|
lipo -create -output swift/libs/idevice-macos.a \
|
||||||
target/aarch64-apple-darwin/release/libidevice_ffi.a \
|
target/aarch64-apple-darwin/release/libidevice_ffi.a \
|
||||||
target/x86_64-apple-darwin/release/libidevice_ffi.a
|
target/x86_64-apple-darwin/release/libidevice_ffi.a
|
||||||
@@ -48,8 +51,9 @@ xcframework: apple-build
|
|||||||
-library target/aarch64-apple-ios/release/libidevice_ffi.a -headers swift/include \
|
-library target/aarch64-apple-ios/release/libidevice_ffi.a -headers swift/include \
|
||||||
-library swift/libs/idevice-ios-sim.a -headers swift/include \
|
-library swift/libs/idevice-ios-sim.a -headers swift/include \
|
||||||
-library swift/libs/idevice-macos.a -headers swift/include \
|
-library swift/libs/idevice-macos.a -headers swift/include \
|
||||||
|
-library swift/libs/idevice-maccatalyst.a -headers swift/include \
|
||||||
-output swift/IDevice.xcframework
|
-output swift/IDevice.xcframework
|
||||||
|
|
||||||
zip -r swift/bundle.zip swift/IDevice.xcframework
|
zip -r swift/bundle.zip swift/IDevice.xcframework
|
||||||
openssl dgst -sha256 swift/bundle.zip
|
openssl dgst -sha256 swift/bundle.zip
|
||||||
|
|
||||||
@@ -68,7 +72,16 @@ apple-build: # requires a Mac
|
|||||||
BINDGEN_EXTRA_CLANG_ARGS="--sysroot=$(xcrun --sdk iphonesimulator --show-sdk-path)" \
|
BINDGEN_EXTRA_CLANG_ARGS="--sysroot=$(xcrun --sdk iphonesimulator --show-sdk-path)" \
|
||||||
cargo build --release --target x86_64-apple-ios
|
cargo build --release --target x86_64-apple-ios
|
||||||
|
|
||||||
|
# Mac Catalyst (arm64)
|
||||||
|
# AWS-LC has an a hard time compiling for an iOS with macabi target, so we switch to ring.
|
||||||
|
BINDGEN_EXTRA_CLANG_ARGS="--sysroot=$(xcrun --sdk macosx --show-sdk-path)" \
|
||||||
|
cargo build --release --target aarch64-apple-ios-macabi --no-default-features --features "ring full"
|
||||||
|
|
||||||
|
# Mac Catalyst (x86_64)
|
||||||
|
# AWS-LC has an a hard time compiling for an iOS with macabi target, so we switch to ring.
|
||||||
|
BINDGEN_EXTRA_CLANG_ARGS="--sysroot=$(xcrun --sdk macosx --show-sdk-path)" \
|
||||||
|
cargo build --release --target x86_64-apple-ios-macabi --no-default-features --features "ring full"
|
||||||
|
|
||||||
# macOS (native) – no special env needed
|
# macOS (native) – no special env needed
|
||||||
cargo build --release --target aarch64-apple-darwin
|
cargo build --release --target aarch64-apple-darwin
|
||||||
cargo build --release --target x86_64-apple-darwin
|
cargo build --release --target x86_64-apple-darwin
|
||||||
|
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ mod lockdown;
|
|||||||
mod misagent;
|
mod misagent;
|
||||||
mod mobilebackup2;
|
mod mobilebackup2;
|
||||||
mod mounter;
|
mod mounter;
|
||||||
|
mod notification_proxy_client;
|
||||||
mod notifications;
|
mod notifications;
|
||||||
mod os_trace_relay;
|
mod os_trace_relay;
|
||||||
mod pair;
|
mod pair;
|
||||||
@@ -113,6 +114,7 @@ async fn main() {
|
|||||||
.with_subcommand("mobilebackup2", mobilebackup2::register())
|
.with_subcommand("mobilebackup2", mobilebackup2::register())
|
||||||
.with_subcommand("mounter", mounter::register())
|
.with_subcommand("mounter", mounter::register())
|
||||||
.with_subcommand("notifications", notifications::register())
|
.with_subcommand("notifications", notifications::register())
|
||||||
|
.with_subcommand("notification_proxy", notification_proxy_client::register())
|
||||||
.with_subcommand("os_trace_relay", os_trace_relay::register())
|
.with_subcommand("os_trace_relay", os_trace_relay::register())
|
||||||
.with_subcommand("pair", pair::register())
|
.with_subcommand("pair", pair::register())
|
||||||
.with_subcommand("pcapd", pcapd::register())
|
.with_subcommand("pcapd", pcapd::register())
|
||||||
@@ -214,6 +216,9 @@ async fn main() {
|
|||||||
"notifications" => {
|
"notifications" => {
|
||||||
notifications::main(sub_args, provider).await;
|
notifications::main(sub_args, provider).await;
|
||||||
}
|
}
|
||||||
|
"notification_proxy" => {
|
||||||
|
notification_proxy_client::main(sub_args, provider).await;
|
||||||
|
}
|
||||||
"os_trace_relay" => {
|
"os_trace_relay" => {
|
||||||
os_trace_relay::main(sub_args, provider).await;
|
os_trace_relay::main(sub_args, provider).await;
|
||||||
}
|
}
|
||||||
|
|||||||
85
tools/src/notification_proxy_client.rs
Normal file
85
tools/src/notification_proxy_client.rs
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
// Jackson Coxson
|
||||||
|
|
||||||
|
use idevice::{
|
||||||
|
IdeviceService, notification_proxy::NotificationProxyClient, provider::IdeviceProvider,
|
||||||
|
};
|
||||||
|
use jkcli::{CollectedArguments, JkArgument, JkCommand};
|
||||||
|
|
||||||
|
pub fn register() -> JkCommand {
|
||||||
|
JkCommand::new()
|
||||||
|
.help("Notification proxy")
|
||||||
|
.with_subcommand(
|
||||||
|
"observe",
|
||||||
|
JkCommand::new()
|
||||||
|
.help("Observe notifications from the device")
|
||||||
|
.with_argument(
|
||||||
|
JkArgument::new()
|
||||||
|
.with_help("The notification ID to observe")
|
||||||
|
.required(true),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.with_subcommand(
|
||||||
|
"post",
|
||||||
|
JkCommand::new()
|
||||||
|
.help("Post a notification to the device")
|
||||||
|
.with_argument(
|
||||||
|
JkArgument::new()
|
||||||
|
.with_help("The notification ID to post")
|
||||||
|
.required(true),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.subcommand_required(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn main(arguments: &CollectedArguments, provider: Box<dyn IdeviceProvider>) {
|
||||||
|
let mut client = NotificationProxyClient::connect(&*provider)
|
||||||
|
.await
|
||||||
|
.expect("Unable to connect to notification proxy");
|
||||||
|
|
||||||
|
let (subcommand, sub_args) = arguments.first_subcommand().unwrap();
|
||||||
|
let mut sub_args = sub_args.clone();
|
||||||
|
|
||||||
|
match subcommand.as_str() {
|
||||||
|
"observe" => {
|
||||||
|
let input: String = sub_args
|
||||||
|
.next_argument::<String>()
|
||||||
|
.expect("No notification ID passed");
|
||||||
|
|
||||||
|
let notifications: Vec<&str> = input.split_whitespace().collect();
|
||||||
|
client
|
||||||
|
.observe_notifications(¬ifications)
|
||||||
|
.await
|
||||||
|
.expect("Failed to observe notifications");
|
||||||
|
|
||||||
|
loop {
|
||||||
|
tokio::select! {
|
||||||
|
_ = tokio::signal::ctrl_c() => {
|
||||||
|
println!("\nShutdown signal received, exiting.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = client.receive_notification() => {
|
||||||
|
match result {
|
||||||
|
Ok(notif) => println!("Received notification: {}", notif),
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Failed to receive notification: {}", e);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"post" => {
|
||||||
|
let notification: String = sub_args
|
||||||
|
.next_argument::<String>()
|
||||||
|
.expect("No notification ID passed");
|
||||||
|
|
||||||
|
client
|
||||||
|
.post_notification(¬ification)
|
||||||
|
.await
|
||||||
|
.expect("Failed to post notification");
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -49,6 +49,25 @@ pub fn register() -> JkCommand {
|
|||||||
"get_interface_orientation",
|
"get_interface_orientation",
|
||||||
JkCommand::new().help("Gets the device's current screen orientation"),
|
JkCommand::new().help("Gets the device's current screen orientation"),
|
||||||
)
|
)
|
||||||
|
.with_subcommand(
|
||||||
|
"get_homescreen_icon_metrics",
|
||||||
|
JkCommand::new().help("Gets home screen icon layout metrics"),
|
||||||
|
)
|
||||||
|
.with_subcommand(
|
||||||
|
"get_icon",
|
||||||
|
JkCommand::new()
|
||||||
|
.help("Gets an app's icon as PNG")
|
||||||
|
.with_argument(
|
||||||
|
JkArgument::new()
|
||||||
|
.with_help("Bundle identifier (e.g. com.apple.Maps)")
|
||||||
|
.required(true),
|
||||||
|
)
|
||||||
|
.with_flag(
|
||||||
|
JkFlag::new("save")
|
||||||
|
.with_help("Path to save the icon PNG file, or icon.png by default")
|
||||||
|
.with_argument(JkArgument::new().required(true)),
|
||||||
|
),
|
||||||
|
)
|
||||||
.subcommand_required(true)
|
.subcommand_required(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,6 +132,30 @@ pub async fn main(arguments: &CollectedArguments, provider: Box<dyn IdeviceProvi
|
|||||||
.expect("Failed to get interface orientation");
|
.expect("Failed to get interface orientation");
|
||||||
println!("{:?}", orientation);
|
println!("{:?}", orientation);
|
||||||
}
|
}
|
||||||
|
"get_homescreen_icon_metrics" => {
|
||||||
|
let metrics = sbc
|
||||||
|
.get_homescreen_icon_metrics()
|
||||||
|
.await
|
||||||
|
.expect("Failed to get homescreen icon metrics");
|
||||||
|
let metrics_value = plist::Value::Dictionary(metrics);
|
||||||
|
println!("{}", pretty_print_plist(&metrics_value));
|
||||||
|
}
|
||||||
|
"get_icon" => {
|
||||||
|
let bundle_id = sub_args.next_argument::<String>().unwrap();
|
||||||
|
|
||||||
|
let icon_data = sbc
|
||||||
|
.get_icon_pngdata(bundle_id)
|
||||||
|
.await
|
||||||
|
.expect("Failed to get icon");
|
||||||
|
|
||||||
|
let save_path = sub_args
|
||||||
|
.get_flag::<String>("save")
|
||||||
|
.unwrap_or("icon.png".to_string());
|
||||||
|
|
||||||
|
tokio::fs::write(&save_path, icon_data)
|
||||||
|
.await
|
||||||
|
.expect("Failed to save icon");
|
||||||
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user