mirror of
https://github.com/jkcoxson/idevice.git
synced 2026-03-02 14:36:16 +01:00
Document modules and public methods
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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?;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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());
|
||||||
|
|
||||||
|
|||||||
@@ -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(),
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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>>,
|
||||||
|
|||||||
@@ -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());
|
||||||
|
|||||||
@@ -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 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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 {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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<'_>,
|
||||||
|
|||||||
@@ -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(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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:?}");
|
||||||
|
|||||||
@@ -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");
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user