Add companion proxy support

This commit is contained in:
Jackson Coxson
2025-08-11 16:40:04 -06:00
parent f8477ed77c
commit c79fb2226a
7 changed files with 326 additions and 159 deletions

View File

@@ -3,7 +3,7 @@ name = "idevice"
description = "A Rust library to interact with services on iOS devices."
authors = ["Jackson Coxson"]
version = "0.1.37"
edition = "2021"
edition = "2024"
license = "MIT"
documentation = "https://docs.rs/idevice"
repository = "https://github.com/jkcoxson/idevice"
@@ -62,6 +62,7 @@ ring = ["rustls/ring", "tokio-rustls/ring"]
afc = ["dep:chrono"]
amfi = []
companion_proxy = []
core_device = ["xpc", "dep:uuid"]
core_device_proxy = ["dep:serde_json", "dep:json", "dep:byteorder"]
crashreportcopymobile = ["afc"]
@@ -95,6 +96,7 @@ xpc = ["dep:indexmap", "dep:uuid"]
full = [
"afc",
"amfi",
"companion_proxy",
"core_device",
"core_device_proxy",
"crashreportcopymobile",

View File

@@ -660,6 +660,10 @@ pub enum IdeviceError {
FfiInvalidString = -61,
#[error("buffer passed is too small - needs {0}, got {1}")]
FfiBufferTooSmall(usize, usize) = -62,
#[error("unsupported watch key")]
UnsupportedWatchKey = -63,
#[error("malformed command")]
MalformedCommand = -64,
}
impl IdeviceError {
@@ -683,6 +687,8 @@ impl IdeviceError {
"UserDeniedPairing" => Some(Self::UserDeniedPairing),
#[cfg(feature = "pair")]
"PasswordProtected" => Some(Self::PasswordProtected),
"UnsupportedWatchKey" => Some(Self::UnsupportedWatchKey),
"MalformedCommand" => Some(Self::MalformedCommand),
"InternalError" => {
let detailed_error = context
.get("DetailedError")
@@ -806,6 +812,8 @@ impl IdeviceError {
IdeviceError::FfiInvalidArg => -60,
IdeviceError::FfiInvalidString => -61,
IdeviceError::FfiBufferTooSmall(_, _) => -62,
IdeviceError::UnsupportedWatchKey => -63,
IdeviceError::MalformedCommand => -64,
}
}
}

View File

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

View File

@@ -2,6 +2,8 @@
pub mod afc;
#[cfg(feature = "amfi")]
pub mod amfi;
#[cfg(feature = "companion_proxy")]
pub mod companion_proxy;
#[cfg(feature = "core_device")]
pub mod core_device;
#[cfg(feature = "core_device_proxy")]