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:
neo
2026-02-14 15:16:26 -05:00
committed by GitHub
parent 54439b85dd
commit bfe44e16e4
11 changed files with 756 additions and 0 deletions

View 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

View 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

View File

@@ -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",

View File

@@ -29,6 +29,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;

View 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) };
}
}

View File

@@ -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",

View File

@@ -865,6 +865,10 @@ 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,
} }
impl IdeviceError { impl IdeviceError {
@@ -1030,6 +1034,9 @@ 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,
} }
} }
} }

View File

@@ -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")]

View 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(())
}
}

View File

@@ -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;
} }

View 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(&notifications)
.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(&notification)
.await
.expect("Failed to post notification");
}
_ => unreachable!(),
}
}