mirror of
https://github.com/jkcoxson/idevice.git
synced 2026-03-02 06:26:15 +01:00
feat: notification proxy (#70)
* init
* chore: clippy and fmt
* feat: ffi wrapper
* feat: multi-observe and timeout to notification proxy
* fix: nitpicks
1. proxy death its onw error in emun #69
2. make returned stream actual stream, copied from 54439b85dd/idevice/src/services/bt_packet_logger.rs (L126-L138)
This commit is contained in:
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
|
||||
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"]
|
||||
dvt = ["idevice/dvt"]
|
||||
heartbeat = ["idevice/heartbeat"]
|
||||
notification_proxy = ["idevice/notification_proxy"]
|
||||
house_arrest = ["idevice/house_arrest"]
|
||||
installation_proxy = ["idevice/installation_proxy"]
|
||||
springboardservices = ["idevice/springboardservices"]
|
||||
@@ -61,6 +62,7 @@ full = [
|
||||
"diagnostics_relay",
|
||||
"dvt",
|
||||
"heartbeat",
|
||||
"notification_proxy",
|
||||
"house_arrest",
|
||||
"installation_proxy",
|
||||
"misagent",
|
||||
|
||||
@@ -29,6 +29,8 @@ pub mod logging;
|
||||
pub mod misagent;
|
||||
#[cfg(feature = "mobile_image_mounter")]
|
||||
pub mod mobile_image_mounter;
|
||||
#[cfg(feature = "notification_proxy")]
|
||||
pub mod notification_proxy;
|
||||
#[cfg(feature = "syslog_relay")]
|
||||
pub mod os_trace_relay;
|
||||
mod pairing_file;
|
||||
|
||||
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) };
|
||||
}
|
||||
}
|
||||
@@ -97,6 +97,7 @@ misagent = []
|
||||
mobile_image_mounter = ["dep:sha2"]
|
||||
mobileactivationd = ["dep:reqwest"]
|
||||
mobilebackup2 = []
|
||||
notification_proxy = ["tokio/macros", "tokio/time", "dep:async-stream", "dep:futures"]
|
||||
location_simulation = []
|
||||
pair = ["chrono/default", "tokio/time", "dep:sha2", "dep:rsa", "dep:x509-cert"]
|
||||
pcapd = []
|
||||
@@ -138,6 +139,7 @@ full = [
|
||||
"mobile_image_mounter",
|
||||
"mobileactivationd",
|
||||
"mobilebackup2",
|
||||
"notification_proxy",
|
||||
"pair",
|
||||
"pcapd",
|
||||
"preboard_service",
|
||||
|
||||
@@ -865,6 +865,10 @@ pub enum IdeviceError {
|
||||
|
||||
#[error("Developer mode is not enabled")]
|
||||
DeveloperModeNotEnabled = -68,
|
||||
|
||||
#[cfg(feature = "notification_proxy")]
|
||||
#[error("notification proxy died")]
|
||||
NotificationProxyDeath = -69,
|
||||
}
|
||||
|
||||
impl IdeviceError {
|
||||
@@ -1030,6 +1034,9 @@ impl IdeviceError {
|
||||
#[cfg(feature = "installation_proxy")]
|
||||
IdeviceError::MalformedPackageArchive(_) => -67,
|
||||
IdeviceError::DeveloperModeNotEnabled => -68,
|
||||
|
||||
#[cfg(feature = "notification_proxy")]
|
||||
IdeviceError::NotificationProxyDeath => -69,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,8 @@ pub mod mobile_image_mounter;
|
||||
pub mod mobileactivationd;
|
||||
#[cfg(feature = "mobilebackup2")]
|
||||
pub mod mobilebackup2;
|
||||
#[cfg(feature = "notification_proxy")]
|
||||
pub mod notification_proxy;
|
||||
#[cfg(feature = "syslog_relay")]
|
||||
pub mod os_trace_relay;
|
||||
#[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(())
|
||||
}
|
||||
}
|
||||
@@ -33,6 +33,7 @@ mod lockdown;
|
||||
mod misagent;
|
||||
mod mobilebackup2;
|
||||
mod mounter;
|
||||
mod notification_proxy_client;
|
||||
mod notifications;
|
||||
mod os_trace_relay;
|
||||
mod pair;
|
||||
@@ -113,6 +114,7 @@ async fn main() {
|
||||
.with_subcommand("mobilebackup2", mobilebackup2::register())
|
||||
.with_subcommand("mounter", mounter::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("pair", pair::register())
|
||||
.with_subcommand("pcapd", pcapd::register())
|
||||
@@ -214,6 +216,9 @@ async fn main() {
|
||||
"notifications" => {
|
||||
notifications::main(sub_args, provider).await;
|
||||
}
|
||||
"notification_proxy" => {
|
||||
notification_proxy_client::main(sub_args, provider).await;
|
||||
}
|
||||
"os_trace_relay" => {
|
||||
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!(),
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user