Document modules and public methods

This commit is contained in:
Jackson Coxson
2025-04-06 10:03:09 -06:00
parent 3cb4f468e6
commit 52b2db404f
26 changed files with 1949 additions and 173 deletions

View File

@@ -2,14 +2,14 @@
use std::ffi::c_void; use std::ffi::c_void;
use idevice::{IdeviceError, IdeviceService, lockdown::LockdowndClient}; use idevice::{IdeviceError, IdeviceService, lockdown::LockdownClient};
use crate::{ use crate::{
IdeviceErrorCode, IdeviceHandle, IdevicePairingFile, RUNTIME, IdeviceErrorCode, IdeviceHandle, IdevicePairingFile, RUNTIME,
provider::{TcpProviderHandle, UsbmuxdProviderHandle}, provider::{TcpProviderHandle, UsbmuxdProviderHandle},
}; };
pub struct LockdowndClientHandle(pub LockdowndClient); pub struct LockdowndClientHandle(pub LockdownClient);
/// Connects to lockdownd service using TCP provider /// Connects to lockdownd service using TCP provider
/// ///
@@ -33,10 +33,10 @@ pub unsafe extern "C" fn lockdownd_connect_tcp(
return IdeviceErrorCode::InvalidArg; return IdeviceErrorCode::InvalidArg;
} }
let res: Result<LockdowndClient, IdeviceError> = RUNTIME.block_on(async move { let res: Result<LockdownClient, IdeviceError> = RUNTIME.block_on(async move {
let provider_box = unsafe { Box::from_raw(provider) }; let provider_box = unsafe { Box::from_raw(provider) };
let provider_ref = &provider_box.0; let provider_ref = &provider_box.0;
let result = LockdowndClient::connect(provider_ref).await; let result = LockdownClient::connect(provider_ref).await;
std::mem::forget(provider_box); std::mem::forget(provider_box);
result result
}); });
@@ -76,10 +76,10 @@ pub unsafe extern "C" fn lockdownd_connect_usbmuxd(
return IdeviceErrorCode::InvalidArg; return IdeviceErrorCode::InvalidArg;
} }
let res: Result<LockdowndClient, IdeviceError> = RUNTIME.block_on(async move { let res: Result<LockdownClient, IdeviceError> = RUNTIME.block_on(async move {
let provider_box = unsafe { Box::from_raw(provider) }; let provider_box = unsafe { Box::from_raw(provider) };
let provider_ref = &provider_box.0; let provider_ref = &provider_box.0;
let result = LockdowndClient::connect(provider_ref).await; let result = LockdownClient::connect(provider_ref).await;
std::mem::forget(provider_box); std::mem::forget(provider_box);
result result
}); });
@@ -115,7 +115,7 @@ pub unsafe extern "C" fn lockdownd_new(
return IdeviceErrorCode::InvalidArg; return IdeviceErrorCode::InvalidArg;
} }
let socket = unsafe { Box::from_raw(socket) }.0; let socket = unsafe { Box::from_raw(socket) }.0;
let r = LockdowndClient::new(socket); let r = LockdownClient::new(socket);
let boxed = Box::new(LockdowndClientHandle(r)); let boxed = Box::new(LockdowndClientHandle(r));
unsafe { *client = Box::into_raw(boxed) }; unsafe { *client = Box::into_raw(boxed) };
IdeviceErrorCode::IdeviceSuccess IdeviceErrorCode::IdeviceSuccess

View File

@@ -7,7 +7,7 @@ use log::warn;
use opcode::AfcOpcode; use opcode::AfcOpcode;
use packet::{AfcPacket, AfcPacketHeader}; use packet::{AfcPacket, AfcPacketHeader};
use crate::{lockdown::LockdowndClient, Idevice, IdeviceError, IdeviceService}; use crate::{lockdown::LockdownClient, Idevice, IdeviceError, IdeviceService};
pub mod errors; pub mod errors;
pub mod opcode; pub mod opcode;
@@ -47,7 +47,7 @@ impl IdeviceService for AfcClient {
async fn connect( async fn connect(
provider: &dyn crate::provider::IdeviceProvider, provider: &dyn crate::provider::IdeviceProvider,
) -> Result<Self, IdeviceError> { ) -> Result<Self, IdeviceError> {
let mut lockdown = LockdowndClient::connect(provider).await?; let mut lockdown = LockdownClient::connect(provider).await?;
lockdown lockdown
.start_session(&provider.get_pairing_file().await?) .start_session(&provider.get_pairing_file().await?)
.await?; .await?;

View File

@@ -1,13 +1,28 @@
// Jackson Coxson //! CoreDeviceProxy and CDTunnelPacket utilities for interacting with
//! iOS's CoreDeviceProxy service. This service starts an L3 (TUN) tunnel
//! for "trusted" services introduced in iOS 17.
//!
//! This module handles the construction and parsing of `CDTunnelPacket` messages
//! and manages a handshake and data tunnel to the CoreDeviceProxy daemon on iOS devices.
//!
//! # Overview
//! - `CDTunnelPacket` is used to parse and serialize packets sent over the CoreDeviceProxy channel.
//! - `CoreDeviceProxy` is a service client that initializes the tunnel, handles handshakes,
//! and optionally supports creating a software-based TCP/IP tunnel (behind a feature flag).
//!
//! # Features
//! - `tunnel_tcp_stack`: Enables software TCP/IP tunnel creation using a virtual adapter. See the tcp moduel.
use crate::{lockdown::LockdowndClient, Idevice, IdeviceError, IdeviceService}; use crate::{lockdown::LockdownClient, Idevice, IdeviceError, IdeviceService};
use byteorder::{BigEndian, WriteBytesExt}; use byteorder::{BigEndian, WriteBytesExt};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::io::{self, Write}; use std::io::{self, Write};
/// A representation of a CDTunnel packet used in the CoreDeviceProxy protocol.
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub struct CDTunnelPacket { pub struct CDTunnelPacket {
/// The body of the packet, typically JSON-encoded data.
body: Vec<u8>, body: Vec<u8>,
} }
@@ -15,27 +30,32 @@ impl CDTunnelPacket {
const MAGIC: &'static [u8] = b"CDTunnel"; const MAGIC: &'static [u8] = b"CDTunnel";
/// Parses a byte slice into a `CDTunnelPacket`. /// Parses a byte slice into a `CDTunnelPacket`.
///
/// # Arguments
///
/// * `input` - A byte slice containing the raw packet data.
///
/// # Returns
///
/// * `Ok(CDTunnelPacket)` if the input is a valid packet.
/// * `Err(IdeviceError)` if parsing fails due to invalid magic, length, or size.
pub fn parse(input: &[u8]) -> Result<Self, IdeviceError> { pub fn parse(input: &[u8]) -> Result<Self, IdeviceError> {
if input.len() < Self::MAGIC.len() + 2 { if input.len() < Self::MAGIC.len() + 2 {
return Err(IdeviceError::CdtunnelPacketTooShort); return Err(IdeviceError::CdtunnelPacketTooShort);
} }
// Validate the magic bytes
if &input[0..Self::MAGIC.len()] != Self::MAGIC { if &input[0..Self::MAGIC.len()] != Self::MAGIC {
return Err(IdeviceError::CdtunnelPacketInvalidMagic); return Err(IdeviceError::CdtunnelPacketInvalidMagic);
} }
// Parse the body length
let length_offset = Self::MAGIC.len(); let length_offset = Self::MAGIC.len();
let body_length = let body_length =
u16::from_be_bytes([input[length_offset], input[length_offset + 1]]) as usize; u16::from_be_bytes([input[length_offset], input[length_offset + 1]]) as usize;
// Validate the body length
if input.len() < length_offset + 2 + body_length { if input.len() < length_offset + 2 + body_length {
return Err(IdeviceError::PacketSizeMismatch); return Err(IdeviceError::PacketSizeMismatch);
} }
// Extract the body
let body_start = length_offset + 2; let body_start = length_offset + 2;
let body = input[body_start..body_start + body_length].to_vec(); let body = input[body_start..body_start + body_length].to_vec();
@@ -43,37 +63,55 @@ impl CDTunnelPacket {
} }
/// Serializes the `CDTunnelPacket` into a byte vector. /// Serializes the `CDTunnelPacket` into a byte vector.
///
/// # Returns
///
/// * `Ok(Vec<u8>)` containing the serialized packet.
/// * `Err(io::Error)` if writing to the output buffer fails.
pub fn serialize(&self) -> io::Result<Vec<u8>> { pub fn serialize(&self) -> io::Result<Vec<u8>> {
let mut output = Vec::new(); let mut output = Vec::new();
// Write the magic bytes
output.write_all(Self::MAGIC)?; output.write_all(Self::MAGIC)?;
// Write the body length
output.write_u16::<BigEndian>(self.body.len() as u16)?; output.write_u16::<BigEndian>(self.body.len() as u16)?;
// Write the body
output.write_all(&self.body)?; output.write_all(&self.body)?;
Ok(output) Ok(output)
} }
} }
/// A high-level client for the `com.apple.internal.devicecompute.CoreDeviceProxy` service.
///
/// Handles session negotiation, handshake, and tunnel communication.
pub struct CoreDeviceProxy { pub struct CoreDeviceProxy {
/// The underlying idevice connection used for communication.
pub idevice: Idevice, pub idevice: Idevice,
/// The handshake response received during initialization.
pub handshake: HandshakeResponse, pub handshake: HandshakeResponse,
/// The maximum transmission unit used for reading.
pub mtu: u32, pub mtu: u32,
} }
impl IdeviceService for CoreDeviceProxy { impl IdeviceService for CoreDeviceProxy {
/// Returns the name of the service used for launching the CoreDeviceProxy.
fn service_name() -> &'static str { fn service_name() -> &'static str {
"com.apple.internal.devicecompute.CoreDeviceProxy" "com.apple.internal.devicecompute.CoreDeviceProxy"
} }
/// Connects to the CoreDeviceProxy service
///
/// # Arguments
///
/// * `provider` - An implementation of `IdeviceProvider` that supplies
/// pairing data and socket connections.
///
/// # Returns
///
/// * `Ok(CoreDeviceProxy)` if connection and handshake succeed.
/// * `Err(IdeviceError)` if any step fails.
async fn connect( async fn connect(
provider: &dyn crate::provider::IdeviceProvider, provider: &dyn crate::provider::IdeviceProvider,
) -> Result<Self, IdeviceError> { ) -> Result<Self, IdeviceError> {
let mut lockdown = LockdowndClient::connect(provider).await?; let mut lockdown = LockdownClient::connect(provider).await?;
lockdown lockdown
.start_session(&provider.get_pairing_file().await?) .start_session(&provider.get_pairing_file().await?)
.await?; .await?;
@@ -91,6 +129,7 @@ impl IdeviceService for CoreDeviceProxy {
} }
} }
/// Request sent to initiate the handshake with the CoreDeviceProxy.
#[derive(Serialize)] #[derive(Serialize)]
struct HandshakeRequest { struct HandshakeRequest {
#[serde(rename = "type")] #[serde(rename = "type")]
@@ -98,13 +137,18 @@ struct HandshakeRequest {
mtu: u32, mtu: u32,
} }
/// Parameters returned as part of the handshake response from the proxy server.
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
pub struct ClientParameters { pub struct ClientParameters {
/// The MTU (maximum transmission unit) for the connection.
pub mtu: u16, pub mtu: u16,
/// The IP address assigned to the client.
pub address: String, pub address: String,
/// The subnet mask for the tunnel.
pub netmask: String, pub netmask: String,
} }
/// Handshake response structure received from the CoreDeviceProxy.
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
pub struct HandshakeResponse { pub struct HandshakeResponse {
#[serde(rename = "clientParameters")] #[serde(rename = "clientParameters")]
@@ -120,6 +164,16 @@ pub struct HandshakeResponse {
impl CoreDeviceProxy { impl CoreDeviceProxy {
const DEFAULT_MTU: u32 = 16000; const DEFAULT_MTU: u32 = 16000;
/// Constructs a new `CoreDeviceProxy` by performing a handshake on the given `Idevice`.
///
/// # Arguments
///
/// * `idevice` - The connected `Idevice` socket.
///
/// # Returns
///
/// * `Ok(CoreDeviceProxy)` on successful handshake.
/// * `Err(IdeviceError)` if the handshake fails.
pub async fn new(mut idevice: Idevice) -> Result<Self, IdeviceError> { pub async fn new(mut idevice: Idevice) -> Result<Self, IdeviceError> {
let req = HandshakeRequest { let req = HandshakeRequest {
packet_type: "clientHandshakeRequest".to_string(), packet_type: "clientHandshakeRequest".to_string(),
@@ -152,15 +206,37 @@ impl CoreDeviceProxy {
}) })
} }
/// Sends a raw data packet through the tunnel.
///
/// # Arguments
///
/// * `data` - The raw bytes to send.
///
/// # Returns
///
/// * `Ok(())` if the data is successfully sent.
/// * `Err(IdeviceError)` if sending fails.
pub async fn send(&mut self, data: &[u8]) -> Result<(), IdeviceError> { pub async fn send(&mut self, data: &[u8]) -> Result<(), IdeviceError> {
self.idevice.send_raw(data).await?; self.idevice.send_raw(data).await?;
Ok(()) Ok(())
} }
/// Receives up to `mtu` bytes from the tunnel.
///
/// # Returns
///
/// * `Ok(Vec<u8>)` containing the received data.
/// * `Err(IdeviceError)` if reading fails.
pub async fn recv(&mut self) -> Result<Vec<u8>, IdeviceError> { pub async fn recv(&mut self) -> Result<Vec<u8>, IdeviceError> {
self.idevice.read_any(self.mtu).await self.idevice.read_any(self.mtu).await
} }
/// Creates a software-based TCP tunnel adapter, if the `tunnel_tcp_stack` feature is enabled.
///
/// # Returns
///
/// * `Ok(Adapter)` for the software TCP stack.
/// * `Err(IdeviceError)` if IP parsing or socket extraction fails.
#[cfg(feature = "tunnel_tcp_stack")] #[cfg(feature = "tunnel_tcp_stack")]
pub fn create_software_tunnel(self) -> Result<crate::tcp::adapter::Adapter, IdeviceError> { pub fn create_software_tunnel(self) -> Result<crate::tcp::adapter::Adapter, IdeviceError> {
let our_ip = self let our_ip = self

View File

@@ -1,5 +1,8 @@
// Jackson Coxson //! GDB Remote Debugging Protocol Implementation for iOS Devices
// https://sourceware.org/gdb/current/onlinedocs/gdb.html/Packets.html#Packets //!
//! Provides functionality for communicating with the iOS debug server using the
//! GDB Remote Serial Protocol as documented at:
//! https://sourceware.org/gdb/current/onlinedocs/gdb.html/Packets.html#Packets
use log::debug; use log::debug;
use std::fmt::Write; use std::fmt::Write;
@@ -7,25 +10,47 @@ use tokio::io::{AsyncReadExt, AsyncWriteExt};
use crate::{IdeviceError, ReadWrite}; use crate::{IdeviceError, ReadWrite};
/// The service name for the debug proxy as registered with lockdownd
pub const SERVICE_NAME: &str = "com.apple.internal.dt.remote.debugproxy"; pub const SERVICE_NAME: &str = "com.apple.internal.dt.remote.debugproxy";
/// Client for interacting with the iOS debug proxy service
///
/// Implements the GDB Remote Serial Protocol for communicating with debugserver
/// on iOS devices. Handles packet formatting, checksums, and acknowledgments.
pub struct DebugProxyClient<R: ReadWrite> { pub struct DebugProxyClient<R: ReadWrite> {
/// The underlying socket connection to debugproxy
pub socket: R, pub socket: R,
/// Flag indicating whether ACK mode is disabled
pub noack_mode: bool, pub noack_mode: bool,
} }
/// Represents a debugserver command with arguments
///
/// Commands follow the GDB Remote Serial Protocol format:
/// $<command>[<hex-encoded args>]#<checksum>
pub struct DebugserverCommand { pub struct DebugserverCommand {
/// The command name (e.g. "qSupported", "vCont")
pub name: String, pub name: String,
/// Command arguments that will be hex-encoded
pub argv: Vec<String>, pub argv: Vec<String>,
} }
impl DebugserverCommand { impl DebugserverCommand {
/// Creates a new debugserver command
///
/// # Arguments
/// * `name` - The command name (without leading $)
/// * `argv` - Arguments that will be hex-encoded in the packet
pub fn new(name: String, argv: Vec<String>) -> Self { pub fn new(name: String, argv: Vec<String>) -> Self {
Self { name, argv } Self { name, argv }
} }
} }
impl<R: ReadWrite> DebugProxyClient<R> { impl<R: ReadWrite> DebugProxyClient<R> {
/// Creates a new debug proxy client with default settings
///
/// # Arguments
/// * `socket` - Established connection to debugproxy service
pub fn new(socket: R) -> Self { pub fn new(socket: R) -> Self {
Self { Self {
socket, socket,
@@ -33,10 +58,24 @@ impl<R: ReadWrite> DebugProxyClient<R> {
} }
} }
/// Consumes the client and returns the underlying socket
pub fn into_inner(self) -> R { pub fn into_inner(self) -> R {
self.socket self.socket
} }
/// Sends a command to debugserver and waits for response
///
/// Formats the command according to GDB Remote Serial Protocol:
/// $<command>[<hex-encoded args>]#<checksum>
///
/// # Arguments
/// * `command` - The command and arguments to send
///
/// # Returns
/// The response string if successful, None if no response received
///
/// # Errors
/// Returns `IdeviceError` if communication fails
pub async fn send_command( pub async fn send_command(
&mut self, &mut self,
command: DebugserverCommand, command: DebugserverCommand,
@@ -69,6 +108,16 @@ impl<R: ReadWrite> DebugProxyClient<R> {
Ok(response) Ok(response)
} }
/// Reads a response packet from debugserver
///
/// Handles the GDB Remote Serial Protocol response format:
/// $<data>#<checksum>
///
/// # Returns
/// The response data without protocol framing if successful
///
/// # Errors
/// Returns `IdeviceError` if communication fails or protocol is violated
pub async fn read_response(&mut self) -> Result<Option<String>, IdeviceError> { pub async fn read_response(&mut self) -> Result<Option<String>, IdeviceError> {
let mut buffer = Vec::new(); let mut buffer = Vec::new();
let mut received_char = [0u8; 1]; let mut received_char = [0u8; 1];
@@ -103,11 +152,28 @@ impl<R: ReadWrite> DebugProxyClient<R> {
Ok(Some(response)) Ok(Some(response))
} }
/// Sends raw bytes directly to the debugproxy connection
///
/// # Arguments
/// * `bytes` - The raw bytes to send
///
/// # Errors
/// Returns `IdeviceError` if writing fails
pub async fn send_raw(&mut self, bytes: &[u8]) -> Result<(), IdeviceError> { pub async fn send_raw(&mut self, bytes: &[u8]) -> Result<(), IdeviceError> {
self.socket.write_all(bytes).await?; self.socket.write_all(bytes).await?;
Ok(()) Ok(())
} }
/// Reads raw bytes from the debugproxy connection
///
/// # Arguments
/// * `len` - Maximum number of bytes to read
///
/// # Returns
/// The received data as a string
///
/// # Errors
/// Returns `IdeviceError` if reading fails or data isn't valid UTF-8
pub async fn read(&mut self, len: usize) -> Result<String, IdeviceError> { pub async fn read(&mut self, len: usize) -> Result<String, IdeviceError> {
let mut buf = vec![0; len]; let mut buf = vec![0; len];
let r = self.socket.read(&mut buf).await?; let r = self.socket.read(&mut buf).await?;
@@ -115,6 +181,19 @@ impl<R: ReadWrite> DebugProxyClient<R> {
Ok(String::from_utf8_lossy(&buf[..r]).to_string()) Ok(String::from_utf8_lossy(&buf[..r]).to_string())
} }
/// Sets program arguments using the 'A' command
///
/// Formats arguments according to GDB protocol:
/// A<arglen>,<argnum>,<argdata>
///
/// # Arguments
/// * `argv` - Program arguments to set
///
/// # Returns
/// The debugserver response
///
/// # Errors
/// Returns `IdeviceError` if arguments are empty or communication fails
pub async fn set_argv(&mut self, argv: Vec<String>) -> Result<String, IdeviceError> { pub async fn set_argv(&mut self, argv: Vec<String>) -> Result<String, IdeviceError> {
if argv.is_empty() { if argv.is_empty() {
return Err(IdeviceError::InvalidArgument); return Err(IdeviceError::InvalidArgument);
@@ -157,26 +236,45 @@ impl<R: ReadWrite> DebugProxyClient<R> {
Ok(response) Ok(response)
} }
/// Sends an acknowledgment (+)
///
/// # Errors
/// Returns `IdeviceError` if writing fails
pub async fn send_ack(&mut self) -> Result<(), IdeviceError> { pub async fn send_ack(&mut self) -> Result<(), IdeviceError> {
self.socket.write_all(b"+").await?; self.socket.write_all(b"+").await?;
Ok(()) Ok(())
} }
/// Sends a negative acknowledgment (-)
///
/// # Errors
/// Returns `IdeviceError` if writing fails
pub async fn send_noack(&mut self) -> Result<(), IdeviceError> { pub async fn send_noack(&mut self) -> Result<(), IdeviceError> {
self.socket.write_all(b"-").await?; self.socket.write_all(b"-").await?;
Ok(()) Ok(())
} }
/// Enables or disables ACK mode
///
/// When disabled, the client won't expect or send acknowledgments
///
/// # Arguments
/// * `enabled` - Whether to enable ACK mode
pub fn set_ack_mode(&mut self, enabled: bool) { pub fn set_ack_mode(&mut self, enabled: bool) {
self.noack_mode = !enabled; self.noack_mode = !enabled;
} }
} }
/// Calculates the checksum for a GDB protocol packet
///
/// The checksum is computed as the modulo 256 sum of all characters
/// between '$' and '#', formatted as two lowercase hex digits.
fn calculate_checksum(data: &str) -> String { fn calculate_checksum(data: &str) -> String {
let checksum = data.bytes().fold(0u8, |acc, byte| acc.wrapping_add(byte)); let checksum = data.bytes().fold(0u8, |acc, byte| acc.wrapping_add(byte));
format!("{:02x}", checksum) format!("{:02x}", checksum)
} }
/// Hex-encodes bytes as uppercase string
fn hex_encode(bytes: &[u8]) -> String { fn hex_encode(bytes: &[u8]) -> String {
bytes.iter().fold(String::new(), |mut output, b| { bytes.iter().fold(String::new(), |mut output, b| {
let _ = write!(output, "{b:02X}"); let _ = write!(output, "{b:02X}");
@@ -185,16 +283,21 @@ fn hex_encode(bytes: &[u8]) -> String {
} }
impl From<String> for DebugserverCommand { impl From<String> for DebugserverCommand {
/// Converts a string into a debugserver command by splitting on whitespace
///
/// The first token becomes the command name, remaining tokens become arguments
fn from(s: String) -> Self { fn from(s: String) -> Self {
// Split string into command and arguments
let mut split = s.split_whitespace(); let mut split = s.split_whitespace();
let command = split.next().unwrap_or("").to_string(); let command = split.next().unwrap_or("").to_string();
let arguments: Vec<String> = split.map(|s| s.to_string()).collect(); let arguments: Vec<String> = split.map(|s| s.to_string()).collect();
Self::new(command, arguments) Self::new(command, arguments)
} }
} }
impl From<&str> for DebugserverCommand { impl From<&str> for DebugserverCommand {
/// Converts a string slice into a debugserver command
fn from(s: &str) -> DebugserverCommand { fn from(s: &str) -> DebugserverCommand {
s.to_string().into() s.to_string().into()
} }
} }

View File

@@ -1,24 +1,72 @@
// Jackson Coxson //! Location Simulation service client for iOS instruments protocol.
//!
//! This module abstracts simulating the device's location over
//! the remote server protocol. Note that a connection must be
//! maintained to keep location simulated.
//!
//! # Example
//! ```rust,no_run
//! #[tokio::main]
//! async fn main() -> Result<(), IdeviceError> {
//! // Create base client (implementation specific)
//! let mut client = RemoteServerClient::new(your_transport);
//!
//! // Create process control client
//! let mut process_control = ProcessControlClient::new(&mut client).await?;
//!
//! // Launch an app
//! let pid = process_control.launch_app(
//! "com.example.app",
//! None, // Environment variables
//! None, // Arguments
//! false, // Start suspended
//! true // Kill existing
//! ).await?;
//! println!("Launched app with PID: {}", pid);
//!
//! // Disable memory limits
//! process_control.disable_memory_limit(pid).await?;
//!
//! // Kill the app
//! process_control.kill_app(pid).await?;
//!
//! Ok(())
//! }
//! ```
use plist::Value; use plist::Value;
use crate::{dvt::message::AuxValue, IdeviceError, ReadWrite}; use crate::{
dvt::{
use super::remote_server::{Channel, RemoteServerClient}; message::AuxValue,
remote_server::{Channel, RemoteServerClient},
},
IdeviceError, ReadWrite,
};
const IDENTIFIER: &str = "com.apple.instruments.server.services.LocationSimulation"; const IDENTIFIER: &str = "com.apple.instruments.server.services.LocationSimulation";
/// A client for the location simulation service
pub struct LocationSimulationClient<'a, R: ReadWrite> { pub struct LocationSimulationClient<'a, R: ReadWrite> {
/// The underlying channel used for communication
channel: Channel<'a, R>, channel: Channel<'a, R>,
} }
impl<'a, R: ReadWrite> LocationSimulationClient<'a, R> { impl<'a, R: ReadWrite> LocationSimulationClient<'a, R> {
/// Opens a new channel on the remote server client for location simulation
///
/// # Arguments
/// * `client` - The remote server client to connect with
///
/// # Returns
/// The client on success, IdeviceError on failure
pub async fn new(client: &'a mut RemoteServerClient<R>) -> Result<Self, IdeviceError> { pub async fn new(client: &'a mut RemoteServerClient<R>) -> Result<Self, IdeviceError> {
let channel = client.make_channel(IDENTIFIER).await?; // Drop `&mut client` before continuing let channel = client.make_channel(IDENTIFIER).await?; // Drop `&mut client` before continuing
Ok(Self { channel }) Ok(Self { channel })
} }
/// Clears the set GPS location
pub async fn clear(&mut self) -> Result<(), IdeviceError> { pub async fn clear(&mut self) -> Result<(), IdeviceError> {
let method = Value::String("stopLocationSimulation".into()); let method = Value::String("stopLocationSimulation".into());
@@ -29,6 +77,14 @@ impl<'a, R: ReadWrite> LocationSimulationClient<'a, R> {
Ok(()) Ok(())
} }
/// Sets the GPS location
///
/// # Arguments
/// * `latitude` - The f64 latitude value
/// * `longitude` - The f64 longitude value
///
/// # Errors
/// Returns an IdeviceError on failure
pub async fn set(&mut self, latitude: f64, longitude: f64) -> Result<(), IdeviceError> { pub async fn set(&mut self, latitude: f64, longitude: f64) -> Result<(), IdeviceError> {
let method = Value::String("simulateLocationWithLatitude:longitude:".into()); let method = Value::String("simulateLocationWithLatitude:longitude:".into());

View File

@@ -1,68 +1,172 @@
// Jackson Coxson //! Instruments protocol message format implementation
// Messages contain: //!
// - 32 byte header //! This module handles the serialization and deserialization of messages used in
// - 16 byte payload header //! the iOS instruments protocol. The message format consists of:
// - Optional auxiliary //! - 32-byte message header
// - 16 byte aux header, useless //! - 16-byte payload header
// - Aux data //! - Optional auxiliary data section
// - Payload (NSKeyedArchive) //! - Payload data (typically NSKeyedArchive format)
//!
//! # Message Structure
//! ```text
//! +---------------------+
//! | MessageHeader | 32 bytes
//! +---------------------+
//! | PayloadHeader | 16 bytes
//! +---------------------+
//! | AuxHeader | 16 bytes (if aux present)
//! | Aux data | variable length
//! +---------------------+
//! | Payload data | variable length (NSKeyedArchive)
//! +---------------------+
//! ```
//!
//! # Example
//! ```rust,no_run
//! use plist::Value;
//! use your_crate::IdeviceError;
//! use your_crate::dvt::message::{Message, MessageHeader, PayloadHeader, AuxValue};
//!
//! # #[tokio::main]
//! # async fn main() -> Result<(), IdeviceError> {
//! // Create a new message
//! let header = MessageHeader::new(
//! 1, // fragment_id
//! 1, // fragment_count
//! 123, // identifier
//! 0, // conversation_index
//! 42, // channel
//! true // expects_reply
//! );
//!
//! let message = Message::new(
//! header,
//! PayloadHeader::method_invocation(),
//! Some(AuxValue::from_values(vec![
//! AuxValue::String("param".into()),
//! AuxValue::U32(123),
//! ])),
//! Some(Value::String("data".into()))
//! );
//!
//! // Serialize message
//! let bytes = message.serialize();
//!
//! // Deserialize message (from async reader)
//! # let mut reader = &bytes[..];
//! let deserialized = Message::from_reader(&mut reader).await?;
//! # Ok(())
//! # }
use plist::Value; use plist::Value;
use tokio::io::{AsyncRead, AsyncReadExt}; use tokio::io::{AsyncRead, AsyncReadExt};
use crate::IdeviceError; use crate::IdeviceError;
/// Message header containing metadata about the message
///
/// 32-byte structure that appears at the start of every message
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct MessageHeader { pub struct MessageHeader {
magic: u32, // 0x795b3d1f /// Magic number identifying the protocol (0x1F3D5B79)
header_len: u32, // will always be 32 bytes magic: u32,
/// Length of this header (always 32)
header_len: u32,
/// Fragment identifier for multipart messages
fragment_id: u16, fragment_id: u16,
/// Total number of fragments
fragment_count: u16, fragment_count: u16,
length: u32, // Length of of the payload /// Total length of payload (headers + aux + data)
length: u32,
/// Unique message identifier
identifier: u32, identifier: u32,
/// Conversation tracking index
conversation_index: u32, conversation_index: u32,
/// Channel number this message belongs to
pub channel: u32, pub channel: u32,
/// Whether a reply is expected
expects_reply: bool, expects_reply: bool,
} }
/// Payload header containing information about the message contents
///
/// 16-byte structure following the message header
#[derive(Debug, Default, Clone, PartialEq)] #[derive(Debug, Default, Clone, PartialEq)]
pub struct PayloadHeader { pub struct PayloadHeader {
/// Flags controlling message processing
flags: u32, flags: u32,
/// Length of auxiliary data section
aux_length: u32, aux_length: u32,
/// Total length of payload (aux + data)
total_length: u64, total_length: u64,
} }
/// Header for auxiliary data section
///
/// 16-byte structure preceding auxiliary data
#[derive(Debug, Default, PartialEq)] #[derive(Debug, Default, PartialEq)]
pub struct AuxHeader { pub struct AuxHeader {
/// Buffer size hint (often 496)
buffer_size: u32, buffer_size: u32,
/// Unknown field (typically 0)
unknown: u32, unknown: u32,
/// Actual size of auxiliary data
aux_size: u32, aux_size: u32,
/// Unknown field (typically 0)
unknown2: u32, unknown2: u32,
} }
/// Auxiliary data container
///
/// Contains a header and a collection of typed values
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub struct Aux { pub struct Aux {
/// Auxiliary data header
pub header: AuxHeader, pub header: AuxHeader,
/// Collection of auxiliary values
pub values: Vec<AuxValue>, pub values: Vec<AuxValue>,
} }
/// Typed auxiliary value that can be included in messages
#[derive(PartialEq)] #[derive(PartialEq)]
pub enum AuxValue { pub enum AuxValue {
String(String), // 0x01 /// UTF-8 string value (type 0x01)
Array(Vec<u8>), // 0x02 String(String),
U32(u32), // 0x03 /// Raw byte array (type 0x02)
I64(i64), // 0x06 Array(Vec<u8>),
/// 32-bit unsigned integer (type 0x03)
U32(u32),
/// 64-bit signed integer (type 0x06)
I64(i64),
} }
/// Complete protocol message
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub struct Message { pub struct Message {
/// Message metadata header
pub message_header: MessageHeader, pub message_header: MessageHeader,
/// Payload description header
pub payload_header: PayloadHeader, pub payload_header: PayloadHeader,
/// Optional auxiliary data
pub aux: Option<Aux>, pub aux: Option<Aux>,
/// Optional payload data (typically NSKeyedArchive)
pub data: Option<Value>, pub data: Option<Value>,
} }
impl Aux { impl Aux {
/// Parses auxiliary data from bytes
///
/// # Arguments
/// * `bytes` - Raw byte slice containing auxiliary data
///
/// # Returns
/// * `Ok(Aux)` - Parsed auxiliary data
/// * `Err(IdeviceError)` - If parsing fails
///
/// # Errors
/// * `IdeviceError::NotEnoughBytes` if input is too short
/// * `IdeviceError::UnknownAuxValueType` for unsupported types
/// * `IdeviceError` for other parsing failures
pub fn from_bytes(bytes: Vec<u8>) -> Result<Self, IdeviceError> { pub fn from_bytes(bytes: Vec<u8>) -> Result<Self, IdeviceError> {
if bytes.len() < 16 { if bytes.len() < 16 {
return Err(IdeviceError::NotEnoughBytes(bytes.len(), 24)); return Err(IdeviceError::NotEnoughBytes(bytes.len(), 24));
@@ -129,8 +233,12 @@ impl Aux {
Ok(Self { header, values }) Ok(Self { header, values })
} }
// Creates the default struct /// Creates new auxiliary data from values
// Note that the header isn't updated until serialization ///
/// Note: Header fields are populated during serialization
///
/// # Arguments
/// * `values` - Collection of auxiliary values to include
pub fn from_values(values: Vec<AuxValue>) -> Self { pub fn from_values(values: Vec<AuxValue>) -> Self {
Self { Self {
header: AuxHeader::default(), header: AuxHeader::default(),
@@ -138,7 +246,9 @@ impl Aux {
} }
} }
/// Serializes the values with the correctly sized header /// Serializes auxiliary data to bytes
///
/// Includes properly formatted header with updated size fields
pub fn serialize(&self) -> Vec<u8> { pub fn serialize(&self) -> Vec<u8> {
let mut values_payload = Vec::new(); let mut values_payload = Vec::new();
for v in self.values.iter() { for v in self.values.iter() {
@@ -180,14 +290,27 @@ impl Aux {
} }
impl AuxValue { impl AuxValue {
// Returns an array AuxType /// Creates an auxiliary value containing NSKeyedArchived data
///
/// # Arguments
/// * `v` - Plist value to archive
pub fn archived_value(v: impl Into<plist::Value>) -> Self { pub fn archived_value(v: impl Into<plist::Value>) -> Self {
Self::Array(ns_keyed_archive::encode::encode_to_bytes(v.into()).expect("Failed to encode")) Self::Array(ns_keyed_archive::encode::encode_to_bytes(v.into()).expect("Failed to encode"))
} }
} }
impl MessageHeader { impl MessageHeader {
/// Creates a new header. Note that during serialization, the length will be updated /// Creates a new message header
///
/// Note: Length field is updated during message serialization
///
/// # Arguments
/// * `fragment_id` - Identifier for message fragments
/// * `fragment_count` - Total fragments in message
/// * `identifier` - Unique message ID
/// * `conversation_index` - Conversation tracking number
/// * `channel` - Channel number
/// * `expects_reply` - Whether response is expected
pub fn new( pub fn new(
fragment_id: u16, fragment_id: u16,
fragment_count: u16, fragment_count: u16,
@@ -209,6 +332,7 @@ impl MessageHeader {
} }
} }
/// Serializes header to bytes
pub fn serialize(&self) -> Vec<u8> { pub fn serialize(&self) -> Vec<u8> {
let mut res = Vec::new(); let mut res = Vec::new();
res.extend_from_slice(&self.magic.to_le_bytes()); res.extend_from_slice(&self.magic.to_le_bytes());
@@ -226,10 +350,12 @@ impl MessageHeader {
} }
impl PayloadHeader { impl PayloadHeader {
/// Creates a new payload header
pub fn new() -> Self { pub fn new() -> Self {
Self::default() Self::default()
} }
/// Serializes header to bytes
pub fn serialize(&self) -> Vec<u8> { pub fn serialize(&self) -> Vec<u8> {
let mut res = Vec::new(); let mut res = Vec::new();
res.extend_from_slice(&self.flags.to_le_bytes()); res.extend_from_slice(&self.flags.to_le_bytes());
@@ -239,6 +365,7 @@ impl PayloadHeader {
res res
} }
/// Creates header for method invocation messages
pub fn method_invocation() -> Self { pub fn method_invocation() -> Self {
Self { Self {
flags: 2, flags: 2,
@@ -246,12 +373,24 @@ impl PayloadHeader {
} }
} }
/// Updates flags to indicate reply expectation
pub fn apply_expects_reply_map(&mut self) { pub fn apply_expects_reply_map(&mut self) {
self.flags |= 0x1000 self.flags |= 0x1000
} }
} }
impl Message { impl Message {
/// Reads and parses a message from an async reader
///
/// # Arguments
/// * `reader` - Async reader to read from
///
/// # Returns
/// * `Ok(Message)` - Parsed message
/// * `Err(IdeviceError)` - If reading/parsing fails
///
/// # Errors
/// * Various IdeviceError variants for IO and parsing failures
pub async fn from_reader<R: AsyncRead + Unpin>(reader: &mut R) -> Result<Self, IdeviceError> { pub async fn from_reader<R: AsyncRead + Unpin>(reader: &mut R) -> Result<Self, IdeviceError> {
let mut buf = [0u8; 32]; let mut buf = [0u8; 32];
reader.read_exact(&mut buf).await?; reader.read_exact(&mut buf).await?;
@@ -304,6 +443,13 @@ impl Message {
}) })
} }
/// Creates a new message
///
/// # Arguments
/// * `message_header` - Message metadata
/// * `payload_header` - Payload description
/// * `aux` - Optional auxiliary data
/// * `data` - Optional payload data
pub fn new( pub fn new(
message_header: MessageHeader, message_header: MessageHeader,
payload_header: PayloadHeader, payload_header: PayloadHeader,
@@ -318,6 +464,9 @@ impl Message {
} }
} }
/// Serializes message to bytes
///
/// Updates length fields in headers automatically
pub fn serialize(&self) -> Vec<u8> { pub fn serialize(&self) -> Vec<u8> {
let aux = match &self.aux { let aux = match &self.aux {
Some(a) => a.serialize(), Some(a) => a.serialize(),

View File

@@ -1,4 +1,38 @@
// Jackson Coxson //! Process Control service client for iOS instruments protocol.
//!
//! This module provides a client for interacting with the process control service
//! on iOS devices through the instruments protocol. It allows launching, killing,
//! and managing processes on the device.
//!
//! # Example
//! ```rust,no_run
//! #[tokio::main]
//! async fn main() -> Result<(), IdeviceError> {
//! // Create base client (implementation specific)
//! let mut client = RemoteServerClient::new(your_transport);
//!
//! // Create process control client
//! let mut process_control = ProcessControlClient::new(&mut client).await?;
//!
//! // Launch an app
//! let pid = process_control.launch_app(
//! "com.example.app",
//! None, // Environment variables
//! None, // Arguments
//! false, // Start suspended
//! true // Kill existing
//! ).await?;
//! println!("Launched app with PID: {}", pid);
//!
//! // Disable memory limits
//! process_control.disable_memory_limit(pid).await?;
//!
//! // Kill the app
//! process_control.kill_app(pid).await?;
//!
//! Ok(())
//! }
//! ```
use log::warn; use log::warn;
use plist::{Dictionary, Value}; use plist::{Dictionary, Value};
@@ -9,17 +43,49 @@ use super::remote_server::{Channel, RemoteServerClient};
const IDENTIFIER: &str = "com.apple.instruments.server.services.processcontrol"; const IDENTIFIER: &str = "com.apple.instruments.server.services.processcontrol";
/// Client for process control operations on iOS devices
///
/// Provides methods for launching, killing, and managing processes through the
/// instruments protocol. Each instance maintains its own communication channel.
pub struct ProcessControlClient<'a, R: ReadWrite> { pub struct ProcessControlClient<'a, R: ReadWrite> {
/// The underlying channel for communication
channel: Channel<'a, R>, channel: Channel<'a, R>,
} }
impl<'a, R: ReadWrite> ProcessControlClient<'a, R> { impl<'a, R: ReadWrite> ProcessControlClient<'a, R> {
/// Creates a new ProcessControlClient
///
/// # Arguments
/// * `client` - The base RemoteServerClient to use
///
/// # Returns
/// * `Ok(ProcessControlClient)` - Connected client instance
/// * `Err(IdeviceError)` - If channel creation fails
///
/// # Errors
/// * Propagates errors from channel creation
pub async fn new(client: &'a mut RemoteServerClient<R>) -> Result<Self, IdeviceError> { pub async fn new(client: &'a mut RemoteServerClient<R>) -> Result<Self, IdeviceError> {
let channel = client.make_channel(IDENTIFIER).await?; // Drop `&mut client` before continuing let channel = client.make_channel(IDENTIFIER).await?; // Drop `&mut client` before continuing
Ok(Self { channel }) Ok(Self { channel })
} }
/// Launches an application on the device
///
/// # Arguments
/// * `bundle_id` - The bundle identifier of the app to launch
/// * `env_vars` - Optional environment variables dictionary
/// * `arguments` - Optional launch arguments dictionary
/// * `start_suspended` - Whether to start the process suspended
/// * `kill_existing` - Whether to kill existing instances of the app
///
/// # Returns
/// * `Ok(u64)` - PID of the launched process
/// * `Err(IdeviceError)` - If launch fails
///
/// # Errors
/// * `IdeviceError::UnexpectedResponse` if server response is invalid
/// * Other communication or serialization errors
pub async fn launch_app( pub async fn launch_app(
&mut self, &mut self,
bundle_id: impl Into<String>, bundle_id: impl Into<String>,
@@ -76,6 +142,17 @@ impl<'a, R: ReadWrite> ProcessControlClient<'a, R> {
} }
} }
/// Kills a running process
///
/// # Arguments
/// * `pid` - Process ID to kill
///
/// # Returns
/// * `Ok(())` - If kill request was sent successfully
/// * `Err(IdeviceError)` - If communication fails
///
/// # Note
/// This method doesn't wait for confirmation that the process was killed.
pub async fn kill_app(&mut self, pid: u64) -> Result<(), IdeviceError> { pub async fn kill_app(&mut self, pid: u64) -> Result<(), IdeviceError> {
self.channel self.channel
.call_method( .call_method(
@@ -88,6 +165,19 @@ impl<'a, R: ReadWrite> ProcessControlClient<'a, R> {
Ok(()) Ok(())
} }
/// Disables memory limits for a process
///
/// # Arguments
/// * `pid` - Process ID to modify
///
/// # Returns
/// * `Ok(())` - If memory limits were disabled
/// * `Err(IdeviceError)` - If operation fails
///
/// # Errors
/// * `IdeviceError::DisableMemoryLimitFailed` if device reports failure
/// * `IdeviceError::UnexpectedResponse` for invalid responses
/// * Other communication errors
pub async fn disable_memory_limit(&mut self, pid: u64) -> Result<(), IdeviceError> { pub async fn disable_memory_limit(&mut self, pid: u64) -> Result<(), IdeviceError> {
self.channel self.channel
.call_method( .call_method(

View File

@@ -1,4 +1,53 @@
// Jackson Coxson //! Remote Server Client implementation for iOS instruments protocol.
//!
//! This module provides a client for communicating with iOS devices through the
//! remote server protocol used by instruments. It handles channel management and
//! message passing between the host and device.
//!
//! Remote Server communicates via NSKeyedArchives. These archives are binary plists
//! formatted specifically for naive recreation at the target.
//! Requests are sent as method calls to objective C objects on the device.
//!
//! # Overview
//! The client manages multiple communication channels and provides methods for:
//! - Creating new channels
//! - Sending method calls
//! - Reading responses
//!
//! # Example
//! ```rust,no_run
//! use std::sync::Arc;
//! use tokio::net::TcpStream;
//! use your_crate::{ReadWrite, IdeviceError};
//! use your_crate::instruments::RemoteServerClient;
//!
//! #[tokio::main]
//! async fn main() -> Result<(), IdeviceError> {
//! // Establish connection to device over the tunnel (see XPC docs)
//! let transport = TcpStream::connect("1.2.3.4:1234").await?;
//!
//! // Create client
//! let mut client = RemoteServerClient::new(transport);
//!
//! // Read the first message
//! client.read_message(0).await?;
//!
//! // Call a method on root channel
//! client.call_method(
//! 0,
//! Some("someMethod"),
//! Some(vec![AuxValue::String("param".into())]),
//! true
//! ).await?;
//!
//! // Read response
//! let response = client.read_message(0).await?;
//! println!("Got response: {:?}", response);
//!
//!
//! Ok(())
//! }
//! ```
use std::collections::{HashMap, VecDeque}; use std::collections::{HashMap, VecDeque};
@@ -6,27 +55,46 @@ use log::{debug, warn};
use tokio::io::AsyncWriteExt; use tokio::io::AsyncWriteExt;
use crate::{ use crate::{
dvt::message::{Aux, Message, MessageHeader, PayloadHeader}, dvt::message::{Aux, AuxValue, Message, MessageHeader, PayloadHeader},
IdeviceError, ReadWrite, IdeviceError, ReadWrite,
}; };
use super::message::AuxValue; /// Message type identifier for instruments protocol
pub const INSTRUMENTS_MESSAGE_TYPE: u32 = 2; pub const INSTRUMENTS_MESSAGE_TYPE: u32 = 2;
/// Client for communicating with iOS remote server protocol
///
/// Manages multiple communication channels and handles message serialization/deserialization.
/// Each channel operates independently and maintains its own message queue.
pub struct RemoteServerClient<R: ReadWrite> { pub struct RemoteServerClient<R: ReadWrite> {
/// The underlying device connection
idevice: R, idevice: R,
/// Counter for message identifiers
current_message: u32, current_message: u32,
/// Next available channel number
new_channel: u32, new_channel: u32,
/// Map of channel numbers to their message queues
channels: HashMap<u32, VecDeque<Message>>, channels: HashMap<u32, VecDeque<Message>>,
} }
/// Handle to a specific communication channel
///
/// Provides channel-specific operations for use on the remote server client.
pub struct Channel<'a, R: ReadWrite> { pub struct Channel<'a, R: ReadWrite> {
/// Reference to parent client
client: &'a mut RemoteServerClient<R>, client: &'a mut RemoteServerClient<R>,
/// Channel number this handle operates on
channel: u32, channel: u32,
} }
impl<R: ReadWrite> RemoteServerClient<R> { impl<R: ReadWrite> RemoteServerClient<R> {
/// Creates a new RemoteServerClient with the given transport
///
/// # Arguments
/// * `idevice` - The underlying transport implementing ReadWrite
///
/// # Returns
/// A new client instance with root channel (0) initialized
pub fn new(idevice: R) -> Self { pub fn new(idevice: R) -> Self {
let mut channels = HashMap::new(); let mut channels = HashMap::new();
channels.insert(0, VecDeque::new()); channels.insert(0, VecDeque::new());
@@ -38,10 +106,12 @@ impl<R: ReadWrite> RemoteServerClient<R> {
} }
} }
/// Consumes the client and returns the underlying transport
pub fn into_inner(self) -> R { pub fn into_inner(self) -> R {
self.idevice self.idevice
} }
/// Returns a handle to the root channel (channel 0)
pub fn root_channel(&mut self) -> Channel<R> { pub fn root_channel(&mut self) -> Channel<R> {
Channel { Channel {
client: self, client: self,
@@ -49,6 +119,18 @@ impl<R: ReadWrite> RemoteServerClient<R> {
} }
} }
/// Creates a new channel with the given identifier
///
/// # Arguments
/// * `identifier` - String identifier for the new channel
///
/// # Returns
/// * `Ok(Channel)` - Handle to the new channel
/// * `Err(IdeviceError)` - If channel creation fails
///
/// # Errors
/// * `IdeviceError::UnexpectedResponse` if server responds with unexpected data
/// * Other IO or serialization errors
pub async fn make_channel( pub async fn make_channel(
&mut self, &mut self,
identifier: impl Into<String>, identifier: impl Into<String>,
@@ -89,6 +171,20 @@ impl<R: ReadWrite> RemoteServerClient<R> {
}) })
} }
/// Calls a method on the specified channel
///
/// # Arguments
/// * `channel` - Channel number to call method on
/// * `data` - Optional method data (plist value)
/// * `args` - Optional arguments for the method
/// * `expect_reply` - Whether to expect a response
///
/// # Returns
/// * `Ok(())` - If method was successfully called
/// * `Err(IdeviceError)` - If call failed
///
/// # Errors
/// IO or serialization errors
pub async fn call_method( pub async fn call_method(
&mut self, &mut self,
channel: u32, channel: u32,
@@ -110,6 +206,20 @@ impl<R: ReadWrite> RemoteServerClient<R> {
Ok(()) Ok(())
} }
/// Reads the next message from the specified channel
///
/// Checks cached messages first, then reads from transport if needed.
///
/// # Arguments
/// * `channel` - Channel number to read from
///
/// # Returns
/// * `Ok(Message)` - The received message
/// * `Err(IdeviceError)` - If read failed
///
/// # Errors
/// * `IdeviceError::UnknownChannel` if channel doesn't exist
/// * Other IO or deserialization errors
pub async fn read_message(&mut self, channel: u32) -> Result<Message, IdeviceError> { pub async fn read_message(&mut self, channel: u32) -> Result<Message, IdeviceError> {
// Determine if we already have a message cached // Determine if we already have a message cached
let cache = match self.channels.get_mut(&channel) { let cache = match self.channels.get_mut(&channel) {
@@ -140,10 +250,32 @@ impl<R: ReadWrite> RemoteServerClient<R> {
} }
impl<R: ReadWrite> Channel<'_, R> { impl<R: ReadWrite> Channel<'_, R> {
/// Reads the next message from the remote server on this channel
///
/// # Returns
/// * `Ok(Message)` - The received message
/// * `Err(IdeviceError)` - If read failed
///
/// # Errors
/// * `IdeviceError::UnknownChannel` if channel doesn't exist
/// * Other IO or deserialization errors
pub async fn read_message(&mut self) -> Result<Message, IdeviceError> { pub async fn read_message(&mut self) -> Result<Message, IdeviceError> {
self.client.read_message(self.channel).await self.client.read_message(self.channel).await
} }
/// Calls a method on the specified channel
///
/// # Arguments
/// * `method` - Optional method data (plist value)
/// * `args` - Optional arguments for the method
/// * `expect_reply` - Whether to expect a response
///
/// # Returns
/// * `Ok(())` - If method was successfully called
/// * `Err(IdeviceError)` - If call failed
///
/// # Errors
/// IO or serialization errors
pub async fn call_method( pub async fn call_method(
&mut self, &mut self,
method: Option<impl Into<plist::Value>>, method: Option<impl Into<plist::Value>>,

View File

@@ -1,21 +1,49 @@
// Jackson Coxson //! iOS Device Heartbeat Service Abstraction
// Abstractions for the heartbeat service on iOS //!
//! iOS automatically closes service connections if there is no heartbeat client connected and
//! responding.
use crate::{lockdown::LockdowndClient, Idevice, IdeviceError, IdeviceService}; use crate::{lockdown::LockdownClient, Idevice, IdeviceError, IdeviceService};
/// Client for interacting with the iOS device heartbeat service
///
/// The heartbeat service provides a keep-alive mechanism and can notify when
/// the device enters sleep mode or disconnects.
/// Note that a running heartbeat client is required to access other services on the device.
/// Implements the standard "Marco-Polo" protocol
/// where the host sends "Polo" in response to the device's "Marco".
pub struct HeartbeatClient { pub struct HeartbeatClient {
/// The underlying device connection with established heartbeat service
pub idevice: Idevice, pub idevice: Idevice,
} }
impl IdeviceService for HeartbeatClient { impl IdeviceService for HeartbeatClient {
/// Returns the heartbeat service name as registered with lockdownd
fn service_name() -> &'static str { fn service_name() -> &'static str {
"com.apple.mobile.heartbeat" "com.apple.mobile.heartbeat"
} }
/// Establishes a connection to the heartbeat service
///
/// # Arguments
/// * `provider` - Device connection provider
///
/// # Returns
/// A connected `HeartbeatClient` instance
///
/// # Errors
/// Returns `IdeviceError` if any step of the connection process fails
///
/// # Process
/// 1. Connects to lockdownd service
/// 2. Starts a lockdown session
/// 3. Requests the heartbeat service port
/// 4. Establishes connection to the heartbeat port
/// 5. Optionally starts TLS if required by service
async fn connect( async fn connect(
provider: &dyn crate::provider::IdeviceProvider, provider: &dyn crate::provider::IdeviceProvider,
) -> Result<Self, IdeviceError> { ) -> Result<Self, IdeviceError> {
let mut lockdown = LockdowndClient::connect(provider).await?; let mut lockdown = LockdownClient::connect(provider).await?;
lockdown lockdown
.start_session(&provider.get_pairing_file().await?) .start_session(&provider.get_pairing_file().await?)
.await?; .await?;
@@ -34,10 +62,31 @@ impl IdeviceService for HeartbeatClient {
} }
impl HeartbeatClient { impl HeartbeatClient {
/// Creates a new heartbeat client from an existing device connection
///
/// # Arguments
/// * `idevice` - Pre-established device connection
pub fn new(idevice: Idevice) -> Self { pub fn new(idevice: Idevice) -> Self {
Self { idevice } Self { idevice }
} }
/// Waits for and processes a "Marco" message from the device
///
/// This will either:
/// - Return the heartbeat interval if received
/// - Return a timeout error if no message received in time
/// - Return a sleep notification if device is going to sleep
///
/// # Arguments
/// * `interval` - Timeout in seconds to wait for message
///
/// # Returns
/// The heartbeat interval in seconds if successful
///
/// # Errors
/// - `HeartbeatTimeout` if no message received before interval
/// - `HeartbeatSleepyTime` if device is going to sleep
/// - `UnexpectedResponse` for malformed messages
pub async fn get_marco(&mut self, interval: u64) -> Result<u64, IdeviceError> { pub async fn get_marco(&mut self, interval: u64) -> Result<u64, IdeviceError> {
// Get a plist or wait for the interval // Get a plist or wait for the interval
let rec = tokio::select! { let rec = tokio::select! {
@@ -67,6 +116,13 @@ impl HeartbeatClient {
} }
} }
/// Sends a "Polo" response to the device
///
/// This acknowledges receipt of a "Marco" message and maintains
/// the connection keep-alive.
///
/// # Errors
/// Returns `IdeviceError` if the message fails to send
pub async fn send_polo(&mut self) -> Result<(), IdeviceError> { pub async fn send_polo(&mut self) -> Result<(), IdeviceError> {
let mut req = plist::Dictionary::new(); let mut req = plist::Dictionary::new();
req.insert("Command".into(), "Polo".into()); req.insert("Command".into(), "Polo".into());

View File

@@ -1,23 +1,48 @@
// Jackson Coxson //! iOS Installation Proxy Service Client
// Incomplete implementation for installation_proxy //!
//! Provides functionality for interacting with the installation_proxy service on iOS devices,
//! which allows querying and managing installed applications.
use std::collections::HashMap; use std::collections::HashMap;
use crate::{lockdown::LockdowndClient, Idevice, IdeviceError, IdeviceService}; use crate::{lockdown::LockdownClient, Idevice, IdeviceError, IdeviceService};
/// Client for interacting with the iOS installation proxy service
///
/// This service provides access to information about installed applications
/// and can perform application management operations.
pub struct InstallationProxyClient { pub struct InstallationProxyClient {
/// The underlying device connection with established installation_proxy service
pub idevice: Idevice, pub idevice: Idevice,
} }
impl IdeviceService for InstallationProxyClient { impl IdeviceService for InstallationProxyClient {
/// Returns the installation proxy service name as registered with lockdownd
fn service_name() -> &'static str { fn service_name() -> &'static str {
"com.apple.mobile.installation_proxy" "com.apple.mobile.installation_proxy"
} }
/// Establishes a connection to the installation proxy service
///
/// # Arguments
/// * `provider` - Device connection provider
///
/// # Returns
/// A connected `InstallationProxyClient` instance
///
/// # Errors
/// Returns `IdeviceError` if any step of the connection process fails
///
/// # Process
/// 1. Connects to lockdownd service
/// 2. Starts a lockdown session
/// 3. Requests the installation proxy service port
/// 4. Establishes connection to the service port
/// 5. Optionally starts TLS if required by service
async fn connect( async fn connect(
provider: &dyn crate::provider::IdeviceProvider, provider: &dyn crate::provider::IdeviceProvider,
) -> Result<Self, IdeviceError> { ) -> Result<Self, IdeviceError> {
let mut lockdown = LockdowndClient::connect(provider).await?; let mut lockdown = LockdownClient::connect(provider).await?;
lockdown lockdown
.start_session(&provider.get_pairing_file().await?) .start_session(&provider.get_pairing_file().await?)
.await?; .await?;
@@ -35,14 +60,39 @@ impl IdeviceService for InstallationProxyClient {
} }
impl InstallationProxyClient { impl InstallationProxyClient {
/// Creates a new installation proxy client from an existing device connection
///
/// # Arguments
/// * `idevice` - Pre-established device connection
pub fn new(idevice: Idevice) -> Self { pub fn new(idevice: Idevice) -> Self {
Self { idevice } Self { idevice }
} }
/// Gets installed apps on the device /// Retrieves information about installed applications
///
/// # Arguments /// # Arguments
/// `application_type` - The application type to filter by /// * `application_type` - Optional filter for application type:
/// `bundle_identifiers` - The identifiers to filter by /// - "System" for system applications
/// - "User" for user-installed applications
/// - "Any" for all applications (default)
/// * `bundle_identifiers` - Optional list of specific bundle IDs to query
///
/// # Returns
/// A HashMap mapping bundle identifiers to application information plist values
///
/// # Errors
/// Returns `IdeviceError` if:
/// - Communication fails
/// - The response is malformed
/// - The service returns an error
///
/// # Example
/// ```rust
/// let apps = client.get_apps(Some("User".to_string()), None).await?;
/// for (bundle_id, info) in apps {
/// println!("{}: {:?}", bundle_id, info);
/// }
/// ```
pub async fn get_apps( pub async fn get_apps(
&mut self, &mut self,
application_type: Option<String>, application_type: Option<String>,
@@ -55,7 +105,7 @@ impl InstallationProxyClient {
.into_iter() .into_iter()
.map(plist::Value::String) .map(plist::Value::String)
.collect::<Vec<plist::Value>>(); .collect::<Vec<plist::Value>>();
options.insert("BundleIDs".into(), ids.into()).unwrap(); options.insert("BundleIDs".into(), ids.into());
} }
options.insert("ApplicationType".into(), application_type.into()); options.insert("ApplicationType".into(), application_type.into());
@@ -75,3 +125,4 @@ impl InstallationProxyClient {
} }
} }
} }

View File

@@ -12,7 +12,7 @@ pub mod dvt;
#[cfg(feature = "heartbeat")] #[cfg(feature = "heartbeat")]
pub mod heartbeat; pub mod heartbeat;
#[cfg(feature = "xpc")] #[cfg(feature = "xpc")]
pub mod http2; mod http2;
#[cfg(feature = "installation_proxy")] #[cfg(feature = "installation_proxy")]
pub mod installation_proxy; pub mod installation_proxy;
pub mod lockdown; pub mod lockdown;
@@ -49,24 +49,58 @@ use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
pub use util::{pretty_print_dictionary, pretty_print_plist}; pub use util::{pretty_print_dictionary, pretty_print_plist};
/// A trait combining all required characteristics for a device communication socket
///
/// This serves as a convenience trait for any type that can be used as an asynchronous
/// read/write socket for device communication. Combines common async I/O traits with
/// thread safety and debugging requirements.
///
/// Tokio's TcpStream and UnixStream implement this trait.
pub trait ReadWrite: AsyncRead + AsyncWrite + Unpin + Send + Sync + std::fmt::Debug {} pub trait ReadWrite: AsyncRead + AsyncWrite + Unpin + Send + Sync + std::fmt::Debug {}
// Blanket implementation for any compatible type
impl<T: AsyncRead + AsyncWrite + Unpin + Send + Sync + std::fmt::Debug> ReadWrite for T {} impl<T: AsyncRead + AsyncWrite + Unpin + Send + Sync + std::fmt::Debug> ReadWrite for T {}
/// Interface for services that can be connected to on an iOS device
///
/// Implement this trait to define new services that can be accessed through the
/// device connection protocol.
pub trait IdeviceService: Sized { pub trait IdeviceService: Sized {
/// Returns the service name as advertised by the device
fn service_name() -> &'static str; fn service_name() -> &'static str;
/// Establishes a connection to this service
///
/// # Arguments
/// * `provider` - The device provider that can supply connections
fn connect( fn connect(
provider: &dyn IdeviceProvider, provider: &dyn IdeviceProvider,
) -> impl std::future::Future<Output = Result<Self, IdeviceError>> + Send; ) -> impl std::future::Future<Output = Result<Self, IdeviceError>> + Send;
} }
/// Type alias for boxed device connection sockets
///
/// Used to enable dynamic dispatch of different connection types while maintaining
/// the required ReadWrite characteristics.
pub type IdeviceSocket = Box<dyn ReadWrite>; pub type IdeviceSocket = Box<dyn ReadWrite>;
/// Main handle for communicating with an iOS device
///
/// Manages the connection socket and provides methods for common device operations
/// and message exchange.
pub struct Idevice { pub struct Idevice {
socket: Option<Box<dyn ReadWrite>>, // in a box for now to use the ReadWrite trait for further uses /// The underlying connection socket, boxed for dynamic dispatch
socket: Option<Box<dyn ReadWrite>>,
/// Unique label identifying this connection
label: String, label: String,
} }
impl Idevice { impl Idevice {
/// Creates a new device connection handle
///
/// # Arguments
/// * `socket` - The established connection socket
/// * `label` - Unique identifier for this connection
pub fn new(socket: Box<dyn ReadWrite>, label: impl Into<String>) -> Self { pub fn new(socket: Box<dyn ReadWrite>, label: impl Into<String>) -> Self {
Self { Self {
socket: Some(socket), socket: Some(socket),
@@ -74,6 +108,15 @@ impl Idevice {
} }
} }
/// Queries the device type
///
/// Sends a QueryType request and parses the response
///
/// # Returns
/// The device type string on success
///
/// # Errors
/// Returns `IdeviceError` if communication fails or response is invalid
pub async fn get_type(&mut self) -> Result<String, IdeviceError> { pub async fn get_type(&mut self) -> Result<String, IdeviceError> {
let mut req = plist::Dictionary::new(); let mut req = plist::Dictionary::new();
req.insert("Label".into(), self.label.clone().into()); req.insert("Label".into(), self.label.clone().into());
@@ -87,6 +130,12 @@ impl Idevice {
} }
} }
/// Performs RSD (Remote Service Discovery) check-in procedure
///
/// Establishes the basic service connection protocol
///
/// # Errors
/// Returns `IdeviceError` if the protocol sequence isn't followed correctly
pub async fn rsd_checkin(&mut self) -> Result<(), IdeviceError> { pub async fn rsd_checkin(&mut self) -> Result<(), IdeviceError> {
let mut req = plist::Dictionary::new(); let mut req = plist::Dictionary::new();
req.insert("Label".into(), self.label.clone().into()); req.insert("Label".into(), self.label.clone().into());
@@ -116,7 +165,13 @@ impl Idevice {
Ok(()) Ok(())
} }
/// Sends a plist to the socket /// Sends a plist-formatted message to the device
///
/// # Arguments
/// * `message` - The plist value to send
///
/// # Errors
/// Returns `IdeviceError` if serialization or transmission fails
async fn send_plist(&mut self, message: plist::Value) -> Result<(), IdeviceError> { async fn send_plist(&mut self, message: plist::Value) -> Result<(), IdeviceError> {
if let Some(socket) = &mut self.socket { if let Some(socket) = &mut self.socket {
debug!("Sending plist: {}", pretty_print_plist(&message)); debug!("Sending plist: {}", pretty_print_plist(&message));
@@ -135,11 +190,30 @@ impl Idevice {
} }
} }
/// Sends raw bytes to the socket /// Sends raw binary data to the device
///
/// # Arguments
/// * `message` - The bytes to send
///
/// # Errors
/// Returns `IdeviceError` if transmission fails
async fn send_raw(&mut self, message: &[u8]) -> Result<(), IdeviceError> { async fn send_raw(&mut self, message: &[u8]) -> Result<(), IdeviceError> {
self.send_raw_with_progress(message, |_| async {}, ()).await self.send_raw_with_progress(message, |_| async {}, ()).await
} }
/// Sends raw binary data with progress callbacks
///
/// # Arguments
/// * `message` - The bytes to send
/// * `callback` - Progress callback invoked after each chunk
/// * `state` - Arbitrary state passed to callback
///
/// # Type Parameters
/// * `Fut` - Future type returned by callback
/// * `S` - Type of state passed to callback
///
/// # Errors
/// Returns `IdeviceError` if transmission fails
async fn send_raw_with_progress<Fut, S>( async fn send_raw_with_progress<Fut, S>(
&mut self, &mut self,
message: &[u8], message: &[u8],
@@ -165,7 +239,16 @@ impl Idevice {
} }
} }
/// Reads raw bytes from the socket /// Reads exactly `len` bytes from the device
///
/// # Arguments
/// * `len` - Exact number of bytes to read
///
/// # Returns
/// The received bytes
///
/// # Errors
/// Returns `IdeviceError` if reading fails or connection is closed prematurely
async fn read_raw(&mut self, len: usize) -> Result<Vec<u8>, IdeviceError> { async fn read_raw(&mut self, len: usize) -> Result<Vec<u8>, IdeviceError> {
if let Some(socket) = &mut self.socket { if let Some(socket) = &mut self.socket {
let mut buf = vec![0; len]; let mut buf = vec![0; len];
@@ -176,7 +259,16 @@ impl Idevice {
} }
} }
/// Reads bytes from the socket until it doesn't /// Reads up to `max_size` bytes from the device
///
/// # Arguments
/// * `max_size` - Maximum number of bytes to read
///
/// # Returns
/// The received bytes (may be shorter than max_size)
///
/// # Errors
/// Returns `IdeviceError` if reading fails
async fn read_any(&mut self, max_size: u32) -> Result<Vec<u8>, IdeviceError> { async fn read_any(&mut self, max_size: u32) -> Result<Vec<u8>, IdeviceError> {
if let Some(socket) = &mut self.socket { if let Some(socket) = &mut self.socket {
let mut buf = vec![0; max_size as usize]; let mut buf = vec![0; max_size as usize];
@@ -187,7 +279,13 @@ impl Idevice {
} }
} }
/// Read a plist from the socket /// Reads a plist-formatted message from the device
///
/// # Returns
/// The parsed plist dictionary
///
/// # Errors
/// Returns `IdeviceError` if reading, parsing fails, or device reports an error
async fn read_plist(&mut self) -> Result<plist::Dictionary, IdeviceError> { async fn read_plist(&mut self) -> Result<plist::Dictionary, IdeviceError> {
if let Some(socket) = &mut self.socket { if let Some(socket) = &mut self.socket {
debug!("Reading response size"); debug!("Reading response size");
@@ -213,7 +311,13 @@ impl Idevice {
} }
} }
/// Wraps current connection in TLS /// Upgrades the connection to TLS using device pairing credentials
///
/// # Arguments
/// * `pairing_file` - Contains the device's identity and certificates
///
/// # Errors
/// Returns `IdeviceError` if TLS handshake fails or credentials are invalid
pub async fn start_session( pub async fn start_session(
&mut self, &mut self,
pairing_file: &pairing_file::PairingFile, pairing_file: &pairing_file::PairingFile,
@@ -235,6 +339,7 @@ impl Idevice {
} }
} }
/// Comprehensive error type for all device communication failures
#[derive(Error, Debug)] #[derive(Error, Debug)]
#[non_exhaustive] #[non_exhaustive]
pub enum IdeviceError { pub enum IdeviceError {
@@ -362,6 +467,14 @@ pub enum IdeviceError {
} }
impl IdeviceError { impl IdeviceError {
/// Converts a device-reported error string to a typed error
///
/// # Arguments
/// * `e` - The error string from device
/// * `context` - Full plist context containing additional error details
///
/// # Returns
/// Some(IdeviceError) if the string maps to a known error type, None otherwise
fn from_device_error_type(e: &str, context: &plist::Dictionary) -> Option<Self> { fn from_device_error_type(e: &str, context: &plist::Dictionary) -> Option<Self> {
match e { match e {
"GetProhibited" => Some(Self::GetProhibited), "GetProhibited" => Some(Self::GetProhibited),

View File

@@ -1,5 +1,7 @@
// Jackson Coxson //! iOS Lockdown Service Client
// Abstractions for lockdownd //!
//! Provides functionality for interacting with the lockdown service on iOS devices,
//! which is the primary service for device management and service discovery.
use log::error; use log::error;
use plist::Value; use plist::Value;
@@ -7,15 +9,33 @@ use serde::{Deserialize, Serialize};
use crate::{pairing_file, Idevice, IdeviceError, IdeviceService}; use crate::{pairing_file, Idevice, IdeviceError, IdeviceService};
pub struct LockdowndClient { /// Client for interacting with the iOS lockdown service
///
/// This is the primary service for device management and provides:
/// - Access to device information and settings
/// - Service discovery and port allocation
/// - Session management and security
pub struct LockdownClient {
/// The underlying device connection with established lockdown service
pub idevice: crate::Idevice, pub idevice: crate::Idevice,
} }
impl IdeviceService for LockdowndClient { impl IdeviceService for LockdownClient {
/// Returns the lockdown service name as registered with the device
fn service_name() -> &'static str { fn service_name() -> &'static str {
"com.apple.mobile.lockdown" "com.apple.mobile.lockdown"
} }
/// Establishes a connection to the lockdown service
///
/// # Arguments
/// * `provider` - Device connection provider
///
/// # Returns
/// A connected `LockdownClient` instance
///
/// # Errors
/// Returns `IdeviceError` if connection fails
async fn connect( async fn connect(
provider: &dyn crate::provider::IdeviceProvider, provider: &dyn crate::provider::IdeviceProvider,
) -> Result<Self, IdeviceError> { ) -> Result<Self, IdeviceError> {
@@ -24,22 +44,48 @@ impl IdeviceService for LockdowndClient {
} }
} }
/// Internal structure for lockdown protocol requests
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")] #[serde(rename_all = "PascalCase")]
struct LockdowndRequest { struct LockdownRequest {
label: String, label: String,
key: Option<String>, key: Option<String>,
request: String, request: String,
} }
impl LockdowndClient { impl LockdownClient {
/// The default TCP port for the lockdown service
pub const LOCKDOWND_PORT: u16 = 62078; pub const LOCKDOWND_PORT: u16 = 62078;
/// Creates a new lockdown client from an existing device connection
///
/// # Arguments
/// * `idevice` - Pre-established device connection
pub fn new(idevice: Idevice) -> Self { pub fn new(idevice: Idevice) -> Self {
Self { idevice } Self { idevice }
} }
/// Retrieves a specific value from the device
///
/// # Arguments
/// * `value` - The name of the value to retrieve (e.g., "DeviceName")
///
/// # Returns
/// The requested value as a plist Value
///
/// # Errors
/// Returns `IdeviceError` if:
/// - Communication fails
/// - The requested value doesn't exist
/// - The response is malformed
///
/// # Example
/// ```rust
/// let device_name = client.get_value("DeviceName").await?;
/// println!("Device name: {:?}", device_name);
/// ```
pub async fn get_value(&mut self, value: impl Into<String>) -> Result<Value, IdeviceError> { pub async fn get_value(&mut self, value: impl Into<String>) -> Result<Value, IdeviceError> {
let req = LockdowndRequest { let req = LockdownRequest {
label: self.idevice.label.clone(), label: self.idevice.label.clone(),
key: Some(value.into()), key: Some(value.into()),
request: "GetValue".to_string(), request: "GetValue".to_string(),
@@ -53,8 +99,25 @@ impl LockdowndClient {
} }
} }
/// Retrieves all available values from the device
///
/// # Returns
/// A dictionary containing all device values
///
/// # Errors
/// Returns `IdeviceError` if:
/// - Communication fails
/// - The response is malformed
///
/// # Example
/// ```rust
/// let all_values = client.get_all_values().await?;
/// for (key, value) in all_values {
/// println!("{}: {:?}", key, value);
/// }
/// ```
pub async fn get_all_values(&mut self) -> Result<plist::Dictionary, IdeviceError> { pub async fn get_all_values(&mut self) -> Result<plist::Dictionary, IdeviceError> {
let req = LockdowndRequest { let req = LockdownRequest {
label: self.idevice.label.clone(), label: self.idevice.label.clone(),
key: None, key: None,
request: "GetValue".to_string(), request: "GetValue".to_string(),
@@ -68,7 +131,19 @@ impl LockdowndClient {
} }
} }
/// Starts a TLS session with the client /// Starts a secure TLS session with the device
///
/// # Arguments
/// * `pairing_file` - Contains the device's identity and certificates
///
/// # Returns
/// `Ok(())` on successful session establishment
///
/// # Errors
/// Returns `IdeviceError` if:
/// - No connection is established
/// - The session request is denied
/// - TLS handshake fails
pub async fn start_session( pub async fn start_session(
&mut self, &mut self,
pairing_file: &pairing_file::PairingFile, pairing_file: &pairing_file::PairingFile,
@@ -116,11 +191,21 @@ impl LockdowndClient {
Ok(()) Ok(())
} }
/// Asks lockdownd to pretty please start a service for us /// Requests to start a service on the device
///
/// # Arguments /// # Arguments
/// `identifier` - The identifier for the service you want to start /// * `identifier` - The service identifier (e.g., "com.apple.debugserver")
///
/// # Returns /// # Returns
/// The port number and whether to enable SSL on success, `IdeviceError` on failure /// A tuple containing:
/// - The port number where the service is available
/// - A boolean indicating whether SSL should be used
///
/// # Errors
/// Returns `IdeviceError` if:
/// - The service cannot be started
/// - The response is malformed
/// - The requested service doesn't exist
pub async fn start_service( pub async fn start_service(
&mut self, &mut self,
identifier: impl Into<String>, identifier: impl Into<String>,
@@ -144,7 +229,7 @@ impl LockdowndClient {
if let Some(port) = port.as_unsigned() { if let Some(port) = port.as_unsigned() {
Ok((port as u16, ssl)) Ok((port as u16, ssl))
} else { } else {
error!("Port isn't an unsiged integer!"); error!("Port isn't an unsigned integer!");
Err(IdeviceError::UnexpectedResponse) Err(IdeviceError::UnexpectedResponse)
} }
} }
@@ -156,7 +241,8 @@ impl LockdowndClient {
} }
} }
impl From<Idevice> for LockdowndClient { impl From<Idevice> for LockdownClient {
/// Converts an existing device connection into a lockdown client
fn from(value: Idevice) -> Self { fn from(value: Idevice) -> Self {
Self::new(value) Self::new(value)
} }

View File

@@ -1,24 +1,51 @@
// Jackson Coxson //! iOS Mobile Installation Agent (misagent) Client
// Incomplete implementation for installation_proxy //!
//! Provides functionality for interacting with the misagent service on iOS devices,
//! which manages provisioning profiles and certificates.
use log::warn; use log::warn;
use plist::Dictionary; use plist::Dictionary;
use crate::{lockdown::LockdowndClient, Idevice, IdeviceError, IdeviceService}; use crate::{lockdown::LockdownClient, Idevice, IdeviceError, IdeviceService};
/// Client for interacting with the iOS misagent service
///
/// The misagent service handles:
/// - Installation of provisioning profiles
/// - Removal of provisioning profiles
/// - Querying installed profiles
pub struct MisagentClient { pub struct MisagentClient {
/// The underlying device connection with established misagent service
pub idevice: Idevice, pub idevice: Idevice,
} }
impl IdeviceService for MisagentClient { impl IdeviceService for MisagentClient {
/// Returns the misagent service name as registered with lockdownd
fn service_name() -> &'static str { fn service_name() -> &'static str {
"com.apple.misagent" "com.apple.misagent"
} }
/// Establishes a connection to the misagent service
///
/// # Arguments
/// * `provider` - Device connection provider
///
/// # Returns
/// A connected `MisagentClient` instance
///
/// # Errors
/// Returns `IdeviceError` if any step of the connection process fails
///
/// # Process
/// 1. Connects to lockdownd service
/// 2. Starts a lockdown session
/// 3. Requests the misagent service port
/// 4. Establishes connection to the service port
/// 5. Optionally starts TLS if required by service
async fn connect( async fn connect(
provider: &dyn crate::provider::IdeviceProvider, provider: &dyn crate::provider::IdeviceProvider,
) -> Result<Self, IdeviceError> { ) -> Result<Self, IdeviceError> {
let mut lockdown = LockdowndClient::connect(provider).await?; let mut lockdown = LockdownClient::connect(provider).await?;
lockdown lockdown
.start_session(&provider.get_pairing_file().await?) .start_session(&provider.get_pairing_file().await?)
.await?; .await?;
@@ -36,15 +63,37 @@ impl IdeviceService for MisagentClient {
} }
impl MisagentClient { impl MisagentClient {
/// Creates a new misagent client from an existing device connection
///
/// # Arguments
/// * `idevice` - Pre-established device connection
pub fn new(idevice: Idevice) -> Self { pub fn new(idevice: Idevice) -> Self {
Self { idevice } Self { idevice }
} }
/// Installs a provisioning profile on the device
///
/// # Arguments
/// * `profile` - The provisioning profile data to install
///
/// # Returns
/// `Ok(())` on successful installation
///
/// # Errors
/// Returns `IdeviceError` if:
/// - Communication fails
/// - The profile is invalid
/// - Installation is not permitted
///
/// # Example
/// ```rust
/// let profile_data = std::fs::read("profile.mobileprovision")?;
/// client.install(profile_data).await?;
/// ```
pub async fn install(&mut self, profile: Vec<u8>) -> Result<(), IdeviceError> { pub async fn install(&mut self, profile: Vec<u8>) -> Result<(), IdeviceError> {
let mut req = Dictionary::new(); let mut req = Dictionary::new();
req.insert("MessageType".into(), "Install".into()); req.insert("MessageType".into(), "Install".into());
req.insert("Profile".into(), plist::Value::Data(profile)); req.insert("Profile".into(), plist::Value::Data(profile));
req.insert("ProfileType".into(), "Provisioning".into()); req.insert("ProfileType".into(), "Provisioning".into());
self.idevice self.idevice
@@ -73,11 +122,28 @@ impl MisagentClient {
} }
} }
/// Removes a provisioning profile from the device
///
/// # Arguments
/// * `id` - The UUID of the profile to remove
///
/// # Returns
/// `Ok(())` on successful removal
///
/// # Errors
/// Returns `IdeviceError` if:
/// - Communication fails
/// - The profile doesn't exist
/// - Removal is not permitted
///
/// # Example
/// ```rust
/// client.remove("asdf").await?;
/// ```
pub async fn remove(&mut self, id: &str) -> Result<(), IdeviceError> { pub async fn remove(&mut self, id: &str) -> Result<(), IdeviceError> {
let mut req = Dictionary::new(); let mut req = Dictionary::new();
req.insert("MessageType".into(), "Remove".into()); req.insert("MessageType".into(), "Remove".into());
req.insert("ProfileID".into(), id.into()); req.insert("ProfileID".into(), id.into());
req.insert("ProfileType".into(), "Provisioning".into()); req.insert("ProfileType".into(), "Provisioning".into());
self.idevice self.idevice
@@ -106,10 +172,26 @@ impl MisagentClient {
} }
} }
/// Retrieves all provisioning profiles from the device
///
/// # Returns
/// A vector containing raw profile data for each installed profile
///
/// # Errors
/// Returns `IdeviceError` if:
/// - Communication fails
/// - The response is malformed
///
/// # Example
/// ```rust
/// let profiles = client.copy_all().await?;
/// for profile in profiles {
/// println!("Profile size: {} bytes", profile.len());
/// }
/// ```
pub async fn copy_all(&mut self) -> Result<Vec<Vec<u8>>, IdeviceError> { pub async fn copy_all(&mut self) -> Result<Vec<Vec<u8>>, IdeviceError> {
let mut req = Dictionary::new(); let mut req = Dictionary::new();
req.insert("MessageType".into(), "CopyAll".into()); req.insert("MessageType".into(), "CopyAll".into());
req.insert("ProfileType".into(), "Provisioning".into()); req.insert("ProfileType".into(), "Provisioning".into());
self.idevice self.idevice
@@ -137,3 +219,4 @@ impl MisagentClient {
} }
} }
} }

View File

@@ -1,28 +1,58 @@
// Jackson Coxson //! iOS Image Mounter Client
//!
//! Provides functionality for mounting disk images on iOS devices, including:
//! - Developer disk images
//! - Personalized images
//! - Cryptex images
//!
//! Handles the complete workflow from uploading images to mounting them with proper signatures.
use log::debug; use log::debug;
use crate::{lockdown::LockdowndClient, Idevice, IdeviceError, IdeviceService}; use crate::{lockdown::LockdownClient, Idevice, IdeviceError, IdeviceService};
#[cfg(feature = "tss")] #[cfg(feature = "tss")]
use crate::tss::TSSRequest; use crate::tss::TSSRequest;
/// Manages mounted images on the idevice. /// Client for interacting with the iOS mobile image mounter service
/// NOTE: A lockdown client must be established and queried after establishing a mounter client, or ///
/// the device will stop responding to requests. /// Manages mounted images on the device.
///
/// # Important Note
/// A lockdown client must be established and queried after establishing a mounter client,
/// or the device will stop responding to requests.
pub struct ImageMounter { pub struct ImageMounter {
/// The underlying device connection with established image mounter service
idevice: Idevice, idevice: Idevice,
} }
impl IdeviceService for ImageMounter { impl IdeviceService for ImageMounter {
/// Returns the image mounter service name as registered with lockdownd
fn service_name() -> &'static str { fn service_name() -> &'static str {
"com.apple.mobile.mobile_image_mounter" "com.apple.mobile.mobile_image_mounter"
} }
/// Establishes a connection to the image mounter service
///
/// # Arguments
/// * `provider` - Device connection provider
///
/// # Returns
/// A connected `ImageMounter` instance
///
/// # Errors
/// Returns `IdeviceError` if any step of the connection process fails
///
/// # Process
/// 1. Connects to lockdownd service
/// 2. Starts a lockdown session
/// 3. Requests the image mounter service port
/// 4. Establishes connection to the service port
/// 5. Optionally starts TLS if required by service
async fn connect( async fn connect(
provider: &dyn crate::provider::IdeviceProvider, provider: &dyn crate::provider::IdeviceProvider,
) -> Result<Self, IdeviceError> { ) -> Result<Self, IdeviceError> {
let mut lockdown = LockdowndClient::connect(provider).await?; let mut lockdown = LockdownClient::connect(provider).await?;
lockdown lockdown
.start_session(&provider.get_pairing_file().await?) .start_session(&provider.get_pairing_file().await?)
.await?; .await?;
@@ -41,10 +71,21 @@ impl IdeviceService for ImageMounter {
} }
impl ImageMounter { impl ImageMounter {
/// Creates a new image mounter client from an existing device connection
///
/// # Arguments
/// * `idevice` - Pre-established device connection
pub fn new(idevice: Idevice) -> Self { pub fn new(idevice: Idevice) -> Self {
Self { idevice } Self { idevice }
} }
/// Retrieves a list of currently mounted devices
///
/// # Returns
/// A vector of plist values describing mounted devices
///
/// # Errors
/// Returns `IdeviceError` if communication fails or response is malformed
pub async fn copy_devices(&mut self) -> Result<Vec<plist::Value>, IdeviceError> { pub async fn copy_devices(&mut self) -> Result<Vec<plist::Value>, IdeviceError> {
let mut req = plist::Dictionary::new(); let mut req = plist::Dictionary::new();
req.insert("Command".into(), "CopyDevices".into()); req.insert("Command".into(), "CopyDevices".into());
@@ -59,7 +100,16 @@ impl ImageMounter {
} }
} }
/// Looks up an image and returns the signature /// Looks up an image by type and returns its signature
///
/// # Arguments
/// * `image_type` - The type of image to lookup (e.g., "Developer")
///
/// # Returns
/// The image signature if found
///
/// # Errors
/// Returns `IdeviceError::NotFound` if image doesn't exist
pub async fn lookup_image( pub async fn lookup_image(
&mut self, &mut self,
image_type: impl Into<String>, image_type: impl Into<String>,
@@ -79,6 +129,15 @@ impl ImageMounter {
} }
} }
/// Uploads an image to the device
///
/// # Arguments
/// * `image_type` - Type of image being uploaded
/// * `image` - The image data
/// * `signature` - Signature for the image
///
/// # Errors
/// Returns `IdeviceError` if upload fails
pub async fn upload_image( pub async fn upload_image(
&mut self, &mut self,
image_type: impl Into<String>, image_type: impl Into<String>,
@@ -89,6 +148,21 @@ impl ImageMounter {
.await .await
} }
/// Uploads an image with progress callbacks
///
/// # Arguments
/// * `image_type` - Type of image being uploaded
/// * `image` - The image data
/// * `signature` - Signature for the image
/// * `callback` - Progress callback
/// * `state` - State to pass to callback
///
/// # Type Parameters
/// * `Fut` - Future type returned by callback
/// * `S` - Type of state passed to callback
///
/// # Errors
/// Returns `IdeviceError` if upload fails
pub async fn upload_image_with_progress<Fut, S>( pub async fn upload_image_with_progress<Fut, S>(
&mut self, &mut self,
image_type: impl Into<String>, image_type: impl Into<String>,
@@ -149,6 +223,16 @@ impl ImageMounter {
Ok(()) Ok(())
} }
/// Mounts an image on the device
///
/// # Arguments
/// * `image_type` - Type of image to mount
/// * `signature` - Signature for the image
/// * `trust_cache` - Optional trust cache data
/// * `info_plist` - Optional info plist for the image
///
/// # Errors
/// Returns `IdeviceError` if mounting fails
pub async fn mount_image( pub async fn mount_image(
&mut self, &mut self,
image_type: impl Into<String>, image_type: impl Into<String>,
@@ -187,9 +271,15 @@ impl ImageMounter {
Ok(()) Ok(())
} }
/// Unmounts an image at a specified path. /// Unmounts an image at the specified path
/// Use ``/Developer`` for pre-iOS 17 developer images. ///
/// Use ``/System/Developer`` for personalized images. /// # Arguments
/// * `mount_path` - Path where image is mounted:
/// - `/Developer` for pre-iOS 17 developer images
/// - `/System/Developer` for personalized images
///
/// # Errors
/// Returns `IdeviceError` if unmounting fails
pub async fn unmount_image( pub async fn unmount_image(
&mut self, &mut self,
mount_path: impl Into<String>, mount_path: impl Into<String>,
@@ -209,8 +299,20 @@ impl ImageMounter {
} }
} }
/// Queries the personalization manifest from the device. /// Queries the personalization manifest from the device
///
/// # Important
/// On failure, the socket must be closed and reestablished. /// On failure, the socket must be closed and reestablished.
///
/// # Arguments
/// * `image_type` - Type of image to query manifest for
/// * `signature` - Signature of the image
///
/// # Returns
/// The personalization manifest data
///
/// # Errors
/// Returns `IdeviceError` if query fails
pub async fn query_personalization_manifest( pub async fn query_personalization_manifest(
&mut self, &mut self,
image_type: impl Into<String>, image_type: impl Into<String>,
@@ -234,6 +336,13 @@ impl ImageMounter {
} }
} }
/// Queries the developer mode status of the device
///
/// # Returns
/// `true` if developer mode is enabled, `false` otherwise
///
/// # Errors
/// Returns `IdeviceError` if query fails
pub async fn query_developer_mode_status(&mut self) -> Result<bool, IdeviceError> { pub async fn query_developer_mode_status(&mut self) -> Result<bool, IdeviceError> {
let mut req = plist::Dictionary::new(); let mut req = plist::Dictionary::new();
req.insert("Command".into(), "QueryDeveloperModeStatus".into()); req.insert("Command".into(), "QueryDeveloperModeStatus".into());
@@ -248,6 +357,16 @@ impl ImageMounter {
} }
} }
/// Queries the nonce value from the device
///
/// # Arguments
/// * `personalized_image_type` - Optional image type to get nonce for
///
/// # Returns
/// The nonce value
///
/// # Errors
/// Returns `IdeviceError` if query fails
pub async fn query_nonce( pub async fn query_nonce(
&mut self, &mut self,
personalized_image_type: Option<String>, personalized_image_type: Option<String>,
@@ -268,6 +387,16 @@ impl ImageMounter {
} }
} }
/// Queries personalization identifiers from the device
///
/// # Arguments
/// * `image_type` - Optional image type to get identifiers for
///
/// # Returns
/// Dictionary of personalization identifiers
///
/// # Errors
/// Returns `IdeviceError` if query fails
pub async fn query_personalization_identifiers( pub async fn query_personalization_identifiers(
&mut self, &mut self,
image_type: Option<String>, image_type: Option<String>,
@@ -288,6 +417,10 @@ impl ImageMounter {
} }
} }
/// Rolls the personalization nonce on the device
///
/// # Errors
/// Returns `IdeviceError` if operation fails
pub async fn roll_personalization_nonce(&mut self) -> Result<(), IdeviceError> { pub async fn roll_personalization_nonce(&mut self) -> Result<(), IdeviceError> {
let mut req = plist::Dictionary::new(); let mut req = plist::Dictionary::new();
req.insert("Command".into(), "RollPersonalizationNonce".into()); req.insert("Command".into(), "RollPersonalizationNonce".into());
@@ -298,6 +431,10 @@ impl ImageMounter {
Ok(()) Ok(())
} }
/// Rolls the cryptex nonce on the device
///
/// # Errors
/// Returns `IdeviceError` if operation fails
pub async fn roll_cryptex_nonce(&mut self) -> Result<(), IdeviceError> { pub async fn roll_cryptex_nonce(&mut self) -> Result<(), IdeviceError> {
let mut req = plist::Dictionary::new(); let mut req = plist::Dictionary::new();
req.insert("Command".into(), "RollCryptexNonce".into()); req.insert("Command".into(), "RollCryptexNonce".into());
@@ -308,6 +445,14 @@ impl ImageMounter {
Ok(()) Ok(())
} }
/// Mounts a developer disk image
///
/// # Arguments
/// * `image` - The developer disk image data
/// * `signature` - Signature for the image
///
/// # Errors
/// Returns `IdeviceError` if mounting fails
pub async fn mount_developer( pub async fn mount_developer(
&mut self, &mut self,
image: &[u8], image: &[u8],
@@ -321,6 +466,18 @@ impl ImageMounter {
} }
#[cfg(feature = "tss")] #[cfg(feature = "tss")]
/// Mounts a personalized image with automatic manifest handling
///
/// # Arguments
/// * `provider` - Device connection provider (used for reconnection if needed)
/// * `image` - The image data
/// * `trust_cache` - Trust cache data
/// * `build_manifest` - Build manifest data
/// * `info_plist` - Optional info plist for the image
/// * `unique_chip_id` - Device's unique chip ID
///
/// # Errors
/// Returns `IdeviceError` if mounting fails
pub async fn mount_personalized( pub async fn mount_personalized(
&mut self, &mut self,
provider: &dyn crate::provider::IdeviceProvider, provider: &dyn crate::provider::IdeviceProvider,
@@ -344,9 +501,28 @@ impl ImageMounter {
} }
#[cfg(feature = "tss")] #[cfg(feature = "tss")]
/// Calling this has the potential of closing the socket, /// Mounts a personalized image with progress callbacks
/// so a provider is required for this abstraction. ///
#[allow(clippy::too_many_arguments)] // literally nobody asked /// # Important
/// This may close the socket on failure, requiring reconnection.
///
/// # Arguments
/// * `provider` - Device connection provider
/// * `image` - The image data
/// * `trust_cache` - Trust cache data
/// * `build_manifest` - Build manifest data
/// * `info_plist` - Optional info plist for the image
/// * `unique_chip_id` - Device's unique chip ID
/// * `callback` - Progress callback
/// * `state` - State to pass to callback
///
/// # Type Parameters
/// * `Fut` - Future type returned by callback
/// * `S` - Type of state passed to callback
///
/// # Errors
/// Returns `IdeviceError` if mounting fails
#[allow(clippy::too_many_arguments)]
pub async fn mount_personalized_with_callback<Fut, S>( pub async fn mount_personalized_with_callback<Fut, S>(
&mut self, &mut self,
provider: &dyn crate::provider::IdeviceProvider, provider: &dyn crate::provider::IdeviceProvider,
@@ -385,7 +561,7 @@ impl ImageMounter {
} }
}; };
debug!("Uploading imaage"); debug!("Uploading image");
self.upload_image_with_progress("Personalized", &image, manifest.clone(), callback, state) self.upload_image_with_progress("Personalized", &image, manifest.clone(), callback, state)
.await?; .await?;
@@ -397,6 +573,17 @@ impl ImageMounter {
} }
#[cfg(feature = "tss")] #[cfg(feature = "tss")]
/// Retrieves a personalization manifest from Apple's TSS server
///
/// # Arguments
/// * `build_manifest` - Build manifest dictionary
/// * `unique_chip_id` - Device's unique chip ID
///
/// # Returns
/// The manifest data
///
/// # Errors
/// Returns `IdeviceError` if manifest retrieval fails
pub async fn get_manifest_from_tss( pub async fn get_manifest_from_tss(
&mut self, &mut self,
build_manifest: &plist::Dictionary, build_manifest: &plist::Dictionary,
@@ -589,3 +776,4 @@ impl ImageMounter {
} }
} }
} }

View File

@@ -1,4 +1,7 @@
// Jackson Coxson //! iOS Device Pairing File Handling
//!
//! Provides functionality for reading, writing, and manipulating iOS device pairing files
//! which contain the cryptographic materials needed for secure communication with devices.
use std::path::Path; use std::path::Path;
@@ -7,20 +10,35 @@ use plist::Data;
use rustls::pki_types::{pem::PemObject, CertificateDer}; use rustls::pki_types::{pem::PemObject, CertificateDer};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
/// Represents a complete iOS device pairing record
///
/// Contains all cryptographic materials and identifiers needed for secure communication
/// with an iOS device, including certificates, private keys, and device identifiers.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct PairingFile { pub struct PairingFile {
/// Device's certificate in DER format
pub device_certificate: CertificateDer<'static>, pub device_certificate: CertificateDer<'static>,
pub host_private_key: Vec<u8>, // the private key doesn't implement clone... /// Host's private key in DER format
pub host_private_key: Vec<u8>,
/// Host's certificate in DER format
pub host_certificate: CertificateDer<'static>, pub host_certificate: CertificateDer<'static>,
/// Root CA's private key in DER format
pub root_private_key: Vec<u8>, pub root_private_key: Vec<u8>,
/// Root CA's certificate in DER format
pub root_certificate: CertificateDer<'static>, pub root_certificate: CertificateDer<'static>,
/// System Build Unique Identifier
pub system_buid: String, pub system_buid: String,
/// Host identifier
pub host_id: String, pub host_id: String,
/// Escrow bag allowing for access while locked
pub escrow_bag: Vec<u8>, pub escrow_bag: Vec<u8>,
/// Device's WiFi MAC address
pub wifi_mac_address: String, pub wifi_mac_address: String,
/// Device's Unique Device Identifier (optional)
pub udid: Option<String>, pub udid: Option<String>,
} }
/// Internal representation of a pairing file for serialization/deserialization
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "PascalCase")] #[serde(rename_all = "PascalCase")]
struct RawPairingFile { struct RawPairingFile {
@@ -41,11 +59,37 @@ struct RawPairingFile {
} }
impl PairingFile { impl PairingFile {
/// Reads a pairing file from disk
///
/// # Arguments
/// * `path` - Path to the pairing file (typically a .plist file)
///
/// # Returns
/// A parsed `PairingFile` on success
///
/// # Errors
/// Returns `IdeviceError` if:
/// - The file cannot be read
/// - The contents are malformed
/// - Cryptographic materials are invalid
pub fn read_from_file(path: impl AsRef<Path>) -> Result<Self, crate::IdeviceError> { pub fn read_from_file(path: impl AsRef<Path>) -> Result<Self, crate::IdeviceError> {
let f = std::fs::read(path)?; let f = std::fs::read(path)?;
Self::from_bytes(&f) Self::from_bytes(&f)
} }
/// Parses a pairing file from raw bytes
///
/// # Arguments
/// * `bytes` - Raw bytes of the pairing file (typically PLIST format)
///
/// # Returns
/// A parsed `PairingFile` on success
///
/// # Errors
/// Returns `IdeviceError` if:
/// - The data cannot be parsed as PLIST
/// - Required fields are missing
/// - Cryptographic materials are invalid
pub fn from_bytes(bytes: &[u8]) -> Result<Self, crate::IdeviceError> { pub fn from_bytes(bytes: &[u8]) -> Result<Self, crate::IdeviceError> {
let r = match ::plist::from_bytes::<RawPairingFile>(bytes) { let r = match ::plist::from_bytes::<RawPairingFile>(bytes) {
Ok(r) => r, Ok(r) => r,
@@ -64,12 +108,31 @@ impl PairingFile {
} }
} }
/// Creates a pairing file from a plist value
///
/// # Arguments
/// * `v` - PLIST value containing pairing data
///
/// # Returns
/// A parsed `PairingFile` on success
///
/// # Errors
/// Returns `IdeviceError` if:
/// - Required fields are missing
/// - Cryptographic materials are invalid
pub fn from_value(v: &plist::Value) -> Result<Self, crate::IdeviceError> { pub fn from_value(v: &plist::Value) -> Result<Self, crate::IdeviceError> {
let raw: RawPairingFile = plist::from_value(v)?; let raw: RawPairingFile = plist::from_value(v)?;
let p = raw.try_into()?; let p = raw.try_into()?;
Ok(p) Ok(p)
} }
/// Serializes the pairing file to a PLIST-formatted byte vector
///
/// # Returns
/// A byte vector containing the serialized pairing file
///
/// # Errors
/// Returns `IdeviceError` if serialization fails
pub fn serialize(self) -> Result<Vec<u8>, crate::IdeviceError> { pub fn serialize(self) -> Result<Vec<u8>, crate::IdeviceError> {
let raw = RawPairingFile::from(self); let raw = RawPairingFile::from(self);
@@ -82,6 +145,9 @@ impl PairingFile {
impl TryFrom<RawPairingFile> for PairingFile { impl TryFrom<RawPairingFile> for PairingFile {
type Error = rustls::pki_types::pem::Error; type Error = rustls::pki_types::pem::Error;
/// Attempts to convert a raw pairing file into a structured pairing file
///
/// Performs validation of cryptographic materials during conversion.
fn try_from(value: RawPairingFile) -> Result<Self, Self::Error> { fn try_from(value: RawPairingFile) -> Result<Self, Self::Error> {
Ok(Self { Ok(Self {
device_certificate: CertificateDer::from_pem_slice(&Into::<Vec<u8>>::into( device_certificate: CertificateDer::from_pem_slice(&Into::<Vec<u8>>::into(
@@ -105,6 +171,7 @@ impl TryFrom<RawPairingFile> for PairingFile {
} }
impl From<PairingFile> for RawPairingFile { impl From<PairingFile> for RawPairingFile {
/// Converts a structured pairing file into a raw pairing file for serialization
fn from(value: PairingFile) -> Self { fn from(value: PairingFile) -> Self {
Self { Self {
device_certificate: Data::new(value.device_certificate.to_vec()), device_certificate: Data::new(value.device_certificate.to_vec()),
@@ -122,7 +189,7 @@ impl From<PairingFile> for RawPairingFile {
} }
#[test] #[test]
fn f1() { fn test_pairing_file_roundtrip() {
let f = std::fs::read("/var/lib/lockdown/test.plist").unwrap(); let f = std::fs::read("/var/lib/lockdown/test.plist").unwrap();
println!("{}", String::from_utf8_lossy(&f)); println!("{}", String::from_utf8_lossy(&f));
@@ -133,3 +200,4 @@ fn f1() {
assert_eq!(f[..output.len()], output); assert_eq!(f[..output.len()], output);
} }

View File

@@ -1,4 +1,7 @@
// Jackson Coxson //! iOS Device Connection Providers
//!
//! Provides abstractions for establishing connections to iOS devices through different
//! transport mechanisms (TCP, USB, etc.).
use std::{ use std::{
future::Future, future::Future,
@@ -14,31 +17,56 @@ use crate::{pairing_file::PairingFile, Idevice, IdeviceError};
#[cfg(feature = "usbmuxd")] #[cfg(feature = "usbmuxd")]
use crate::usbmuxd::UsbmuxdAddr; use crate::usbmuxd::UsbmuxdAddr;
/// A provider for connecting to the iOS device /// Trait for providers that can establish connections to iOS devices
/// This is an ugly trait until async traits are stabilized ///
/// This is an async trait that abstracts over different connection methods
/// (TCP, USB, etc.).
pub trait IdeviceProvider: Unpin + Send + Sync + std::fmt::Debug { pub trait IdeviceProvider: Unpin + Send + Sync + std::fmt::Debug {
/// Establishes a connection to the specified port on the device
///
/// # Arguments
/// * `port` - The port number to connect to
///
/// # Returns
/// A future that resolves to an `Idevice` connection handle
fn connect( fn connect(
&self, &self,
port: u16, port: u16,
) -> Pin<Box<dyn Future<Output = Result<Idevice, IdeviceError>> + Send>>; ) -> Pin<Box<dyn Future<Output = Result<Idevice, IdeviceError>> + Send>>;
/// Returns a label identifying this provider/connection
fn label(&self) -> &str; fn label(&self) -> &str;
/// Retrieves the pairing file needed for secure communication
///
/// # Returns
/// A future that resolves to the device's `PairingFile`
fn get_pairing_file( fn get_pairing_file(
&self, &self,
) -> Pin<Box<dyn Future<Output = Result<PairingFile, IdeviceError>> + Send>>; ) -> Pin<Box<dyn Future<Output = Result<PairingFile, IdeviceError>> + Send>>;
} }
/// TCP-based device connection provider
#[cfg(feature = "tcp")] #[cfg(feature = "tcp")]
#[derive(Debug)] #[derive(Debug)]
pub struct TcpProvider { pub struct TcpProvider {
/// IP address of the device
pub addr: IpAddr, pub addr: IpAddr,
/// Pairing file for secure communication
pub pairing_file: PairingFile, pub pairing_file: PairingFile,
/// Label identifying this connection
pub label: String, pub label: String,
} }
#[cfg(feature = "tcp")] #[cfg(feature = "tcp")]
impl IdeviceProvider for TcpProvider { impl IdeviceProvider for TcpProvider {
/// Connects to the device over TCP
///
/// # Arguments
/// * `port` - The TCP port to connect to
///
/// # Returns
/// An `Idevice` wrapped in a future
fn connect( fn connect(
&self, &self,
port: u16, port: u16,
@@ -52,10 +80,12 @@ impl IdeviceProvider for TcpProvider {
}) })
} }
/// Returns the connection label
fn label(&self) -> &str { fn label(&self) -> &str {
&self.label &self.label
} }
/// Returns the pairing file (cloned from the provider)
fn get_pairing_file( fn get_pairing_file(
&self, &self,
) -> Pin<Box<dyn Future<Output = Result<PairingFile, IdeviceError>> + Send>> { ) -> Pin<Box<dyn Future<Output = Result<PairingFile, IdeviceError>> + Send>> {
@@ -64,18 +94,31 @@ impl IdeviceProvider for TcpProvider {
} }
} }
/// USB-based device connection provider using usbmuxd
#[cfg(feature = "usbmuxd")] #[cfg(feature = "usbmuxd")]
#[derive(Debug)] #[derive(Debug)]
pub struct UsbmuxdProvider { pub struct UsbmuxdProvider {
/// USB connection address
pub addr: UsbmuxdAddr, pub addr: UsbmuxdAddr,
/// Connection tag/identifier
pub tag: u32, pub tag: u32,
/// Device UDID
pub udid: String, pub udid: String,
/// Device ID
pub device_id: u32, pub device_id: u32,
/// Connection label
pub label: String, pub label: String,
} }
#[cfg(feature = "usbmuxd")] #[cfg(feature = "usbmuxd")]
impl IdeviceProvider for UsbmuxdProvider { impl IdeviceProvider for UsbmuxdProvider {
/// Connects to the device over USB via usbmuxd
///
/// # Arguments
/// * `port` - The port number to connect to on the device
///
/// # Returns
/// An `Idevice` wrapped in a future
fn connect( fn connect(
&self, &self,
port: u16, port: u16,
@@ -91,10 +134,12 @@ impl IdeviceProvider for UsbmuxdProvider {
}) })
} }
/// Returns the connection label
fn label(&self) -> &str { fn label(&self) -> &str {
&self.label &self.label
} }
/// Retrieves the pairing record from usbmuxd
fn get_pairing_file( fn get_pairing_file(
&self, &self,
) -> Pin<Box<dyn Future<Output = Result<PairingFile, IdeviceError>> + Send>> { ) -> Pin<Box<dyn Future<Output = Result<PairingFile, IdeviceError>> + Send>> {
@@ -108,3 +153,4 @@ impl IdeviceProvider for UsbmuxdProvider {
}) })
} }
} }

View File

@@ -1,18 +1,46 @@
use crate::{lockdown::LockdowndClient, Idevice, IdeviceError, IdeviceService}; //! SpringBoard Services Client
//!
//! Provides functionality for interacting with the SpringBoard services on iOS devices,
//! which manages home screen and app icon related operations.
use crate::{lockdown::LockdownClient, Idevice, IdeviceError, IdeviceService};
/// Client for interacting with the iOS SpringBoard services
///
/// This service provides access to home screen and app icon functionality,
/// such as retrieving app icons.
pub struct SpringBoardServicesClient { pub struct SpringBoardServicesClient {
/// The underlying device connection with established SpringBoard services
pub idevice: Idevice, pub idevice: Idevice,
} }
impl IdeviceService for SpringBoardServicesClient { impl IdeviceService for SpringBoardServicesClient {
/// Returns the SpringBoard services name as registered with lockdownd
fn service_name() -> &'static str { fn service_name() -> &'static str {
"com.apple.springboardservices" "com.apple.springboardservices"
} }
/// Establishes a connection to the SpringBoard services
///
/// # Arguments
/// * `provider` - Device connection provider
///
/// # Returns
/// A connected `SpringBoardServicesClient` instance
///
/// # Errors
/// Returns `IdeviceError` if any step of the connection process fails
///
/// # Process
/// 1. Connects to lockdownd service
/// 2. Starts a lockdown session
/// 3. Requests the SpringBoard services port
/// 4. Establishes connection to the service port
/// 5. Optionally starts TLS if required by service
async fn connect( async fn connect(
provider: &dyn crate::provider::IdeviceProvider, provider: &dyn crate::provider::IdeviceProvider,
) -> Result<Self, IdeviceError> { ) -> Result<Self, IdeviceError> {
let mut lockdown = LockdowndClient::connect(provider).await?; let mut lockdown = LockdownClient::connect(provider).await?;
lockdown lockdown
.start_session(&provider.get_pairing_file().await?) .start_session(&provider.get_pairing_file().await?)
.await?; .await?;
@@ -31,13 +59,33 @@ impl IdeviceService for SpringBoardServicesClient {
} }
impl SpringBoardServicesClient { impl SpringBoardServicesClient {
/// Creates a new SpringBoard services client from an existing device connection
///
/// # Arguments
/// * `idevice` - Pre-established device connection
pub fn new(idevice: Idevice) -> Self { pub fn new(idevice: Idevice) -> Self {
Self { idevice } Self { idevice }
} }
/// Gets the icon of a spceified app /// Retrieves the PNG icon data for a specified app
///
/// # Arguments /// # Arguments
/// `bundle_identifier` - The identifier of the app to get icon /// * `bundle_identifier` - The bundle identifier of the app (e.g., "com.apple.Maps")
///
/// # Returns
/// The raw PNG data of the app icon
///
/// # Errors
/// Returns `IdeviceError` if:
/// - Communication fails
/// - The app doesn't exist
/// - The response is malformed
///
/// # Example
/// ```rust
/// let icon_data = client.get_icon_pngdata("com.apple.Maps".to_string()).await?;
/// std::fs::write("maps_icon.png", icon_data)?;
/// ```
pub async fn get_icon_pngdata( pub async fn get_icon_pngdata(
&mut self, &mut self,
bundle_identifier: String, bundle_identifier: String,
@@ -56,3 +104,4 @@ impl SpringBoardServicesClient {
} }
} }
} }

View File

@@ -1,5 +1,65 @@
// Jackson Coxson //! A simplified TCP network stack implementation.
// Simpler TCP stack //!
//! This module provides a naive TCP stack implementation designed for simple,
//! reliable network environments. It handles basic TCP operations while making
//! significant simplifying assumptions about the underlying transport.
//!
//! # Features
//! - Basic TCP connection establishment (3-way handshake)
//! - Data transmission with PSH flag
//! - Connection teardown
//! - Optional PCAP packet capture
//! - Implements `AsyncRead` and `AsyncWrite` for Tokio compatibility
//!
//! # Limitations
//! - Only supports one connection at a time
//! - No proper sequence number tracking
//! - No retransmission or congestion control
//! - Requires 100% reliable underlying transport
//! - Minimal error handling
//!
//! # Example
//! ```rust,no_run
//! use std::net::{IpAddr, Ipv4Addr};
//! use tokio::io::{AsyncReadExt, AsyncWriteExt};
//! use your_crate::tcp::Adapter;
//! use your_crate::ReadWrite; // Assuming you have a ReadWrite trait
//!
//! #[tokio::main]
//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
//! // Create a transport connection (this would be your actual transport)
//! let transport = /* your transport implementing ReadWrite */;
//!
//! // Create TCP adapter
//! let host_ip = IpAddr::V4(Ipv4Addr::new(192, 168, 1, 2));
//! let peer_ip = IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1));
//! let mut adapter = Adapter::new(Box::new(transport), host_ip, peer_ip);
//!
//! // Optional: enable packet capture
//! adapter.pcap("capture.pcap").await?;
//!
//! // Connect to remote server
//! adapter.connect(80).await?;
//!
//! // Send HTTP request
//! adapter.write_all(b"GET / HTTP/1.1\r\nHost: example.com\r\n\r\n").await?;
//! adapter.flush().await?;
//!
//! // Read response
//! let mut buf = vec![0; 1024];
//! let n = adapter.read(&mut buf).await?;
//! println!("Received: {}", String::from_utf8_lossy(&buf[..n]));
//!
//! // Close connection
//! adapter.close().await?;
//!
//! Ok(())
//! }
//! ```
//!
//! # Warning
//! This implementation makes significant simplifications and should not be used
//! in production environments or with unreliable network transports.
use std::{future::Future, net::IpAddr, path::Path, sync::Arc, task::Poll}; use std::{future::Future, net::IpAddr, path::Path, sync::Arc, task::Poll};
@@ -19,32 +79,57 @@ enum AdapterState {
None, None,
} }
/// An extremely naive, limited and dangerous stack /// A simplified TCP network stack implementation.
/// Only one connection can be live at a time ///
/// Acks aren't tracked, and are silently ignored /// This is an extremely naive, limited, and dangerous TCP stack implementation.
/// Only use when the underlying transport is 100% reliable /// Key limitations:
/// - Only one connection can be active at a time
/// - ACKs aren't properly tracked and are silently ignored
/// - Should only be used when the underlying transport is 100% reliable
///
/// The adapter implements `AsyncRead` and `AsyncWrite` for convenient IO operations.
#[derive(Debug)] #[derive(Debug)]
pub struct Adapter { pub struct Adapter {
/// The underlying transport connection
peer: Box<dyn ReadWrite>, peer: Box<dyn ReadWrite>,
/// The local IP address
host_ip: IpAddr, host_ip: IpAddr,
/// The remote peer's IP address
peer_ip: IpAddr, peer_ip: IpAddr,
/// Current connection state
state: AdapterState, state: AdapterState,
// TCP state // TCP state
/// Current sequence number
seq: u32, seq: u32,
/// Current acknowledgement number
ack: u32, ack: u32,
/// Local port number
host_port: u16, host_port: u16,
/// Remote port number
peer_port: u16, peer_port: u16,
// Read buffer to cache unused bytes // Read buffer to cache unused bytes
/// Buffer for storing unread received data
read_buffer: Vec<u8>, read_buffer: Vec<u8>,
/// Buffer for storing data to be sent
write_buffer: Vec<u8>, write_buffer: Vec<u8>,
// Logging // Logging
/// Optional PCAP file for packet logging
pcap: Option<Arc<Mutex<tokio::fs::File>>>, pcap: Option<Arc<Mutex<tokio::fs::File>>>,
} }
impl Adapter { impl Adapter {
/// Creates a new TCP adapter instance.
///
/// # Arguments
/// * `peer` - The underlying transport connection implementing `ReadWrite`
/// * `host_ip` - The local IP address to use
/// * `peer_ip` - The remote IP address to connect to
///
/// # Returns
/// A new unconnected `Adapter` instance
pub fn new(peer: Box<dyn ReadWrite>, host_ip: IpAddr, peer_ip: IpAddr) -> Self { pub fn new(peer: Box<dyn ReadWrite>, host_ip: IpAddr, peer_ip: IpAddr) -> Self {
Self { Self {
peer, peer,
@@ -61,6 +146,18 @@ impl Adapter {
} }
} }
/// Initiates a TCP connection to the specified port.
///
/// # Arguments
/// * `port` - The remote port number to connect to
///
/// # Returns
/// * `Ok(())` if connection was successful
/// * `Err(std::io::Error)` if connection failed
///
/// # Errors
/// * Returns `InvalidData` if the SYN-ACK response is invalid
/// * Returns other IO errors if underlying transport fails
pub async fn connect(&mut self, port: u16) -> Result<(), std::io::Error> { pub async fn connect(&mut self, port: u16) -> Result<(), std::io::Error> {
self.read_buffer = Vec::new(); self.read_buffer = Vec::new();
self.write_buffer = Vec::new(); self.write_buffer = Vec::new();
@@ -110,6 +207,14 @@ impl Adapter {
Ok(()) Ok(())
} }
/// Enables packet capture to a PCAP file.
///
/// # Arguments
/// * `path` - The filesystem path to write the PCAP data to
///
/// # Returns
/// * `Ok(())` if PCAP file was successfully created
/// * `Err(std::io::Error)` if file creation failed
pub async fn pcap(&mut self, path: impl AsRef<Path>) -> Result<(), std::io::Error> { pub async fn pcap(&mut self, path: impl AsRef<Path>) -> Result<(), std::io::Error> {
let mut file = tokio::fs::File::create(path).await?; let mut file = tokio::fs::File::create(path).await?;
@@ -134,6 +239,14 @@ impl Adapter {
Ok(()) Ok(())
} }
/// Closes the TCP connection.
///
/// # Returns
/// * `Ok(())` if connection was closed cleanly
/// * `Err(std::io::Error)` if closing failed
///
/// # Errors
/// * Returns IO errors if underlying transport fails during close
pub async fn close(&mut self) -> Result<(), std::io::Error> { pub async fn close(&mut self) -> Result<(), std::io::Error> {
let tcp_packet = TcpPacket::create( let tcp_packet = TcpPacket::create(
self.host_ip, self.host_ip,
@@ -191,7 +304,17 @@ impl Adapter {
Ok(()) Ok(())
} }
/// Sends a packet /// Sends a TCP packet with PSH flag set (pushing data).
///
/// # Arguments
/// * `data` - The payload data to send
///
/// # Returns
/// * `Ok(())` if data was sent successfully
/// * `Err(std::io::Error)` if sending failed
///
/// # Errors
/// * Returns IO errors if underlying transport fails
pub async fn psh(&mut self, data: &[u8]) -> Result<(), std::io::Error> { pub async fn psh(&mut self, data: &[u8]) -> Result<(), std::io::Error> {
trace!("pshing {} bytes", data.len()); trace!("pshing {} bytes", data.len());
let tcp_packet = TcpPacket::create( let tcp_packet = TcpPacket::create(
@@ -230,6 +353,15 @@ impl Adapter {
Ok(()) Ok(())
} }
/// Receives data from the connection.
///
/// # Returns
/// * `Ok(Vec<u8>)` containing received data
/// * `Err(std::io::Error)` if receiving failed
///
/// # Errors
/// * Returns `ConnectionReset` if connection was reset or closed
/// * Returns other IO errors if underlying transport fails
pub async fn recv(&mut self) -> Result<Vec<u8>, std::io::Error> { pub async fn recv(&mut self) -> Result<Vec<u8>, std::io::Error> {
loop { loop {
let res = self.read_tcp_packet().await?; let res = self.read_tcp_packet().await?;
@@ -324,6 +456,18 @@ impl Adapter {
} }
impl AsyncRead for Adapter { impl AsyncRead for Adapter {
/// Attempts to read from the connection into the provided buffer.
///
/// Uses an internal read buffer to cache any extra received data.
///
/// # Returns
/// * `Poll::Ready(Ok(()))` if data was read successfully
/// * `Poll::Ready(Err(e))` if an error occurred
/// * `Poll::Pending` if operation would block
///
/// # Errors
/// * Returns `NotConnected` if adapter isn't connected
/// * Propagates any underlying transport errors
fn poll_read( fn poll_read(
mut self: std::pin::Pin<&mut Self>, mut self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>, cx: &mut std::task::Context<'_>,
@@ -377,6 +521,17 @@ impl AsyncRead for Adapter {
} }
impl AsyncWrite for Adapter { impl AsyncWrite for Adapter {
/// Attempts to write data to the connection.
///
/// Data is buffered internally until flushed.
///
/// # Returns
/// * `Poll::Ready(Ok(n))` with number of bytes written
/// * `Poll::Ready(Err(e))` if an error occurred
/// * `Poll::Pending` if operation would block
///
/// # Errors
/// * Returns `NotConnected` if adapter isn't connected
fn poll_write( fn poll_write(
mut self: std::pin::Pin<&mut Self>, mut self: std::pin::Pin<&mut Self>,
_cx: &mut std::task::Context<'_>, _cx: &mut std::task::Context<'_>,

View File

@@ -1,20 +1,34 @@
// Jackson Coxson //! Ticket Signature Server (TSS) Client
// Thanks pymobiledevice3 //!
//! Provides functionality for interacting with Apple's TSS service to:
//! - Request personalized firmware components
//! - Apply restore request rules for device-specific parameters
//! - Handle cryptographic signing operations
use log::{debug, warn}; use log::{debug, warn};
use plist::Value; use plist::Value;
use crate::{util::plist_to_xml_bytes, IdeviceError}; use crate::{util::plist_to_xml_bytes, IdeviceError};
/// TSS client version string sent in requests
const TSS_CLIENT_VERSION_STRING: &str = "libauthinstall-1033.0.2"; const TSS_CLIENT_VERSION_STRING: &str = "libauthinstall-1033.0.2";
/// Apple's TSS endpoint URL
const TSS_CONTROLLER_ACTION_URL: &str = "http://gs.apple.com/TSS/controller?action=2"; const TSS_CONTROLLER_ACTION_URL: &str = "http://gs.apple.com/TSS/controller?action=2";
/// Represents a TSS request to Apple's signing server
#[derive(Debug)] #[derive(Debug)]
pub struct TSSRequest { pub struct TSSRequest {
/// The underlying plist dictionary containing request parameters
inner: plist::Dictionary, inner: plist::Dictionary,
} }
impl TSSRequest { impl TSSRequest {
/// Creates a new TSS request with default headers
///
/// Initializes with:
/// - Host platform info
/// - Client version string
/// - Random UUID for request identification
pub fn new() -> Self { pub fn new() -> Self {
let mut inner = plist::Dictionary::new(); let mut inner = plist::Dictionary::new();
inner.insert("@HostPlatformInfo".into(), "mac".into()); inner.insert("@HostPlatformInfo".into(), "mac".into());
@@ -26,12 +40,35 @@ impl TSSRequest {
Self { inner } Self { inner }
} }
/// Inserts a key-value pair into the TSS request
///
/// # Arguments
/// * `key` - The parameter name
/// * `val` - The parameter value (will be converted to plist::Value)
pub fn insert(&mut self, key: impl Into<String>, val: impl Into<Value>) { pub fn insert(&mut self, key: impl Into<String>, val: impl Into<Value>) {
let key = key.into(); let key = key.into();
let val = val.into(); let val = val.into();
self.inner.insert(key, val); self.inner.insert(key, val);
} }
/// Sends the TSS request to Apple's servers
///
/// # Returns
/// The parsed plist response from Apple
///
/// # Errors
/// Returns `IdeviceError` if:
/// - The request fails
/// - The response is malformed
/// - Apple returns a non-success status
///
/// # Example
/// ```rust
/// let mut request = TSSRequest::new();
/// request.insert("ApBoardID", board_id);
/// request.insert("ApChipID", chip_id);
/// let response = request.send().await?;
/// ```
pub async fn send(&self) -> Result<plist::Value, IdeviceError> { pub async fn send(&self) -> Result<plist::Value, IdeviceError> {
debug!( debug!(
"Sending TSS request: {}", "Sending TSS request: {}",
@@ -51,7 +88,7 @@ impl TSSRequest {
.text() .text()
.await?; .await?;
debug!("Apple responeded with {res}"); debug!("Apple responded with {res}");
let res = res.trim_start_matches("STATUS=0&"); let res = res.trim_start_matches("STATUS=0&");
let res = res.trim_start_matches("MESSAGE="); let res = res.trim_start_matches("MESSAGE=");
if !res.starts_with("SUCCESS") { if !res.starts_with("SUCCESS") {
@@ -68,11 +105,24 @@ impl TSSRequest {
} }
impl Default for TSSRequest { impl Default for TSSRequest {
/// Creates a default TSS request (same as `new()`)
fn default() -> Self { fn default() -> Self {
Self::new() Self::new()
} }
} }
/// Applies restore request rules to modify input parameters
///
/// # Arguments
/// * `input` - The dictionary to modify based on rules
/// * `parameters` - Device parameters to check conditions against
/// * `rules` - List of rules to apply
///
/// # Process
/// For each rule:
/// 1. Checks all conditions against the parameters
/// 2. If all conditions are met, applies the rule's actions
/// 3. Actions can add, modify or remove parameters
pub fn apply_restore_request_rules( pub fn apply_restore_request_rules(
input: &mut plist::Dictionary, input: &mut plist::Dictionary,
parameters: &plist::Dictionary, parameters: &plist::Dictionary,
@@ -122,6 +172,7 @@ pub fn apply_restore_request_rules(
}; };
for (key, value) in actions { for (key, value) in actions {
// Skip special values (255 typically means "ignore")
if let Some(i) = value.as_unsigned_integer() { if let Some(i) = value.as_unsigned_integer() {
if i == 255 { if i == 255 {
continue; continue;
@@ -133,7 +184,7 @@ pub fn apply_restore_request_rules(
} }
} }
input.remove(key); // Explicitly remove before inserting, like Python input.remove(key); // Explicitly remove before inserting
input.insert(key.to_owned(), value.to_owned()); input.insert(key.to_owned(), value.to_owned());
} }
} else { } else {
@@ -141,3 +192,4 @@ pub fn apply_restore_request_rules(
} }
} }
} }

View File

@@ -1,4 +1,7 @@
// Shim code for using pymobiledevice3's tunneld //! Tunneld Client Implementation
//!
//! Provides functionality for interacting with pymobiledevice3's tunneld service,
//! which creates network tunnels to iOS devices over USB.
use std::{collections::HashMap, net::SocketAddr}; use std::{collections::HashMap, net::SocketAddr};
@@ -8,25 +11,55 @@ use serde_json::Value;
use crate::IdeviceError; use crate::IdeviceError;
/// Default port number for the tunneld service
pub const DEFAULT_PORT: u16 = 49151; pub const DEFAULT_PORT: u16 = 49151;
/// Represents a device connected through tunneld
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TunneldDevice { pub struct TunneldDevice {
/// Network interface name
pub interface: String, pub interface: String,
/// Tunnel IP address
#[serde(rename = "tunnel-address")] #[serde(rename = "tunnel-address")]
pub tunnel_address: String, pub tunnel_address: String,
/// Tunnel port number
#[serde(rename = "tunnel-port")] #[serde(rename = "tunnel-port")]
pub tunnel_port: u16, pub tunnel_port: u16,
} }
/// Retrieves all devices currently connected through tunneld
///
/// # Arguments
/// * `socket` - Socket address of the tunneld service (typically localhost with DEFAULT_PORT)
///
/// # Returns
/// A HashMap mapping device UDIDs to their tunnel information
///
/// # Errors
/// Returns `IdeviceError` if:
/// - The HTTP request fails
/// - The response format is invalid
/// - JSON parsing fails
///
/// # Example
/// ```rust
/// let host = SocketAddr::new(IpAddr::from_str("127.0.0.1").unwrap(), DEFAULT_PORT);
/// let devices = get_tunneld_devices(host).await?;
/// for (udid, device) in devices {
/// println!("Device {} is available at {}:{}",
/// udid, device.tunnel_address, device.tunnel_port);
/// }
/// ```
pub async fn get_tunneld_devices( pub async fn get_tunneld_devices(
socket: SocketAddr, socket: SocketAddr,
) -> Result<HashMap<String, TunneldDevice>, IdeviceError> { ) -> Result<HashMap<String, TunneldDevice>, IdeviceError> {
// Make HTTP GET request to tunneld endpoint
let res: Value = reqwest::get(format!("http://{socket}")) let res: Value = reqwest::get(format!("http://{socket}"))
.await? .await?
.json() .json()
.await?; .await?;
// Verify response is a JSON object
let res = match res.as_object() { let res = match res.as_object() {
Some(r) => r, Some(r) => r,
None => { None => {
@@ -35,6 +68,7 @@ pub async fn get_tunneld_devices(
} }
}; };
// Parse each device entry
let mut to_return = HashMap::new(); let mut to_return = HashMap::new();
for (udid, v) in res.into_iter() { for (udid, v) in res.into_iter() {
let mut v: Vec<TunneldDevice> = match serde_json::from_value(v.clone()) { let mut v: Vec<TunneldDevice> = match serde_json::from_value(v.clone()) {
@@ -62,9 +96,14 @@ mod tests {
use super::*; use super::*;
/// Test case for verifying tunneld device listing
#[tokio::test] #[tokio::test]
async fn t1() { async fn test_get_tunneld_devices() {
let host = SocketAddr::new(IpAddr::from_str("127.0.0.1").unwrap(), DEFAULT_PORT); let host = SocketAddr::new(IpAddr::from_str("127.0.0.1").unwrap(), DEFAULT_PORT);
println!("{:#?}", get_tunneld_devices(host).await); match get_tunneld_devices(host).await {
Ok(devices) => println!("Found tunneld devices: {:#?}", devices),
Err(e) => println!("Error querying tunneld: {}", e),
} }
} }
}

View File

@@ -1,4 +1,7 @@
// Jackson Coxson //! USB Multiplexing Daemon (usbmuxd) Client
//!
//! Provides functionality for interacting with the usbmuxd service which manages
//! connections to iOS devices over USB and network and pairing files
use std::{ use std::{
net::{AddrParseError, IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}, net::{AddrParseError, IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr},
@@ -18,36 +21,57 @@ use crate::{
mod des; mod des;
mod raw_packet; mod raw_packet;
/// Represents the connection type of a device
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum Connection { pub enum Connection {
/// Connected via USB
Usb, Usb,
/// Connected via network with specific IP address
Network(IpAddr), Network(IpAddr),
/// Unknown connection type with description
Unknown(String), Unknown(String),
} }
/// Represents a device connected through usbmuxd
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct UsbmuxdDevice { pub struct UsbmuxdDevice {
/// How the device is connected
pub connection_type: Connection, pub connection_type: Connection,
/// Unique Device Identifier
pub udid: String, pub udid: String,
/// usbmuxd-assigned device ID
pub device_id: u32, pub device_id: u32,
} }
/// Active connection to the usbmuxd service
pub struct UsbmuxdConnection { pub struct UsbmuxdConnection {
socket: Box<dyn ReadWrite>, socket: Box<dyn ReadWrite>,
tag: u32, tag: u32,
} }
/// Address of the usbmuxd service
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum UsbmuxdAddr { pub enum UsbmuxdAddr {
/// Unix domain socket path (Unix systems only)
#[cfg(unix)] #[cfg(unix)]
UnixSocket(String), UnixSocket(String),
/// TCP socket address
TcpSocket(SocketAddr), TcpSocket(SocketAddr),
} }
impl UsbmuxdAddr { impl UsbmuxdAddr {
/// Default TCP port for usbmuxd
pub const DEFAULT_PORT: u16 = 27015; pub const DEFAULT_PORT: u16 = 27015;
/// Default Unix socket path for usbmuxd
pub const SOCKET_FILE: &'static str = "/var/run/usbmuxd"; pub const SOCKET_FILE: &'static str = "/var/run/usbmuxd";
/// Connects to the usbmuxd service
///
/// # Returns
/// A boxed transport stream
///
/// # Errors
/// Returns `IdeviceError` if connection fails
pub async fn to_socket(&self) -> Result<Box<dyn ReadWrite>, IdeviceError> { pub async fn to_socket(&self) -> Result<Box<dyn ReadWrite>, IdeviceError> {
Ok(match self { Ok(match self {
#[cfg(unix)] #[cfg(unix)]
@@ -56,11 +80,24 @@ impl UsbmuxdAddr {
}) })
} }
/// Creates a new usbmuxd connection
///
/// # Arguments
/// * `tag` - Connection tag/identifier
///
/// # Returns
/// A connected `UsbmuxdConnection`
pub async fn connect(&self, tag: u32) -> Result<UsbmuxdConnection, IdeviceError> { pub async fn connect(&self, tag: u32) -> Result<UsbmuxdConnection, IdeviceError> {
let socket = self.to_socket().await?; let socket = self.to_socket().await?;
Ok(UsbmuxdConnection::new(socket, tag)) Ok(UsbmuxdConnection::new(socket, tag))
} }
/// Creates a UsbmuxdAddr from environment variable
///
/// Checks `USBMUXD_SOCKET_ADDRESS` environment variable, falls back to default
///
/// # Returns
/// Configured UsbmuxdAddr or parse error
pub fn from_env_var() -> Result<Self, AddrParseError> { pub fn from_env_var() -> Result<Self, AddrParseError> {
Ok(match std::env::var("USBMUXD_SOCKET_ADDRESS") { Ok(match std::env::var("USBMUXD_SOCKET_ADDRESS") {
Ok(var) => { Ok(var) => {
@@ -79,6 +116,9 @@ impl UsbmuxdAddr {
} }
impl Default for UsbmuxdAddr { impl Default for UsbmuxdAddr {
/// Creates default usbmuxd address based on platform:
/// - Unix: Uses default socket path
/// - Non-Unix: Uses localhost TCP port
fn default() -> Self { fn default() -> Self {
#[cfg(not(unix))] #[cfg(not(unix))]
{ {
@@ -93,12 +133,22 @@ impl Default for UsbmuxdAddr {
} }
impl UsbmuxdConnection { impl UsbmuxdConnection {
/// Binary PLIST protocol version
pub const BINARY_PLIST_VERSION: u32 = 0; pub const BINARY_PLIST_VERSION: u32 = 0;
/// XML PLIST protocol version
pub const XML_PLIST_VERSION: u32 = 1; pub const XML_PLIST_VERSION: u32 = 1;
/// Result message type
pub const RESULT_MESSAGE_TYPE: u32 = 1; pub const RESULT_MESSAGE_TYPE: u32 = 1;
/// PLIST message type
pub const PLIST_MESSAGE_TYPE: u32 = 8; pub const PLIST_MESSAGE_TYPE: u32 = 8;
/// Creates a default usbmuxd connection
///
/// Uses default address based on platform
///
/// # Returns
/// Connected `UsbmuxdConnection` or error
pub async fn default() -> Result<Self, IdeviceError> { pub async fn default() -> Result<Self, IdeviceError> {
let socket = UsbmuxdAddr::default().to_socket().await?; let socket = UsbmuxdAddr::default().to_socket().await?;
@@ -108,10 +158,25 @@ impl UsbmuxdConnection {
}) })
} }
/// Creates a new usbmuxd connection
///
/// # Arguments
/// * `socket` - The transport stream
/// * `tag` - Connection tag/identifier
pub fn new(socket: Box<dyn ReadWrite>, tag: u32) -> Self { pub fn new(socket: Box<dyn ReadWrite>, tag: u32) -> Self {
Self { socket, tag } Self { socket, tag }
} }
/// Lists all connected devices
///
/// # Returns
/// Vector of connected devices
///
/// # Errors
/// Returns `IdeviceError` if:
/// - Communication fails
/// - Response is malformed
/// - Device info is incomplete
pub async fn get_devices(&mut self) -> Result<Vec<UsbmuxdDevice>, IdeviceError> { pub async fn get_devices(&mut self) -> Result<Vec<UsbmuxdDevice>, IdeviceError> {
let mut req = plist::Dictionary::new(); let mut req = plist::Dictionary::new();
req.insert("MessageType".into(), "ListDevices".into()); req.insert("MessageType".into(), "ListDevices".into());
@@ -135,13 +200,13 @@ impl UsbmuxdConnection {
match addr[0] { match addr[0] {
0x02 => { 0x02 => {
// ipv4 // IPv4
Connection::Network(IpAddr::V4(Ipv4Addr::new( Connection::Network(IpAddr::V4(Ipv4Addr::new(
addr[4], addr[5], addr[6], addr[7], addr[4], addr[5], addr[6], addr[7],
))) )))
} }
0x1E => { 0x1E => {
// ipv6 // IPv6
if addr.len() < 24 { if addr.len() < 24 {
warn!("IPv6 address is less than 24 bytes"); warn!("IPv6 address is less than 24 bytes");
return Err(IdeviceError::UnexpectedResponse); return Err(IdeviceError::UnexpectedResponse);
@@ -182,6 +247,13 @@ impl UsbmuxdConnection {
Ok(devs) Ok(devs)
} }
/// Gets a specific device by UDID
///
/// # Arguments
/// * `udid` - The device UDID to find
///
/// # Returns
/// The matching device or error if not found
pub async fn get_device(&mut self, udid: &str) -> Result<UsbmuxdDevice, IdeviceError> { pub async fn get_device(&mut self, udid: &str) -> Result<UsbmuxdDevice, IdeviceError> {
let devices = self.get_devices().await?; let devices = self.get_devices().await?;
match devices.into_iter().find(|x| x.udid == udid) { match devices.into_iter().find(|x| x.udid == udid) {
@@ -190,6 +262,13 @@ impl UsbmuxdConnection {
} }
} }
/// Gets the pairing record for a device
///
/// # Arguments
/// * `udid` - The device UDID
///
/// # Returns
/// The pairing file or error
pub async fn get_pair_record(&mut self, udid: &str) -> Result<PairingFile, IdeviceError> { pub async fn get_pair_record(&mut self, udid: &str) -> Result<PairingFile, IdeviceError> {
debug!("Getting pair record for {udid}"); debug!("Getting pair record for {udid}");
let mut req = plist::Dictionary::new(); let mut req = plist::Dictionary::new();
@@ -204,6 +283,10 @@ impl UsbmuxdConnection {
} }
} }
/// Gets the BUID
///
/// # Returns
/// The BUID string or error
pub async fn get_buid(&mut self) -> Result<String, IdeviceError> { pub async fn get_buid(&mut self) -> Result<String, IdeviceError> {
let mut req = plist::Dictionary::new(); let mut req = plist::Dictionary::new();
req.insert("MessageType".into(), "ReadBUID".into()); req.insert("MessageType".into(), "ReadBUID".into());
@@ -216,6 +299,15 @@ impl UsbmuxdConnection {
} }
} }
/// Connects to a service on the device
///
/// # Arguments
/// * `device_id` - usbmuxd device ID
/// * `port` - TCP port to connect to (host byte order)
/// * `label` - Connection label
///
/// # Returns
/// An `Idevice` connection or error
pub async fn connect_to_device( pub async fn connect_to_device(
mut self, mut self,
device_id: u32, device_id: u32,
@@ -243,6 +335,7 @@ impl UsbmuxdConnection {
} }
} }
/// Writes a PLIST message to usbmuxd
async fn write_plist(&mut self, req: plist::Dictionary) -> Result<(), IdeviceError> { async fn write_plist(&mut self, req: plist::Dictionary) -> Result<(), IdeviceError> {
let raw = raw_packet::RawPacket::new( let raw = raw_packet::RawPacket::new(
req, req,
@@ -257,6 +350,7 @@ impl UsbmuxdConnection {
Ok(()) Ok(())
} }
/// Reads a PLIST message from usbmuxd
async fn read_plist(&mut self) -> Result<plist::Dictionary, IdeviceError> { async fn read_plist(&mut self) -> Result<plist::Dictionary, IdeviceError> {
let mut header_buffer = [0; 16]; let mut header_buffer = [0; 16];
self.socket.read_exact(&mut header_buffer).await?; self.socket.read_exact(&mut header_buffer).await?;
@@ -276,6 +370,15 @@ impl UsbmuxdConnection {
} }
impl UsbmuxdDevice { impl UsbmuxdDevice {
/// Creates a provider for this device
///
/// # Arguments
/// * `addr` - usbmuxd address
/// * `tag` - Connection tag
/// * `label` - Connection label
///
/// # Returns
/// Configured `UsbmuxdProvider`
pub fn to_provider( pub fn to_provider(
&self, &self,
addr: UsbmuxdAddr, addr: UsbmuxdAddr,
@@ -293,3 +396,4 @@ impl UsbmuxdDevice {
} }
} }
} }

View File

@@ -1,7 +1,27 @@
// Jackson Coxson //! Utility Functions
//!
//! Provides helper functions for working with Apple's Property List (PLIST) format,
//! including serialization and pretty-printing utilities.
use plist::Value; use plist::Value;
/// Converts a PLIST dictionary to XML-formatted bytes
///
/// # Arguments
/// * `p` - The PLIST dictionary to serialize
///
/// # Returns
/// A byte vector containing the XML representation
///
/// # Panics
/// Will panic if serialization fails (should only happen with invalid data)
///
/// # Example
/// ```rust
/// let mut dict = plist::Dictionary::new();
/// dict.insert("key".into(), "value".into());
/// let xml_bytes = plist_to_xml_bytes(&dict);
/// ```
pub fn plist_to_xml_bytes(p: &plist::Dictionary) -> Vec<u8> { pub fn plist_to_xml_bytes(p: &plist::Dictionary) -> Vec<u8> {
let buf = Vec::new(); let buf = Vec::new();
let mut writer = std::io::BufWriter::new(buf); let mut writer = std::io::BufWriter::new(buf);
@@ -10,10 +30,32 @@ pub fn plist_to_xml_bytes(p: &plist::Dictionary) -> Vec<u8> {
writer.into_inner().unwrap() writer.into_inner().unwrap()
} }
/// Pretty-prints a PLIST value with indentation
///
/// # Arguments
/// * `p` - The PLIST value to format
///
/// # Returns
/// A formatted string representation
pub fn pretty_print_plist(p: &Value) -> String { pub fn pretty_print_plist(p: &Value) -> String {
print_plist(p, 0) print_plist(p, 0)
} }
/// Pretty-prints a PLIST dictionary with key-value pairs
///
/// # Arguments
/// * `dict` - The dictionary to format
///
/// # Returns
/// A formatted string representation with newlines and indentation
///
/// # Example
/// ```rust
/// let mut dict = plist::Dictionary::new();
/// dict.insert("name".into(), "John".into());
/// dict.insert("age".into(), 30.into());
/// println!("{}", pretty_print_dictionary(&dict));
/// ```
pub fn pretty_print_dictionary(dict: &plist::Dictionary) -> String { pub fn pretty_print_dictionary(dict: &plist::Dictionary) -> String {
let items: Vec<String> = dict let items: Vec<String> = dict
.iter() .iter()
@@ -22,6 +64,14 @@ pub fn pretty_print_dictionary(dict: &plist::Dictionary) -> String {
format!("{{\n{}\n}}", items.join(",\n")) format!("{{\n{}\n}}", items.join(",\n"))
} }
/// Internal recursive function for printing PLIST values with indentation
///
/// # Arguments
/// * `p` - The PLIST value to format
/// * `indentation` - Current indentation level
///
/// # Returns
/// Formatted string representation
fn print_plist(p: &Value, indentation: usize) -> String { fn print_plist(p: &Value, indentation: usize) -> String {
let indent = " ".repeat(indentation); let indent = " ".repeat(indentation);
match p { match p {
@@ -75,3 +125,4 @@ fn print_plist(p: &Value, indentation: usize) -> String {
_ => "Unknown".to_string(), _ => "Unknown".to_string(),
} }
} }

View File

@@ -1,28 +0,0 @@
// DebianArch
use json::JsonValue;
pub struct CDTunnel {}
impl CDTunnel {
const MAGIC: &'static [u8; 8] = b"CDTunnel";
pub fn decode(data: &[u8]) -> Result<JsonValue, Box<dyn std::error::Error>> {
let magic_len = CDTunnel::MAGIC.len();
if &data[0..magic_len] != CDTunnel::MAGIC {
Err("Invalid Magic")?;
}
let size = u16::from_be_bytes(data[magic_len..magic_len + 2].try_into()?) as usize;
let content = &data[magic_len + 2..magic_len + 2 + size];
Ok(json::parse(&String::from_utf8(content.to_vec())?)?)
}
pub fn encode(value: JsonValue) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
let mut buf = Vec::new();
let json_str = value.dump();
buf.extend_from_slice(CDTunnel::MAGIC);
buf.extend_from_slice(&u16::to_be_bytes(json_str.len().try_into()?));
buf.extend_from_slice(json_str.as_bytes());
Ok(buf)
}
}

View File

@@ -1,4 +1,7 @@
// Thanks DebianArch //! XPC (Cross-Process Communication) Implementation
//!
//! Provides functionality for interacting with Apple's XPC protocol over HTTP/2,
//! which is used for inter-process communication between iOS/macOS components.
use std::collections::HashMap; use std::collections::HashMap;
@@ -14,24 +17,33 @@ use format::{XPCFlag, XPCMessage, XPCObject};
use log::{debug, warn}; use log::{debug, warn};
use serde::Deserialize; use serde::Deserialize;
pub mod cdtunnel;
pub mod error; pub mod error;
pub mod format; mod format;
/// Represents an XPC connection to a device with available services
pub struct XPCDevice<R: ReadWrite> { pub struct XPCDevice<R: ReadWrite> {
/// The underlying XPC connection
pub connection: XPCConnection<R>, pub connection: XPCConnection<R>,
/// Map of available XPC services by name
pub services: HashMap<String, XPCService>, pub services: HashMap<String, XPCService>,
} }
/// Describes an available XPC service
#[derive(Debug, Clone, Deserialize)] #[derive(Debug, Clone, Deserialize)]
pub struct XPCService { pub struct XPCService {
/// Required entitlement to access this service
pub entitlement: String, pub entitlement: String,
/// Port number where the service is available
pub port: u16, pub port: u16,
/// Whether the service uses remote XPC
pub uses_remote_xpc: bool, pub uses_remote_xpc: bool,
/// Optional list of supported features
pub features: Option<Vec<String>>, pub features: Option<Vec<String>>,
/// Optional service version number
pub service_version: Option<i64>, pub service_version: Option<i64>,
} }
/// Manages an active XPC connection over HTTP/2
pub struct XPCConnection<R: ReadWrite> { pub struct XPCConnection<R: ReadWrite> {
pub(crate) inner: http2::Connection<R>, pub(crate) inner: http2::Connection<R>,
root_message_id: u64, root_message_id: u64,
@@ -39,9 +51,22 @@ pub struct XPCConnection<R: ReadWrite> {
} }
impl<R: ReadWrite> XPCDevice<R> { impl<R: ReadWrite> XPCDevice<R> {
/// Creates a new XPC device connection
///
/// # Arguments
/// * `stream` - The underlying transport stream
///
/// # Returns
/// A connected XPCDevice instance with discovered services
///
/// # Errors
/// Returns `IdeviceError` if:
/// - The connection fails
/// - The service discovery response is malformed
pub async fn new(stream: R) -> Result<Self, IdeviceError> { pub async fn new(stream: R) -> Result<Self, IdeviceError> {
let mut connection = XPCConnection::new(stream).await?; let mut connection = XPCConnection::new(stream).await?;
// Read initial services message
let data = connection.read_message(http2::ROOT_CHANNEL).await?; let data = connection.read_message(http2::ROOT_CHANNEL).await?;
let data = match data.message { let data = match data.message {
@@ -56,6 +81,7 @@ impl<R: ReadWrite> XPCDevice<R> {
None => return Err(IdeviceError::UnexpectedResponse), None => return Err(IdeviceError::UnexpectedResponse),
}; };
// Parse available services
let mut services = HashMap::new(); let mut services = HashMap::new();
for (name, service) in data.into_iter() { for (name, service) in data.into_iter() {
match service.as_dictionary() { match service.as_dictionary() {
@@ -131,18 +157,34 @@ impl<R: ReadWrite> XPCDevice<R> {
}) })
} }
/// Consumes the device and returns the underlying transport stream
pub fn into_inner(self) -> R { pub fn into_inner(self) -> R {
self.connection.inner.stream self.connection.inner.stream
} }
} }
impl<R: ReadWrite> XPCConnection<R> { impl<R: ReadWrite> XPCConnection<R> {
/// Channel ID for root messages
pub const ROOT_CHANNEL: u32 = http2::ROOT_CHANNEL; pub const ROOT_CHANNEL: u32 = http2::ROOT_CHANNEL;
/// Channel ID for reply messages
pub const REPLY_CHANNEL: u32 = http2::REPLY_CHANNEL; pub const REPLY_CHANNEL: u32 = http2::REPLY_CHANNEL;
/// Initial stream ID for HTTP/2 connection
const INIT_STREAM: u32 = http2::INIT_STREAM; const INIT_STREAM: u32 = http2::INIT_STREAM;
/// Establishes a new XPC connection
///
/// # Arguments
/// * `stream` - The underlying transport stream
///
/// # Returns
/// A connected XPCConnection instance
///
/// # Errors
/// Returns `XPCError` if the connection handshake fails
pub async fn new(stream: R) -> Result<Self, XPCError> { pub async fn new(stream: R) -> Result<Self, XPCError> {
let mut client = http2::Connection::new(stream).await?; let mut client = http2::Connection::new(stream).await?;
// Configure HTTP/2 settings
client client
.send_frame(SettingsFrame::new( .send_frame(SettingsFrame::new(
[ [
@@ -154,14 +196,19 @@ impl<R: ReadWrite> XPCConnection<R> {
Default::default(), Default::default(),
)) ))
.await?; .await?;
// Update window size
client client
.send_frame(WindowUpdateFrame::new(Self::INIT_STREAM, 983041)) .send_frame(WindowUpdateFrame::new(Self::INIT_STREAM, 983041))
.await?; .await?;
let mut xpc_client = Self { let mut xpc_client = Self {
inner: client, inner: client,
root_message_id: 1, root_message_id: 1,
reply_message_id: 1, reply_message_id: 1,
}; };
// Perform XPC handshake
xpc_client xpc_client
.send_recv_message( .send_recv_message(
Self::ROOT_CHANNEL, Self::ROOT_CHANNEL,
@@ -173,7 +220,6 @@ impl<R: ReadWrite> XPCConnection<R> {
) )
.await?; .await?;
// we are here. we send data to stream_id 3 yet we get data from stream 1 ???
xpc_client xpc_client
.send_recv_message( .send_recv_message(
Self::REPLY_CHANNEL, Self::REPLY_CHANNEL,
@@ -195,6 +241,14 @@ impl<R: ReadWrite> XPCConnection<R> {
Ok(xpc_client) Ok(xpc_client)
} }
/// Sends a message and waits for the response
///
/// # Arguments
/// * `stream_id` - The channel/stream to use
/// * `message` - The XPC message to send
///
/// # Returns
/// The response message
pub async fn send_recv_message( pub async fn send_recv_message(
&mut self, &mut self,
stream_id: u32, stream_id: u32,
@@ -204,6 +258,7 @@ impl<R: ReadWrite> XPCConnection<R> {
self.read_message(stream_id).await self.read_message(stream_id).await
} }
/// Sends an XPC message without waiting for a response
pub async fn send_message( pub async fn send_message(
&mut self, &mut self,
stream_id: u32, stream_id: u32,
@@ -215,6 +270,7 @@ impl<R: ReadWrite> XPCConnection<R> {
Ok(()) Ok(())
} }
/// Reads an XPC message from the specified stream
pub async fn read_message(&mut self, stream_id: u32) -> Result<XPCMessage, XPCError> { pub async fn read_message(&mut self, stream_id: u32) -> Result<XPCMessage, XPCError> {
let mut buf = self.inner.read_streamid(stream_id).await?; let mut buf = self.inner.read_streamid(stream_id).await?;
loop { loop {
@@ -236,3 +292,4 @@ impl<R: ReadWrite> XPCConnection<R> {
} }
} }
} }

View File

@@ -2,7 +2,7 @@
// idevice Rust implementation of libimobiledevice's ideviceinfo // idevice Rust implementation of libimobiledevice's ideviceinfo
use clap::{Arg, Command}; use clap::{Arg, Command};
use idevice::{lockdown::LockdowndClient, IdeviceService}; use idevice::{lockdown::LockdownClient, IdeviceService};
mod common; mod common;
@@ -57,7 +57,7 @@ async fn main() {
} }
}; };
let mut lockdown_client = match LockdowndClient::connect(&*provider).await { let mut lockdown_client = match LockdownClient::connect(&*provider).await {
Ok(l) => l, Ok(l) => l,
Err(e) => { Err(e) => {
eprintln!("Unable to connect to lockdown: {e:?}"); eprintln!("Unable to connect to lockdown: {e:?}");

View File

@@ -5,7 +5,7 @@ use std::{io::Write, path::PathBuf};
use clap::{arg, value_parser, Arg, Command}; use clap::{arg, value_parser, Arg, Command};
use idevice::{ use idevice::{
lockdown::LockdowndClient, mobile_image_mounter::ImageMounter, pretty_print_plist, lockdown::LockdownClient, mobile_image_mounter::ImageMounter, pretty_print_plist,
IdeviceService, IdeviceService,
}; };
@@ -85,7 +85,7 @@ async fn main() {
} }
}; };
let mut lockdown_client = LockdowndClient::connect(&*provider) let mut lockdown_client = LockdownClient::connect(&*provider)
.await .await
.expect("Unable to connect to lockdown"); .expect("Unable to connect to lockdown");