From 5cfe7124c3998de98ffa14026d1934709a9e34e8 Mon Sep 17 00:00:00 2001 From: Jackson Coxson Date: Wed, 21 May 2025 16:17:23 -0600 Subject: [PATCH 01/16] Initial http2 client rewrite --- idevice/src/lib.rs | 12 +- idevice/src/services/xpc/error.rs | 103 ------ idevice/src/services/xpc/format.rs | 448 ------------------------ idevice/src/services/xpc/http2/error.rs | 62 ---- idevice/src/services/xpc/http2/frame.rs | 186 ++++++++++ idevice/src/services/xpc/http2/h2.rs | 256 -------------- idevice/src/services/xpc/http2/mod.rs | 218 +++++------- idevice/src/services/xpc/mod.rs | 289 +-------------- 8 files changed, 293 insertions(+), 1281 deletions(-) delete mode 100644 idevice/src/services/xpc/error.rs delete mode 100644 idevice/src/services/xpc/format.rs delete mode 100644 idevice/src/services/xpc/http2/error.rs create mode 100644 idevice/src/services/xpc/http2/frame.rs delete mode 100644 idevice/src/services/xpc/http2/h2.rs diff --git a/idevice/src/lib.rs b/idevice/src/lib.rs index 4b5ea4a..8dd769d 100644 --- a/idevice/src/lib.rs +++ b/idevice/src/lib.rs @@ -498,8 +498,16 @@ pub enum IdeviceError { InternalError(String), #[cfg(feature = "xpc")] - #[error("xpc message failed")] - Xpc(#[from] xpc::error::XPCError), + #[error("unknown http frame type")] + UnknownFrame(u8), + + #[cfg(feature = "xpc")] + #[error("unknown http setting type")] + UnknownHttpSetting(u16), + + #[cfg(feature = "xpc")] + #[error("Unintialized stream ID")] + UninitializedStreamId, #[cfg(feature = "dvt")] #[error("NSKeyedArchive error")] diff --git a/idevice/src/services/xpc/error.rs b/idevice/src/services/xpc/error.rs deleted file mode 100644 index 96d3ba5..0000000 --- a/idevice/src/services/xpc/error.rs +++ /dev/null @@ -1,103 +0,0 @@ -// DebianArch - -use super::http2::error::Http2Error; -use std::{ - array::TryFromSliceError, error::Error, ffi::FromVecWithNulError, io, num::TryFromIntError, - str::Utf8Error, -}; - -#[derive(Debug)] -pub enum XPCError { - Io(io::Error), - Http2Error(Http2Error), - ParseError(ParseError), - Custom(String), -} - -#[derive(Debug)] -pub enum ParseError { - TryFromSliceError(TryFromSliceError), - TryFromIntError(TryFromIntError), - FromVecWithNulError(FromVecWithNulError), - Utf8Error(Utf8Error), -} - -impl From for XPCError { - fn from(value: TryFromSliceError) -> Self { - Self::ParseError(ParseError::TryFromSliceError(value)) - } -} - -impl From for XPCError { - fn from(value: TryFromIntError) -> Self { - Self::ParseError(ParseError::TryFromIntError(value)) - } -} - -impl From for XPCError { - fn from(value: ParseError) -> Self { - Self::ParseError(value) - } -} - -impl From for XPCError { - fn from(value: FromVecWithNulError) -> Self { - Self::ParseError(ParseError::FromVecWithNulError(value)) - } -} - -impl From for XPCError { - fn from(value: Utf8Error) -> Self { - Self::ParseError(ParseError::Utf8Error(value)) - } -} - -impl From for XPCError { - fn from(value: io::Error) -> Self { - Self::Io(value) - } -} - -impl From<&str> for XPCError { - fn from(value: &str) -> Self { - Self::Custom(value.to_string()) - } -} - -impl From for XPCError { - fn from(value: Http2Error) -> Self { - Self::Http2Error(value) - } -} - -impl std::fmt::Display for XPCError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "XPCError({})", - match self { - Self::Io(io) => io.to_string(), - Self::Http2Error(http2) => http2.to_string(), - Self::ParseError(e) => e.to_string(), - Self::Custom(s) => s.clone(), - } - ) - } -} - -impl std::fmt::Display for ParseError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "ParseError({})", - match self { - Self::TryFromSliceError(e) => e.to_string(), - Self::TryFromIntError(e) => e.to_string(), - Self::FromVecWithNulError(e) => e.to_string(), - Self::Utf8Error(e) => e.to_string(), - } - ) - } -} - -impl Error for XPCError {} diff --git a/idevice/src/services/xpc/format.rs b/idevice/src/services/xpc/format.rs deleted file mode 100644 index 9e850e1..0000000 --- a/idevice/src/services/xpc/format.rs +++ /dev/null @@ -1,448 +0,0 @@ -// DebianArch - -use std::{ - ffi::CString, - io::{BufRead, Cursor, Read}, - ops::{BitOr, BitOrAssign}, -}; - -use super::error::XPCError; -use indexmap::IndexMap; -use log::debug; -use serde::{Deserialize, Serialize}; - -#[derive(Clone, Copy, Debug)] -#[repr(u32)] -pub enum XPCFlag { - AlwaysSet, - DataFlag, - WantingReply, - InitHandshake, - - Custom(u32), -} - -impl From for u32 { - fn from(value: XPCFlag) -> Self { - match value { - XPCFlag::AlwaysSet => 0x00000001, - XPCFlag::DataFlag => 0x00000100, - XPCFlag::WantingReply => 0x00010000, - XPCFlag::InitHandshake => 0x00400000, - XPCFlag::Custom(inner) => inner, - } - } -} - -impl BitOr for XPCFlag { - fn bitor(self, rhs: Self) -> Self::Output { - XPCFlag::Custom(u32::from(self) | u32::from(rhs)) - } - - type Output = XPCFlag; -} - -impl BitOrAssign for XPCFlag { - fn bitor_assign(&mut self, rhs: Self) { - *self = self.bitor(rhs); - } -} - -impl PartialEq for XPCFlag { - fn eq(&self, other: &Self) -> bool { - u32::from(*self) == u32::from(*other) - } -} - -#[repr(u32)] -pub enum XPCType { - Bool = 0x00002000, - Dictionary = 0x0000f000, - Array = 0x0000e000, - - Int64 = 0x00003000, - UInt64 = 0x00004000, - - String = 0x00009000, - Data = 0x00008000, - Uuid = 0x0000a000, -} - -impl TryFrom for XPCType { - type Error = XPCError; - - fn try_from(value: u32) -> Result { - match value { - 0x00002000 => Ok(Self::Bool), - 0x0000f000 => Ok(Self::Dictionary), - 0x0000e000 => Ok(Self::Array), - 0x00003000 => Ok(Self::Int64), - 0x00004000 => Ok(Self::UInt64), - 0x00009000 => Ok(Self::String), - 0x00008000 => Ok(Self::Data), - 0x0000a000 => Ok(Self::Uuid), - _ => Err("Invalid XPCType")?, - } - } -} - -pub type Dictionary = IndexMap; - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub enum XPCObject { - Bool(bool), - Dictionary(Dictionary), - Array(Vec), - - Int64(i64), - UInt64(u64), - - String(String), - Data(Vec), - Uuid(uuid::Uuid), -} - -impl From for XPCObject { - fn from(value: plist::Value) -> Self { - match value { - plist::Value::Array(v) => { - XPCObject::Array(v.iter().map(|item| XPCObject::from(item.clone())).collect()) - } - plist::Value::Dictionary(v) => { - let mut dict = Dictionary::new(); - for (k, v) in v.into_iter() { - dict.insert(k.clone(), XPCObject::from(v)); - } - XPCObject::Dictionary(dict) - } - plist::Value::Boolean(v) => XPCObject::Bool(v), - plist::Value::Data(v) => XPCObject::Data(v), - plist::Value::Date(_) => todo!(), - plist::Value::Real(_) => todo!(), - plist::Value::Integer(v) => XPCObject::Int64(v.as_signed().unwrap()), - plist::Value::String(v) => XPCObject::String(v), - plist::Value::Uid(_) => todo!(), - _ => todo!(), - } - } -} - -impl XPCObject { - pub fn to_plist(&self) -> plist::Value { - match self { - Self::Bool(v) => plist::Value::Boolean(*v), - Self::Uuid(uuid) => plist::Value::String(uuid.to_string()), - Self::UInt64(v) => plist::Value::Integer({ *v }.into()), - Self::Int64(v) => plist::Value::Integer({ *v }.into()), - Self::String(v) => plist::Value::String(v.clone()), - Self::Data(v) => plist::Value::Data(v.clone()), - Self::Array(v) => plist::Value::Array(v.iter().map(|item| item.to_plist()).collect()), - Self::Dictionary(v) => { - let mut dict = plist::Dictionary::new(); - for (k, v) in v.into_iter() { - dict.insert(k.clone(), v.to_plist()); - } - plist::Value::Dictionary(dict) - } - } - } - - pub fn to_value(value: &T) -> Self { - match plist::to_value(value) { - Ok(v) => Self::from(v), - Err(_) => panic!("oof"), - } - } - - pub fn encode(&self) -> Result, XPCError> { - let mut buf = Vec::new(); - buf.extend_from_slice(&0x42133742_u32.to_le_bytes()); - buf.extend_from_slice(&0x00000005_u32.to_le_bytes()); - self.encode_object(&mut buf)?; - Ok(buf) - } - - fn encode_object(&self, buf: &mut Vec) -> Result<(), XPCError> { - match self { - XPCObject::Bool(val) => { - buf.extend_from_slice(&(XPCType::Bool as u32).to_le_bytes()); - buf.push(if *val { 0 } else { 1 }); - buf.extend_from_slice(&[0].repeat(3)); - } - XPCObject::Dictionary(dict) => { - buf.extend_from_slice(&(XPCType::Dictionary as u32).to_le_bytes()); - buf.extend_from_slice(&0_u32.to_le_bytes()); // represents l, no idea what this is. - buf.extend_from_slice(&(dict.len() as u32).to_le_bytes()); - for (k, v) in dict { - let padding = Self::calculate_padding(k.len() + 1); - buf.extend_from_slice(k.as_bytes()); - buf.push(0); - buf.extend_from_slice(&[0].repeat(padding)); - v.encode_object(buf)?; - } - } - XPCObject::Array(items) => { - buf.extend_from_slice(&(XPCType::Array as u32).to_le_bytes()); - buf.extend_from_slice(&0_u32.to_le_bytes()); // represents l, no idea what this is. - buf.extend_from_slice(&(items.len() as u32).to_le_bytes()); - for item in items { - item.encode_object(buf)?; - } - } - - XPCObject::Int64(num) => { - buf.extend_from_slice(&(XPCType::Int64 as u32).to_le_bytes()); - buf.extend_from_slice(&num.to_le_bytes()); - } - XPCObject::UInt64(num) => { - buf.extend_from_slice(&(XPCType::UInt64 as u32).to_le_bytes()); - buf.extend_from_slice(&num.to_le_bytes()); - } - XPCObject::String(item) => { - let l = item.len() + 1; - let padding = Self::calculate_padding(l); - buf.extend_from_slice(&(XPCType::String as u32).to_le_bytes()); - buf.extend_from_slice(&(l as u32).to_le_bytes()); - buf.extend_from_slice(item.as_bytes()); - buf.push(0); - buf.extend_from_slice(&[0].repeat(padding)); - } - XPCObject::Data(data) => { - let l = data.len(); - let padding = Self::calculate_padding(l); - buf.extend_from_slice(&(XPCType::Data as u32).to_le_bytes()); - buf.extend_from_slice(&(l as u32).to_le_bytes()); - buf.extend_from_slice(data); - buf.extend_from_slice(&[0].repeat(padding)); - } - XPCObject::Uuid(uuid) => { - buf.extend_from_slice(&(XPCType::Uuid as u32).to_le_bytes()); - buf.extend_from_slice(&16_u32.to_le_bytes()); - buf.extend_from_slice(uuid.as_bytes()); - } - } - Ok(()) - } - - pub fn decode(buf: &[u8]) -> Result { - let magic = u32::from_le_bytes(buf[0..4].try_into()?); - if magic != 0x42133742 { - Err("Invalid magic for XPCObject")? - } - - let version = u32::from_le_bytes(buf[4..8].try_into()?); - if version != 0x00000005 { - Err("Unexpected version for XPCObject")? - } - - Self::decode_object(&mut Cursor::new(&buf[8..])) - } - - fn decode_object(mut cursor: &mut Cursor<&[u8]>) -> Result { - let mut buf_32: [u8; 4] = Default::default(); - cursor.read_exact(&mut buf_32)?; - let xpc_type = u32::from_le_bytes(buf_32); - let xpc_type: XPCType = xpc_type.try_into()?; - match xpc_type { - XPCType::Dictionary => { - let mut ret = IndexMap::new(); - - cursor.read_exact(&mut buf_32)?; - let _l = u32::from_le_bytes(buf_32); - cursor.read_exact(&mut buf_32)?; - let num_entries = u32::from_le_bytes(buf_32); - for _i in 0..num_entries { - let mut key_buf = Vec::new(); - BufRead::read_until(&mut cursor, 0, &mut key_buf)?; - let key = CString::from_vec_with_nul(key_buf)?.to_str()?.to_string(); - let padding = Self::calculate_padding(key.len() + 1); - - BufRead::consume(&mut cursor, padding); - ret.insert(key, Self::decode_object(cursor)?); - } - Ok(XPCObject::Dictionary(ret)) - } - XPCType::Array => { - cursor.read_exact(&mut buf_32)?; - let _l = u32::from_le_bytes(buf_32); - cursor.read_exact(&mut buf_32)?; - let num_entries = u32::from_le_bytes(buf_32); - - let mut ret = Vec::new(); - for _i in 0..num_entries { - ret.push(Self::decode_object(cursor)?); - } - Ok(XPCObject::Array(ret)) - } - XPCType::Int64 => { - let mut buf: [u8; 8] = Default::default(); - cursor.read_exact(&mut buf)?; - Ok(XPCObject::Int64(i64::from_le_bytes(buf))) - } - XPCType::UInt64 => { - let mut buf: [u8; 8] = Default::default(); - cursor.read_exact(&mut buf)?; - Ok(XPCObject::UInt64(u64::from_le_bytes(buf))) - } - XPCType::String => { - // 'l' includes utf8 '\0' character. - cursor.read_exact(&mut buf_32)?; - let l = u32::from_le_bytes(buf_32) as usize; - let padding = Self::calculate_padding(l); - - let mut key_buf = vec![0; l]; - cursor.read_exact(&mut key_buf)?; - let key = CString::from_vec_with_nul(key_buf)?.to_str()?.to_string(); - BufRead::consume(&mut cursor, padding); - Ok(XPCObject::String(key)) - } - XPCType::Bool => { - let mut buf: [u8; 4] = Default::default(); - cursor.read_exact(&mut buf)?; - Ok(XPCObject::Bool(buf[0] != 0)) - } - XPCType::Data => { - cursor.read_exact(&mut buf_32)?; - let l = u32::from_le_bytes(buf_32) as usize; - let padding = Self::calculate_padding(l); - - let mut data = vec![0; l]; - cursor.read_exact(&mut data)?; - BufRead::consume(&mut cursor, padding); - Ok(XPCObject::Data(data)) - } - XPCType::Uuid => { - let mut data: [u8; 16] = Default::default(); - cursor.read_exact(&mut data)?; - Ok(XPCObject::Uuid(uuid::Builder::from_bytes(data).into_uuid())) - } - } - } - - pub fn as_dictionary(&self) -> Option<&Dictionary> { - match self { - XPCObject::Dictionary(dict) => Some(dict), - _ => None, - } - } - - pub fn as_array(&self) -> Option<&Vec> { - match self { - XPCObject::Array(array) => Some(array), - _ => None, - } - } - - pub fn as_string(&self) -> Option<&str> { - match self { - XPCObject::String(s) => Some(s), - _ => None, - } - } - - pub fn as_bool(&self) -> Option<&bool> { - match self { - XPCObject::Bool(b) => Some(b), - _ => None, - } - } - - pub fn as_signed_integer(&self) -> Option { - match self { - XPCObject::String(s) => s.parse().ok(), - XPCObject::Int64(v) => Some(*v), - _ => None, - } - } - - pub fn as_unsigned_integer(&self) -> Option { - match self { - XPCObject::String(s) => s.parse().ok(), - XPCObject::UInt64(v) => Some(*v), - _ => None, - } - } - - fn calculate_padding(len: usize) -> usize { - let c = ((len as f64) / 4.0).ceil(); - (c * 4.0 - (len as f64)) as usize - } -} - -impl From for XPCObject { - fn from(value: Dictionary) -> Self { - XPCObject::Dictionary(value) - } -} - -#[derive(Debug)] -pub struct XPCMessage { - pub flags: u32, - pub message: Option, - pub message_id: Option, -} - -impl XPCMessage { - pub fn new( - flags: Option, - message: Option, - message_id: Option, - ) -> XPCMessage { - XPCMessage { - flags: flags.unwrap_or(XPCFlag::AlwaysSet).into(), - message, - message_id, - } - } - - pub fn decode(data: &[u8]) -> Result { - if data.len() < 24 { - Err("XPCMessage must be at least 24 bytes.")? - } - - let magic = u32::from_le_bytes(data[0..4].try_into()?); - if magic != 0x29b00b92_u32 { - Err("XPCMessage magic is invalid.")? - } - - let flags = u32::from_le_bytes(data[4..8].try_into()?); - let body_len = u64::from_le_bytes(data[8..16].try_into()?); - let message_id = u64::from_le_bytes(data[16..24].try_into()?); - if body_len + 24 > data.len().try_into()? { - Err("XPCMessage body length given is incorrect.")? - } - - // for some reason the above if check doesn't work ??? - debug!("Body length {} : {}", body_len, data.len()); - if body_len == 0 { - return Ok(XPCMessage { - flags, - message: None, - message_id: Some(message_id), - }); - } - Ok(XPCMessage { - flags, - message: Some(XPCObject::decode(&data[24..24 + body_len as usize])?), - message_id: Some(message_id), - }) - } - - pub fn encode(self, message_id: u64) -> Result, XPCError> { - let mut out = 0x29b00b92_u32.to_le_bytes().to_vec(); - out.extend_from_slice(&self.flags.to_le_bytes()); - match self.message { - Some(message) => { - let body = message.encode()?; - out.extend_from_slice(&(body.len() as u64).to_le_bytes()); // body length - out.extend_from_slice(&message_id.to_le_bytes()); // messageId - out.extend_from_slice(&body); - } - _ => { - out.extend_from_slice(&0_u64.to_le_bytes()); - out.extend_from_slice(&message_id.to_le_bytes()); - } - } - Ok(out) - } -} diff --git a/idevice/src/services/xpc/http2/error.rs b/idevice/src/services/xpc/http2/error.rs deleted file mode 100644 index f067aaf..0000000 --- a/idevice/src/services/xpc/http2/error.rs +++ /dev/null @@ -1,62 +0,0 @@ -// DebianArch - -use std::{array::TryFromSliceError, error::Error, io, num::TryFromIntError}; - -use tokio::sync::mpsc::error::SendError; - -#[derive(Debug)] -pub enum Http2Error { - Io(io::Error), - SendError, - TryFromIntError(TryFromIntError), - TryFromSliceError(TryFromSliceError), - Custom(String), -} - -impl From for Http2Error { - fn from(value: io::Error) -> Self { - Self::Io(value) - } -} - -impl From> for Http2Error { - fn from(_: SendError) -> Self { - Self::SendError - } -} - -impl From<&str> for Http2Error { - fn from(value: &str) -> Self { - Self::Custom(value.to_string()) - } -} - -impl From for Http2Error { - fn from(value: TryFromIntError) -> Self { - Self::TryFromIntError(value) - } -} - -impl From for Http2Error { - fn from(value: TryFromSliceError) -> Self { - Self::TryFromSliceError(value) - } -} - -impl std::fmt::Display for Http2Error { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "Http2Error({})", - match self { - Self::Io(io) => io.to_string(), - Self::SendError => "SendError".to_string(), - Self::TryFromIntError(e) => e.to_string(), - Self::TryFromSliceError(e) => e.to_string(), - Self::Custom(s) => s.clone(), - } - ) - } -} - -impl Error for Http2Error {} diff --git a/idevice/src/services/xpc/http2/frame.rs b/idevice/src/services/xpc/http2/frame.rs new file mode 100644 index 0000000..d3f1195 --- /dev/null +++ b/idevice/src/services/xpc/http2/frame.rs @@ -0,0 +1,186 @@ +// Jackson Coxson + +use crate::{IdeviceError, ReadWrite}; +use tokio::io::AsyncReadExt; + +pub trait HttpFrame { + fn serialize(&self) -> Vec; +} + +pub enum Frame { + Settings(SettingsFrame), + WindowUpdate(WindowUpdateFrame), + Headers(HeadersFrame), + Data(DataFrame), +} + +impl Frame { + pub async fn next(socket: &mut impl ReadWrite) -> Result { + // Read the len of the frame + let mut buf = [0u8; 3]; + socket.read_exact(&mut buf).await?; + let frame_len = u32::from_be_bytes([0x00, buf[0], buf[1], buf[2]]); + + // Read the fields + let frame_type = socket.read_u8().await?; + let flags = socket.read_u8().await?; + let stream_id = socket.read_u32().await?; + + let body = vec![0; frame_len as usize]; + socket.read_exact(&mut buf).await?; + + Ok(match frame_type { + 0x00 => { + // data + Self::Data(DataFrame { + stream_id, + payload: body, + }) + } + 0x01 => { + // headers + Self::Headers(HeadersFrame { stream_id }) + } + 0x04 => { + // settings + let mut body = std::io::Cursor::new(body); + let mut settings = Vec::new(); + + while let Ok(setting_type) = body.read_u16().await { + settings.push(match setting_type { + 0x03 => { + let max_streams = body.read_u32().await?; + Setting::MaxConcurrentStreams(max_streams) + } + 0x04 => { + let window_size = body.read_u32().await?; + Setting::InitialWindowSize(window_size) + } + _ => { + return Err(IdeviceError::UnknownHttpSetting(setting_type)); + } + }); + } + Self::Settings(SettingsFrame { + settings, + stream_id, + flags, + }) + } + 0x08 => { + // window update + if body.len() != 4 { + return Err(IdeviceError::UnexpectedResponse); + } + + let window = u32::from_be_bytes([body[0], body[1], body[2], body[3]]); + Self::WindowUpdate(WindowUpdateFrame { + increment_size: window, + stream_id, + }) + } + _ => { + return Err(IdeviceError::UnknownFrame(frame_type)); + } + }) + } +} + +pub struct SettingsFrame { + pub settings: Vec, + pub stream_id: u32, + pub flags: u8, +} + +pub enum Setting { + MaxConcurrentStreams(u32), + InitialWindowSize(u32), +} + +impl SettingsFrame { + pub fn ack(&mut self) { + self.flags = 1; // this seems to be the only http flag used + } +} + +impl Setting { + fn serialize(&self) -> Vec { + match self { + Setting::MaxConcurrentStreams(m) => { + let mut res = vec![0x00, 0x03]; + res.extend(m.to_be_bytes()); + res + } + Setting::InitialWindowSize(s) => { + let mut res = vec![0x00, 0x04]; + res.extend(s.to_be_bytes()); + res + } + } + } +} + +impl HttpFrame for SettingsFrame { + fn serialize(&self) -> Vec { + let settings = self + .settings + .iter() + .map(|x| x.serialize()) + .collect::>>() + .concat(); + let settings_len = (settings.len() as u32).to_be_bytes(); + let mut res = vec![ + settings_len[1], + settings_len[2], + settings_len[3], + 0x04, + self.flags, + ]; + res.extend(self.stream_id.to_be_bytes()); + res.extend(settings); + res + } +} + +pub struct WindowUpdateFrame { + pub increment_size: u32, + pub stream_id: u32, +} + +impl HttpFrame for WindowUpdateFrame { + fn serialize(&self) -> Vec { + let mut res = vec![0x00, 0x00, 0x04, 0x08, 0x00]; // size, frame ID, flags + res.extend(self.stream_id.to_be_bytes()); + res.extend(self.increment_size.to_be_bytes()); + res + } +} + +/// We don't actually care about this frame according to spec. This is just to open new channels. +pub struct HeadersFrame { + pub stream_id: u32, +} + +impl HttpFrame for HeadersFrame { + fn serialize(&self) -> Vec { + let mut res = vec![0x00, 0x00, 0x00, 0x01, 0x04]; + res.extend(self.stream_id.to_be_bytes()); + res + } +} + +pub struct DataFrame { + pub stream_id: u32, + pub payload: Vec, +} + +impl HttpFrame for DataFrame { + fn serialize(&self) -> Vec { + let mut res = (self.payload.len() as u32).to_be_bytes().to_vec(); + res.remove(0); // only 3 significant bytes + res.extend([0x00, 0x00]); // frame type, flags + res.extend(self.stream_id.to_be_bytes()); + res.extend(self.payload.clone()); + res + } +} diff --git a/idevice/src/services/xpc/http2/h2.rs b/idevice/src/services/xpc/http2/h2.rs deleted file mode 100644 index c4ee9dc..0000000 --- a/idevice/src/services/xpc/http2/h2.rs +++ /dev/null @@ -1,256 +0,0 @@ -// DebianArch - -use std::collections::HashMap; - -use super::error::Http2Error; - -pub const HTTP2_MAGIC: &[u8; 24] = b"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"; - -#[derive(Debug)] -pub struct Frame { - pub stream_id: u32, - pub flags: u8, - pub frame_type: FrameType, - - pub body: Vec, -} - -impl Frame { - pub fn deserialize(buf: &[u8]) -> Result { - let mut len_buf = buf[0..3].to_vec(); - len_buf.insert(0, 0); - - let body_len = u32::from_be_bytes(len_buf.try_into().unwrap()) as usize; - let frame_type = buf[3]; - let flags = buf[4]; - let stream_id = u32::from_be_bytes(buf[5..9].try_into()?); - let body = buf[9..9 + body_len].to_vec(); - Ok(Self { - stream_id, - flags, - frame_type: frame_type.into(), - body, - }) - } -} - -impl Framable for Frame { - fn serialize(&self) -> Vec { - let mut res = Vec::new(); - - let body_len = (self.body.len() as u32).to_be_bytes(); - res.extend_from_slice(&[body_len[1], body_len[2], body_len[3]]); // [0..3] - res.push(self.frame_type.into()); // [3] - res.push(self.flags); // flag mask [4] - res.extend_from_slice(&self.stream_id.to_be_bytes()); // [4..8] - res.extend_from_slice(&self.body); // [9..9+len] - res - } -} - -pub trait Framable: From { - fn serialize(&self) -> Vec; -} - -// Frame implementations: -pub struct SettingsFrame { - frame: Frame, - pub settings: HashMap, -} - -impl SettingsFrame { - pub const MAX_CONCURRENT_STREAMS: u16 = 0x03; - pub const INITIAL_WINDOW_SIZE: u16 = 0x04; - - pub const ACK: u8 = 0x01; - pub fn new(/*stream_id: u32, */ settings: HashMap, flags: u8) -> Self { - let mut body = Vec::new(); - for setting in settings.clone() { - body.extend_from_slice(&setting.0.to_be_bytes()); - body.extend_from_slice(&setting.1.to_be_bytes()); - } - Self { - frame: Frame { - /*stream_id*/ stream_id: 0, - flags, - frame_type: FrameType::Settings, - body, - }, - settings, - } - } - - pub fn ack(/*stream_id: u32*/) -> Self { - Self { - frame: Frame { - /*stream_id*/ stream_id: 0, - flags: Self::ACK, - frame_type: FrameType::Settings, - body: Vec::new(), - }, - settings: HashMap::new(), - } - } -} - -impl Framable for SettingsFrame { - fn serialize(&self) -> Vec { - self.frame.serialize() - } -} - -impl From for SettingsFrame { - fn from(value: Frame) -> Self { - let mut idx = 0; - let mut settings = HashMap::new(); - while idx < value.body.len() { - let key = u16::from_be_bytes(value.body[idx..idx + 2].try_into().unwrap()); - let value = u32::from_be_bytes(value.body[idx + 2..idx + 2 + 4].try_into().unwrap()); - settings.insert(key, value); - idx += 2 + 4; - } - Self { - frame: value, - settings, - } - } -} - -pub struct WindowUpdateFrame { - frame: Frame, -} - -impl WindowUpdateFrame { - // the frame's stream identifier indicates the affected stream; in the latter, the value "0" indicates that the entire connection is the subject of the frame. - pub fn new(stream_id: u32, window_increment: u32) -> Self { - if window_increment == 0 { - panic!("PROTOCOL_ERROR"); - } - Self { - frame: Frame { - stream_id, - flags: Default::default(), - frame_type: FrameType::WindowUpdate, - body: window_increment.to_be_bytes().to_vec(), - }, - } - } -} - -impl Framable for WindowUpdateFrame { - fn serialize(&self) -> Vec { - self.frame.serialize() - } -} - -impl From for WindowUpdateFrame { - fn from(value: Frame) -> Self { - Self { frame: value } - } -} - -pub struct HeadersFrame { - frame: Frame, -} - -impl HeadersFrame { - pub const END_HEADERS: u8 = 0x04; - pub const PADDED: u8 = 0x08; - pub const PRIORITY: u8 = 0x20; - pub fn new(stream_id: u32, flags: u8) -> Self { - Self { - frame: Frame { - stream_id, - flags, - frame_type: FrameType::Headers, - body: Vec::new(), - }, - } - } -} - -impl Framable for HeadersFrame { - fn serialize(&self) -> Vec { - if self.frame.flags & Self::PADDED == Self::PADDED { - unimplemented!("haven't added padding support !") - }; - - if self.frame.flags & Self::PRIORITY == Self::PRIORITY { - unimplemented!("haven't added priority support !") - }; - - // let padding = 0; for 'PADDED' flag - // let priority_data = b""; // for PRIORITY flag - self.frame.serialize() - } -} - -impl From for HeadersFrame { - fn from(value: Frame) -> Self { - if value.flags & Self::PADDED == Self::PADDED { - unimplemented!("haven't added padding support !") - }; - - if value.flags & Self::PRIORITY == Self::PRIORITY { - unimplemented!("haven't added priority support !") - }; - Self { frame: value } - } -} - -pub struct DataFrame { - frame: Frame, -} - -impl DataFrame { - // TODO: Handle padding flag. - pub fn new(stream_id: u32, data: Vec, flags: u8) -> Self { - Self { - frame: Frame { - stream_id, - flags, - frame_type: FrameType::Data, - body: data, - }, - } - } -} - -impl Framable for DataFrame { - fn serialize(&self) -> Vec { - self.frame.serialize() - } -} - -impl From for DataFrame { - fn from(value: Frame) -> Self { - Self { frame: value } - } -} - -#[repr(u8)] -#[derive(Clone, Copy, Debug)] -pub enum FrameType { - Data = 0, - Headers = 1, - Priority = 2, - RstStream = 3, - Settings = 4, - PushPromise = 5, - Ping = 6, - GoAway = 7, - WindowUpdate = 8, - Continuation = 9, -} - -impl From for u8 { - fn from(value: FrameType) -> Self { - value as u8 - } -} - -impl From for FrameType { - fn from(value: u8) -> Self { - unsafe { std::mem::transmute::<_, FrameType>(value) } - } -} diff --git a/idevice/src/services/xpc/http2/mod.rs b/idevice/src/services/xpc/http2/mod.rs index c862901..361dc2f 100644 --- a/idevice/src/services/xpc/http2/mod.rs +++ b/idevice/src/services/xpc/http2/mod.rs @@ -1,143 +1,115 @@ -// DebianArch +// Jackson Coxson -use async_recursion::async_recursion; -use error::Http2Error; -use std::collections::HashMap; +use frame::HttpFrame; +use log::warn; +use std::collections::{HashMap, VecDeque}; +use tokio::io::AsyncWriteExt; -use tokio::{ - io::{AsyncReadExt, AsyncWriteExt}, - sync::mpsc::{self, Receiver, Sender}, -}; +use crate::{IdeviceError, ReadWrite}; -pub mod error; -pub mod h2; +mod frame; +pub use frame::Setting; -use h2::{ - DataFrame, Framable, Frame, FrameType, HeadersFrame, SettingsFrame, WindowUpdateFrame, - HTTP2_MAGIC, -}; +const HTTP2_MAGIC: &[u8] = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n".as_bytes(); -use crate::ReadWrite; - -pub type Channels = HashMap>, Receiver>)>; - -pub const INIT_STREAM: u32 = 0; -pub const ROOT_CHANNEL: u32 = 1; -pub const REPLY_CHANNEL: u32 = 3; - -pub struct Connection { - pub stream: R, - channels: Channels, - window_size: u32, +pub struct Http2Client { + inner: R, + cache: HashMap>>, } -impl Connection { - pub async fn new(mut stream: R) -> Result { - stream.write_all(HTTP2_MAGIC).await?; +impl Http2Client { + /// Writes the magic and inits the caches + pub async fn new(mut inner: R) -> Result { + inner.write_all(HTTP2_MAGIC).await?; + inner.flush().await?; Ok(Self { - stream, - channels: HashMap::new(), - window_size: 1048576, + inner, + cache: HashMap::new(), }) } - pub async fn send_frame(&mut self, frame: A) -> Result<(), Http2Error> { - let body = &frame.serialize(); - if body.len() > self.window_size as usize { - panic!("we need to chunk it :D") - } - self.stream.write_all(body).await?; - Ok(()) - } - - pub async fn read_data(&mut self) -> Result, Http2Error> { - loop { - let frame = self.read_frame().await?; - match frame.frame_type { - FrameType::Data => { - if frame.stream_id % 2 == 0 && !frame.body.is_empty() { - let frame_len: u32 = frame.body.len().try_into()?; - self.send_frame(WindowUpdateFrame::new(0, frame_len)) - .await?; - self.send_frame(WindowUpdateFrame::new(frame.stream_id, frame_len)) - .await?; - } - match self.channels.get_mut(&frame.stream_id) { - Some((sender, _receiver)) => { - sender.send(frame.body.clone()).await?; - } - None => { - let chan = mpsc::channel(100); - chan.0.send(frame.body.clone()).await?; - self.channels.insert(frame.stream_id, chan); - } - } - return Ok(frame.body); - } - FrameType::GoAway | FrameType::RstStream => { - let _last_streamid = u32::from_be_bytes(frame.body[0..4].try_into().unwrap()); - return Err("connection closed, bye")?; - } - FrameType::Settings => { - let flags = frame.flags; - let settings_frame: SettingsFrame = frame.into(); - if flags & SettingsFrame::ACK != SettingsFrame::ACK { - self.send_frame(SettingsFrame::ack()).await?; - } - if let Some(&window_size) = settings_frame - .settings - .get(&SettingsFrame::INITIAL_WINDOW_SIZE) - { - self.window_size = window_size; - } - } - _ => continue, - } - } - } - - pub async fn read_frame(&mut self) -> Result { - let mut length_buf = vec![0; 3]; - self.stream.read_exact(&mut length_buf).await?; - length_buf.insert(0, 0); - let len = u32::from_be_bytes(length_buf.clone().try_into().unwrap()) as usize; - let mut rest = vec![0; 9 - 3 + len]; - self.stream.read_exact(&mut rest).await?; - - let mut content = vec![length_buf[1], length_buf[2], length_buf[3]]; - content.extend_from_slice(&rest); - Frame::deserialize(&content) - } - - // pub async fn multiplex_write(&mut self, stream_id: u32) -> Result<()> {} - - // gets a Reader + Writer for a channel. - pub async fn write_streamid( + pub async fn set_settings( &mut self, + settings: Vec, stream_id: u32, - data: Vec, - ) -> Result<(), Http2Error> { - // TODO: If we ever allow concurrent writes we must not always send 'END_HEADERS'. - self.send_frame(HeadersFrame::new(stream_id, HeadersFrame::END_HEADERS)) - .await?; - self.send_frame(DataFrame::new(stream_id, data, Default::default())) - .await?; + ) -> Result<(), IdeviceError> { + let frame = frame::SettingsFrame { + settings, + stream_id, + flags: 0, + } + .serialize(); + self.inner.write_all(&frame).await?; Ok(()) } - #[async_recursion] - pub async fn read_streamid(&mut self, stream_id: u32) -> Result, Http2Error> { - match self.channels.get_mut(&stream_id) { - Some((_sender, receiver)) => match receiver.try_recv().ok() { - Some(data) => Ok(data), - None => { - self.read_data().await?; - self.read_streamid(stream_id).await - } - }, + pub async fn window_update( + &mut self, + increment_size: u32, + stream_id: u32, + ) -> Result<(), IdeviceError> { + let frame = frame::WindowUpdateFrame { + increment_size, + stream_id, + } + .serialize(); + self.inner.write_all(&frame).await?; + Ok(()) + } + + pub async fn open_stream(&mut self, stream_id: u32) -> Result<(), IdeviceError> { + self.cache.insert(stream_id, VecDeque::new()); + let frame = frame::HeadersFrame { stream_id }.serialize(); + self.inner.write_all(&frame).await?; + Ok(()) + } + + pub async fn read(&mut self, stream_id: u32) -> Result, IdeviceError> { + // See if we already have a cached message from another read + let c = match self.cache.get_mut(&stream_id) { + Some(c) => c, None => { - self.read_data().await?; - self.read_streamid(stream_id).await + warn!("Requested stream ID is not in cache"); + return Err(IdeviceError::UninitializedStreamId); + } + }; + if let Some(d) = c.pop_front() { + return Ok(d); + } + + // handle packets until we get what we want + loop { + let frame = frame::Frame::next(&mut self.inner).await?; + match frame { + frame::Frame::Settings(settings_frame) => { + if settings_frame.flags != 1 { + // ack that + let frame = frame::SettingsFrame { + settings: Vec::new(), + stream_id: settings_frame.stream_id, + flags: 1, + } + .serialize(); + self.inner.write_all(&frame).await?; + } + } + frame::Frame::Data(data_frame) => { + if data_frame.stream_id == stream_id { + return Ok(data_frame.payload); + } else { + let c = match self.cache.get_mut(&data_frame.stream_id) { + Some(c) => c, + None => { + warn!("Received message for stream ID not in cache"); + continue; + } + }; + c.push_back(data_frame.payload); + } + } + _ => { + // do nothing, we shouldn't receive these frames + } } } } diff --git a/idevice/src/services/xpc/mod.rs b/idevice/src/services/xpc/mod.rs index b84fb58..b1141ec 100644 --- a/idevice/src/services/xpc/mod.rs +++ b/idevice/src/services/xpc/mod.rs @@ -1,290 +1,5 @@ -//! 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. +// Jackson Coxson -use std::collections::HashMap; mod http2; -use crate::{IdeviceError, ReadWrite}; -use error::XPCError; -use format::{XPCFlag, XPCMessage, XPCObject}; -use http2::h2::{SettingsFrame, WindowUpdateFrame}; -use log::{debug, warn}; -use serde::Deserialize; - -pub mod error; -mod format; - -/// Represents an XPC connection to a device with available services -pub struct XPCDevice { - /// The underlying XPC connection - pub connection: XPCConnection, - /// Map of available XPC services by name - pub services: HashMap, -} - -/// Describes an available XPC service -#[derive(Debug, Clone, Deserialize)] -pub struct XPCService { - /// Required entitlement to access this service - pub entitlement: String, - /// Port number where the service is available - pub port: u16, - /// Whether the service uses remote XPC - pub uses_remote_xpc: bool, - /// Optional list of supported features - pub features: Option>, - /// Optional service version number - pub service_version: Option, -} - -/// Manages an active XPC connection over HTTP/2 -pub struct XPCConnection { - pub(crate) inner: http2::Connection, - root_message_id: u64, - reply_message_id: u64, -} - -impl XPCDevice { - /// 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 { - let mut connection = XPCConnection::new(stream).await?; - - // Read initial services message - let data = connection.read_message(http2::ROOT_CHANNEL).await?; - - let data = match data.message { - Some(d) => match d - .as_dictionary() - .and_then(|x| x.get("Services")) - .and_then(|x| x.as_dictionary()) - { - Some(d) => d.to_owned(), - None => return Err(IdeviceError::UnexpectedResponse), - }, - None => return Err(IdeviceError::UnexpectedResponse), - }; - - // Parse available services - let mut services = HashMap::new(); - for (name, service) in data.into_iter() { - match service.as_dictionary() { - Some(service) => { - let entitlement = match service.get("Entitlement").and_then(|x| x.as_string()) { - Some(e) => e.to_string(), - None => { - warn!("Service did not contain entitlement string"); - continue; - } - }; - let port = match service - .get("Port") - .and_then(|x| x.as_string()) - .and_then(|x| x.parse::().ok()) - { - Some(e) => e, - None => { - warn!("Service did not contain port string"); - continue; - } - }; - let uses_remote_xpc = match service - .get("Properties") - .and_then(|x| x.as_dictionary()) - .and_then(|x| x.get("UsesRemoteXPC")) - .and_then(|x| x.as_bool()) - { - Some(e) => e.to_owned(), - None => false, // default is false - }; - - let features = service - .get("Properties") - .and_then(|x| x.as_dictionary()) - .and_then(|x| x.get("Features")) - .and_then(|x| x.as_array()) - .map(|f| { - f.iter() - .filter_map(|x| x.as_string()) - .map(|x| x.to_string()) - .collect::>() - }); - - let service_version = service - .get("Properties") - .and_then(|x| x.as_dictionary()) - .and_then(|x| x.get("ServiceVersion")) - .and_then(|x| x.as_signed_integer()) - .map(|e| e.to_owned()); - - services.insert( - name, - XPCService { - entitlement, - port, - uses_remote_xpc, - features, - service_version, - }, - ); - } - None => { - warn!("Service is not a dictionary!"); - continue; - } - } - } - - Ok(Self { - connection, - services, - }) - } - - /// Consumes the device and returns the underlying transport stream - pub fn into_inner(self) -> R { - self.connection.inner.stream - } -} - -impl XPCConnection { - /// Channel ID for root messages - pub const ROOT_CHANNEL: u32 = http2::ROOT_CHANNEL; - /// Channel ID for reply messages - pub const REPLY_CHANNEL: u32 = http2::REPLY_CHANNEL; - /// Initial stream ID for HTTP/2 connection - 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 { - let mut client = http2::Connection::new(stream).await?; - - // Configure HTTP/2 settings - client - .send_frame(SettingsFrame::new( - [ - (SettingsFrame::MAX_CONCURRENT_STREAMS, 100), - (SettingsFrame::INITIAL_WINDOW_SIZE, 1048576), - ] - .into_iter() - .collect(), - Default::default(), - )) - .await?; - - // Update window size - client - .send_frame(WindowUpdateFrame::new(Self::INIT_STREAM, 983041)) - .await?; - - let mut xpc_client = Self { - inner: client, - root_message_id: 1, - reply_message_id: 1, - }; - - // Perform XPC handshake - xpc_client - .send_recv_message( - Self::ROOT_CHANNEL, - XPCMessage::new( - Some(XPCFlag::AlwaysSet), - Some(XPCObject::Dictionary(Default::default())), - None, - ), - ) - .await?; - - xpc_client - .send_recv_message( - Self::REPLY_CHANNEL, - XPCMessage::new( - Some(XPCFlag::InitHandshake | XPCFlag::AlwaysSet), - None, - None, - ), - ) - .await?; - - xpc_client - .send_recv_message( - Self::ROOT_CHANNEL, - XPCMessage::new(Some(XPCFlag::Custom(0x201)), None, None), - ) - .await?; - - 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( - &mut self, - stream_id: u32, - message: XPCMessage, - ) -> Result { - self.send_message(stream_id, message).await?; - self.read_message(stream_id).await - } - - /// Sends an XPC message without waiting for a response - pub async fn send_message( - &mut self, - stream_id: u32, - message: XPCMessage, - ) -> Result<(), XPCError> { - self.inner - .write_streamid(stream_id, message.encode(self.root_message_id)?) - .await?; - Ok(()) - } - - /// Reads an XPC message from the specified stream - pub async fn read_message(&mut self, stream_id: u32) -> Result { - let mut buf = self.inner.read_streamid(stream_id).await?; - loop { - match XPCMessage::decode(&buf) { - Ok(decoded) => { - debug!("Decoded message: {:?}", decoded); - match stream_id { - 1 => self.root_message_id += 1, - 3 => self.reply_message_id += 1, - _ => {} - } - return Ok(decoded); - } - Err(err) => { - log::warn!("Error decoding message: {:?}", err); - buf.extend_from_slice(&self.inner.read_streamid(stream_id).await?); - } - } - } - } -} +pub trait XpcBackend {} From 9a1987d9234e09be6aa910ba87e55cb8bdc285f9 Mon Sep 17 00:00:00 2001 From: Jackson Coxson Date: Thu, 22 May 2025 15:13:05 -0600 Subject: [PATCH 02/16] Write new XPC implementation --- idevice/src/lib.rs | 28 ++ idevice/src/services/xpc/format.rs | 467 ++++++++++++++++++++++++ idevice/src/services/xpc/http2/frame.rs | 30 +- idevice/src/services/xpc/http2/mod.rs | 40 +- idevice/src/services/xpc/mod.rs | 115 +++++- tools/src/core_device_proxy_tun.rs | 2 +- tools/src/remotexpc.rs | 6 +- 7 files changed, 663 insertions(+), 25 deletions(-) create mode 100644 idevice/src/services/xpc/format.rs diff --git a/idevice/src/lib.rs b/idevice/src/lib.rs index 8dd769d..cc8c4f7 100644 --- a/idevice/src/lib.rs +++ b/idevice/src/lib.rs @@ -509,6 +509,34 @@ pub enum IdeviceError { #[error("Unintialized stream ID")] UninitializedStreamId, + #[cfg(feature = "xpc")] + #[error("unknown XPC type")] + UnknownXpcType(u32), + + #[cfg(feature = "xpc")] + #[error("malformed XPC message")] + MalformedXpc, + + #[cfg(feature = "xpc")] + #[error("invalid XPC magic")] + InvalidXpcMagic, + + #[cfg(feature = "xpc")] + #[error("unexpected XPC version")] + UnexpectedXpcVersion, + + #[cfg(feature = "xpc")] + #[error("invalid C string")] + InvalidCString, + + #[cfg(feature = "xpc")] + #[error("stream reset")] + HttpStreamReset, + + #[cfg(feature = "xpc")] + #[error("go away packet received")] + HttpGoAway(String), + #[cfg(feature = "dvt")] #[error("NSKeyedArchive error")] NsKeyedArchiveError(#[from] ns_keyed_archive::ConverterError), diff --git a/idevice/src/services/xpc/format.rs b/idevice/src/services/xpc/format.rs new file mode 100644 index 0000000..025f02f --- /dev/null +++ b/idevice/src/services/xpc/format.rs @@ -0,0 +1,467 @@ +use std::{ + ffi::CString, + io::{BufRead, Cursor, Read}, + ops::{BitOr, BitOrAssign}, +}; + +use indexmap::IndexMap; +use log::warn; +use serde::{Deserialize, Serialize}; + +use crate::IdeviceError; + +#[derive(Clone, Copy, Debug)] +#[repr(u32)] +pub enum XPCFlag { + AlwaysSet, + DataFlag, + WantingReply, + InitHandshake, + + Custom(u32), +} + +impl From for u32 { + fn from(value: XPCFlag) -> Self { + match value { + XPCFlag::AlwaysSet => 0x00000001, + XPCFlag::DataFlag => 0x00000100, + XPCFlag::WantingReply => 0x00010000, + XPCFlag::InitHandshake => 0x00400000, + XPCFlag::Custom(inner) => inner, + } + } +} + +impl BitOr for XPCFlag { + fn bitor(self, rhs: Self) -> Self::Output { + XPCFlag::Custom(u32::from(self) | u32::from(rhs)) + } + + type Output = XPCFlag; +} + +impl BitOrAssign for XPCFlag { + fn bitor_assign(&mut self, rhs: Self) { + *self = self.bitor(rhs); + } +} + +impl PartialEq for XPCFlag { + fn eq(&self, other: &Self) -> bool { + u32::from(*self) == u32::from(*other) + } +} + +#[repr(u32)] +pub enum XPCType { + Bool = 0x00002000, + Dictionary = 0x0000f000, + Array = 0x0000e000, + + Int64 = 0x00003000, + UInt64 = 0x00004000, + + String = 0x00009000, + Data = 0x00008000, + Uuid = 0x0000a000, +} + +impl TryFrom for XPCType { + type Error = IdeviceError; + + fn try_from(value: u32) -> Result { + match value { + 0x00002000 => Ok(Self::Bool), + 0x0000f000 => Ok(Self::Dictionary), + 0x0000e000 => Ok(Self::Array), + 0x00003000 => Ok(Self::Int64), + 0x00004000 => Ok(Self::UInt64), + 0x00009000 => Ok(Self::String), + 0x00008000 => Ok(Self::Data), + 0x0000a000 => Ok(Self::Uuid), + _ => Err(IdeviceError::UnknownXpcType(value))?, + } + } +} + +pub type Dictionary = IndexMap; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum XPCObject { + Bool(bool), + Dictionary(Dictionary), + Array(Vec), + + Int64(i64), + UInt64(u64), + + String(String), + Data(Vec), + Uuid(uuid::Uuid), +} + +impl From for XPCObject { + fn from(value: plist::Value) -> Self { + match value { + plist::Value::Array(v) => { + XPCObject::Array(v.iter().map(|item| XPCObject::from(item.clone())).collect()) + } + plist::Value::Dictionary(v) => { + let mut dict = Dictionary::new(); + for (k, v) in v.into_iter() { + dict.insert(k.clone(), XPCObject::from(v)); + } + XPCObject::Dictionary(dict) + } + plist::Value::Boolean(v) => XPCObject::Bool(v), + plist::Value::Data(v) => XPCObject::Data(v), + plist::Value::Date(_) => todo!(), + plist::Value::Real(_) => todo!(), + plist::Value::Integer(v) => XPCObject::Int64(v.as_signed().unwrap()), + plist::Value::String(v) => XPCObject::String(v), + plist::Value::Uid(_) => todo!(), + _ => todo!(), + } + } +} + +impl XPCObject { + pub fn to_plist(&self) -> plist::Value { + match self { + Self::Bool(v) => plist::Value::Boolean(*v), + Self::Uuid(uuid) => plist::Value::String(uuid.to_string()), + Self::UInt64(v) => plist::Value::Integer({ *v }.into()), + Self::Int64(v) => plist::Value::Integer({ *v }.into()), + Self::String(v) => plist::Value::String(v.clone()), + Self::Data(v) => plist::Value::Data(v.clone()), + Self::Array(v) => plist::Value::Array(v.iter().map(|item| item.to_plist()).collect()), + Self::Dictionary(v) => { + let mut dict = plist::Dictionary::new(); + for (k, v) in v.into_iter() { + dict.insert(k.clone(), v.to_plist()); + } + plist::Value::Dictionary(dict) + } + } + } + + pub fn encode(&self) -> Result, IdeviceError> { + let mut buf = Vec::new(); + buf.extend_from_slice(&0x42133742_u32.to_le_bytes()); + buf.extend_from_slice(&0x00000005_u32.to_le_bytes()); + self.encode_object(&mut buf)?; + Ok(buf) + } + + fn encode_object(&self, buf: &mut Vec) -> Result<(), IdeviceError> { + match self { + XPCObject::Bool(val) => { + buf.extend_from_slice(&(XPCType::Bool as u32).to_le_bytes()); + buf.push(if *val { 0 } else { 1 }); + buf.extend_from_slice(&[0].repeat(3)); + } + XPCObject::Dictionary(dict) => { + buf.extend_from_slice(&(XPCType::Dictionary as u32).to_le_bytes()); + buf.extend_from_slice(&0_u32.to_le_bytes()); // represents l, no idea what this is. + buf.extend_from_slice(&(dict.len() as u32).to_le_bytes()); + for (k, v) in dict { + let padding = Self::calculate_padding(k.len() + 1); + buf.extend_from_slice(k.as_bytes()); + buf.push(0); + buf.extend_from_slice(&[0].repeat(padding)); + v.encode_object(buf)?; + } + } + XPCObject::Array(items) => { + buf.extend_from_slice(&(XPCType::Array as u32).to_le_bytes()); + buf.extend_from_slice(&0_u32.to_le_bytes()); // represents l, no idea what this is. + buf.extend_from_slice(&(items.len() as u32).to_le_bytes()); + for item in items { + item.encode_object(buf)?; + } + } + + XPCObject::Int64(num) => { + buf.extend_from_slice(&(XPCType::Int64 as u32).to_le_bytes()); + buf.extend_from_slice(&num.to_le_bytes()); + } + XPCObject::UInt64(num) => { + buf.extend_from_slice(&(XPCType::UInt64 as u32).to_le_bytes()); + buf.extend_from_slice(&num.to_le_bytes()); + } + XPCObject::String(item) => { + let l = item.len() + 1; + let padding = Self::calculate_padding(l); + buf.extend_from_slice(&(XPCType::String as u32).to_le_bytes()); + buf.extend_from_slice(&(l as u32).to_le_bytes()); + buf.extend_from_slice(item.as_bytes()); + buf.push(0); + buf.extend_from_slice(&[0].repeat(padding)); + } + XPCObject::Data(data) => { + let l = data.len(); + let padding = Self::calculate_padding(l); + buf.extend_from_slice(&(XPCType::Data as u32).to_le_bytes()); + buf.extend_from_slice(&(l as u32).to_le_bytes()); + buf.extend_from_slice(data); + buf.extend_from_slice(&[0].repeat(padding)); + } + XPCObject::Uuid(uuid) => { + buf.extend_from_slice(&(XPCType::Uuid as u32).to_le_bytes()); + buf.extend_from_slice(&16_u32.to_le_bytes()); + buf.extend_from_slice(uuid.as_bytes()); + } + } + Ok(()) + } + + pub fn decode(buf: &[u8]) -> Result { + if buf.len() < 8 { + return Err(IdeviceError::NotEnoughBytes(buf.len(), 8)); + } + let magic = u32::from_le_bytes([buf[0], buf[1], buf[2], buf[3]]); + if magic != 0x42133742 { + warn!("Invalid magic for XPCObject"); + return Err(IdeviceError::InvalidXpcMagic); + } + + let version = u32::from_le_bytes([buf[4], buf[5], buf[6], buf[7]]); + if version != 0x00000005 { + warn!("Unexpected version for XPCObject"); + return Err(IdeviceError::UnexpectedXpcVersion); + } + + Self::decode_object(&mut Cursor::new(&buf[8..])) + } + + fn decode_object(mut cursor: &mut Cursor<&[u8]>) -> Result { + let mut buf_32: [u8; 4] = Default::default(); + cursor.read_exact(&mut buf_32)?; + let xpc_type = u32::from_le_bytes(buf_32); + let xpc_type: XPCType = xpc_type.try_into()?; + match xpc_type { + XPCType::Dictionary => { + let mut ret = IndexMap::new(); + + cursor.read_exact(&mut buf_32)?; + let _l = u32::from_le_bytes(buf_32); + cursor.read_exact(&mut buf_32)?; + let num_entries = u32::from_le_bytes(buf_32); + for _ in 0..num_entries { + let mut key_buf = Vec::new(); + BufRead::read_until(&mut cursor, 0, &mut key_buf)?; + let key = match CString::from_vec_with_nul(key_buf) + .ok() + .and_then(|x| x.to_str().ok().map(|x| x.to_string())) + { + Some(k) => k, + None => { + return Err(IdeviceError::InvalidCString); + } + }; + let padding = Self::calculate_padding(key.len() + 1); + + BufRead::consume(&mut cursor, padding); + ret.insert(key, Self::decode_object(cursor)?); + } + Ok(XPCObject::Dictionary(ret)) + } + XPCType::Array => { + cursor.read_exact(&mut buf_32)?; + let _l = u32::from_le_bytes(buf_32); + cursor.read_exact(&mut buf_32)?; + let num_entries = u32::from_le_bytes(buf_32); + + let mut ret = Vec::new(); + for _i in 0..num_entries { + ret.push(Self::decode_object(cursor)?); + } + Ok(XPCObject::Array(ret)) + } + XPCType::Int64 => { + let mut buf: [u8; 8] = Default::default(); + cursor.read_exact(&mut buf)?; + Ok(XPCObject::Int64(i64::from_le_bytes(buf))) + } + XPCType::UInt64 => { + let mut buf: [u8; 8] = Default::default(); + cursor.read_exact(&mut buf)?; + Ok(XPCObject::UInt64(u64::from_le_bytes(buf))) + } + XPCType::String => { + // 'l' includes utf8 '\0' character. + cursor.read_exact(&mut buf_32)?; + let l = u32::from_le_bytes(buf_32) as usize; + let padding = Self::calculate_padding(l); + + let mut key_buf = vec![0; l]; + cursor.read_exact(&mut key_buf)?; + let key = match CString::from_vec_with_nul(key_buf) + .ok() + .and_then(|x| x.to_str().ok().map(|x| x.to_string())) + { + Some(k) => k, + None => return Err(IdeviceError::InvalidCString), + }; + BufRead::consume(&mut cursor, padding); + Ok(XPCObject::String(key)) + } + XPCType::Bool => { + let mut buf: [u8; 4] = Default::default(); + cursor.read_exact(&mut buf)?; + Ok(XPCObject::Bool(buf[0] != 0)) + } + XPCType::Data => { + cursor.read_exact(&mut buf_32)?; + let l = u32::from_le_bytes(buf_32) as usize; + let padding = Self::calculate_padding(l); + + let mut data = vec![0; l]; + cursor.read_exact(&mut data)?; + BufRead::consume(&mut cursor, padding); + Ok(XPCObject::Data(data)) + } + XPCType::Uuid => { + let mut data: [u8; 16] = Default::default(); + cursor.read_exact(&mut data)?; + Ok(XPCObject::Uuid(uuid::Builder::from_bytes(data).into_uuid())) + } + } + } + + pub fn as_dictionary(&self) -> Option<&Dictionary> { + match self { + XPCObject::Dictionary(dict) => Some(dict), + _ => None, + } + } + + pub fn as_array(&self) -> Option<&Vec> { + match self { + XPCObject::Array(array) => Some(array), + _ => None, + } + } + + pub fn as_string(&self) -> Option<&str> { + match self { + XPCObject::String(s) => Some(s), + _ => None, + } + } + + pub fn as_bool(&self) -> Option<&bool> { + match self { + XPCObject::Bool(b) => Some(b), + _ => None, + } + } + + pub fn as_signed_integer(&self) -> Option { + match self { + XPCObject::String(s) => s.parse().ok(), + XPCObject::Int64(v) => Some(*v), + _ => None, + } + } + + pub fn as_unsigned_integer(&self) -> Option { + match self { + XPCObject::String(s) => s.parse().ok(), + XPCObject::UInt64(v) => Some(*v), + _ => None, + } + } + + fn calculate_padding(len: usize) -> usize { + let c = ((len as f64) / 4.0).ceil(); + (c * 4.0 - (len as f64)) as usize + } +} + +impl From for XPCObject { + fn from(value: Dictionary) -> Self { + XPCObject::Dictionary(value) + } +} + +#[derive(Debug)] +pub struct XPCMessage { + pub flags: u32, + pub message: Option, + pub message_id: Option, +} + +impl XPCMessage { + pub fn new( + flags: Option, + message: Option, + message_id: Option, + ) -> XPCMessage { + XPCMessage { + flags: flags.unwrap_or(XPCFlag::AlwaysSet).into(), + message, + message_id, + } + } + + pub fn decode(data: &[u8]) -> Result { + if data.len() < 24 { + Err(IdeviceError::NotEnoughBytes(data.len(), 24))? + } + + let magic = u32::from_le_bytes([data[0], data[1], data[2], data[3]]); + if magic != 0x29b00b92_u32 { + warn!("XPCMessage magic is invalid."); + Err(IdeviceError::MalformedXpc)? + } + + let flags = u32::from_le_bytes([data[4], data[5], data[6], data[7]]); + let body_len = u64::from_le_bytes([ + data[8], data[9], data[10], data[11], data[12], data[13], data[14], data[15], + ]); + let message_id = u64::from_le_bytes([ + data[16], data[17], data[18], data[19], data[20], data[21], data[22], data[23], + ]); + if body_len + 24 > data.len() as u64 { + warn!( + "Body length is {body_len}, but received bytes is {}", + data.len() + ); + println!("{}", String::from_utf8_lossy(data)); + Err(IdeviceError::PacketSizeMismatch)? + } + + if body_len == 0 { + return Ok(XPCMessage { + flags, + message: None, + message_id: Some(message_id), + }); + } + Ok(XPCMessage { + flags, + message: Some(XPCObject::decode(&data[24..24 + body_len as usize])?), + message_id: Some(message_id), + }) + } + + pub fn encode(self, message_id: u64) -> Result, IdeviceError> { + let mut out = 0x29b00b92_u32.to_le_bytes().to_vec(); + out.extend_from_slice(&self.flags.to_le_bytes()); + match self.message { + Some(message) => { + let body = message.encode()?; + out.extend_from_slice(&(body.len() as u64).to_le_bytes()); // body length + out.extend_from_slice(&message_id.to_le_bytes()); // messageId + out.extend_from_slice(&body); + } + _ => { + out.extend_from_slice(&0_u64.to_le_bytes()); + out.extend_from_slice(&message_id.to_le_bytes()); + } + } + Ok(out) + } +} diff --git a/idevice/src/services/xpc/http2/frame.rs b/idevice/src/services/xpc/http2/frame.rs index d3f1195..f083d41 100644 --- a/idevice/src/services/xpc/http2/frame.rs +++ b/idevice/src/services/xpc/http2/frame.rs @@ -7,6 +7,8 @@ pub trait HttpFrame { fn serialize(&self) -> Vec; } +#[derive(Debug)] +#[allow(dead_code)] // we don't care about frames from the device pub enum Frame { Settings(SettingsFrame), WindowUpdate(WindowUpdateFrame), @@ -15,10 +17,10 @@ pub enum Frame { } impl Frame { - pub async fn next(socket: &mut impl ReadWrite) -> Result { + pub async fn next(mut socket: &mut impl ReadWrite) -> Result { // Read the len of the frame let mut buf = [0u8; 3]; - socket.read_exact(&mut buf).await?; + tokio::io::AsyncReadExt::read_exact(&mut socket, &mut buf).await?; let frame_len = u32::from_be_bytes([0x00, buf[0], buf[1], buf[2]]); // Read the fields @@ -26,8 +28,8 @@ impl Frame { let flags = socket.read_u8().await?; let stream_id = socket.read_u32().await?; - let body = vec![0; frame_len as usize]; - socket.read_exact(&mut buf).await?; + let mut body = vec![0; frame_len as usize]; + socket.read_exact(&mut body).await?; Ok(match frame_type { 0x00 => { @@ -41,6 +43,7 @@ impl Frame { // headers Self::Headers(HeadersFrame { stream_id }) } + 0x03 => return Err(IdeviceError::HttpStreamReset), 0x04 => { // settings let mut body = std::io::Cursor::new(body); @@ -67,6 +70,14 @@ impl Frame { flags, }) } + 0x07 => { + let msg = if body.len() < 8 { + "".to_string() + } else { + String::from_utf8_lossy(&body[8..]).to_string() + }; + return Err(IdeviceError::HttpGoAway(msg)); + } 0x08 => { // window update if body.len() != 4 { @@ -86,23 +97,19 @@ impl Frame { } } +#[derive(Debug, Clone)] pub struct SettingsFrame { pub settings: Vec, pub stream_id: u32, pub flags: u8, } +#[derive(Debug, Clone)] pub enum Setting { MaxConcurrentStreams(u32), InitialWindowSize(u32), } -impl SettingsFrame { - pub fn ack(&mut self) { - self.flags = 1; // this seems to be the only http flag used - } -} - impl Setting { fn serialize(&self) -> Vec { match self { @@ -142,6 +149,7 @@ impl HttpFrame for SettingsFrame { } } +#[derive(Debug, Clone)] pub struct WindowUpdateFrame { pub increment_size: u32, pub stream_id: u32, @@ -156,6 +164,7 @@ impl HttpFrame for WindowUpdateFrame { } } +#[derive(Debug, Clone)] /// We don't actually care about this frame according to spec. This is just to open new channels. pub struct HeadersFrame { pub stream_id: u32, @@ -169,6 +178,7 @@ impl HttpFrame for HeadersFrame { } } +#[derive(Debug, Clone)] pub struct DataFrame { pub stream_id: u32, pub payload: Vec, diff --git a/idevice/src/services/xpc/http2/mod.rs b/idevice/src/services/xpc/http2/mod.rs index 361dc2f..ea0e473 100644 --- a/idevice/src/services/xpc/http2/mod.rs +++ b/idevice/src/services/xpc/http2/mod.rs @@ -1,13 +1,13 @@ // Jackson Coxson use frame::HttpFrame; -use log::warn; +use log::{debug, warn}; use std::collections::{HashMap, VecDeque}; use tokio::io::AsyncWriteExt; use crate::{IdeviceError, ReadWrite}; -mod frame; +pub mod frame; pub use frame::Setting; const HTTP2_MAGIC: &[u8] = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n".as_bytes(); @@ -40,6 +40,7 @@ impl Http2Client { } .serialize(); self.inner.write_all(&frame).await?; + self.inner.flush().await?; Ok(()) } @@ -54,6 +55,7 @@ impl Http2Client { } .serialize(); self.inner.write_all(&frame).await?; + self.inner.flush().await?; Ok(()) } @@ -61,25 +63,34 @@ impl Http2Client { self.cache.insert(stream_id, VecDeque::new()); let frame = frame::HeadersFrame { stream_id }.serialize(); self.inner.write_all(&frame).await?; + self.inner.flush().await?; + Ok(()) + } + + pub async fn send(&mut self, payload: Vec, stream_id: u32) -> Result<(), IdeviceError> { + let frame = frame::DataFrame { stream_id, payload }.serialize(); + self.inner.write_all(&frame).await?; + self.inner.flush().await?; Ok(()) } pub async fn read(&mut self, stream_id: u32) -> Result, IdeviceError> { // See if we already have a cached message from another read - let c = match self.cache.get_mut(&stream_id) { - Some(c) => c, + match self.cache.get_mut(&stream_id) { + Some(c) => { + if let Some(d) = c.pop_front() { + return Ok(d); + } + } None => { - warn!("Requested stream ID is not in cache"); - return Err(IdeviceError::UninitializedStreamId); + self.cache.insert(stream_id, VecDeque::new()); } }; - if let Some(d) = c.pop_front() { - return Ok(d); - } // handle packets until we get what we want loop { let frame = frame::Frame::next(&mut self.inner).await?; + // debug!("Got frame: {frame:#?}"); match frame { frame::Frame::Settings(settings_frame) => { if settings_frame.flags != 1 { @@ -91,16 +102,25 @@ impl Http2Client { } .serialize(); self.inner.write_all(&frame).await?; + self.inner.flush().await?; } } frame::Frame::Data(data_frame) => { + debug!( + "Got data frame for {} with {} bytes", + data_frame.stream_id, + data_frame.payload.len() + ); if data_frame.stream_id == stream_id { return Ok(data_frame.payload); } else { let c = match self.cache.get_mut(&data_frame.stream_id) { Some(c) => c, None => { - warn!("Received message for stream ID not in cache"); + warn!( + "Received message for stream ID {} not in cache", + data_frame.stream_id + ); continue; } }; diff --git a/idevice/src/services/xpc/mod.rs b/idevice/src/services/xpc/mod.rs index b1141ec..6ef2b05 100644 --- a/idevice/src/services/xpc/mod.rs +++ b/idevice/src/services/xpc/mod.rs @@ -1,5 +1,118 @@ // Jackson Coxson +use http2::Setting; +use log::debug; + +use crate::{IdeviceError, ReadWrite}; + +mod format; mod http2; -pub trait XpcBackend {} +pub use format::XPCMessage; +use format::{XPCFlag, XPCObject}; + +const ROOT_CHANNEL: u32 = 1; +const REPLY_CHANNEL: u32 = 3; + +pub struct RemoteXpcClient { + h2_client: http2::Http2Client, + root_id: u64, + reply_id: u64, +} + +impl RemoteXpcClient { + pub async fn new(socket: R) -> Result { + Ok(Self { + h2_client: http2::Http2Client::new(socket).await?, + root_id: 1, + reply_id: 1, + }) + } + + pub async fn do_handshake(&mut self) -> Result { + self.h2_client + .set_settings( + vec![ + Setting::MaxConcurrentStreams(10), + Setting::InitialWindowSize(1048576), + ], + 0, + ) + .await?; + self.h2_client.window_update(983041, 0).await?; + self.h2_client.open_stream(1).await?; // root channel + + debug!("Sending empty dictionary"); + self.send_root(XPCMessage::new( + Some(XPCFlag::AlwaysSet), + Some(XPCObject::Dictionary(Default::default())), + None, + )) + .await?; + self.h2_client.read(ROOT_CHANNEL).await?; + self.h2_client.read(ROOT_CHANNEL).await?; + + debug!("Sending weird flags"); + self.send_root(XPCMessage::new(Some(XPCFlag::Custom(0x201)), None, None)) + .await?; + + debug!("Opening reply stream"); + self.h2_client.open_stream(REPLY_CHANNEL).await?; + self.send_reply(XPCMessage::new( + Some(XPCFlag::InitHandshake | XPCFlag::AlwaysSet), + None, + None, + )) + .await?; + + let mut total_msg = Vec::new(); + loop { + // We receive from the root channel for this message + total_msg.extend(self.h2_client.read(ROOT_CHANNEL).await?); + let msg = match XPCMessage::decode(&total_msg) { + Ok(m) => m, + Err(IdeviceError::PacketSizeMismatch) => { + continue; + } + Err(e) => { + return Err(e); + } + }; + + match msg.message { + Some(msg) => { + return Ok(msg.to_plist()); + } + None => { + return Err(IdeviceError::UnexpectedResponse); + } + }; + } + } + + pub async fn recv(&mut self) -> Result { + loop { + let msg = self.h2_client.read(REPLY_CHANNEL).await?; + + let msg = XPCMessage::decode(&msg)?; + if let Some(msg) = msg.message { + return Ok(msg.to_plist()); + } + self.reply_id += 1; + } + } + + async fn send_root(&mut self, msg: XPCMessage) -> Result<(), IdeviceError> { + self.h2_client + .send(msg.encode(self.root_id)?, ROOT_CHANNEL) + .await?; + Ok(()) + } + + async fn send_reply(&mut self, msg: XPCMessage) -> Result<(), IdeviceError> { + self.h2_client + .send(msg.encode(self.root_id)?, REPLY_CHANNEL) + .await?; + Ok(()) + } +} diff --git a/tools/src/core_device_proxy_tun.rs b/tools/src/core_device_proxy_tun.rs index 2f9b457..a6df7b1 100644 --- a/tools/src/core_device_proxy_tun.rs +++ b/tools/src/core_device_proxy_tun.rs @@ -96,7 +96,7 @@ async fn main() { println!("rsd port: {}", tun_proxy.handshake.server_rsd_port); println!("-----------------------------"); - let mut buf = vec![0; 1500]; + let mut buf = vec![0; 20_000]; // XPC is big lol loop { tokio::select! { Ok(len) = async_dev.recv(&mut buf) => { diff --git a/tools/src/remotexpc.rs b/tools/src/remotexpc.rs index 1b1be65..9aa5c4c 100644 --- a/tools/src/remotexpc.rs +++ b/tools/src/remotexpc.rs @@ -2,7 +2,7 @@ // Print out all the RemoteXPC services use clap::{Arg, Command}; -use idevice::{core_device_proxy::CoreDeviceProxy, xpc::XPCDevice, IdeviceService}; +use idevice::{core_device_proxy::CoreDeviceProxy, xpc::RemoteXpcClient, IdeviceService}; mod common; @@ -66,7 +66,7 @@ async fn main() { adapter.connect(rsd_port).await.expect("no RSD connect"); // Make the connection to RemoteXPC - let client = XPCDevice::new(Box::new(adapter)).await.unwrap(); + let mut client = RemoteXpcClient::new(Box::new(adapter)).await.unwrap(); - println!("{:#?}", client.services); + println!("{:#?}", client.do_handshake().await); } From 9a02e2bb6d52d138a9a25adc4e7d66ae420f8c8f Mon Sep 17 00:00:00 2001 From: Jackson Coxson Date: Thu, 22 May 2025 21:54:45 -0600 Subject: [PATCH 03/16] Log decoded XPC messages --- idevice/src/services/xpc/format.rs | 26 ++++++++++++++------------ idevice/src/services/xpc/mod.rs | 16 ++++++++++++++-- 2 files changed, 28 insertions(+), 14 deletions(-) diff --git a/idevice/src/services/xpc/format.rs b/idevice/src/services/xpc/format.rs index 025f02f..db42e15 100644 --- a/idevice/src/services/xpc/format.rs +++ b/idevice/src/services/xpc/format.rs @@ -5,7 +5,7 @@ use std::{ }; use indexmap::IndexMap; -use log::warn; +use log::{debug, warn}; use serde::{Deserialize, Serialize}; use crate::IdeviceError; @@ -217,6 +217,7 @@ impl XPCObject { } pub fn decode(buf: &[u8]) -> Result { + debug!("Decoding {buf:02X?}"); if buf.len() < 8 { return Err(IdeviceError::NotEnoughBytes(buf.len(), 8)); } @@ -407,6 +408,7 @@ impl XPCMessage { } pub fn decode(data: &[u8]) -> Result { + debug!("Decoding {data:02X?}"); if data.len() < 24 { Err(IdeviceError::NotEnoughBytes(data.len(), 24))? } @@ -421,6 +423,7 @@ impl XPCMessage { let body_len = u64::from_le_bytes([ data[8], data[9], data[10], data[11], data[12], data[13], data[14], data[15], ]); + debug!("Body_len: {body_len}"); let message_id = u64::from_le_bytes([ data[16], data[17], data[18], data[19], data[20], data[21], data[22], data[23], ]); @@ -429,22 +432,21 @@ impl XPCMessage { "Body length is {body_len}, but received bytes is {}", data.len() ); - println!("{}", String::from_utf8_lossy(data)); Err(IdeviceError::PacketSizeMismatch)? } - if body_len == 0 { - return Ok(XPCMessage { - flags, - message: None, - message_id: Some(message_id), - }); - } - Ok(XPCMessage { + let res = XPCMessage { flags, - message: Some(XPCObject::decode(&data[24..24 + body_len as usize])?), + message: if body_len > 0 { + Some(XPCObject::decode(&data[24..24 + body_len as usize])?) + } else { + None + }, message_id: Some(message_id), - }) + }; + + debug!("Decoded {res:#?}"); + Ok(res) } pub fn encode(self, message_id: u64) -> Result, IdeviceError> { diff --git a/idevice/src/services/xpc/mod.rs b/idevice/src/services/xpc/mod.rs index 6ef2b05..3a76f43 100644 --- a/idevice/src/services/xpc/mod.rs +++ b/idevice/src/services/xpc/mod.rs @@ -49,8 +49,9 @@ impl RemoteXpcClient { None, )) .await?; - self.h2_client.read(ROOT_CHANNEL).await?; - self.h2_client.read(ROOT_CHANNEL).await?; + + self.recv_root().await?; + self.recv_root().await?; debug!("Sending weird flags"); self.send_root(XPCMessage::new(Some(XPCFlag::Custom(0x201)), None, None)) @@ -102,6 +103,17 @@ impl RemoteXpcClient { } } + pub async fn recv_root(&mut self) -> Result, IdeviceError> { + let msg = self.h2_client.read(ROOT_CHANNEL).await?; + let msg = XPCMessage::decode(&msg)?; + + if let Some(msg) = msg.message { + Ok(Some(msg.to_plist())) + } else { + Ok(None) + } + } + async fn send_root(&mut self, msg: XPCMessage) -> Result<(), IdeviceError> { self.h2_client .send(msg.encode(self.root_id)?, ROOT_CHANNEL) From 6bf32afe82610c526a9fa11c4e7c86814224d7d1 Mon Sep 17 00:00:00 2001 From: Jackson Coxson Date: Thu, 22 May 2025 22:42:39 -0600 Subject: [PATCH 04/16] Create RSD service --- idevice/Cargo.toml | 2 + idevice/src/lib.rs | 3 + idevice/src/services/mod.rs | 4 +- idevice/src/services/rsd.rs | 121 ++++++++++++++++++ idevice/src/{services => }/xpc/format.rs | 0 idevice/src/{services => }/xpc/http2/frame.rs | 0 idevice/src/{services => }/xpc/http2/mod.rs | 0 idevice/src/{services => }/xpc/mod.rs | 0 8 files changed, 128 insertions(+), 2 deletions(-) create mode 100644 idevice/src/services/rsd.rs rename idevice/src/{services => }/xpc/format.rs (100%) rename idevice/src/{services => }/xpc/http2/frame.rs (100%) rename idevice/src/{services => }/xpc/http2/mod.rs (100%) rename idevice/src/{services => }/xpc/mod.rs (100%) diff --git a/idevice/Cargo.toml b/idevice/Cargo.toml index c9d1735..a550591 100644 --- a/idevice/Cargo.toml +++ b/idevice/Cargo.toml @@ -69,6 +69,7 @@ misagent = [] mobile_image_mounter = ["dep:sha2"] location_simulation = [] pair = ["chrono/default", "tokio/time", "dep:sha2", "dep:rsa", "dep:x509-cert"] +rsd = ["xpc"] syslog_relay = ["dep:bytes"] tcp = ["tokio/net"] tunnel_tcp_stack = ["dep:rand", "dep:futures", "tokio/fs", "tokio/sync"] @@ -98,6 +99,7 @@ full = [ "usbmuxd", "xpc", "location_simulation", + "rsd", "tcp", "tunnel_tcp_stack", "tss", diff --git a/idevice/src/lib.rs b/idevice/src/lib.rs index cc8c4f7..d6a54dd 100644 --- a/idevice/src/lib.rs +++ b/idevice/src/lib.rs @@ -15,9 +15,12 @@ pub mod tunneld; #[cfg(feature = "usbmuxd")] pub mod usbmuxd; mod util; +#[cfg(feature = "xpc")] +pub mod xpc; pub mod services; pub use services::*; +pub use xpc::RemoteXpcClient; use log::{debug, error, trace}; use provider::IdeviceProvider; diff --git a/idevice/src/services/mod.rs b/idevice/src/services/mod.rs index a276190..1c035f4 100644 --- a/idevice/src/services/mod.rs +++ b/idevice/src/services/mod.rs @@ -23,9 +23,9 @@ pub mod misagent; pub mod mobile_image_mounter; #[cfg(feature = "syslog_relay")] pub mod os_trace_relay; +#[cfg(feature = "rsd")] +pub mod rsd; #[cfg(feature = "springboardservices")] pub mod springboardservices; #[cfg(feature = "syslog_relay")] pub mod syslog_relay; -#[cfg(feature = "xpc")] -pub mod xpc; diff --git a/idevice/src/services/rsd.rs b/idevice/src/services/rsd.rs new file mode 100644 index 0000000..c6b7dc8 --- /dev/null +++ b/idevice/src/services/rsd.rs @@ -0,0 +1,121 @@ +//! Remote Service Discovery +//! Communicates via XPC and returns advertised services + +use std::collections::HashMap; + +use log::warn; +use serde::Deserialize; + +use crate::{IdeviceError, ReadWrite, RemoteXpcClient}; + +/// Describes an available XPC service +#[derive(Debug, Clone, Deserialize)] +pub struct XPCService { + /// Required entitlement to access this service + pub entitlement: String, + /// Port number where the service is available + pub port: u16, + /// Whether the service uses remote XPC + pub uses_remote_xpc: bool, + /// Optional list of supported features + pub features: Option>, + /// Optional service version number + pub service_version: Option, +} + +pub struct RsdClient { + inner: RemoteXpcClient, +} + +impl RsdClient { + pub async fn new(socket: R) -> Result { + Ok(Self { + inner: RemoteXpcClient::new(socket).await?, + }) + } + + pub async fn get_services(&mut self) -> Result, IdeviceError> { + let data = self.inner.do_handshake().await?; + + let data = match data + .as_dictionary() + .and_then(|x| x.get("Services")) + .and_then(|x| x.as_dictionary()) + { + Some(d) => d, + None => return Err(IdeviceError::UnexpectedResponse), + }; + + // Parse available services + let mut services = HashMap::new(); + for (name, service) in data.into_iter() { + match service.as_dictionary() { + Some(service) => { + let entitlement = match service.get("Entitlement").and_then(|x| x.as_string()) { + Some(e) => e.to_string(), + None => { + warn!("Service did not contain entitlement string"); + continue; + } + }; + let port = match service + .get("Port") + .and_then(|x| x.as_string()) + .and_then(|x| x.parse::().ok()) + { + Some(e) => e, + None => { + warn!("Service did not contain port string"); + continue; + } + }; + let uses_remote_xpc = match service + .get("Properties") + .and_then(|x| x.as_dictionary()) + .and_then(|x| x.get("UsesRemoteXPC")) + .and_then(|x| x.as_boolean()) + { + Some(e) => e.to_owned(), + None => false, // default is false + }; + + let features = service + .get("Properties") + .and_then(|x| x.as_dictionary()) + .and_then(|x| x.get("Features")) + .and_then(|x| x.as_array()) + .map(|f| { + f.iter() + .filter_map(|x| x.as_string()) + .map(|x| x.to_string()) + .collect::>() + }); + + let service_version = service + .get("Properties") + .and_then(|x| x.as_dictionary()) + .and_then(|x| x.get("ServiceVersion")) + .and_then(|x| x.as_signed_integer()) + .map(|e| e.to_owned()); + + services.insert( + name.to_string(), + XPCService { + entitlement, + port, + uses_remote_xpc, + features, + service_version, + }, + ); + } + None => { + warn!("Service is not a dictionary!"); + continue; + } + } + } + + Ok(services) + } +} diff --git a/idevice/src/services/xpc/format.rs b/idevice/src/xpc/format.rs similarity index 100% rename from idevice/src/services/xpc/format.rs rename to idevice/src/xpc/format.rs diff --git a/idevice/src/services/xpc/http2/frame.rs b/idevice/src/xpc/http2/frame.rs similarity index 100% rename from idevice/src/services/xpc/http2/frame.rs rename to idevice/src/xpc/http2/frame.rs diff --git a/idevice/src/services/xpc/http2/mod.rs b/idevice/src/xpc/http2/mod.rs similarity index 100% rename from idevice/src/services/xpc/http2/mod.rs rename to idevice/src/xpc/http2/mod.rs diff --git a/idevice/src/services/xpc/mod.rs b/idevice/src/xpc/mod.rs similarity index 100% rename from idevice/src/services/xpc/mod.rs rename to idevice/src/xpc/mod.rs From 525136662eec10199320ae3d30dd347627c84b91 Mon Sep 17 00:00:00 2001 From: Jackson Coxson Date: Fri, 23 May 2025 01:20:09 -0600 Subject: [PATCH 05/16] Rewrite TCP stack for multiple streams --- idevice/Cargo.toml | 2 +- idevice/src/lib.rs | 2 + idevice/src/tcp/adapter.rs | 582 ++++++++++++++++++------------------- idevice/src/tcp/mod.rs | 24 +- idevice/src/tcp/packets.rs | 4 +- idevice/src/tcp/stream.rs | 158 ++++++++++ tools/src/remotexpc.rs | 12 +- 7 files changed, 470 insertions(+), 314 deletions(-) create mode 100644 idevice/src/tcp/stream.rs diff --git a/idevice/Cargo.toml b/idevice/Cargo.toml index a550591..344a64f 100644 --- a/idevice/Cargo.toml +++ b/idevice/Cargo.toml @@ -50,7 +50,7 @@ x509-cert = { version = "0.2", optional = true, features = [ ], default-features = false } [dev-dependencies] -tokio = { version = "1.43", features = ["fs"] } +tokio = { version = "1.43", features = ["full"] } tun-rs = { version = "2.0.8", features = ["async_tokio"] } bytes = "1.10.1" diff --git a/idevice/src/lib.rs b/idevice/src/lib.rs index d6a54dd..b7ead1d 100644 --- a/idevice/src/lib.rs +++ b/idevice/src/lib.rs @@ -20,6 +20,8 @@ pub mod xpc; pub mod services; pub use services::*; + +#[cfg(feature = "xpc")] pub use xpc::RemoteXpcClient; use log::{debug, error, trace}; diff --git a/idevice/src/tcp/adapter.rs b/idevice/src/tcp/adapter.rs index 4b0eb8d..5222c5f 100644 --- a/idevice/src/tcp/adapter.rs +++ b/idevice/src/tcp/adapter.rs @@ -61,22 +61,45 @@ //! 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::{collections::HashMap, io::ErrorKind, net::IpAddr, path::Path, sync::Arc}; use log::trace; -use tokio::{ - io::{AsyncRead, AsyncWrite, AsyncWriteExt}, - sync::Mutex, -}; +use tokio::{io::AsyncWriteExt, sync::Mutex}; use crate::ReadWrite; use super::packets::{Ipv4Packet, Ipv6Packet, ProtocolNumber, TcpFlags, TcpPacket}; -#[derive(Clone, Debug, PartialEq)] -enum AdapterState { +#[derive(Debug, Clone)] +struct ConnectionState { + seq: u32, + ack: u32, + host_port: u16, + peer_port: u16, + read_buffer: Vec, + write_buffer: Vec, + status: ConnectionStatus, +} + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub(crate) enum ConnectionStatus { + WaitingForSyn, Connected, - None, + Error(ErrorKind), +} + +impl ConnectionState { + fn new(host_port: u16, peer_port: u16) -> Self { + Self { + seq: rand::random(), + ack: 0, + host_port, + peer_port, + read_buffer: Vec::new(), + write_buffer: Vec::new(), + status: ConnectionStatus::WaitingForSyn, + } + } } /// A simplified TCP network stack implementation. @@ -96,24 +119,8 @@ pub struct Adapter { host_ip: IpAddr, /// The remote peer's IP address peer_ip: IpAddr, - /// Current connection state - state: AdapterState, - // TCP state - /// Current sequence number - seq: u32, - /// Current acknowledgement number - ack: u32, - /// Local port number - host_port: u16, - /// Remote port number - peer_port: u16, - - // Read buffer to cache unused bytes - /// Buffer for storing unread received data - read_buffer: Vec, - /// Buffer for storing data to be sent - write_buffer: Vec, + states: HashMap, // host port by state // Logging /// Optional PCAP file for packet logging @@ -135,13 +142,7 @@ impl Adapter { peer, host_ip, peer_ip, - state: AdapterState::None, - seq: 0, - ack: 0, - host_port: 1024, - peer_port: 1024, - read_buffer: Vec::new(), - write_buffer: Vec::new(), + states: HashMap::new(), pcap: None, } } @@ -158,26 +159,25 @@ impl Adapter { /// # 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> { - self.read_buffer = Vec::new(); - self.write_buffer = Vec::new(); - - // Randomize seq - self.seq = rand::random(); - self.ack = 0; - - // Choose a random port - self.host_port = rand::random(); - self.peer_port = port; + pub(crate) async fn connect(&mut self, port: u16) -> Result { + let host_port = loop { + let host_port: u16 = rand::random(); + if self.states.contains_key(&host_port) { + continue; + } else { + break host_port; + } + }; + let state = ConnectionState::new(host_port, port); // Create the TCP packet let tcp_packet = TcpPacket::create( self.host_ip, self.peer_ip, - self.host_port, - self.peer_port, - self.seq, - self.ack, + state.host_port, + state.peer_port, + state.seq, + state.ack, TcpFlags { syn: true, ..Default::default() @@ -187,24 +187,28 @@ impl Adapter { ); let ip_packet = self.ip_wrap(&tcp_packet); self.peer.write_all(&ip_packet).await?; - self.log_packet(&ip_packet).await?; + self.log_packet(&ip_packet)?; // Wait for the syn ack - let res = self.read_tcp_packet().await?; - if !(res.flags.syn && res.flags.ack) { - log::error!("Didn't get syn ack: {res:#?}, {self:#?}"); - return Err(std::io::Error::new( - std::io::ErrorKind::InvalidData, - "No syn ack", - )); + self.states.insert(host_port, state); + loop { + self.process_tcp_packet().await?; + if let Some(s) = self.states.get(&host_port) { + match s.status { + ConnectionStatus::Connected => { + break; + } + ConnectionStatus::Error(e) => { + return Err(std::io::Error::new(e, "failed to connect")) + } + ConnectionStatus::WaitingForSyn => { + continue; + } + } + } } - self.seq = self.seq.wrapping_add(1); - // Ack back - self.ack().await?; - - self.state = AdapterState::Connected; - Ok(()) + Ok(host_port) } /// Enables packet capture to a PCAP file. @@ -232,9 +236,9 @@ impl Adapter { Ok(()) } - async fn log_packet(&mut self, packet: &[u8]) -> Result<(), std::io::Error> { + fn log_packet(&self, packet: &[u8]) -> Result<(), std::io::Error> { if let Some(file) = &self.pcap { - super::log_packet(file, packet).await; + super::log_packet(file, packet); } Ok(()) } @@ -247,61 +251,63 @@ impl Adapter { /// /// # Errors /// * Returns IO errors if underlying transport fails during close - pub async fn close(&mut self) -> Result<(), std::io::Error> { - let tcp_packet = TcpPacket::create( - self.host_ip, - self.peer_ip, - self.host_port, - self.peer_port, - self.seq, - self.ack, - TcpFlags { - fin: true, - ack: true, - ..Default::default() - }, - u16::MAX - 1, - &[], - ); - let ip_packet = self.ip_wrap(&tcp_packet); - self.peer.write_all(&ip_packet).await?; - self.log_packet(&ip_packet).await?; + pub(crate) async fn close(&mut self, host_port: u16) -> Result<(), std::io::Error> { + if let Some(state) = self.states.remove(&host_port) { + let tcp_packet = TcpPacket::create( + self.host_ip, + self.peer_ip, + state.host_port, + state.peer_port, + state.seq, + state.ack, + TcpFlags { + fin: true, + ack: true, + ..Default::default() + }, + u16::MAX - 1, + &[], + ); + let ip_packet = self.ip_wrap(&tcp_packet); + self.peer.write_all(&ip_packet).await?; + self.log_packet(&ip_packet)?; - loop { - let res = self.read_tcp_packet().await?; - if res.flags.psh || !res.payload.is_empty() { - self.ack().await?; - continue; - } - - if res.flags.ack || res.flags.fin || res.flags.rst { - break; - } + Ok(()) + } else { + Err(std::io::Error::new( + ErrorKind::NotConnected, + "not connected", + )) } - self.state = AdapterState::None; - Ok(()) } - async fn ack(&mut self) -> Result<(), std::io::Error> { - let tcp_packet = TcpPacket::create( - self.host_ip, - self.peer_ip, - self.host_port, - self.peer_port, - self.seq, - self.ack, - TcpFlags { - ack: true, - ..Default::default() - }, - u16::MAX - 1, - &[], - ); - let ip_packet = self.ip_wrap(&tcp_packet); - self.peer.write_all(&ip_packet).await?; - self.log_packet(&ip_packet).await?; + async fn ack(&mut self, host_port: u16) -> Result<(), std::io::Error> { + if let Some(state) = self.states.get_mut(&host_port) { + let tcp_packet = TcpPacket::create( + self.host_ip, + self.peer_ip, + state.host_port, + state.peer_port, + state.seq, + state.ack, + TcpFlags { + ack: true, + ..Default::default() + }, + u16::MAX - 1, + &[], + ); + let ip_packet = self.ip_wrap(&tcp_packet); + self.peer.write_all(&ip_packet).await?; + self.log_packet(&ip_packet)?; - Ok(()) + Ok(()) + } else { + Err(std::io::Error::new( + ErrorKind::NotConnected, + "not connected", + )) + } } /// Sends a TCP packet with PSH flag set (pushing data). @@ -315,44 +321,133 @@ impl Adapter { /// /// # Errors /// * Returns IO errors if underlying transport fails - pub async fn psh(&mut self, data: &[u8]) -> Result<(), std::io::Error> { - trace!("pshing {} bytes", data.len()); - let tcp_packet = TcpPacket::create( - self.host_ip, - self.peer_ip, - self.host_port, - self.peer_port, - self.seq, - self.ack, - TcpFlags { - psh: true, - ack: true, - ..Default::default() - }, - u16::MAX - 1, - data, - ); - let ip_packet = self.ip_wrap(&tcp_packet); - self.peer.write_all(&ip_packet).await?; - self.log_packet(&ip_packet).await?; + async fn psh(&mut self, data: &[u8], host_port: u16) -> Result<(), std::io::Error> { + let data_len = if let Some(state) = self.states.get(&host_port) { + // Check to make sure we haven't closed since last operation + if let ConnectionStatus::Error(e) = state.status { + return Err(std::io::Error::new(e, "socket error")); + } + trace!("pshing {} bytes", data.len()); + let tcp_packet = TcpPacket::create( + self.host_ip, + self.peer_ip, + state.host_port, + state.peer_port, + state.seq, + state.ack, + TcpFlags { + psh: true, + ack: true, + ..Default::default() + }, + u16::MAX - 1, + data, + ); + let ip_packet = self.ip_wrap(&tcp_packet); + self.peer.write_all(&ip_packet).await?; + self.log_packet(&ip_packet)?; + data.len() as u32 + } else { + return Err(std::io::Error::new( + ErrorKind::NotConnected, + "not connected", + )); + }; - self.seq = self.seq.wrapping_add(data.len() as u32); + // We have to re-borrow, since we're mutating state + if let Some(state) = self.states.get_mut(&host_port) { + state.seq = state.seq.wrapping_add(data_len); + } Ok(()) } /// Flushes the packets - async fn write_buffer_flush(&mut self) -> Result<(), std::io::Error> { - if self.write_buffer.is_empty() { - return Ok(()); + pub(crate) async fn write_buffer_flush(&mut self) -> Result<(), std::io::Error> { + for (_, state) in self.states.clone() { + let writer_buffer = state.write_buffer.clone(); + if writer_buffer.is_empty() { + continue; + } + + println!("flushing..."); + self.psh(&writer_buffer, state.host_port).await.ok(); // don't care + println!("flushed {} bytes", writer_buffer.len()); + + // we have to borrow mutably after self.psh + if let Some(state) = self.states.get_mut(&state.host_port) { + state.write_buffer.clear(); + } } - trace!("Flushing {} bytes", self.write_buffer.len()); - let write_buffer = self.write_buffer.clone(); - self.psh(&write_buffer).await?; - self.write_buffer = Vec::new(); Ok(()) } + pub(crate) fn queue_send( + &mut self, + payload: &[u8], + host_port: u16, + ) -> Result<(), std::io::Error> { + if let Some(state) = self.states.get_mut(&host_port) { + state.write_buffer.extend_from_slice(payload); + } else { + return Err(std::io::Error::new( + ErrorKind::NotConnected, + "not connected", + )); + } + Ok(()) + } + + pub(crate) fn uncache( + &mut self, + to_copy: usize, + host_port: u16, + ) -> Result, std::io::Error> { + if let Some(state) = self.states.get_mut(&host_port) { + let to_copy = if to_copy > state.read_buffer.len() { + state.read_buffer.len() + } else { + to_copy + }; + + let res = state.read_buffer[..to_copy].to_vec(); + state.read_buffer = state.read_buffer[to_copy..].to_vec(); + Ok(res) + } else { + Err(std::io::Error::new( + ErrorKind::NotConnected, + "not connected", + )) + } + } + + pub(crate) fn cache_read( + &mut self, + payload: &[u8], + host_port: u16, + ) -> Result<(), std::io::Error> { + if let Some(state) = self.states.get_mut(&host_port) { + state.read_buffer.extend_from_slice(payload); + Ok(()) + } else { + Err(std::io::Error::new( + ErrorKind::NotConnected, + "not connected", + )) + } + } + + pub(crate) fn get_status(&self, host_port: u16) -> Result { + if let Some(state) = self.states.get(&host_port) { + Ok(state.status.clone()) + } else { + Err(std::io::Error::new( + ErrorKind::NotConnected, + "not connected", + )) + } + } + /// Receives data from the connection. /// /// # Returns @@ -362,31 +457,26 @@ impl Adapter { /// # Errors /// * Returns `ConnectionReset` if connection was reset or closed /// * Returns other IO errors if underlying transport fails - pub async fn recv(&mut self) -> Result, std::io::Error> { + pub(crate) async fn recv(&mut self, host_port: u16) -> Result, std::io::Error> { loop { - let res = self.read_tcp_packet().await?; - if res.destination_port != self.host_port || res.source_port != self.peer_port { - continue; - } - if res.flags.psh || !res.payload.is_empty() { - self.ack().await?; - break Ok(res.payload); - } - if res.flags.rst { - self.state = AdapterState::None; - break Err(std::io::Error::new( - std::io::ErrorKind::ConnectionReset, - "Connection reset", - )); - } - if res.flags.fin { - self.ack().await?; - self.state = AdapterState::None; - break Err(std::io::Error::new( - std::io::ErrorKind::ConnectionReset, - "Connection reset", + // Check to see if we already have some cached + if let Some(state) = self.states.get_mut(&host_port) { + if !state.read_buffer.is_empty() { + let res = state.read_buffer.clone(); + state.read_buffer = Vec::new(); + return Ok(res); + } + if let ConnectionStatus::Error(e) = state.status { + return Err(std::io::Error::new(e, "socket io error")); + } + } else { + return Err(std::io::Error::new( + ErrorKind::NotConnected, + "not connected", )); } + + self.process_tcp_packet().await?; } } @@ -413,24 +503,40 @@ impl Adapter { }) } - async fn read_tcp_packet(&mut self) -> Result { - loop { - let ip_packet = self.read_ip_packet().await?; - let tcp_packet = TcpPacket::parse(&ip_packet)?; - if tcp_packet.destination_port != self.host_port - || tcp_packet.source_port != self.peer_port - { - continue; - } - trace!("TCP packet: {tcp_packet:#?}"); - self.ack = tcp_packet.sequence_number - + if tcp_packet.payload.is_empty() { + async fn process_tcp_packet(&mut self) -> Result<(), std::io::Error> { + let ip_packet = self.read_ip_packet().await?; + let res = TcpPacket::parse(&ip_packet)?; + let mut ack_me = None; + if let Some(state) = self.states.get_mut(&res.destination_port) { + state.ack = res.sequence_number + + if res.payload.is_empty() { 1 } else { - tcp_packet.payload.len() as u32 + res.payload.len() as u32 }; - break Ok(tcp_packet); + if res.flags.psh || !res.payload.is_empty() { + ack_me = Some(res.destination_port); + state.read_buffer.extend(res.payload) + } + if res.flags.rst { + state.status = ConnectionStatus::Error(ErrorKind::ConnectionReset); + } + if res.flags.fin { + ack_me = Some(res.destination_port); + state.status = ConnectionStatus::Error(ErrorKind::ConnectionReset); + } + if res.flags.syn && res.flags.ack { + ack_me = Some(res.destination_port); + state.seq = state.seq.wrapping_add(1); + state.status = ConnectionStatus::Connected; + } } + + // we have to ack outside of the mutable state borrow + if let Some(a) = ack_me { + self.ack(a).await?; + } + Ok(()) } fn ip_wrap(&self, packet: &[u8]) -> Vec { @@ -454,125 +560,3 @@ impl 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( - mut self: std::pin::Pin<&mut Self>, - cx: &mut std::task::Context<'_>, - buf: &mut tokio::io::ReadBuf<'_>, - ) -> std::task::Poll> { - // First, check if we have any cached data - if !self.read_buffer.is_empty() { - let to_copy = std::cmp::min(buf.remaining(), self.read_buffer.len()); - buf.put_slice(&self.read_buffer[..to_copy]); - - // Keep any remaining data in the buffer - if to_copy < self.read_buffer.len() { - self.read_buffer = self.read_buffer[to_copy..].to_vec(); - } else { - self.read_buffer.clear(); - } - - return std::task::Poll::Ready(Ok(())); - } - - // If no cached data and not connected, return error - if self.state != AdapterState::Connected { - return std::task::Poll::Ready(Err(std::io::Error::new( - std::io::ErrorKind::NotConnected, - "Adapter not connected", - ))); - } - - // If no cached data, try to receive new data - let future = async { - match self.recv().await { - Ok(data) => { - let len = std::cmp::min(buf.remaining(), data.len()); - buf.put_slice(&data[..len]); - - // If we received more data than needed, cache the rest - if len < data.len() { - self.read_buffer = data[len..].to_vec(); - } - - Ok(()) - } - Err(e) => Err(e), - } - }; - - // Pin the future and poll it - futures::pin_mut!(future); - future.poll(cx) - } -} - -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( - mut self: std::pin::Pin<&mut Self>, - _cx: &mut std::task::Context<'_>, - buf: &[u8], - ) -> std::task::Poll> { - trace!("poll psh {}", buf.len()); - if self.state != AdapterState::Connected { - return std::task::Poll::Ready(Err(std::io::Error::new( - std::io::ErrorKind::NotConnected, - "Adapter not connected", - ))); - } - self.write_buffer.extend_from_slice(buf); - Poll::Ready(Ok(buf.len())) - } - - fn poll_flush( - mut self: std::pin::Pin<&mut Self>, - cx: &mut std::task::Context<'_>, - ) -> std::task::Poll> { - let future = async { - match self.write_buffer_flush().await { - Ok(_) => Ok(()), - Err(e) => Err(e), - } - }; - - // Pin the future and poll it - futures::pin_mut!(future); - future.poll(cx) - } - - fn poll_shutdown( - mut self: std::pin::Pin<&mut Self>, - cx: &mut std::task::Context<'_>, - ) -> std::task::Poll> { - // Create a future that can be polled - let future = async { self.close().await }; - - // Pin the future and poll it - futures::pin_mut!(future); - future.poll(cx) - } -} diff --git a/idevice/src/tcp/mod.rs b/idevice/src/tcp/mod.rs index b1da728..c8fda3e 100644 --- a/idevice/src/tcp/mod.rs +++ b/idevice/src/tcp/mod.rs @@ -10,8 +10,9 @@ use tokio::io::AsyncWriteExt; pub mod adapter; pub mod packets; +pub mod stream; -pub(crate) async fn log_packet(file: &Arc>, packet: &[u8]) { +pub(crate) fn log_packet(file: &Arc>, packet: &[u8]) { debug!("Logging {} byte packet", packet.len()); let packet = packet.to_vec(); let file = file.to_owned(); @@ -49,6 +50,7 @@ mod tests { pin::Pin, task::{Context, Poll}, }; + use stream::AdapterStream; use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tun_rs::DeviceBuilder; @@ -186,31 +188,35 @@ mod tests { let mut buf = Vec::new(); let _ = tokio::io::stdin().read(&mut buf).await.unwrap(); - if let Err(e) = adapter.connect(SERVER_PORT).await { - println!("no connect: {e:?}"); - } + let mut stream = match AdapterStream::connect(&mut adapter, SERVER_PORT).await { + Ok(s) => s, + Err(e) => { + println!("no connect: {e:?}"); + return; + } + }; - if let Err(e) = adapter.write_all(&[1, 2, 3, 4, 5]).await { + if let Err(e) = stream.write_all(&[1, 2, 3, 4, 5]).await { println!("no send: {e:?}"); } else { let mut buf = [0u8; 4]; - match adapter.read_exact(&mut buf).await { + match stream.read_exact(&mut buf).await { Ok(_) => println!("recv'd {buf:?}"), Err(e) => println!("no recv: {e:?}"), } } - if let Err(e) = adapter.write_all(&[69, 69, 42, 0, 1]).await { + if let Err(e) = stream.write_all(&[69, 69, 42, 0, 1]).await { println!("no send: {e:?}"); } else { let mut buf = [0u8; 6]; - match adapter.read_exact(&mut buf).await { + match stream.read_exact(&mut buf).await { Ok(_) => println!("recv'd {buf:?}"), Err(e) => println!("no recv: {e:?}"), } } - if let Err(e) = adapter.close().await { + if let Err(e) = stream.close().await { println!("no close: {e:?}"); } diff --git a/idevice/src/tcp/packets.rs b/idevice/src/tcp/packets.rs index 83f64eb..fa9e94f 100644 --- a/idevice/src/tcp/packets.rs +++ b/idevice/src/tcp/packets.rs @@ -143,7 +143,7 @@ impl Ipv4Packet { reader.read_exact(&mut payload).await?; if let Some(log) = log { log_packet.extend_from_slice(&payload); - super::log_packet(log, &log_packet).await; + super::log_packet(log, &log_packet); } Ok(Self { @@ -324,7 +324,7 @@ impl Ipv6Packet { reader.read_exact(&mut payload).await?; if let Some(log) = log { log_packet.extend_from_slice(&payload); - super::log_packet(log, &log_packet).await; + super::log_packet(log, &log_packet); } Ok(Self { diff --git a/idevice/src/tcp/stream.rs b/idevice/src/tcp/stream.rs new file mode 100644 index 0000000..2ada436 --- /dev/null +++ b/idevice/src/tcp/stream.rs @@ -0,0 +1,158 @@ +// Jackson Coxson + +use std::{future::Future, task::Poll}; + +use log::trace; +use tokio::io::{AsyncRead, AsyncWrite}; + +use crate::tcp::adapter::ConnectionStatus; + +use super::adapter::Adapter; + +#[derive(Debug)] +pub struct AdapterStream<'a> { + pub(crate) adapter: &'a mut Adapter, + pub host_port: u16, + pub peer_port: u16, +} + +impl<'a> AdapterStream<'a> { + pub async fn connect(adapter: &'a mut Adapter, port: u16) -> Result { + let host_port = adapter.connect(port).await?; + Ok(Self { + adapter, + host_port, + peer_port: port, + }) + } + + pub async fn close(&mut self) -> Result<(), std::io::Error> { + self.adapter.close(self.host_port).await + } +} + +impl AsyncRead for AdapterStream<'_> { + /// 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( + mut self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + buf: &mut tokio::io::ReadBuf<'_>, + ) -> std::task::Poll> { + match self.adapter.get_status(self.host_port) { + Ok(ConnectionStatus::Error(e)) => { + return std::task::Poll::Ready(Err(std::io::Error::new(e, "io error"))); + } + Err(e) => { + return std::task::Poll::Ready(Err(e)); + } + _ => {} + } + + // First, check if we have any cached data + let p = self.host_port; + let cache = match self.adapter.uncache(buf.remaining(), p) { + Ok(c) => c, + Err(e) => return std::task::Poll::Ready(Err(e)), + }; + if !cache.is_empty() { + buf.put_slice(&cache); + return std::task::Poll::Ready(Ok(())); + } + + // If no cached data, try to receive new data + let future = async { + match self.adapter.recv(p).await { + Ok(data) => { + let len = std::cmp::min(buf.remaining(), data.len()); + buf.put_slice(&data[..len]); + + // If we received more data than needed, cache the rest + if len < data.len() { + self.adapter.cache_read(&data[len..], p)? + } + + Ok(()) + } + Err(e) => Err(e), + } + }; + + // Pin the future and poll it + futures::pin_mut!(future); + future.poll(cx) + } +} + +impl AsyncWrite for AdapterStream<'_> { + /// 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( + mut self: std::pin::Pin<&mut Self>, + _cx: &mut std::task::Context<'_>, + buf: &[u8], + ) -> std::task::Poll> { + trace!("poll psh {}", buf.len()); + match self.adapter.get_status(self.host_port) { + Ok(ConnectionStatus::Error(e)) => { + return std::task::Poll::Ready(Err(std::io::Error::new(e, "io error"))); + } + Err(e) => { + return std::task::Poll::Ready(Err(e)); + } + _ => {} + } + let p = self.host_port; + match self.adapter.queue_send(buf, p) { + Ok(_) => Poll::Ready(Ok(buf.len())), + Err(e) => Poll::Ready(Err(e)), + } + } + + fn poll_flush( + mut self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> std::task::Poll> { + let future = async { + match self.adapter.write_buffer_flush().await { + Ok(_) => Ok(()), + Err(e) => Err(e), + } + }; + + // Pin the future and poll it + futures::pin_mut!(future); + future.poll(cx) + } + + fn poll_shutdown( + mut self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> std::task::Poll> { + // Create a future that can be polled + let future = async { self.close().await }; + + // Pin the future and poll it + futures::pin_mut!(future); + future.poll(cx) + } +} diff --git a/tools/src/remotexpc.rs b/tools/src/remotexpc.rs index 9aa5c4c..7f79180 100644 --- a/tools/src/remotexpc.rs +++ b/tools/src/remotexpc.rs @@ -2,7 +2,10 @@ // Print out all the RemoteXPC services use clap::{Arg, Command}; -use idevice::{core_device_proxy::CoreDeviceProxy, xpc::RemoteXpcClient, IdeviceService}; +use idevice::{ + core_device_proxy::CoreDeviceProxy, tcp::stream::AdapterStream, xpc::RemoteXpcClient, + IdeviceService, +}; mod common; @@ -63,10 +66,13 @@ async fn main() { let rsd_port = proxy.handshake.server_rsd_port; let mut adapter = proxy.create_software_tunnel().expect("no software tunnel"); - adapter.connect(rsd_port).await.expect("no RSD connect"); + adapter.pcap("new_xpc.pcap").await.unwrap(); + let conn = AdapterStream::connect(&mut adapter, rsd_port) + .await + .expect("no RSD connect"); // Make the connection to RemoteXPC - let mut client = RemoteXpcClient::new(Box::new(adapter)).await.unwrap(); + let mut client = RemoteXpcClient::new(Box::new(conn)).await.unwrap(); println!("{:#?}", client.do_handshake().await); } From de4a4e272279aee5b4fbea0000562ae982e7c9f4 Mon Sep 17 00:00:00 2001 From: Jackson Coxson Date: Fri, 23 May 2025 01:36:36 -0600 Subject: [PATCH 06/16] Remove testing logging --- idevice/src/tcp/adapter.rs | 2 -- idevice/src/xpc/format.rs | 2 -- 2 files changed, 4 deletions(-) diff --git a/idevice/src/tcp/adapter.rs b/idevice/src/tcp/adapter.rs index 5222c5f..2f06326 100644 --- a/idevice/src/tcp/adapter.rs +++ b/idevice/src/tcp/adapter.rs @@ -370,9 +370,7 @@ impl Adapter { continue; } - println!("flushing..."); self.psh(&writer_buffer, state.host_port).await.ok(); // don't care - println!("flushed {} bytes", writer_buffer.len()); // we have to borrow mutably after self.psh if let Some(state) = self.states.get_mut(&state.host_port) { diff --git a/idevice/src/xpc/format.rs b/idevice/src/xpc/format.rs index db42e15..7ae053c 100644 --- a/idevice/src/xpc/format.rs +++ b/idevice/src/xpc/format.rs @@ -217,7 +217,6 @@ impl XPCObject { } pub fn decode(buf: &[u8]) -> Result { - debug!("Decoding {buf:02X?}"); if buf.len() < 8 { return Err(IdeviceError::NotEnoughBytes(buf.len(), 8)); } @@ -408,7 +407,6 @@ impl XPCMessage { } pub fn decode(data: &[u8]) -> Result { - debug!("Decoding {data:02X?}"); if data.len() < 24 { Err(IdeviceError::NotEnoughBytes(data.len(), 24))? } From 9c5d872cf42f6ea88cdd08e7c9cf953e84b9ec96 Mon Sep 17 00:00:00 2001 From: Jackson Coxson Date: Fri, 23 May 2025 01:36:43 -0600 Subject: [PATCH 07/16] Update tools to use new TCP stack --- tools/src/debug_proxy.rs | 109 +++++++---------------- tools/src/location_simulation.rs | 25 ++++-- tools/src/process_control.rs | 145 ++++++++----------------------- 3 files changed, 87 insertions(+), 192 deletions(-) diff --git a/tools/src/debug_proxy.rs b/tools/src/debug_proxy.rs index e5206c5..809b24d 100644 --- a/tools/src/debug_proxy.rs +++ b/tools/src/debug_proxy.rs @@ -1,17 +1,12 @@ // Jackson Coxson -use std::{ - io::Write, - net::{IpAddr, SocketAddr}, - str::FromStr, -}; +use std::io::Write; use clap::{Arg, Command}; use idevice::{ - core_device_proxy::CoreDeviceProxy, debug_proxy::DebugProxyClient, - tunneld::get_tunneld_devices, xpc::XPCDevice, IdeviceService, ReadWrite, + core_device_proxy::CoreDeviceProxy, debug_proxy::DebugProxyClient, rsd::RsdClient, + tcp::stream::AdapterStream, IdeviceService, }; -use tokio::net::TcpStream; mod common; @@ -63,79 +58,41 @@ async fn main() { let pairing_file = matches.get_one::("pairing_file"); let host = matches.get_one::("host"); - let mut dp: DebugProxyClient> = if matches.get_flag("tunneld") { - let socket = SocketAddr::new( - IpAddr::from_str("127.0.0.1").unwrap(), - idevice::tunneld::DEFAULT_PORT, - ); - let mut devices = get_tunneld_devices(socket) - .await - .expect("Failed to get tunneld devices"); - - let (_udid, device) = match udid { - Some(u) => ( - u.to_owned(), - devices.remove(u).expect("Device not in tunneld"), - ), - None => devices.into_iter().next().expect("No devices"), + let provider = + match common::get_provider(udid, host, pairing_file, "debug-proxy-jkcoxson").await { + Ok(p) => p, + Err(e) => { + eprintln!("{e}"); + return; + } }; + let proxy = CoreDeviceProxy::connect(&*provider) + .await + .expect("no core proxy"); + let rsd_port = proxy.handshake.server_rsd_port; - // Make the connection to RemoteXPC - let client = XPCDevice::new(Box::new( - TcpStream::connect((device.tunnel_address.as_str(), device.tunnel_port)) - .await - .unwrap(), - )) + let mut adapter = proxy.create_software_tunnel().expect("no software tunnel"); + let stream = AdapterStream::connect(&mut adapter, rsd_port) + .await + .expect("no RSD connect"); + + // Make the connection to RemoteXPC + let mut client = RsdClient::new(stream).await.unwrap(); + + // Get the debug proxy + let service = client + .get_services() + .await + .unwrap() + .get(idevice::debug_proxy::SERVICE_NAME) + .expect("Client did not contain debug proxy service") + .to_owned(); + + let stream = AdapterStream::connect(&mut adapter, service.port) .await .unwrap(); - // Get the debug proxy - let service = client - .services - .get(idevice::debug_proxy::SERVICE_NAME) - .expect("Client did not contain debug proxy service"); - - let stream = TcpStream::connect(SocketAddr::new( - IpAddr::from_str(&device.tunnel_address).unwrap(), - service.port, - )) - .await - .expect("Failed to connect"); - - DebugProxyClient::new(Box::new(stream)) - } else { - let provider = - match common::get_provider(udid, host, pairing_file, "debug-proxy-jkcoxson").await { - Ok(p) => p, - Err(e) => { - eprintln!("{e}"); - return; - } - }; - let proxy = CoreDeviceProxy::connect(&*provider) - .await - .expect("no core proxy"); - let rsd_port = proxy.handshake.server_rsd_port; - - let mut adapter = proxy.create_software_tunnel().expect("no software tunnel"); - adapter.connect(rsd_port).await.expect("no RSD connect"); - - // Make the connection to RemoteXPC - let client = XPCDevice::new(Box::new(adapter)).await.unwrap(); - - // Get the debug proxy - let service = client - .services - .get(idevice::debug_proxy::SERVICE_NAME) - .expect("Client did not contain debug proxy service") - .to_owned(); - - let mut adapter = client.into_inner(); - adapter.close().await.unwrap(); - adapter.connect(service.port).await.unwrap(); - - DebugProxyClient::new(Box::new(adapter)) - }; + let mut dp = DebugProxyClient::new(stream); println!("Shell connected!"); loop { diff --git a/tools/src/location_simulation.rs b/tools/src/location_simulation.rs index 26d7284..471a239 100644 --- a/tools/src/location_simulation.rs +++ b/tools/src/location_simulation.rs @@ -2,7 +2,9 @@ // Just lists apps for now use clap::{Arg, Command}; -use idevice::{core_device_proxy::CoreDeviceProxy, xpc::XPCDevice, IdeviceService}; +use idevice::{ + core_device_proxy::CoreDeviceProxy, rsd::RsdClient, tcp::stream::AdapterStream, IdeviceService, +}; mod common; @@ -69,26 +71,31 @@ async fn main() { let rsd_port = proxy.handshake.server_rsd_port; let mut adapter = proxy.create_software_tunnel().expect("no software tunnel"); - adapter.connect(rsd_port).await.expect("no RSD connect"); + let stream = AdapterStream::connect(&mut adapter, rsd_port) + .await + .expect("no RSD connect"); // Make the connection to RemoteXPC - let client = XPCDevice::new(Box::new(adapter)).await.unwrap(); + let mut client = RsdClient::new(stream).await.unwrap(); // Get the debug proxy let service = client - .services + .get_services() + .await + .unwrap() .get(idevice::dvt::SERVICE_NAME) .expect("Client did not contain DVT service") .to_owned(); - let mut adapter = client.into_inner(); - adapter.connect(service.port).await.unwrap(); + let stream = AdapterStream::connect(&mut adapter, service.port) + .await + .unwrap(); - let mut rs_client = idevice::dvt::remote_server::RemoteServerClient::new(Box::new(adapter)); - rs_client.read_message(0).await.expect("no read??"); + let mut ls_client = idevice::dvt::remote_server::RemoteServerClient::new(stream); + ls_client.read_message(0).await.expect("no read??"); let mut ls_client = - idevice::dvt::location_simulation::LocationSimulationClient::new(&mut rs_client) + idevice::dvt::location_simulation::LocationSimulationClient::new(&mut ls_client) .await .expect("Unable to get channel for location simulation"); diff --git a/tools/src/process_control.rs b/tools/src/process_control.rs index 412ccb4..0f7f84d 100644 --- a/tools/src/process_control.rs +++ b/tools/src/process_control.rs @@ -1,16 +1,9 @@ // Jackson Coxson -use std::{ - net::{IpAddr, SocketAddr}, - str::FromStr, -}; - use clap::{Arg, Command}; use idevice::{ - core_device_proxy::CoreDeviceProxy, tunneld::get_tunneld_devices, xpc::XPCDevice, - IdeviceService, + core_device_proxy::CoreDeviceProxy, rsd::RsdClient, tcp::stream::AdapterStream, IdeviceService, }; -use tokio::net::TcpStream; mod common; @@ -71,70 +64,8 @@ async fn main() { .get_one::("bundle_id") .expect("No bundle ID specified"); - if matches.get_flag("tunneld") { - let socket = SocketAddr::new( - IpAddr::from_str("127.0.0.1").unwrap(), - idevice::tunneld::DEFAULT_PORT, - ); - let mut devices = get_tunneld_devices(socket) - .await - .expect("Failed to get tunneld devices"); - - let (_udid, device) = match udid { - Some(u) => ( - u.to_owned(), - devices.remove(u).expect("Device not in tunneld"), - ), - None => devices.into_iter().next().expect("No devices"), - }; - - // Make the connection to RemoteXPC - let client = XPCDevice::new(Box::new( - TcpStream::connect((device.tunnel_address.as_str(), device.tunnel_port)) - .await - .unwrap(), - )) - .await - .unwrap(); - - // Get the debug proxy - let service = client - .services - .get(idevice::dvt::SERVICE_NAME) - .expect("Client did not contain DVT service"); - - let stream = TcpStream::connect(SocketAddr::new( - IpAddr::from_str(&device.tunnel_address).unwrap(), - service.port, - )) - .await - .expect("Failed to connect"); - - let mut rs_client = idevice::dvt::remote_server::RemoteServerClient::new(Box::new(stream)); - rs_client.read_message(0).await.expect("no read??"); - let mut pc_client = - idevice::dvt::process_control::ProcessControlClient::new(&mut rs_client) - .await - .unwrap(); - - let pid = pc_client - .launch_app(bundle_id, None, None, true, false) - .await - .expect("no launch??"); - pc_client - .disable_memory_limit(pid) - .await - .expect("no disable??"); - println!("PID: {pid}"); - } else { - let provider = match common::get_provider( - udid, - host, - pairing_file, - "process_control-jkcoxson", - ) - .await - { + let provider = + match common::get_provider(udid, host, pairing_file, "process_control-jkcoxson").await { Ok(p) => p, Err(e) => { eprintln!("{e}"); @@ -142,45 +73,45 @@ async fn main() { } }; - let proxy = CoreDeviceProxy::connect(&*provider) - .await - .expect("no core proxy"); - let rsd_port = proxy.handshake.server_rsd_port; + let proxy = CoreDeviceProxy::connect(&*provider) + .await + .expect("no core proxy"); + let rsd_port = proxy.handshake.server_rsd_port; - let mut adapter = proxy.create_software_tunnel().expect("no software tunnel"); - adapter.connect(rsd_port).await.expect("no RSD connect"); + let mut adapter = proxy.create_software_tunnel().expect("no software tunnel"); + let stream = AdapterStream::connect(&mut adapter, rsd_port) + .await + .expect("no RSD connect"); - // Make the connection to RemoteXPC - let client = XPCDevice::new(Box::new(adapter)).await.unwrap(); + // Make the connection to RemoteXPC + let mut client = RsdClient::new(stream).await.unwrap(); - // Get the debug proxy - let service = client - .services - .get(idevice::dvt::SERVICE_NAME) - .expect("Client did not contain DVT service") - .to_owned(); + // Get the debug proxy + let service = client + .get_services() + .await + .unwrap() + .get(idevice::dvt::SERVICE_NAME) + .expect("Client did not contain DVT service") + .to_owned(); - let mut adapter = client.into_inner(); - adapter.connect(service.port).await.unwrap(); + let stream = AdapterStream::connect(&mut adapter, service.port) + .await + .unwrap(); - let mut rs_client = idevice::dvt::remote_server::RemoteServerClient::new(Box::new(adapter)); - rs_client.read_message(0).await.expect("no read??"); - let mut pc_client = - idevice::dvt::process_control::ProcessControlClient::new(&mut rs_client) - .await - .unwrap(); + let mut rs_client = idevice::dvt::remote_server::RemoteServerClient::new(stream); + rs_client.read_message(0).await.expect("no read??"); + let mut pc_client = idevice::dvt::process_control::ProcessControlClient::new(&mut rs_client) + .await + .unwrap(); - let pid = pc_client - .launch_app(bundle_id, None, None, true, false) - .await - .expect("no launch??"); - pc_client - .disable_memory_limit(pid) - .await - .expect("no disable??"); - println!("PID: {pid}"); - - // let mut adapter = rs_client.into_inner(); - // adapter.close().await.expect("no close??"); - } + let pid = pc_client + .launch_app(bundle_id, None, None, true, false) + .await + .expect("no launch??"); + pc_client + .disable_memory_limit(pid) + .await + .expect("no disable??"); + println!("PID: {pid}"); } From f4ba4210faa13e63616ad05e24082398fb9e0aee Mon Sep 17 00:00:00 2001 From: Jackson Coxson Date: Fri, 23 May 2025 01:37:58 -0600 Subject: [PATCH 08/16] Rename XPCService to RsdService --- idevice/src/services/rsd.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/idevice/src/services/rsd.rs b/idevice/src/services/rsd.rs index c6b7dc8..5844b24 100644 --- a/idevice/src/services/rsd.rs +++ b/idevice/src/services/rsd.rs @@ -10,7 +10,7 @@ use crate::{IdeviceError, ReadWrite, RemoteXpcClient}; /// Describes an available XPC service #[derive(Debug, Clone, Deserialize)] -pub struct XPCService { +pub struct RsdService { /// Required entitlement to access this service pub entitlement: String, /// Port number where the service is available @@ -34,7 +34,7 @@ impl RsdClient { }) } - pub async fn get_services(&mut self) -> Result, IdeviceError> { + pub async fn get_services(&mut self) -> Result, IdeviceError> { let data = self.inner.do_handshake().await?; let data = match data @@ -100,7 +100,7 @@ impl RsdClient { services.insert( name.to_string(), - XPCService { + RsdService { entitlement, port, uses_remote_xpc, From 22034edcaba6c293989ec14fdd4e89b5180b17b4 Mon Sep 17 00:00:00 2001 From: Jackson Coxson Date: Fri, 23 May 2025 02:01:52 -0600 Subject: [PATCH 09/16] Add drop to AdapterStream --- idevice/src/tcp/adapter.rs | 46 +++++++++++++++++++++++++------------- idevice/src/tcp/stream.rs | 10 ++++++++- 2 files changed, 39 insertions(+), 17 deletions(-) diff --git a/idevice/src/tcp/adapter.rs b/idevice/src/tcp/adapter.rs index 2f06326..f1a59ef 100644 --- a/idevice/src/tcp/adapter.rs +++ b/idevice/src/tcp/adapter.rs @@ -11,8 +11,7 @@ //! - Optional PCAP packet capture //! - Implements `AsyncRead` and `AsyncWrite` for Tokio compatibility //! -//! # Limitations -//! - Only supports one connection at a time +//! # Limitations (unecessary for CDTunnel) //! - No proper sequence number tracking //! - No retransmission or congestion control //! - Requires 100% reliable underlying transport @@ -22,8 +21,8 @@ //! ```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 +//! use idevice::tcp::{adapter::Adapter, stream::AdapterStream}; +//! use idevice::ReadWrite; //! //! #[tokio::main] //! async fn main() -> Result<(), Box> { @@ -39,19 +38,19 @@ //! adapter.pcap("capture.pcap").await?; //! //! // Connect to remote server -//! adapter.connect(80).await?; +//! let stream = AdapterStream::new(&mut adapter, 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?; +//! stream.write_all(b"GET / HTTP/1.1\r\nHost: example.com\r\n\r\n").await?; +//! stream.flush().await?; //! //! // Read response //! let mut buf = vec![0; 1024]; -//! let n = adapter.read(&mut buf).await?; +//! let n = stream.read(&mut buf).await?; //! println!("Received: {}", String::from_utf8_lossy(&buf[..n])); //! //! // Close connection -//! adapter.close().await?; +//! stream.close().await?; //! //! Ok(()) //! } @@ -59,7 +58,7 @@ //! //! # Warning //! This implementation makes significant simplifications and should not be used -//! in production environments or with unreliable network transports. +//! with unreliable network transports. use std::{collections::HashMap, io::ErrorKind, net::IpAddr, path::Path, sync::Arc}; @@ -106,11 +105,8 @@ impl ConnectionState { /// /// This is an extremely naive, limited, and dangerous TCP stack implementation. /// 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)] pub struct Adapter { /// The underlying transport connection @@ -120,9 +116,10 @@ pub struct Adapter { /// The remote peer's IP address peer_ip: IpAddr, + /// The states of the connections states: HashMap, // host port by state + dropped: Vec, - // Logging /// Optional PCAP file for packet logging pcap: Option>>, } @@ -136,13 +133,14 @@ impl Adapter { /// * `peer_ip` - The remote IP address to connect to /// /// # Returns - /// A new unconnected `Adapter` instance + /// A new `Adapter` instance pub fn new(peer: Box, host_ip: IpAddr, peer_ip: IpAddr) -> Self { Self { peer, host_ip, peer_ip, states: HashMap::new(), + dropped: Vec::new(), pcap: None, } } @@ -153,7 +151,7 @@ impl Adapter { /// * `port` - The remote port number to connect to /// /// # Returns - /// * `Ok(())` if connection was successful + /// * `Ok(u16)` the chosen host port if successful /// * `Err(std::io::Error)` if connection failed /// /// # Errors @@ -362,6 +360,10 @@ impl Adapter { Ok(()) } + pub(crate) fn connection_drop(&mut self, host_port: u16) { + self.dropped.push(host_port); + } + /// Flushes the packets pub(crate) async fn write_buffer_flush(&mut self) -> Result<(), std::io::Error> { for (_, state) in self.states.clone() { @@ -377,6 +379,18 @@ impl Adapter { state.write_buffer.clear(); } } + + // Since we have extra clocks and we haven't been cancelled by the runtime, let's reap the + // dropped connections + for d in self.dropped.clone() { + if let Some(state) = self.states.remove(&d) { + self.close(state.host_port).await.ok(); + } + } + // We can't clear until it's all done, since we can get cancelled by the runtime at any + // point. + self.dropped.clear(); + Ok(()) } diff --git a/idevice/src/tcp/stream.rs b/idevice/src/tcp/stream.rs index 2ada436..54dfde7 100644 --- a/idevice/src/tcp/stream.rs +++ b/idevice/src/tcp/stream.rs @@ -1,4 +1,4 @@ -// Jackson Coxson +//! A stream for the adapter use std::{future::Future, task::Poll}; @@ -17,6 +17,7 @@ pub struct AdapterStream<'a> { } impl<'a> AdapterStream<'a> { + /// Connect to the specified port pub async fn connect(adapter: &'a mut Adapter, port: u16) -> Result { let host_port = adapter.connect(port).await?; Ok(Self { @@ -26,6 +27,7 @@ impl<'a> AdapterStream<'a> { }) } + /// Gracefully closes the stream pub async fn close(&mut self) -> Result<(), std::io::Error> { self.adapter.close(self.host_port).await } @@ -156,3 +158,9 @@ impl AsyncWrite for AdapterStream<'_> { future.poll(cx) } } + +impl Drop for AdapterStream<'_> { + fn drop(&mut self) { + self.adapter.connection_drop(self.host_port); + } +} From 2a8a921951341f8a96180e8aa38cd3b3670a203e Mon Sep 17 00:00:00 2001 From: Jackson Coxson Date: Fri, 23 May 2025 02:13:31 -0600 Subject: [PATCH 10/16] Remove unused dependencies --- Cargo.lock | 12 ------------ idevice/Cargo.toml | 9 +-------- 2 files changed, 1 insertion(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3fdf17c..f964cec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -112,17 +112,6 @@ dependencies = [ "pin-project-lite", ] -[[package]] -name = "async-recursion" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.101", -] - [[package]] name = "async-task" version = "4.7.1" @@ -1186,7 +1175,6 @@ dependencies = [ name = "idevice" version = "0.1.33" dependencies = [ - "async-recursion", "base64", "byteorder", "bytes", diff --git a/idevice/Cargo.toml b/idevice/Cargo.toml index 344a64f..03a43aa 100644 --- a/idevice/Cargo.toml +++ b/idevice/Cargo.toml @@ -26,7 +26,6 @@ base64 = { version = "0.22" } indexmap = { version = "2.7", features = ["serde"], optional = true } uuid = { version = "1.12", features = ["serde", "v4"], optional = true } -async-recursion = { version = "1.1", optional = true } chrono = { version = "0.4.40", optional = true, default-features = false } serde_json = { version = "1", optional = true } @@ -76,13 +75,7 @@ tunnel_tcp_stack = ["dep:rand", "dep:futures", "tokio/fs", "tokio/sync"] tss = ["dep:uuid", "dep:reqwest"] tunneld = ["dep:serde_json", "dep:json", "dep:reqwest"] usbmuxd = ["tokio/net"] -xpc = [ - "tokio/sync", - "dep:indexmap", - "dep:uuid", - "dep:async-recursion", - "dep:json", -] +xpc = ["dep:indexmap", "dep:uuid"] full = [ "afc", "amfi", From a39cbd1d3d5ed309e2895f21c5f0e88badb19508 Mon Sep 17 00:00:00 2001 From: Jackson Coxson Date: Sun, 25 May 2025 13:11:59 -0600 Subject: [PATCH 11/16] Create new RSD service trait --- idevice/src/lib.rs | 23 +++++++- idevice/src/provider.rs | 10 +++- idevice/src/services/debug_proxy.rs | 19 +++++-- idevice/src/services/dvt/mod.rs | 14 ++++- idevice/src/services/rsd.rs | 88 +++++++++++++++++++++++------ idevice/src/tcp/mod.rs | 15 +++++ tools/src/debug_proxy.rs | 22 ++------ tools/src/location_simulation.rs | 23 +++----- tools/src/process_control.rs | 23 +++----- 9 files changed, 166 insertions(+), 71 deletions(-) diff --git a/idevice/src/lib.rs b/idevice/src/lib.rs index b7ead1d..550112a 100644 --- a/idevice/src/lib.rs +++ b/idevice/src/lib.rs @@ -25,7 +25,7 @@ pub use services::*; pub use xpc::RemoteXpcClient; use log::{debug, error, trace}; -use provider::IdeviceProvider; +use provider::{IdeviceProvider, RsdProvider}; use rustls::{crypto::CryptoProvider, pki_types::ServerName}; use std::{ io::{self, BufWriter}, @@ -65,6 +65,25 @@ pub trait IdeviceService: Sized { ) -> impl std::future::Future> + Send; } +pub trait RsdService: Sized { + fn rsd_service_name() -> &'static str; + fn from_stream( + stream: Self::Stream, + ) -> impl std::future::Future> + Send; + fn connect_rsd<'a, S>( + provider: &'a mut impl RsdProvider<'a, Stream = S>, + handshake: &mut rsd::RsdHandshake, + ) -> impl std::future::Future> + where + Self: crate::RsdService, + S: ReadWrite, + { + handshake.connect(provider) + } + + type Stream: ReadWrite; +} + /// Type alias for boxed device connection sockets /// /// Used to enable dynamic dispatch of different connection types while maintaining @@ -424,6 +443,8 @@ pub enum IdeviceError { HeartbeatTimeout, #[error("not found")] NotFound, + #[error("service not found")] + ServiceNotFound, #[error("CDTunnel packet too short")] CdtunnelPacketTooShort, #[error("CDTunnel packet invalid magic")] diff --git a/idevice/src/provider.rs b/idevice/src/provider.rs index 740f6a1..bab7aeb 100644 --- a/idevice/src/provider.rs +++ b/idevice/src/provider.rs @@ -8,7 +8,7 @@ use std::{future::Future, pin::Pin}; #[cfg(feature = "tcp")] use tokio::net::TcpStream; -use crate::{pairing_file::PairingFile, Idevice, IdeviceError}; +use crate::{pairing_file::PairingFile, Idevice, IdeviceError, ReadWrite}; #[cfg(feature = "usbmuxd")] use crate::usbmuxd::UsbmuxdAddr; @@ -42,6 +42,14 @@ pub trait IdeviceProvider: Unpin + Send + Sync + std::fmt::Debug { ) -> Pin> + Send>>; } +pub trait RsdProvider<'a>: Unpin + Send + Sync + std::fmt::Debug { + fn connect_to_service_port( + &'a mut self, + port: u16, + ) -> impl std::future::Future> + Send; + type Stream: ReadWrite; +} + /// TCP-based device connection provider #[cfg(feature = "tcp")] #[derive(Debug)] diff --git a/idevice/src/services/debug_proxy.rs b/idevice/src/services/debug_proxy.rs index a8de60e..022d5f2 100644 --- a/idevice/src/services/debug_proxy.rs +++ b/idevice/src/services/debug_proxy.rs @@ -8,10 +8,22 @@ use log::debug; use std::fmt::Write; use tokio::io::{AsyncReadExt, AsyncWriteExt}; -use crate::{IdeviceError, ReadWrite}; +use crate::{IdeviceError, ReadWrite, RsdService}; -/// The service name for the debug proxy as registered with lockdownd -pub const SERVICE_NAME: &str = "com.apple.internal.dt.remote.debugproxy"; +impl RsdService for DebugProxyClient { + fn rsd_service_name() -> &'static str { + "com.apple.internal.dt.remote.debugproxy" + } + + async fn from_stream(stream: R) -> Result { + Ok(Self { + socket: stream, + noack_mode: false, + }) + } + + type Stream = R; +} /// Client for interacting with the iOS debug proxy service /// @@ -300,4 +312,3 @@ impl From<&str> for DebugserverCommand { s.to_string().into() } } - diff --git a/idevice/src/services/dvt/mod.rs b/idevice/src/services/dvt/mod.rs index 55063a6..5764ded 100644 --- a/idevice/src/services/dvt/mod.rs +++ b/idevice/src/services/dvt/mod.rs @@ -1,9 +1,21 @@ // Jackson Coxson +use crate::{IdeviceError, ReadWrite, RsdService}; + #[cfg(feature = "location_simulation")] pub mod location_simulation; pub mod message; pub mod process_control; pub mod remote_server; -pub const SERVICE_NAME: &str = "com.apple.instruments.dtservicehub"; +impl RsdService for remote_server::RemoteServerClient { + fn rsd_service_name() -> &'static str { + "com.apple.instruments.dtservicehub" + } + + async fn from_stream(stream: R) -> Result { + Ok(Self::new(stream)) + } + + type Stream = R; +} diff --git a/idevice/src/services/rsd.rs b/idevice/src/services/rsd.rs index 5844b24..7073b44 100644 --- a/idevice/src/services/rsd.rs +++ b/idevice/src/services/rsd.rs @@ -6,7 +6,7 @@ use std::collections::HashMap; use log::warn; use serde::Deserialize; -use crate::{IdeviceError, ReadWrite, RemoteXpcClient}; +use crate::{provider::RsdProvider, IdeviceError, ReadWrite, RemoteXpcClient}; /// Describes an available XPC service #[derive(Debug, Clone, Deserialize)] @@ -23,21 +23,19 @@ pub struct RsdService { pub service_version: Option, } -pub struct RsdClient { - inner: RemoteXpcClient, +pub struct RsdHandshake { + pub services: HashMap, + pub protocol_version: usize, + pub properties: HashMap, + pub uuid: String, } -impl RsdClient { - pub async fn new(socket: R) -> Result { - Ok(Self { - inner: RemoteXpcClient::new(socket).await?, - }) - } +impl RsdHandshake { + pub async fn new(socket: impl ReadWrite) -> Result { + let mut xpc_client = RemoteXpcClient::new(socket).await?; + let data = xpc_client.do_handshake().await?; - pub async fn get_services(&mut self) -> Result, IdeviceError> { - let data = self.inner.do_handshake().await?; - - let data = match data + let services_dict = match data .as_dictionary() .and_then(|x| x.get("Services")) .and_then(|x| x.as_dictionary()) @@ -47,8 +45,8 @@ impl RsdClient { }; // Parse available services - let mut services = HashMap::new(); - for (name, service) in data.into_iter() { + let mut services: HashMap = HashMap::new(); + for (name, service) in services_dict.into_iter() { match service.as_dictionary() { Some(service) => { let entitlement = match service.get("Entitlement").and_then(|x| x.as_string()) { @@ -116,6 +114,64 @@ impl RsdClient { } } - Ok(services) + let protocol_version = match data.as_dictionary().and_then(|x| { + x.get("MessagingProtocolVersion") + .and_then(|x| x.as_signed_integer()) + }) { + Some(p) => p as usize, + None => { + return Err(IdeviceError::UnexpectedResponse); + } + }; + + let uuid = match data + .as_dictionary() + .and_then(|x| x.get("UUID").and_then(|x| x.as_string())) + { + Some(u) => u.to_string(), + None => { + return Err(IdeviceError::UnexpectedResponse); + } + }; + + let properties = match data + .as_dictionary() + .and_then(|x| x.get("Properties").and_then(|x| x.as_dictionary())) + { + Some(d) => d + .into_iter() + .map(|(name, prop)| (name.to_owned(), prop.to_owned())) + .collect::>(), + None => { + return Err(IdeviceError::UnexpectedResponse); + } + }; + + Ok(Self { + services, + protocol_version, + properties, + uuid, + }) + } + + pub async fn connect<'a, T, S>( + &mut self, + provider: &'a mut impl RsdProvider<'a, Stream = S>, + ) -> Result + where + T: crate::RsdService, + S: ReadWrite, + { + let service_name = T::rsd_service_name(); + let service = match self.services.get(service_name) { + Some(s) => s, + None => { + return Err(IdeviceError::ServiceNotFound); + } + }; + + let stream = provider.connect_to_service_port(service.port).await?; + T::from_stream(stream).await } } diff --git a/idevice/src/tcp/mod.rs b/idevice/src/tcp/mod.rs index c8fda3e..0c4424d 100644 --- a/idevice/src/tcp/mod.rs +++ b/idevice/src/tcp/mod.rs @@ -6,8 +6,11 @@ use std::{ }; use log::debug; +use stream::AdapterStream; use tokio::io::AsyncWriteExt; +use crate::provider::RsdProvider; + pub mod adapter; pub mod packets; pub mod stream; @@ -36,6 +39,18 @@ pub(crate) fn log_packet(file: &Arc>, packet }); } +impl<'a> RsdProvider<'a> for adapter::Adapter { + async fn connect_to_service_port( + &'a mut self, + port: u16, + ) -> Result, crate::IdeviceError> { + let s = stream::AdapterStream::connect(self, port).await?; + Ok(s) + } + + type Stream = AdapterStream<'a>; +} + #[cfg(test)] mod tests { use std::{ diff --git a/tools/src/debug_proxy.rs b/tools/src/debug_proxy.rs index 809b24d..ba3be4d 100644 --- a/tools/src/debug_proxy.rs +++ b/tools/src/debug_proxy.rs @@ -4,8 +4,8 @@ use std::io::Write; use clap::{Arg, Command}; use idevice::{ - core_device_proxy::CoreDeviceProxy, debug_proxy::DebugProxyClient, rsd::RsdClient, - tcp::stream::AdapterStream, IdeviceService, + core_device_proxy::CoreDeviceProxy, debug_proxy::DebugProxyClient, rsd::RsdHandshake, + tcp::stream::AdapterStream, IdeviceService, RsdService, }; mod common; @@ -77,22 +77,12 @@ async fn main() { .expect("no RSD connect"); // Make the connection to RemoteXPC - let mut client = RsdClient::new(stream).await.unwrap(); + let mut handshake = RsdHandshake::new(stream).await.unwrap(); + println!("{:?}", handshake.services); - // Get the debug proxy - let service = client - .get_services() + let mut dp = DebugProxyClient::connect_rsd(&mut adapter, &mut handshake) .await - .unwrap() - .get(idevice::debug_proxy::SERVICE_NAME) - .expect("Client did not contain debug proxy service") - .to_owned(); - - let stream = AdapterStream::connect(&mut adapter, service.port) - .await - .unwrap(); - - let mut dp = DebugProxyClient::new(stream); + .expect("no connect"); println!("Shell connected!"); loop { diff --git a/tools/src/location_simulation.rs b/tools/src/location_simulation.rs index 471a239..146bf64 100644 --- a/tools/src/location_simulation.rs +++ b/tools/src/location_simulation.rs @@ -3,7 +3,8 @@ use clap::{Arg, Command}; use idevice::{ - core_device_proxy::CoreDeviceProxy, rsd::RsdClient, tcp::stream::AdapterStream, IdeviceService, + core_device_proxy::CoreDeviceProxy, rsd::RsdHandshake, tcp::stream::AdapterStream, + IdeviceService, RsdService, }; mod common; @@ -76,22 +77,12 @@ async fn main() { .expect("no RSD connect"); // Make the connection to RemoteXPC - let mut client = RsdClient::new(stream).await.unwrap(); + let mut handshake = RsdHandshake::new(stream).await.unwrap(); - // Get the debug proxy - let service = client - .get_services() - .await - .unwrap() - .get(idevice::dvt::SERVICE_NAME) - .expect("Client did not contain DVT service") - .to_owned(); - - let stream = AdapterStream::connect(&mut adapter, service.port) - .await - .unwrap(); - - let mut ls_client = idevice::dvt::remote_server::RemoteServerClient::new(stream); + let mut ls_client = + idevice::dvt::remote_server::RemoteServerClient::connect_rsd(&mut adapter, &mut handshake) + .await + .expect("Failed to connect"); ls_client.read_message(0).await.expect("no read??"); let mut ls_client = diff --git a/tools/src/process_control.rs b/tools/src/process_control.rs index 0f7f84d..173633d 100644 --- a/tools/src/process_control.rs +++ b/tools/src/process_control.rs @@ -2,7 +2,8 @@ use clap::{Arg, Command}; use idevice::{ - core_device_proxy::CoreDeviceProxy, rsd::RsdClient, tcp::stream::AdapterStream, IdeviceService, + core_device_proxy::CoreDeviceProxy, rsd::RsdHandshake, tcp::stream::AdapterStream, + IdeviceService, RsdService, }; mod common; @@ -84,22 +85,12 @@ async fn main() { .expect("no RSD connect"); // Make the connection to RemoteXPC - let mut client = RsdClient::new(stream).await.unwrap(); + let mut handshake = RsdHandshake::new(stream).await.unwrap(); - // Get the debug proxy - let service = client - .get_services() - .await - .unwrap() - .get(idevice::dvt::SERVICE_NAME) - .expect("Client did not contain DVT service") - .to_owned(); - - let stream = AdapterStream::connect(&mut adapter, service.port) - .await - .unwrap(); - - let mut rs_client = idevice::dvt::remote_server::RemoteServerClient::new(stream); + let mut rs_client = + idevice::dvt::remote_server::RemoteServerClient::connect_rsd(&mut adapter, &mut handshake) + .await + .expect("no connect"); rs_client.read_message(0).await.expect("no read??"); let mut pc_client = idevice::dvt::process_control::ProcessControlClient::new(&mut rs_client) .await From 15261861e07ff024d164ad9aea57c88aee5107bf Mon Sep 17 00:00:00 2001 From: Jackson Coxson Date: Sun, 25 May 2025 17:01:44 -0600 Subject: [PATCH 12/16] Add IpAddr impl for RsdProvider --- idevice/src/provider.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/idevice/src/provider.rs b/idevice/src/provider.rs index bab7aeb..917fa18 100644 --- a/idevice/src/provider.rs +++ b/idevice/src/provider.rs @@ -157,3 +157,15 @@ impl IdeviceProvider for UsbmuxdProvider { }) } } + +#[cfg(feature = "tcp")] +impl<'a> RsdProvider<'a> for std::net::IpAddr { + async fn connect_to_service_port( + &'a mut self, + port: u16, + ) -> Result { + Ok(tokio::net::TcpStream::connect((*self, port)).await?) + } + + type Stream = tokio::net::TcpStream; +} From fa88c2c87d5e3b6edfbed8ec996529e1baed9fb7 Mon Sep 17 00:00:00 2001 From: Jackson Coxson Date: Mon, 26 May 2025 12:52:23 -0600 Subject: [PATCH 13/16] Refactor FFI bindings --- ffi/Cargo.toml | 51 +- ffi/src/adapter.rs | 28 +- ffi/src/afc.rs | 162 ++---- ffi/src/amfi.rs | 128 +---- ffi/src/core_device_proxy.rs | 112 ++-- ffi/src/debug_proxy.rs | 109 ++-- ffi/src/errors.rs | 38 +- ffi/src/heartbeat.rs | 110 +--- ffi/src/installation_proxy.rs | 100 +--- ffi/src/lib.rs | 22 +- ffi/src/location_simulation.rs | 20 +- ffi/src/lockdown.rs | 95 +--- ffi/src/misagent.rs | 79 +-- .../{mounter.rs => mobile_image_mounter.rs} | 360 ++----------- ffi/src/process_control.rs | 20 +- ffi/src/provider.rs | 55 +- ffi/src/remote_server.rs | 62 +-- ffi/src/remotexpc.rs | 288 ---------- ffi/src/rsd.rs | 502 ++++++++++++++++++ .../{sbservices.rs => springboardservices.rs} | 82 +-- ffi/src/syslog_relay.rs | 47 +- idevice/src/tcp/stream.rs | 11 + 22 files changed, 1005 insertions(+), 1476 deletions(-) rename ffi/src/{mounter.rs => mobile_image_mounter.rs} (67%) delete mode 100644 ffi/src/remotexpc.rs create mode 100644 ffi/src/rsd.rs rename ffi/src/{sbservices.rs => springboardservices.rs} (64%) diff --git a/ffi/Cargo.toml b/ffi/Cargo.toml index cc6c97f..fef6311 100644 --- a/ffi/Cargo.toml +++ b/ffi/Cargo.toml @@ -5,7 +5,7 @@ edition = "2024" [dependencies] -idevice = { path = "../idevice", features = ["full"] } +idevice = { path = "../idevice" } log = "0.4.26" simplelog = "0.12.2" once_cell = "1.21.1" @@ -14,6 +14,55 @@ libc = "0.2.171" plist = "1.7.1" plist_plus = { version = "0.2.6", features = ["dynamic"] } +[features] +afc = ["idevice/afc"] +amfi = ["idevice/amfi"] +core_device_proxy = ["idevice/core_device_proxy"] +crashreportcopymobile = ["idevice/crashreportcopymobile"] +debug_proxy = ["idevice/debug_proxy"] +dvt = ["idevice/dvt"] +heartbeat = ["idevice/heartbeat"] +house_arrest = ["idevice/house_arrest"] +installation_proxy = ["idevice/installation_proxy"] +springboardservices = ["idevice/springboardservices"] +misagent = ["idevice/misagent"] +mobile_image_mounter = ["idevice/mobile_image_mounter"] +location_simulation = ["idevice/location_simulation"] +pair = ["idevice/pair"] +rsd = ["idevice/rsd"] +syslog_relay = ["idevice/syslog_relay"] +tcp = ["idevice/tcp"] +tunnel_tcp_stack = ["idevice/tunnel_tcp_stack"] +tss = ["idevice/tss"] +tunneld = ["idevice/tunneld"] +usbmuxd = ["idevice/usbmuxd"] +xpc = ["idevice/xpc"] +full = [ + "afc", + "amfi", + "core_device_proxy", + "crashreportcopymobile", + "debug_proxy", + "dvt", + "heartbeat", + "house_arrest", + "installation_proxy", + "misagent", + "mobile_image_mounter", + "pair", + "usbmuxd", + "xpc", + "location_simulation", + "rsd", + "tcp", + "tunnel_tcp_stack", + "tss", + "tunneld", + "springboardservices", + "syslog_relay", +] +default = ["full"] + [build-dependencies] cbindgen = "0.28.0" diff --git a/ffi/src/adapter.rs b/ffi/src/adapter.rs index e6f0d8b..20fdb88 100644 --- a/ffi/src/adapter.rs +++ b/ffi/src/adapter.rs @@ -2,31 +2,39 @@ use std::ffi::{CString, c_char}; +use idevice::tcp::stream::AdapterStream; + use crate::core_device_proxy::AdapterHandle; use crate::{IdeviceErrorCode, RUNTIME}; +pub struct AdapterStreamHandle<'a>(pub AdapterStream<'a>); + /// Connects the adapter to a specific port /// /// # Arguments -/// * [`handle`] - The adapter handle +/// * [`adapter_handle`] - The adapter handle /// * [`port`] - The port to connect to +/// * [`stream_handle`] - A pointer to allocate the new stream to /// /// # Returns /// An error code indicating success or failure /// /// # Safety -/// `handle` must be a valid pointer to a handle allocated by this library +/// `handle` must be a valid pointer to a handle allocated by this library. +/// Any stream allocated must be used in the same thread as the adapter. The handles are NOT thread +/// safe. #[unsafe(no_mangle)] pub unsafe extern "C" fn adapter_connect( - handle: *mut AdapterHandle, + adapter_handle: *mut AdapterHandle, port: u16, + stream_handle: *mut *mut AdapterStreamHandle, ) -> IdeviceErrorCode { - if handle.is_null() { + if adapter_handle.is_null() || stream_handle.is_null() { return IdeviceErrorCode::InvalidArg; } - let adapter = unsafe { &mut (*handle).0 }; - let res = RUNTIME.block_on(async move { adapter.connect(port).await }); + let adapter = unsafe { &mut (*adapter_handle).0 }; + let res = RUNTIME.block_on(async move { AdapterStream::connect(adapter, port).await }); match res { Ok(_) => IdeviceErrorCode::IdeviceSuccess, @@ -79,7 +87,7 @@ pub unsafe extern "C" fn adapter_pcap( /// Closes the adapter connection /// /// # Arguments -/// * [`handle`] - The adapter handle +/// * [`handle`] - The adapter stream handle /// /// # Returns /// An error code indicating success or failure @@ -87,7 +95,7 @@ pub unsafe extern "C" fn adapter_pcap( /// # Safety /// `handle` must be a valid pointer to a handle allocated by this library #[unsafe(no_mangle)] -pub unsafe extern "C" fn adapter_close(handle: *mut AdapterHandle) -> IdeviceErrorCode { +pub unsafe extern "C" fn adapter_close(handle: *mut AdapterStreamHandle) -> IdeviceErrorCode { if handle.is_null() { return IdeviceErrorCode::InvalidArg; } @@ -119,7 +127,7 @@ pub unsafe extern "C" fn adapter_close(handle: *mut AdapterHandle) -> IdeviceErr /// `data` must be a valid pointer to at least `length` bytes #[unsafe(no_mangle)] pub unsafe extern "C" fn adapter_send( - handle: *mut AdapterHandle, + handle: *mut AdapterStreamHandle, data: *const u8, length: usize, ) -> IdeviceErrorCode { @@ -158,7 +166,7 @@ pub unsafe extern "C" fn adapter_send( /// `length` must be a valid pointer to a usize #[unsafe(no_mangle)] pub unsafe extern "C" fn adapter_recv( - handle: *mut AdapterHandle, + handle: *mut AdapterStreamHandle, data: *mut u8, length: *mut usize, max_length: usize, diff --git a/ffi/src/afc.rs b/ffi/src/afc.rs index 0b5c704..8b8a016 100644 --- a/ffi/src/afc.rs +++ b/ffi/src/afc.rs @@ -1,23 +1,19 @@ // Jackson Coxson -use std::ffi::c_void; - use idevice::{ IdeviceError, IdeviceService, afc::{AfcClient, DeviceInfo, FileInfo}, + provider::IdeviceProvider, }; -use crate::{ - IdeviceErrorCode, IdeviceHandle, RUNTIME, - provider::{TcpProviderHandle, UsbmuxdProviderHandle}, -}; +use crate::{IdeviceErrorCode, IdeviceHandle, RUNTIME, provider::IdeviceProviderHandle}; pub struct AfcClientHandle(pub AfcClient); /// Connects to the AFC service using a TCP provider /// /// # Arguments -/// * [`provider`] - A TcpProvider +/// * [`provider`] - An IdeviceProvider /// * [`client`] - On success, will be set to point to a newly allocated AfcClient handle /// /// # Returns @@ -27,8 +23,8 @@ pub struct AfcClientHandle(pub AfcClient); /// `provider` must be a valid pointer to a handle allocated by this library /// `client` must be a valid, non-null pointer to a location where the handle will be stored #[unsafe(no_mangle)] -pub unsafe extern "C" fn afc_client_connect_tcp( - provider: *mut TcpProviderHandle, +pub unsafe extern "C" fn afc_client_connect( + provider: *mut IdeviceProviderHandle, client: *mut *mut AfcClientHandle, ) -> IdeviceErrorCode { if provider.is_null() || client.is_null() { @@ -36,55 +32,10 @@ pub unsafe extern "C" fn afc_client_connect_tcp( return IdeviceErrorCode::InvalidArg; } - let res: Result = RUNTIME.block_on(async move { - let provider_box = unsafe { Box::from_raw(provider) }; - let provider_ref = &provider_box.0; - let result = AfcClient::connect(provider_ref).await; - std::mem::forget(provider_box); - result - }); + let res = RUNTIME.block_on(async { + let provider_ref: &dyn IdeviceProvider = unsafe { &*(*provider).0 }; - match res { - Ok(r) => { - let boxed = Box::new(AfcClientHandle(r)); - unsafe { *client = Box::into_raw(boxed) }; - IdeviceErrorCode::IdeviceSuccess - } - Err(e) => { - let _ = unsafe { Box::from_raw(provider) }; - e.into() - } - } -} - -/// Connects to the AFC service using a Usbmuxd provider -/// -/// # Arguments -/// * [`provider`] - A UsbmuxdProvider -/// * [`client`] - On success, will be set to point to a newly allocated AfcClient handle -/// -/// # Returns -/// An error code indicating success or failure -/// -/// # Safety -/// `provider` must be a valid pointer to a handle allocated by this library -/// `client` must be a valid, non-null pointer to a location where the handle will be stored -#[unsafe(no_mangle)] -pub unsafe extern "C" fn afc_client_connect_usbmuxd( - provider: *mut UsbmuxdProviderHandle, - client: *mut *mut AfcClientHandle, -) -> IdeviceErrorCode { - if provider.is_null() { - log::error!("Provider is null"); - return IdeviceErrorCode::InvalidArg; - } - - let res: Result = RUNTIME.block_on(async move { - let provider_box = unsafe { Box::from_raw(provider) }; - let provider_ref = &provider_box.0; - let result = AfcClient::connect(provider_ref).await; - std::mem::forget(provider_box); - result + AfcClient::connect(provider_ref).await }); match res { @@ -114,7 +65,7 @@ pub unsafe extern "C" fn afc_client_new( socket: *mut IdeviceHandle, client: *mut *mut AfcClientHandle, ) -> IdeviceErrorCode { - if socket.is_null() { + if socket.is_null() || client.is_null() { return IdeviceErrorCode::InvalidArg; } let socket = unsafe { Box::from_raw(socket) }.0; @@ -231,7 +182,7 @@ pub unsafe extern "C" fn afc_make_directory( client: *mut AfcClientHandle, path: *const libc::c_char, ) -> IdeviceErrorCode { - if path.is_null() { + if client.is_null() || path.is_null() { return IdeviceErrorCode::InvalidArg; } @@ -242,11 +193,8 @@ pub unsafe extern "C" fn afc_make_directory( }; let res: Result<(), IdeviceError> = RUNTIME.block_on(async move { - let mut client_box = unsafe { Box::from_raw(client) }; - let client_ref = &mut client_box.0; - let result = client_ref.mk_dir(path).await; - std::mem::forget(client_box); - result + let client_ref = unsafe { &mut (*client).0 }; + client_ref.mk_dir(path).await }); match res { @@ -286,7 +234,7 @@ pub unsafe extern "C" fn afc_get_file_info( path: *const libc::c_char, info: *mut AfcFileInfo, ) -> IdeviceErrorCode { - if path.is_null() || info.is_null() { + if client.is_null() || path.is_null() || info.is_null() { return IdeviceErrorCode::InvalidArg; } @@ -297,11 +245,8 @@ pub unsafe extern "C" fn afc_get_file_info( }; let res: Result = RUNTIME.block_on(async move { - let mut client_box = unsafe { Box::from_raw(client) }; - let client_ref = &mut client_box.0; - let result = client_ref.get_file_info(path).await; - std::mem::forget(client_box); - result + let client_ref = unsafe { &mut (*client).0 }; + client_ref.get_file_info(path).await }); match res { @@ -380,16 +325,13 @@ pub unsafe extern "C" fn afc_get_device_info( client: *mut AfcClientHandle, info: *mut AfcDeviceInfo, ) -> IdeviceErrorCode { - if info.is_null() { + if client.is_null() || info.is_null() { return IdeviceErrorCode::InvalidArg; } let res: Result = RUNTIME.block_on(async move { - let mut client_box = unsafe { Box::from_raw(client) }; - let client_ref = &mut client_box.0; - let result = client_ref.get_device_info().await; - std::mem::forget(client_box); - result + let client_ref = unsafe { &mut (*client).0 }; + client_ref.get_device_info().await }); match res { @@ -441,7 +383,7 @@ pub unsafe extern "C" fn afc_remove_path( client: *mut AfcClientHandle, path: *const libc::c_char, ) -> IdeviceErrorCode { - if path.is_null() { + if client.is_null() || path.is_null() { return IdeviceErrorCode::InvalidArg; } @@ -452,11 +394,8 @@ pub unsafe extern "C" fn afc_remove_path( }; let res: Result<(), IdeviceError> = RUNTIME.block_on(async move { - let mut client_box = unsafe { Box::from_raw(client) }; - let client_ref = &mut client_box.0; - let result = client_ref.remove(path).await; - std::mem::forget(client_box); - result + let client_ref = unsafe { &mut (*client).0 }; + client_ref.remove(path).await }); match res { @@ -482,7 +421,7 @@ pub unsafe extern "C" fn afc_remove_path_and_contents( client: *mut AfcClientHandle, path: *const libc::c_char, ) -> IdeviceErrorCode { - if path.is_null() { + if client.is_null() || path.is_null() { return IdeviceErrorCode::InvalidArg; } @@ -493,11 +432,8 @@ pub unsafe extern "C" fn afc_remove_path_and_contents( }; let res: Result<(), IdeviceError> = RUNTIME.block_on(async move { - let mut client_box = unsafe { Box::from_raw(client) }; - let client_ref = &mut client_box.0; - let result = client_ref.remove_all(path).await; - std::mem::forget(client_box); - result + let client_ref = unsafe { &mut (*client).0 }; + client_ref.remove_all(path).await }); match res { @@ -531,7 +467,7 @@ impl From for idevice::afc::opcode::AfcFopenMode { /// Handle for an open file on the device #[allow(dead_code)] -pub struct AfcFileHandle(*mut c_void); // Opaque pointer +pub struct AfcFileHandle<'a>(Box>); // Opaque pointer /// Opens a file on the device /// @@ -546,7 +482,9 @@ pub struct AfcFileHandle(*mut c_void); // Opaque pointer /// /// # Safety /// All pointers must be valid and non-null -/// `path` must be a valid null-terminated C string +/// `path` must be a valid null-terminated C string. +/// The file handle MAY NOT be used from another thread, and is +/// dependant upon the client it was created by. #[unsafe(no_mangle)] pub unsafe extern "C" fn afc_file_open( client: *mut AfcClientHandle, @@ -554,7 +492,7 @@ pub unsafe extern "C" fn afc_file_open( mode: AfcFopenMode, handle: *mut *mut AfcFileHandle, ) -> IdeviceErrorCode { - if path.is_null() || handle.is_null() { + if client.is_null() || path.is_null() || handle.is_null() { return IdeviceErrorCode::InvalidArg; } @@ -567,18 +505,15 @@ pub unsafe extern "C" fn afc_file_open( let mode = mode.into(); let res: Result<*mut AfcFileHandle, IdeviceError> = RUNTIME.block_on(async move { - let mut client_box = unsafe { Box::from_raw(client) }; - let client_ref = &mut client_box.0; + let client_ref = unsafe { &mut (*client).0 }; let result = client_ref.open(path, mode).await; - let res = match result { + match result { Ok(f) => { let boxed = Box::new(f); Ok(Box::into_raw(boxed) as *mut AfcFileHandle) } Err(e) => Err(e), - }; - std::mem::forget(client_box); - res + } }); match res { @@ -716,7 +651,7 @@ pub unsafe extern "C" fn afc_make_link( source: *const libc::c_char, link_type: AfcLinkType, ) -> IdeviceErrorCode { - if target.is_null() || source.is_null() { + if client.is_null() || target.is_null() || source.is_null() { return IdeviceErrorCode::InvalidArg; } @@ -738,11 +673,8 @@ pub unsafe extern "C" fn afc_make_link( }; let res: Result<(), IdeviceError> = RUNTIME.block_on(async move { - let mut client_box = unsafe { Box::from_raw(client) }; - let client_ref = &mut client_box.0; - let result = client_ref.link(target, source, link_type).await; - std::mem::forget(client_box); - result + let client_ref = unsafe { &mut (*client).0 }; + client_ref.link(target, source, link_type).await }); match res { @@ -770,7 +702,7 @@ pub unsafe extern "C" fn afc_rename_path( source: *const libc::c_char, target: *const libc::c_char, ) -> IdeviceErrorCode { - if source.is_null() || target.is_null() { + if client.is_null() || source.is_null() || target.is_null() { return IdeviceErrorCode::InvalidArg; } @@ -787,11 +719,8 @@ pub unsafe extern "C" fn afc_rename_path( }; let res: Result<(), IdeviceError> = RUNTIME.block_on(async move { - let mut client_box = unsafe { Box::from_raw(client) }; - let client_ref = &mut client_box.0; - let result = client_ref.rename(source, target).await; - std::mem::forget(client_box); - result + let client_ref = unsafe { &mut (*client).0 }; + client_ref.rename(source, target).await }); match res { @@ -799,3 +728,18 @@ pub unsafe extern "C" fn afc_rename_path( Err(e) => e.into(), } } + +/// Frees memory allocated by a file read function allocated by this library +/// +/// # Arguments +/// * [`info`] - Pointer to AfcDeviceInfo struct to free +/// +/// # Safety +/// `info` must be a valid pointer to an AfcDeviceInfo struct previously returned by afc_get_device_info +#[unsafe(no_mangle)] +pub unsafe extern "C" fn afc_file_read_data_free(data: *mut u8, length: libc::size_t) { + if !data.is_null() { + let boxed = unsafe { Box::from_raw(std::slice::from_raw_parts_mut(data, length)) }; + drop(boxed); + } +} diff --git a/ffi/src/amfi.rs b/ffi/src/amfi.rs index 56daf7e..4cd5ce8 100644 --- a/ffi/src/amfi.rs +++ b/ffi/src/amfi.rs @@ -1,18 +1,15 @@ // Jackson Coxson -use idevice::{IdeviceError, IdeviceService, amfi::AmfiClient}; +use idevice::{IdeviceError, IdeviceService, amfi::AmfiClient, provider::IdeviceProvider}; -use crate::{ - IdeviceErrorCode, IdeviceHandle, RUNTIME, - provider::{TcpProviderHandle, UsbmuxdProviderHandle}, -}; +use crate::{IdeviceErrorCode, IdeviceHandle, RUNTIME, provider::IdeviceProviderHandle}; pub struct AmfiClientHandle(pub AmfiClient); /// Automatically creates and connects to AMFI service, returning a client handle /// /// # Arguments -/// * [`provider`] - A TcpProvider +/// * [`provider`] - An IdeviceProvider /// * [`client`] - On success, will be set to point to a newly allocated AmfiClient handle /// /// # Returns @@ -22,8 +19,8 @@ pub struct AmfiClientHandle(pub AmfiClient); /// `provider` must be a valid pointer to a handle allocated by this library /// `client` must be a valid, non-null pointer to a location where the handle will be stored #[unsafe(no_mangle)] -pub unsafe extern "C" fn amfi_connect_tcp( - provider: *mut TcpProviderHandle, +pub unsafe extern "C" fn amfi_connect( + provider: *mut IdeviceProviderHandle, client: *mut *mut AmfiClientHandle, ) -> IdeviceErrorCode { if provider.is_null() || client.is_null() { @@ -32,70 +29,10 @@ pub unsafe extern "C" fn amfi_connect_tcp( } let res: Result = RUNTIME.block_on(async move { - // Take ownership of the provider (without immediately dropping it) - let provider_box = unsafe { Box::from_raw(provider) }; - - // Get a reference to the inner value - let provider_ref = &provider_box.0; + let provider_ref: &dyn IdeviceProvider = unsafe { &*(*provider).0 }; // Connect using the reference - let result = AmfiClient::connect(provider_ref).await; - - // Explicitly keep the provider_box alive until after connect completes - std::mem::forget(provider_box); - result - }); - - match res { - Ok(r) => { - let boxed = Box::new(AmfiClientHandle(r)); - unsafe { *client = Box::into_raw(boxed) }; - IdeviceErrorCode::IdeviceSuccess - } - Err(e) => { - // If connection failed, the provider_box was already forgotten, - // so we need to reconstruct it to avoid leak - let _ = unsafe { Box::from_raw(provider) }; - e.into() - } - } -} - -/// Automatically creates and connects to AMFI service, returning a client handle -/// -/// # Arguments -/// * [`provider`] - A UsbmuxdProvider -/// * [`client`] - On success, will be set to point to a newly allocated AmfiClient handle -/// -/// # Returns -/// An error code indicating success or failure -/// -/// # Safety -/// `provider` must be a valid pointer to a handle allocated by this library -/// `client` must be a valid, non-null pointer to a location where the handle will be stored -#[unsafe(no_mangle)] -pub unsafe extern "C" fn amfi_connect_usbmuxd( - provider: *mut UsbmuxdProviderHandle, - client: *mut *mut AmfiClientHandle, -) -> IdeviceErrorCode { - if provider.is_null() { - log::error!("Provider is null"); - return IdeviceErrorCode::InvalidArg; - } - - let res: Result = RUNTIME.block_on(async move { - // Take ownership of the provider (without immediately dropping it) - let provider_box = unsafe { Box::from_raw(provider) }; - - // Get a reference to the inner value - let provider_ref = &provider_box.0; - - // Connect using the reference - let result = AmfiClient::connect(provider_ref).await; - - // Explicitly keep the provider_box alive until after connect completes - std::mem::forget(provider_box); - result + AmfiClient::connect(provider_ref).await }); match res { @@ -118,16 +55,18 @@ pub unsafe extern "C" fn amfi_connect_usbmuxd( /// An error code indicating success or failure /// /// # Safety -/// `socket` must be a valid pointer to a handle allocated by this library +/// `socket` must be a valid pointer to a handle allocated by this library. It is consumed, and +/// should not be used again. /// `client` must be a valid, non-null pointer to a location where the handle will be stored #[unsafe(no_mangle)] pub unsafe extern "C" fn amfi_new( socket: *mut IdeviceHandle, client: *mut *mut AmfiClientHandle, ) -> IdeviceErrorCode { - if socket.is_null() { + if socket.is_null() || client.is_null() { return IdeviceErrorCode::InvalidArg; } + let socket = unsafe { Box::from_raw(socket) }.0; let r = AmfiClient::new(socket); let boxed = Box::new(AmfiClientHandle(r)); @@ -149,16 +88,13 @@ pub unsafe extern "C" fn amfi_new( pub unsafe extern "C" fn amfi_reveal_developer_mode_option_in_ui( client: *mut AmfiClientHandle, ) -> IdeviceErrorCode { + if client.is_null() { + return IdeviceErrorCode::InvalidArg; + } + let res: Result<(), IdeviceError> = RUNTIME.block_on(async move { - // Take ownership of the client - let mut client_box = unsafe { Box::from_raw(client) }; - - // Get a reference to the inner value - let client_ref = &mut client_box.0; - let res = client_ref.reveal_developer_mode_option_in_ui().await; - - std::mem::forget(client_box); - res + let client_ref = unsafe { &mut (*client).0 }; + client_ref.reveal_developer_mode_option_in_ui().await }); match res { Ok(_) => IdeviceErrorCode::IdeviceSuccess, @@ -180,16 +116,13 @@ pub unsafe extern "C" fn amfi_reveal_developer_mode_option_in_ui( pub unsafe extern "C" fn amfi_enable_developer_mode( client: *mut AmfiClientHandle, ) -> IdeviceErrorCode { + if client.is_null() { + return IdeviceErrorCode::InvalidArg; + } + let res: Result<(), IdeviceError> = RUNTIME.block_on(async move { - // Take ownership of the client - let mut client_box = unsafe { Box::from_raw(client) }; - - // Get a reference to the inner value - let client_ref = &mut client_box.0; - let res = client_ref.enable_developer_mode().await; - - std::mem::forget(client_box); - res + let client_ref = unsafe { &mut (*client).0 }; + client_ref.enable_developer_mode().await }); match res { Ok(_) => IdeviceErrorCode::IdeviceSuccess, @@ -211,16 +144,13 @@ pub unsafe extern "C" fn amfi_enable_developer_mode( pub unsafe extern "C" fn amfi_accept_developer_mode( client: *mut AmfiClientHandle, ) -> IdeviceErrorCode { + if client.is_null() { + return IdeviceErrorCode::InvalidArg; + } + let res: Result<(), IdeviceError> = RUNTIME.block_on(async move { - // Take ownership of the client - let mut client_box = unsafe { Box::from_raw(client) }; - - // Get a reference to the inner value - let client_ref = &mut client_box.0; - let res = client_ref.accept_developer_mode().await; - - std::mem::forget(client_box); - res + let client_ref = unsafe { &mut (*client).0 }; + client_ref.accept_developer_mode().await }); match res { Ok(_) => IdeviceErrorCode::IdeviceSuccess, diff --git a/ffi/src/core_device_proxy.rs b/ffi/src/core_device_proxy.rs index 124980d..4e027bd 100644 --- a/ffi/src/core_device_proxy.rs +++ b/ffi/src/core_device_proxy.rs @@ -3,13 +3,11 @@ use std::ffi::{CString, c_char}; use idevice::{ - IdeviceError, IdeviceService, core_device_proxy::CoreDeviceProxy, tcp::adapter::Adapter, + IdeviceError, IdeviceService, core_device_proxy::CoreDeviceProxy, provider::IdeviceProvider, + tcp::adapter::Adapter, }; -use crate::{ - IdeviceErrorCode, IdeviceHandle, RUNTIME, - provider::{TcpProviderHandle, UsbmuxdProviderHandle}, -}; +use crate::{IdeviceErrorCode, IdeviceHandle, RUNTIME, provider::IdeviceProviderHandle}; pub struct CoreDeviceProxyHandle(pub CoreDeviceProxy); pub struct AdapterHandle(pub Adapter); @@ -17,7 +15,7 @@ pub struct AdapterHandle(pub Adapter); /// Automatically creates and connects to Core Device Proxy, returning a client handle /// /// # Arguments -/// * [`provider`] - A TcpProvider +/// * [`provider`] - An IdeviceProvider /// * [`client`] - On success, will be set to point to a newly allocated CoreDeviceProxy handle /// /// # Returns @@ -27,8 +25,8 @@ pub struct AdapterHandle(pub Adapter); /// `provider` must be a valid pointer to a handle allocated by this library /// `client` must be a valid, non-null pointer to a location where the handle will be stored #[unsafe(no_mangle)] -pub unsafe extern "C" fn core_device_proxy_connect_tcp( - provider: *mut TcpProviderHandle, +pub unsafe extern "C" fn core_device_proxy_connect( + provider: *mut IdeviceProviderHandle, client: *mut *mut CoreDeviceProxyHandle, ) -> IdeviceErrorCode { if provider.is_null() || client.is_null() { @@ -37,70 +35,10 @@ pub unsafe extern "C" fn core_device_proxy_connect_tcp( } let res: Result = RUNTIME.block_on(async move { - // Take ownership of the provider (without immediately dropping it) - let provider_box = unsafe { Box::from_raw(provider) }; - - // Get a reference to the inner value - let provider_ref = &provider_box.0; + let provider_ref: &dyn IdeviceProvider = unsafe { &*(*provider).0 }; // Connect using the reference - let result = CoreDeviceProxy::connect(provider_ref).await; - - // Explicitly keep the provider_box alive until after connect completes - std::mem::forget(provider_box); - result - }); - - match res { - Ok(r) => { - let boxed = Box::new(CoreDeviceProxyHandle(r)); - unsafe { *client = Box::into_raw(boxed) }; - IdeviceErrorCode::IdeviceSuccess - } - Err(e) => { - // If connection failed, the provider_box was already forgotten, - // so we need to reconstruct it to avoid leak - let _ = unsafe { Box::from_raw(provider) }; - e.into() - } - } -} - -/// Automatically creates and connects to Core Device Proxy, returning a client handle -/// -/// # Arguments -/// * [`provider`] - A UsbmuxdProvider -/// * [`client`] - On success, will be set to point to a newly allocated CoreDeviceProxy handle -/// -/// # Returns -/// An error code indicating success or failure -/// -/// # Safety -/// `provider` must be a valid pointer to a handle allocated by this library -/// `client` must be a valid, non-null pointer to a location where the handle will be stored -#[unsafe(no_mangle)] -pub unsafe extern "C" fn core_device_proxy_connect_usbmuxd( - provider: *mut UsbmuxdProviderHandle, - client: *mut *mut CoreDeviceProxyHandle, -) -> IdeviceErrorCode { - if provider.is_null() { - log::error!("Provider is null"); - return IdeviceErrorCode::InvalidArg; - } - - let res: Result = RUNTIME.block_on(async move { - // Take ownership of the provider (without immediately dropping it) - let provider_box = unsafe { Box::from_raw(provider) }; - - // Get a reference to the inner value - let provider_ref = &provider_box.0; - - // Connect using the reference - let result = CoreDeviceProxy::connect(provider_ref).await; - - // Explicitly keep the provider_box alive until after connect completes - std::mem::forget(provider_box); - result + CoreDeviceProxy::connect(provider_ref).await }); match res { @@ -123,14 +61,15 @@ pub unsafe extern "C" fn core_device_proxy_connect_usbmuxd( /// An error code indicating success or failure /// /// # Safety -/// `socket` must be a valid pointer to a handle allocated by this library +/// `socket` must be a valid pointer to a handle allocated by this library. It is consumed and +/// may not be used again. /// `client` must be a valid, non-null pointer to a location where the handle will be stored #[unsafe(no_mangle)] pub unsafe extern "C" fn core_device_proxy_new( socket: *mut IdeviceHandle, client: *mut *mut CoreDeviceProxyHandle, ) -> IdeviceErrorCode { - if socket.is_null() { + if socket.is_null() || client.is_null() { return IdeviceErrorCode::InvalidArg; } let socket = unsafe { Box::from_raw(socket) }.0; @@ -233,8 +172,8 @@ pub unsafe extern "C" fn core_device_proxy_recv( /// # Arguments /// * [`handle`] - The CoreDeviceProxy handle /// * [`mtu`] - Pointer to store the MTU value -/// * [`address`] - Pointer to store the IP address string (must be at least 16 bytes) -/// * [`netmask`] - Pointer to store the netmask string (must be at least 16 bytes) +/// * [`address`] - Pointer to store the IP address string +/// * [`netmask`] - Pointer to store the netmask string /// /// # Returns /// An error code indicating success or failure @@ -262,9 +201,21 @@ pub unsafe extern "C" fn core_device_proxy_get_client_parameters( *mtu = params.mtu; } + // Allocate both strings, but handle partial failure + let address_cstring = match CString::new(params.address.clone()) { + Ok(s) => s, + Err(_) => return IdeviceErrorCode::InvalidString, + }; + + let netmask_cstring = match CString::new(params.netmask.clone()) { + Ok(s) => s, + Err(_) => return IdeviceErrorCode::InvalidString, + }; + + // Only assign to output pointers after both succeed unsafe { - *address = CString::new(params.address.clone()).unwrap().into_raw(); - *netmask = CString::new(params.netmask.clone()).unwrap().into_raw(); + *address = address_cstring.into_raw(); + *netmask = netmask_cstring.into_raw(); } IdeviceErrorCode::IdeviceSuccess @@ -274,7 +225,7 @@ pub unsafe extern "C" fn core_device_proxy_get_client_parameters( /// /// # Arguments /// * [`handle`] - The CoreDeviceProxy handle -/// * [`address`] - Pointer to store the server address string (must be at least 16 bytes) +/// * [`address`] - Pointer to store the server address string /// /// # Returns /// An error code indicating success or failure @@ -294,9 +245,10 @@ pub unsafe extern "C" fn core_device_proxy_get_server_address( let proxy = unsafe { &(*handle).0 }; unsafe { - *address = CString::new(proxy.handshake.server_address.clone()) - .unwrap() - .into_raw(); + *address = match CString::new(proxy.handshake.server_address.clone()) { + Ok(s) => s.into_raw(), + Err(_) => return IdeviceErrorCode::InvalidString, + }; } IdeviceErrorCode::IdeviceSuccess diff --git a/ffi/src/debug_proxy.rs b/ffi/src/debug_proxy.rs index b3334ff..800c4dd 100644 --- a/ffi/src/debug_proxy.rs +++ b/ffi/src/debug_proxy.rs @@ -4,14 +4,13 @@ use std::ffi::{CStr, CString, c_char}; use std::os::raw::c_int; use std::ptr; +use idevice::ReadWrite; use idevice::debug_proxy::{DebugProxyClient, DebugserverCommand}; -use idevice::tcp::adapter::Adapter; -use crate::core_device_proxy::AdapterHandle; use crate::{IdeviceErrorCode, RUNTIME}; /// Opaque handle to a DebugProxyClient -pub struct DebugProxyAdapterHandle(pub DebugProxyClient); +pub struct DebugProxyHandle(pub DebugProxyClient>); /// Represents a debugserver command #[repr(C)] @@ -56,16 +55,26 @@ pub unsafe extern "C" fn debugserver_command_new( let argv_len = argv_vec.len(); let boxed = Box::new(DebugserverCommandHandle { - name: CString::new(name).unwrap().into_raw(), + name: match CString::new(name) { + Ok(n) => n.into_raw(), + Err(_) => return ptr::null_mut(), + }, argv: if argv_vec.is_empty() { ptr::null_mut() } else { - let mut argv_ptrs: Vec<*mut c_char> = argv_vec + let argv_ptrs: Result, _> = argv_vec .into_iter() - .map(|s| CString::new(s).unwrap().into_raw()) + .map(|s| CString::new(s).map(|cs| cs.into_raw())) .collect(); + + let mut argv_ptrs = match argv_ptrs { + Ok(ptrs) => ptrs, + Err(_) => return ptr::null_mut(), + }; argv_ptrs.shrink_to_fit(); - argv_ptrs.as_mut_ptr() + let ptr = argv_ptrs.as_mut_ptr(); + std::mem::forget(argv_ptrs); + ptr }, argv_count: argv_len, }); @@ -106,7 +115,7 @@ pub unsafe extern "C" fn debugserver_command_free(command: *mut DebugserverComma /// Creates a new DebugProxyClient /// /// # Arguments -/// * [`socket`] - The socket to use for communication +/// * [`socket`] - The socket to use for communication. Any object that supports ReadWrite. /// * [`handle`] - Pointer to store the newly created DebugProxyClient handle /// /// # Returns @@ -116,19 +125,19 @@ pub unsafe extern "C" fn debugserver_command_free(command: *mut DebugserverComma /// `socket` must be a valid pointer to a handle allocated by this library /// `handle` must be a valid pointer to a location where the handle will be stored #[unsafe(no_mangle)] -pub unsafe extern "C" fn debug_proxy_adapter_new( - socket: *mut AdapterHandle, - handle: *mut *mut DebugProxyAdapterHandle, +pub unsafe extern "C" fn debug_proxy_new( + socket: *mut Box, + handle: *mut *mut DebugProxyHandle, ) -> IdeviceErrorCode { if socket.is_null() || handle.is_null() { return IdeviceErrorCode::InvalidArg; } let socket = unsafe { Box::from_raw(socket) }; - let client = DebugProxyClient::new(socket.0); + let client = DebugProxyClient::new(*socket); + let new_handle = DebugProxyHandle(client); - let boxed = Box::new(DebugProxyAdapterHandle(client)); - unsafe { *handle = Box::into_raw(boxed) }; + unsafe { *handle = Box::into_raw(Box::new(new_handle)) }; IdeviceErrorCode::IdeviceSuccess } @@ -140,7 +149,7 @@ pub unsafe extern "C" fn debug_proxy_adapter_new( /// # Safety /// `handle` must be a valid pointer to a handle allocated by this library or NULL #[unsafe(no_mangle)] -pub unsafe extern "C" fn debug_proxy_free(handle: *mut DebugProxyAdapterHandle) { +pub unsafe extern "C" fn debug_proxy_free(handle: *mut DebugProxyHandle) { if !handle.is_null() { let _ = unsafe { Box::from_raw(handle) }; } @@ -161,7 +170,7 @@ pub unsafe extern "C" fn debug_proxy_free(handle: *mut DebugProxyAdapterHandle) /// `response` must be a valid pointer to a location where the string will be stored #[unsafe(no_mangle)] pub unsafe extern "C" fn debug_proxy_send_command( - handle: *mut DebugProxyAdapterHandle, + handle: *mut DebugProxyHandle, command: *mut DebugserverCommandHandle, response: *mut *mut c_char, ) -> IdeviceErrorCode { @@ -192,7 +201,10 @@ pub unsafe extern "C" fn debug_proxy_send_command( match res { Ok(Some(r)) => { - let cstr = CString::new(r).unwrap(); + let cstr = match CString::new(r) { + Ok(c) => c, + Err(_) => return IdeviceErrorCode::InvalidString, + }; unsafe { *response = cstr.into_raw() }; IdeviceErrorCode::IdeviceSuccess } @@ -218,7 +230,7 @@ pub unsafe extern "C" fn debug_proxy_send_command( /// `response` must be a valid pointer to a location where the string will be stored #[unsafe(no_mangle)] pub unsafe extern "C" fn debug_proxy_read_response( - handle: *mut DebugProxyAdapterHandle, + handle: *mut DebugProxyHandle, response: *mut *mut c_char, ) -> IdeviceErrorCode { if handle.is_null() || response.is_null() { @@ -230,7 +242,10 @@ pub unsafe extern "C" fn debug_proxy_read_response( match res { Ok(Some(r)) => { - let cstr = CString::new(r).unwrap(); + let cstr = match CString::new(r) { + Ok(c) => c, + Err(_) => return IdeviceErrorCode::InvalidString, + }; unsafe { *response = cstr.into_raw() }; IdeviceErrorCode::IdeviceSuccess } @@ -257,7 +272,7 @@ pub unsafe extern "C" fn debug_proxy_read_response( /// `data` must be a valid pointer to `len` bytes #[unsafe(no_mangle)] pub unsafe extern "C" fn debug_proxy_send_raw( - handle: *mut DebugProxyAdapterHandle, + handle: *mut DebugProxyHandle, data: *const u8, len: usize, ) -> IdeviceErrorCode { @@ -290,7 +305,7 @@ pub unsafe extern "C" fn debug_proxy_send_raw( /// `response` must be a valid pointer to a location where the string will be stored #[unsafe(no_mangle)] pub unsafe extern "C" fn debug_proxy_read( - handle: *mut DebugProxyAdapterHandle, + handle: *mut DebugProxyHandle, len: usize, response: *mut *mut c_char, ) -> IdeviceErrorCode { @@ -303,7 +318,10 @@ pub unsafe extern "C" fn debug_proxy_read( match res { Ok(r) => { - let cstr = CString::new(r).unwrap(); + let cstr = match CString::new(r) { + Ok(c) => c, + Err(_) => return IdeviceErrorCode::InvalidString, + }; unsafe { *response = cstr.into_raw() }; IdeviceErrorCode::IdeviceSuccess } @@ -328,7 +346,7 @@ pub unsafe extern "C" fn debug_proxy_read( /// `response` must be a valid pointer to a location where the string will be stored #[unsafe(no_mangle)] pub unsafe extern "C" fn debug_proxy_set_argv( - handle: *mut DebugProxyAdapterHandle, + handle: *mut DebugProxyHandle, argv: *const *const c_char, argv_count: usize, response: *mut *mut c_char, @@ -358,7 +376,10 @@ pub unsafe extern "C" fn debug_proxy_set_argv( match res { Ok(r) => { - let cstr = CString::new(r).unwrap(); + let cstr = match CString::new(r) { + Ok(c) => c, + Err(_) => return IdeviceErrorCode::InvalidString, + }; unsafe { *response = cstr.into_raw() }; IdeviceErrorCode::IdeviceSuccess } @@ -377,9 +398,7 @@ pub unsafe extern "C" fn debug_proxy_set_argv( /// # Safety /// `handle` must be a valid pointer #[unsafe(no_mangle)] -pub unsafe extern "C" fn debug_proxy_send_ack( - handle: *mut DebugProxyAdapterHandle, -) -> IdeviceErrorCode { +pub unsafe extern "C" fn debug_proxy_send_ack(handle: *mut DebugProxyHandle) -> IdeviceErrorCode { if handle.is_null() { return IdeviceErrorCode::InvalidArg; } @@ -404,9 +423,7 @@ pub unsafe extern "C" fn debug_proxy_send_ack( /// # Safety /// `handle` must be a valid pointer #[unsafe(no_mangle)] -pub unsafe extern "C" fn debug_proxy_send_nack( - handle: *mut DebugProxyAdapterHandle, -) -> IdeviceErrorCode { +pub unsafe extern "C" fn debug_proxy_send_nack(handle: *mut DebugProxyHandle) -> IdeviceErrorCode { if handle.is_null() { return IdeviceErrorCode::InvalidArg; } @@ -429,39 +446,9 @@ pub unsafe extern "C" fn debug_proxy_send_nack( /// # Safety /// `handle` must be a valid pointer #[unsafe(no_mangle)] -pub unsafe extern "C" fn debug_proxy_set_ack_mode( - handle: *mut DebugProxyAdapterHandle, - enabled: c_int, -) { +pub unsafe extern "C" fn debug_proxy_set_ack_mode(handle: *mut DebugProxyHandle, enabled: c_int) { if !handle.is_null() { let client = unsafe { &mut (*handle).0 }; client.set_ack_mode(enabled != 0); } } - -/// Returns the underlying socket from a DebugProxyClient -/// -/// # Arguments -/// * [`handle`] - The handle to get the socket from -/// * [`adapter`] - The newly allocated ConnectionHandle -/// -/// # Returns -/// An error code indicating success or failure -/// -/// # Safety -/// `handle` must be a valid pointer to a handle allocated by this library or NULL, and never used again -#[unsafe(no_mangle)] -pub unsafe extern "C" fn debug_proxy_adapter_into_inner( - handle: *mut DebugProxyAdapterHandle, - adapter: *mut *mut AdapterHandle, -) -> IdeviceErrorCode { - if handle.is_null() { - return IdeviceErrorCode::InvalidArg; - } - - let client = unsafe { Box::from_raw(handle) }; - let socket_obj = client.0.into_inner(); - let boxed = Box::new(AdapterHandle(socket_obj)); - unsafe { *adapter = Box::into_raw(boxed) }; - IdeviceErrorCode::IdeviceSuccess -} diff --git a/ffi/src/errors.rs b/ffi/src/errors.rs index 53a44c3..375430f 100644 --- a/ffi/src/errors.rs +++ b/ffi/src/errors.rs @@ -33,7 +33,6 @@ pub enum IdeviceErrorCode { ImageNotMounted = -25, Reqwest = -26, InternalError = -27, - Xpc = -28, NsKeyedArchiveError = -29, UnknownAuxValueType = -30, UnknownChannel = -31, @@ -50,9 +49,24 @@ pub enum IdeviceErrorCode { UnknownAfcOpcode = -42, InvalidAfcMagic = -43, AfcMissingAttribute = -44, + ServiceNotFound = -45, + PairingDialogResponsePending = -46, + UserDeniedPairing = -47, + PasswordProtected = -48, + CrashReportMoverBadResponse = -49, + UnknownFrame = -50, + UnknownHttpSetting = -51, + UninitializedStreamId = -52, + UnknownXpcType = -53, + MalformedXpc = -54, + InvalidXpcMagic = -55, + UnexpectedXpcVersion = -56, + InvalidCString = -57, + HttpStreamReset = -58, + HttpGoAway = -59, + // FFI specific bindings AdapterIOFailed = -996, - ServiceNotFound = -997, BufferTooSmall = -998, InvalidString = -999, InvalidArg = -1000, @@ -90,7 +104,6 @@ impl From for IdeviceErrorCode { IdeviceError::ImageNotMounted => IdeviceErrorCode::ImageNotMounted, IdeviceError::Reqwest(_) => IdeviceErrorCode::Reqwest, IdeviceError::InternalError(_) => IdeviceErrorCode::InternalError, - IdeviceError::Xpc(_) => IdeviceErrorCode::Xpc, IdeviceError::NsKeyedArchiveError(_) => IdeviceErrorCode::NsKeyedArchiveError, IdeviceError::UnknownAuxValueType(_) => IdeviceErrorCode::UnknownAuxValueType, IdeviceError::UnknownChannel(_) => IdeviceErrorCode::UnknownChannel, @@ -109,6 +122,25 @@ impl From for IdeviceErrorCode { IdeviceError::UnknownAfcOpcode => IdeviceErrorCode::UnknownAfcOpcode, IdeviceError::InvalidAfcMagic => IdeviceErrorCode::InvalidAfcMagic, IdeviceError::AfcMissingAttribute => IdeviceErrorCode::AfcMissingAttribute, + IdeviceError::ServiceNotFound => IdeviceErrorCode::ServiceNotFound, + IdeviceError::PairingDialogResponsePending => { + IdeviceErrorCode::PairingDialogResponsePending + } + IdeviceError::UserDeniedPairing => IdeviceErrorCode::UserDeniedPairing, + IdeviceError::PasswordProtected => IdeviceErrorCode::PasswordProtected, + IdeviceError::CrashReportMoverBadResponse(_) => { + IdeviceErrorCode::CrashReportMoverBadResponse + } + IdeviceError::UnknownFrame(_) => IdeviceErrorCode::UnknownFrame, + IdeviceError::UnknownHttpSetting(_) => IdeviceErrorCode::UnknownHttpSetting, + IdeviceError::UninitializedStreamId => IdeviceErrorCode::UninitializedStreamId, + IdeviceError::UnknownXpcType(_) => IdeviceErrorCode::UnknownXpcType, + IdeviceError::MalformedXpc => IdeviceErrorCode::MalformedXpc, + IdeviceError::InvalidXpcMagic => IdeviceErrorCode::InvalidXpcMagic, + IdeviceError::UnexpectedXpcVersion => IdeviceErrorCode::UnexpectedXpcVersion, + IdeviceError::InvalidCString => IdeviceErrorCode::InvalidCString, + IdeviceError::HttpStreamReset => IdeviceErrorCode::HttpStreamReset, + IdeviceError::HttpGoAway(_) => IdeviceErrorCode::HttpGoAway, _ => IdeviceErrorCode::InternalError, } } diff --git a/ffi/src/heartbeat.rs b/ffi/src/heartbeat.rs index 822722f..6b30261 100644 --- a/ffi/src/heartbeat.rs +++ b/ffi/src/heartbeat.rs @@ -1,20 +1,17 @@ // Jackson Coxson -use idevice::{IdeviceError, IdeviceService, heartbeat::HeartbeatClient}; - -use crate::{ - IdeviceErrorCode, IdeviceHandle, RUNTIME, - provider::{TcpProviderHandle, UsbmuxdProviderHandle}, +use idevice::{ + IdeviceError, IdeviceService, heartbeat::HeartbeatClient, provider::IdeviceProvider, }; +use crate::{IdeviceErrorCode, IdeviceHandle, RUNTIME, provider::IdeviceProviderHandle}; + pub struct HeartbeatClientHandle(pub HeartbeatClient); -#[allow(non_camel_case_types)] -pub struct plist_t; /// Automatically creates and connects to Installation Proxy, returning a client handle /// /// # Arguments -/// * [`provider`] - A TcpProvider +/// * [`provider`] - An IdeviceProvider /// * [`client`] - On success, will be set to point to a newly allocated InstallationProxyClient handle /// /// # Returns @@ -24,8 +21,8 @@ pub struct plist_t; /// `provider` must be a valid pointer to a handle allocated by this library /// `client` must be a valid, non-null pointer to a location where the handle will be stored #[unsafe(no_mangle)] -pub unsafe extern "C" fn heartbeat_connect_tcp( - provider: *mut TcpProviderHandle, +pub unsafe extern "C" fn heartbeat_connect( + provider: *mut IdeviceProviderHandle, client: *mut *mut HeartbeatClientHandle, ) -> IdeviceErrorCode { if provider.is_null() || client.is_null() { @@ -34,18 +31,9 @@ pub unsafe extern "C" fn heartbeat_connect_tcp( } let res: Result = RUNTIME.block_on(async move { - // Take ownership of the provider (without immediately dropping it) - let provider_box = unsafe { Box::from_raw(provider) }; - - // Get a reference to the inner value - let provider_ref = &provider_box.0; - + let provider_ref: &dyn IdeviceProvider = unsafe { &*(*provider).0 }; // Connect using the reference - let result = HeartbeatClient::connect(provider_ref).await; - - // Explicitly keep the provider_box alive until after connect completes - std::mem::forget(provider_box); - result + HeartbeatClient::connect(provider_ref).await }); match res { @@ -63,53 +51,6 @@ pub unsafe extern "C" fn heartbeat_connect_tcp( } } -/// Automatically creates and connects to Installation Proxy, returning a client handle -/// -/// # Arguments -/// * [`provider`] - A UsbmuxdProvider -/// * [`client`] - On success, will be set to point to a newly allocated InstallationProxyClient handle -/// -/// # Returns -/// An error code indicating success or failure -/// -/// # Safety -/// `provider` must be a valid pointer to a handle allocated by this library -/// `client` must be a valid, non-null pointer to a location where the handle will be stored -#[unsafe(no_mangle)] -pub unsafe extern "C" fn heartbeat_connect_usbmuxd( - provider: *mut UsbmuxdProviderHandle, - client: *mut *mut HeartbeatClientHandle, -) -> IdeviceErrorCode { - if provider.is_null() { - log::error!("Provider is null"); - return IdeviceErrorCode::InvalidArg; - } - - let res: Result = RUNTIME.block_on(async move { - // Take ownership of the provider (without immediately dropping it) - let provider_box = unsafe { Box::from_raw(provider) }; - - // Get a reference to the inner value - let provider_ref = &provider_box.0; - - // Connect using the reference - let result = HeartbeatClient::connect(provider_ref).await; - - // Explicitly keep the provider_box alive until after connect completes - std::mem::forget(provider_box); - result - }); - - match res { - Ok(r) => { - let boxed = Box::new(HeartbeatClientHandle(r)); - unsafe { *client = Box::into_raw(boxed) }; - IdeviceErrorCode::IdeviceSuccess - } - Err(e) => e.into(), - } -} - /// Automatically creates and connects to Installation Proxy, returning a client handle /// /// # Arguments @@ -120,14 +61,15 @@ pub unsafe extern "C" fn heartbeat_connect_usbmuxd( /// An error code indicating success or failure /// /// # Safety -/// `socket` must be a valid pointer to a handle allocated by this library +/// `socket` must be a valid pointer to a handle allocated by this library. The socket is consumed, +/// and may not be used again. /// `client` must be a valid, non-null pointer to a location where the handle will be stored #[unsafe(no_mangle)] pub unsafe extern "C" fn heartbeat_new( socket: *mut IdeviceHandle, client: *mut *mut HeartbeatClientHandle, ) -> IdeviceErrorCode { - if socket.is_null() { + if socket.is_null() || client.is_null() { return IdeviceErrorCode::InvalidArg; } let socket = unsafe { Box::from_raw(socket) }.0; @@ -151,16 +93,12 @@ pub unsafe extern "C" fn heartbeat_new( pub unsafe extern "C" fn heartbeat_send_polo( client: *mut HeartbeatClientHandle, ) -> IdeviceErrorCode { + if client.is_null() { + return IdeviceErrorCode::InvalidArg; + } let res: Result<(), IdeviceError> = RUNTIME.block_on(async move { - // Take ownership of the client - let mut client_box = unsafe { Box::from_raw(client) }; - - // Get a reference to the inner value - let client_ref = &mut client_box.0; - let res = client_ref.send_polo().await; - - std::mem::forget(client_box); - res + let client_ref = unsafe { &mut (*client).0 }; + client_ref.send_polo().await }); match res { Ok(_) => IdeviceErrorCode::IdeviceSuccess, @@ -186,16 +124,12 @@ pub unsafe extern "C" fn heartbeat_get_marco( interval: u64, new_interval: *mut u64, ) -> IdeviceErrorCode { + if client.is_null() || new_interval.is_null() { + return IdeviceErrorCode::InvalidArg; + } let res: Result = RUNTIME.block_on(async move { - // Take ownership of the client - let mut client_box = unsafe { Box::from_raw(client) }; - - // Get a reference to the inner value - let client_ref = &mut client_box.0; - let new = client_ref.get_marco(interval).await; - - std::mem::forget(client_box); - new + let client_ref = unsafe { &mut (*client).0 }; + client_ref.get_marco(interval).await }); match res { Ok(n) => { diff --git a/ffi/src/installation_proxy.rs b/ffi/src/installation_proxy.rs index 108aa82..42c50dc 100644 --- a/ffi/src/installation_proxy.rs +++ b/ffi/src/installation_proxy.rs @@ -2,20 +2,19 @@ use std::ffi::c_void; -use idevice::{IdeviceError, IdeviceService, installation_proxy::InstallationProxyClient}; - -use crate::{ - IdeviceErrorCode, IdeviceHandle, RUNTIME, - provider::{TcpProviderHandle, UsbmuxdProviderHandle}, - util, +use idevice::{ + IdeviceError, IdeviceService, installation_proxy::InstallationProxyClient, + provider::IdeviceProvider, }; +use crate::{IdeviceErrorCode, IdeviceHandle, RUNTIME, provider::IdeviceProviderHandle, util}; + pub struct InstallationProxyClientHandle(pub InstallationProxyClient); /// Automatically creates and connects to Installation Proxy, returning a client handle /// /// # Arguments -/// * [`provider`] - A TcpProvider +/// * [`provider`] - An IdeviceProvider /// * [`client`] - On success, will be set to point to a newly allocated InstallationProxyClient handle /// /// # Returns @@ -26,7 +25,7 @@ pub struct InstallationProxyClientHandle(pub InstallationProxyClient); /// `client` must be a valid, non-null pointer to a location where the handle will be stored #[unsafe(no_mangle)] pub unsafe extern "C" fn installation_proxy_connect_tcp( - provider: *mut TcpProviderHandle, + provider: *mut IdeviceProviderHandle, client: *mut *mut InstallationProxyClientHandle, ) -> IdeviceErrorCode { if provider.is_null() || client.is_null() { @@ -35,70 +34,8 @@ pub unsafe extern "C" fn installation_proxy_connect_tcp( } let res: Result = RUNTIME.block_on(async move { - // Take ownership of the provider (without immediately dropping it) - let provider_box = unsafe { Box::from_raw(provider) }; - - // Get a reference to the inner value - let provider_ref = &provider_box.0; - - // Connect using the reference - let result = InstallationProxyClient::connect(provider_ref).await; - - // Explicitly keep the provider_box alive until after connect completes - std::mem::forget(provider_box); - result - }); - - match res { - Ok(r) => { - let boxed = Box::new(InstallationProxyClientHandle(r)); - unsafe { *client = Box::into_raw(boxed) }; - IdeviceErrorCode::IdeviceSuccess - } - Err(e) => { - // If connection failed, the provider_box was already forgotten, - // so we need to reconstruct it to avoid leak - let _ = unsafe { Box::from_raw(provider) }; - e.into() - } - } -} - -/// Automatically creates and connects to Installation Proxy, returning a client handle -/// -/// # Arguments -/// * [`provider`] - A UsbmuxdProvider -/// * [`client`] - On success, will be set to point to a newly allocated InstallationProxyClient handle -/// -/// # Returns -/// An error code indicating success or failure -/// -/// # Safety -/// `provider` must be a valid pointer to a handle allocated by this library -/// `client` must be a valid, non-null pointer to a location where the handle will be stored -#[unsafe(no_mangle)] -pub unsafe extern "C" fn installation_proxy_connect_usbmuxd( - provider: *mut UsbmuxdProviderHandle, - client: *mut *mut InstallationProxyClientHandle, -) -> IdeviceErrorCode { - if provider.is_null() { - log::error!("Provider is null"); - return IdeviceErrorCode::InvalidArg; - } - - let res: Result = RUNTIME.block_on(async move { - // Take ownership of the provider (without immediately dropping it) - let provider_box = unsafe { Box::from_raw(provider) }; - - // Get a reference to the inner value - let provider_ref = &provider_box.0; - - // Connect using the reference - let result = InstallationProxyClient::connect(provider_ref).await; - - // Explicitly keep the provider_box alive until after connect completes - std::mem::forget(provider_box); - result + let provider_ref: &dyn IdeviceProvider = unsafe { &*(*provider).0 }; + InstallationProxyClient::connect(provider_ref).await }); match res { @@ -121,14 +58,15 @@ pub unsafe extern "C" fn installation_proxy_connect_usbmuxd( /// An error code indicating success or failure /// /// # Safety -/// `socket` must be a valid pointer to a handle allocated by this library +/// `socket` must be a valid pointer to a handle allocated by this library. The socket is consumed, +/// and may not be used again. /// `client` must be a valid, non-null pointer to a location where the handle will be stored #[unsafe(no_mangle)] pub unsafe extern "C" fn installation_proxy_new( socket: *mut IdeviceHandle, client: *mut *mut InstallationProxyClientHandle, ) -> IdeviceErrorCode { - if socket.is_null() { + if socket.is_null() || client.is_null() { return IdeviceErrorCode::InvalidArg; } let socket = unsafe { Box::from_raw(socket) }.0; @@ -201,11 +139,10 @@ pub unsafe extern "C" fn installation_proxy_get_apps( }); match res { - Ok(r) => { + Ok(mut r) => { + let ptr = r.as_mut_ptr(); let len = r.len(); - let boxed_slice = r.into_boxed_slice(); - let ptr = boxed_slice.as_ptr(); - std::mem::forget(boxed_slice); + std::mem::forget(r); unsafe { *out_result = ptr as *mut c_void; @@ -637,11 +574,10 @@ pub unsafe extern "C" fn installation_proxy_browse( }); match res { - Ok(r) => { + Ok(mut r) => { + let ptr = r.as_mut_ptr(); let len = r.len(); - let boxed_slice = r.into_boxed_slice(); - let ptr = boxed_slice.as_ptr(); - std::mem::forget(boxed_slice); + std::mem::forget(r); unsafe { *out_result = ptr as *mut c_void; diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index f850e44..abb86bb 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -1,25 +1,41 @@ // Jackson Coxson +#[cfg(feature = "tunnel_tcp_stack")] pub mod adapter; +#[cfg(feature = "afc")] pub mod afc; +#[cfg(feature = "amfi")] pub mod amfi; +#[cfg(feature = "core_device_proxy")] pub mod core_device_proxy; +#[cfg(feature = "debug_proxy")] pub mod debug_proxy; mod errors; +#[cfg(feature = "heartbeat")] pub mod heartbeat; +#[cfg(feature = "installation_proxy")] pub mod installation_proxy; +#[cfg(feature = "location_simulation")] pub mod location_simulation; pub mod lockdown; pub mod logging; +#[cfg(feature = "misagent")] pub mod misagent; -pub mod mounter; +#[cfg(feature = "mobile_image_mounter")] +pub mod mobile_image_mounter; mod pairing_file; +#[cfg(feature = "dvt")] pub mod process_control; pub mod provider; +#[cfg(feature = "dvt")] pub mod remote_server; -pub mod remotexpc; -pub mod sbservices; +#[cfg(feature = "xpc")] +pub mod rsd; +#[cfg(feature = "springboardservices")] +pub mod springboardservices; +#[cfg(feature = "syslog_relay")] pub mod syslog_relay; +#[cfg(feature = "usbmuxd")] pub mod usbmuxd; pub mod util; diff --git a/ffi/src/location_simulation.rs b/ffi/src/location_simulation.rs index 7e21c7c..e4ca9fe 100644 --- a/ffi/src/location_simulation.rs +++ b/ffi/src/location_simulation.rs @@ -1,11 +1,11 @@ // Jackson Coxson -use idevice::{dvt::location_simulation::LocationSimulationClient, tcp::adapter::Adapter}; +use idevice::{ReadWrite, dvt::location_simulation::LocationSimulationClient}; -use crate::{IdeviceErrorCode, RUNTIME, remote_server::RemoteServerAdapterHandle}; +use crate::{IdeviceErrorCode, RUNTIME, remote_server::RemoteServerHandle}; /// Opaque handle to a ProcessControlClient -pub struct LocationSimulationAdapterHandle<'a>(pub LocationSimulationClient<'a, Adapter>); +pub struct LocationSimulationHandle<'a>(pub LocationSimulationClient<'a, Box>); /// Creates a new ProcessControlClient from a RemoteServerClient /// @@ -21,8 +21,8 @@ pub struct LocationSimulationAdapterHandle<'a>(pub LocationSimulationClient<'a, /// `handle` must be a valid pointer to a location where the handle will be stored #[unsafe(no_mangle)] pub unsafe extern "C" fn location_simulation_new( - server: *mut RemoteServerAdapterHandle, - handle: *mut *mut LocationSimulationAdapterHandle<'static>, + server: *mut RemoteServerHandle, + handle: *mut *mut LocationSimulationHandle<'static>, ) -> IdeviceErrorCode { if server.is_null() || handle.is_null() { return IdeviceErrorCode::InvalidArg; @@ -33,7 +33,7 @@ pub unsafe extern "C" fn location_simulation_new( match res { Ok(client) => { - let boxed = Box::new(LocationSimulationAdapterHandle(client)); + let boxed = Box::new(LocationSimulationHandle(client)); unsafe { *handle = Box::into_raw(boxed) }; IdeviceErrorCode::IdeviceSuccess } @@ -49,9 +49,7 @@ pub unsafe extern "C" fn location_simulation_new( /// # Safety /// `handle` must be a valid pointer to a handle allocated by this library or NULL #[unsafe(no_mangle)] -pub unsafe extern "C" fn location_simulation_free( - handle: *mut LocationSimulationAdapterHandle<'static>, -) { +pub unsafe extern "C" fn location_simulation_free(handle: *mut LocationSimulationHandle<'static>) { if !handle.is_null() { let _ = unsafe { Box::from_raw(handle) }; } @@ -69,7 +67,7 @@ pub unsafe extern "C" fn location_simulation_free( /// All pointers must be valid or NULL where appropriate #[unsafe(no_mangle)] pub unsafe extern "C" fn location_simulation_clear( - handle: *mut LocationSimulationAdapterHandle<'static>, + handle: *mut LocationSimulationHandle<'static>, ) -> IdeviceErrorCode { if handle.is_null() { return IdeviceErrorCode::InvalidArg; @@ -98,7 +96,7 @@ pub unsafe extern "C" fn location_simulation_clear( /// All pointers must be valid or NULL where appropriate #[unsafe(no_mangle)] pub unsafe extern "C" fn location_simulation_set( - handle: *mut LocationSimulationAdapterHandle<'static>, + handle: *mut LocationSimulationHandle<'static>, latitude: f64, longitude: f64, ) -> IdeviceErrorCode { diff --git a/ffi/src/lockdown.rs b/ffi/src/lockdown.rs index 1034394..66aefe9 100644 --- a/ffi/src/lockdown.rs +++ b/ffi/src/lockdown.rs @@ -2,11 +2,10 @@ use std::ffi::c_void; -use idevice::{IdeviceError, IdeviceService, lockdown::LockdownClient}; +use idevice::{IdeviceError, IdeviceService, lockdown::LockdownClient, provider::IdeviceProvider}; use crate::{ - IdeviceErrorCode, IdeviceHandle, IdevicePairingFile, RUNTIME, - provider::{TcpProviderHandle, UsbmuxdProviderHandle}, + IdeviceErrorCode, IdeviceHandle, IdevicePairingFile, RUNTIME, provider::IdeviceProviderHandle, }; pub struct LockdowndClientHandle(pub LockdownClient); @@ -14,7 +13,7 @@ pub struct LockdowndClientHandle(pub LockdownClient); /// Connects to lockdownd service using TCP provider /// /// # Arguments -/// * [`provider`] - A TcpProvider +/// * [`provider`] - An IdeviceProvider /// * [`client`] - On success, will be set to point to a newly allocated LockdowndClient handle /// /// # Returns @@ -24,8 +23,8 @@ pub struct LockdowndClientHandle(pub LockdownClient); /// `provider` must be a valid pointer to a handle allocated by this library /// `client` must be a valid, non-null pointer to a location where the handle will be stored #[unsafe(no_mangle)] -pub unsafe extern "C" fn lockdownd_connect_tcp( - provider: *mut TcpProviderHandle, +pub unsafe extern "C" fn lockdownd_connect( + provider: *mut IdeviceProviderHandle, client: *mut *mut LockdowndClientHandle, ) -> IdeviceErrorCode { if provider.is_null() || client.is_null() { @@ -34,11 +33,8 @@ pub unsafe extern "C" fn lockdownd_connect_tcp( } let res: Result = RUNTIME.block_on(async move { - let provider_box = unsafe { Box::from_raw(provider) }; - let provider_ref = &provider_box.0; - let result = LockdownClient::connect(provider_ref).await; - std::mem::forget(provider_box); - result + let provider_ref: &dyn IdeviceProvider = unsafe { &*(*provider).0 }; + LockdownClient::connect(provider_ref).await }); match res { @@ -54,57 +50,18 @@ pub unsafe extern "C" fn lockdownd_connect_tcp( } } -/// Connects to lockdownd service using Usbmuxd provider -/// -/// # Arguments -/// * [`provider`] - A UsbmuxdProvider -/// * [`client`] - On success, will be set to point to a newly allocated LockdowndClient handle -/// -/// # Returns -/// An error code indicating success or failure -/// -/// # Safety -/// `provider` must be a valid pointer to a handle allocated by this library -/// `client` must be a valid, non-null pointer to a location where the handle will be stored -#[unsafe(no_mangle)] -pub unsafe extern "C" fn lockdownd_connect_usbmuxd( - provider: *mut UsbmuxdProviderHandle, - client: *mut *mut LockdowndClientHandle, -) -> IdeviceErrorCode { - if provider.is_null() || client.is_null() { - log::error!("Null pointer provided"); - return IdeviceErrorCode::InvalidArg; - } - - let res: Result = RUNTIME.block_on(async move { - let provider_box = unsafe { Box::from_raw(provider) }; - let provider_ref = &provider_box.0; - let result = LockdownClient::connect(provider_ref).await; - std::mem::forget(provider_box); - result - }); - - match res { - Ok(r) => { - let boxed = Box::new(LockdowndClientHandle(r)); - unsafe { *client = Box::into_raw(boxed) }; - IdeviceErrorCode::IdeviceSuccess - } - Err(e) => e.into(), - } -} - /// Creates a new LockdowndClient from an existing Idevice connection /// /// # Arguments -/// * [`socket`] - An IdeviceSocket handle +/// * [`socket`] - An IdeviceSocket handle. /// * [`client`] - On success, will be set to point to a newly allocated LockdowndClient handle /// /// # Returns /// An error code indicating success or failure /// /// # Safety -/// `socket` must be a valid pointer to a handle allocated by this library +/// `socket` must be a valid pointer to a handle allocated by this library. The socket is consumed, +/// and maybe not be used again. /// `client` must be a valid, non-null pointer to a location where the handle will be stored #[unsafe(no_mangle)] pub unsafe extern "C" fn lockdownd_new( @@ -139,15 +96,10 @@ pub unsafe extern "C" fn lockdownd_start_session( pairing_file: *mut IdevicePairingFile, ) -> IdeviceErrorCode { let res: Result<(), IdeviceError> = RUNTIME.block_on(async move { - let mut client_box = unsafe { Box::from_raw(client) }; - let pairing_file = unsafe { Box::from_raw(pairing_file) }; + let client_ref = unsafe { &mut (*client).0 }; + let pairing_file_ref = unsafe { &(*pairing_file).0 }; - let client_ref = &mut client_box.0; - let res = client_ref.start_session(&pairing_file.0).await; - - std::mem::forget(client_box); - std::mem::forget(pairing_file); - res + client_ref.start_session(pairing_file_ref).await }); match res { @@ -187,11 +139,8 @@ pub unsafe extern "C" fn lockdownd_start_service( .into_owned(); let res: Result<(u16, bool), IdeviceError> = RUNTIME.block_on(async move { - let mut client_box = unsafe { Box::from_raw(client) }; - let client_ref = &mut client_box.0; - let res = client_ref.start_service(identifier).await; - std::mem::forget(client_box); - res + let client_ref = unsafe { &mut (*client).0 }; + client_ref.start_service(identifier).await }); match res { @@ -247,11 +196,8 @@ pub unsafe extern "C" fn lockdownd_get_value( }; let res: Result = RUNTIME.block_on(async move { - let mut client_box = unsafe { Box::from_raw(client) }; - let client_ref = &mut client_box.0; - let res = client_ref.get_value(value, domain).await; - std::mem::forget(client_box); - res + let client_ref = unsafe { &mut (*client).0 }; + client_ref.get_value(value, domain).await }); match res { @@ -287,11 +233,8 @@ pub unsafe extern "C" fn lockdownd_get_all_values( } let res: Result = RUNTIME.block_on(async move { - let mut client_box = unsafe { Box::from_raw(client) }; - let client_ref = &mut client_box.0; - let res = client_ref.get_all_values().await; - std::mem::forget(client_box); - res + let client_ref = unsafe { &mut (*client).0 }; + client_ref.get_all_values().await }); match res { diff --git a/ffi/src/misagent.rs b/ffi/src/misagent.rs index 70c9bac..704d58b 100644 --- a/ffi/src/misagent.rs +++ b/ffi/src/misagent.rs @@ -2,19 +2,16 @@ //! //! Provides C-compatible bindings for interacting with the misagent service on iOS devices. -use idevice::{IdeviceError, IdeviceService, misagent::MisagentClient}; +use idevice::{IdeviceError, IdeviceService, misagent::MisagentClient, provider::IdeviceProvider}; -use crate::{ - IdeviceErrorCode, RUNTIME, - provider::{TcpProviderHandle, UsbmuxdProviderHandle}, -}; +use crate::{IdeviceErrorCode, RUNTIME, provider::IdeviceProviderHandle}; pub struct MisagentClientHandle(pub MisagentClient); /// Automatically creates and connects to Misagent, returning a client handle /// /// # Arguments -/// * [`provider`] - A TcpProvider +/// * [`provider`] - An IdeviceProvider /// * [`client`] - On success, will be set to point to a newly allocated MisagentClient handle /// /// # Returns @@ -24,8 +21,8 @@ pub struct MisagentClientHandle(pub MisagentClient); /// `provider` must be a valid pointer to a handle allocated by this library /// `client` must be a valid, non-null pointer to a location where the handle will be stored #[unsafe(no_mangle)] -pub unsafe extern "C" fn misagent_connect_tcp( - provider: *mut TcpProviderHandle, +pub unsafe extern "C" fn misagent_connect( + provider: *mut IdeviceProviderHandle, client: *mut *mut MisagentClientHandle, ) -> IdeviceErrorCode { if provider.is_null() || client.is_null() { @@ -34,70 +31,8 @@ pub unsafe extern "C" fn misagent_connect_tcp( } let res: Result = RUNTIME.block_on(async move { - // Take ownership of the provider (without immediately dropping it) - let provider_box = unsafe { Box::from_raw(provider) }; - - // Get a reference to the inner value - let provider_ref = &provider_box.0; - - // Connect using the reference - let result = MisagentClient::connect(provider_ref).await; - - // Explicitly keep the provider_box alive until after connect completes - std::mem::forget(provider_box); - result - }); - - match res { - Ok(r) => { - let boxed = Box::new(MisagentClientHandle(r)); - unsafe { *client = Box::into_raw(boxed) }; - IdeviceErrorCode::IdeviceSuccess - } - Err(e) => { - // If connection failed, the provider_box was already forgotten, - // so we need to reconstruct it to avoid leak - let _ = unsafe { Box::from_raw(provider) }; - e.into() - } - } -} - -/// Automatically creates and connects to Misagent, returning a client handle -/// -/// # Arguments -/// * [`provider`] - A UsbmuxdProvider -/// * [`client`] - On success, will be set to point to a newly allocated MisagentClient handle -/// -/// # Returns -/// An error code indicating success or failure -/// -/// # Safety -/// `provider` must be a valid pointer to a handle allocated by this library -/// `client` must be a valid, non-null pointer to a location where the handle will be stored -#[unsafe(no_mangle)] -pub unsafe extern "C" fn misagent_connect_usbmuxd( - provider: *mut UsbmuxdProviderHandle, - client: *mut *mut MisagentClientHandle, -) -> IdeviceErrorCode { - if provider.is_null() { - log::error!("Provider is null"); - return IdeviceErrorCode::InvalidArg; - } - - let res: Result = RUNTIME.block_on(async move { - // Take ownership of the provider (without immediately dropping it) - let provider_box = unsafe { Box::from_raw(provider) }; - - // Get a reference to the inner value - let provider_ref = &provider_box.0; - - // Connect using the reference - let result = MisagentClient::connect(provider_ref).await; - - // Explicitly keep the provider_box alive until after connect completes - std::mem::forget(provider_box); - result + let provider_ref: &dyn IdeviceProvider = unsafe { &*(*provider).0 }; + MisagentClient::connect(provider_ref).await }); match res { diff --git a/ffi/src/mounter.rs b/ffi/src/mobile_image_mounter.rs similarity index 67% rename from ffi/src/mounter.rs rename to ffi/src/mobile_image_mounter.rs index 4100418..c20839c 100644 --- a/ffi/src/mounter.rs +++ b/ffi/src/mobile_image_mounter.rs @@ -2,21 +2,19 @@ use std::ffi::c_void; -use idevice::{IdeviceError, IdeviceService, mobile_image_mounter::ImageMounter}; +use idevice::{ + IdeviceError, IdeviceService, mobile_image_mounter::ImageMounter, provider::IdeviceProvider, +}; use plist::Value; -use crate::{ - IdeviceErrorCode, IdeviceHandle, RUNTIME, - provider::{TcpProviderHandle, UsbmuxdProviderHandle}, - util, -}; +use crate::{IdeviceErrorCode, IdeviceHandle, RUNTIME, provider::IdeviceProviderHandle, util}; pub struct ImageMounterHandle(pub ImageMounter); -/// Connects to the Image Mounter service using a TCP provider +/// Connects to the Image Mounter service using a provider /// /// # Arguments -/// * [`provider`] - A TcpProvider +/// * [`provider`] - An IdeviceProvider /// * [`client`] - On success, will be set to point to a newly allocated ImageMounter handle /// /// # Returns @@ -26,8 +24,8 @@ pub struct ImageMounterHandle(pub ImageMounter); /// `provider` must be a valid pointer to a handle allocated by this library /// `client` must be a valid, non-null pointer to a location where the handle will be stored #[unsafe(no_mangle)] -pub unsafe extern "C" fn image_mounter_connect_tcp( - provider: *mut TcpProviderHandle, +pub unsafe extern "C" fn image_mounter_connect( + provider: *mut IdeviceProviderHandle, client: *mut *mut ImageMounterHandle, ) -> IdeviceErrorCode { if provider.is_null() || client.is_null() { @@ -36,11 +34,8 @@ pub unsafe extern "C" fn image_mounter_connect_tcp( } let res: Result = RUNTIME.block_on(async move { - let provider_box = unsafe { Box::from_raw(provider) }; - let provider_ref = &provider_box.0; - let result = ImageMounter::connect(provider_ref).await; - std::mem::forget(provider_box); - result + let provider_ref: &dyn IdeviceProvider = unsafe { &*(*provider).0 }; + ImageMounter::connect(provider_ref).await }); match res { @@ -56,46 +51,6 @@ pub unsafe extern "C" fn image_mounter_connect_tcp( } } -/// Connects to the Image Mounter service using a Usbmuxd provider -/// -/// # Arguments -/// * [`provider`] - A UsbmuxdProvider -/// * [`client`] - On success, will be set to point to a newly allocated ImageMounter handle -/// -/// # Returns -/// An error code indicating success or failure -/// -/// # Safety -/// `provider` must be a valid pointer to a handle allocated by this library -/// `client` must be a valid, non-null pointer to a location where the handle will be stored -#[unsafe(no_mangle)] -pub unsafe extern "C" fn image_mounter_connect_usbmuxd( - provider: *mut UsbmuxdProviderHandle, - client: *mut *mut ImageMounterHandle, -) -> IdeviceErrorCode { - if provider.is_null() { - log::error!("Provider is null"); - return IdeviceErrorCode::InvalidArg; - } - - let res: Result = RUNTIME.block_on(async move { - let provider_box = unsafe { Box::from_raw(provider) }; - let provider_ref = &provider_box.0; - let result = ImageMounter::connect(provider_ref).await; - std::mem::forget(provider_box); - result - }); - - match res { - Ok(r) => { - let boxed = Box::new(ImageMounterHandle(r)); - unsafe { *client = Box::into_raw(boxed) }; - IdeviceErrorCode::IdeviceSuccess - } - Err(e) => e.into(), - } -} - /// Creates a new ImageMounter client from an existing Idevice connection /// /// # Arguments @@ -159,11 +114,8 @@ pub unsafe extern "C" fn image_mounter_copy_devices( devices_len: *mut libc::size_t, ) -> IdeviceErrorCode { let res: Result, IdeviceError> = RUNTIME.block_on(async move { - let mut client_box = unsafe { Box::from_raw(client) }; - let client_ref = &mut client_box.0; - let result = client_ref.copy_devices().await; - std::mem::forget(client_box); - result + let client_ref = unsafe { &mut (*client).0 }; + client_ref.copy_devices().await }); match res { @@ -219,11 +171,8 @@ pub unsafe extern "C" fn image_mounter_lookup_image( }; let res: Result, IdeviceError> = RUNTIME.block_on(async move { - let mut client_box = unsafe { Box::from_raw(client) }; - let client_ref = &mut client_box.0; - let result = client_ref.lookup_image(image_type).await; - std::mem::forget(client_box); - result + let client_ref = unsafe { &mut (*client).0 }; + client_ref.lookup_image(image_type).await }); match res { @@ -279,13 +228,10 @@ pub unsafe extern "C" fn image_mounter_upload_image( let signature_slice = unsafe { std::slice::from_raw_parts(signature, signature_len) }; let res: Result<(), IdeviceError> = RUNTIME.block_on(async move { - let mut client_box = unsafe { Box::from_raw(client) }; - let client_ref = &mut client_box.0; - let result = client_ref + let client_ref = unsafe { &mut (*client).0 }; + client_ref .upload_image(image_type, image_slice, signature_slice.to_vec()) - .await; - std::mem::forget(client_box); - result + .await }); match res { @@ -349,18 +295,15 @@ pub unsafe extern "C" fn image_mounter_mount_image( }; let res: Result<(), IdeviceError> = RUNTIME.block_on(async move { - let mut client_box = unsafe { Box::from_raw(client) }; - let client_ref = &mut client_box.0; - let result = client_ref + let client_ref = unsafe { &mut (*client).0 }; + client_ref .mount_image( image_type, signature_slice.to_vec(), trust_cache, info_plist, ) - .await; - std::mem::forget(client_box); - result + .await }); match res { @@ -397,11 +340,8 @@ pub unsafe extern "C" fn image_mounter_unmount_image( }; let res: Result<(), IdeviceError> = RUNTIME.block_on(async move { - let mut client_box = unsafe { Box::from_raw(client) }; - let client_ref = &mut client_box.0; - let result = client_ref.unmount_image(mount_path).await; - std::mem::forget(client_box); - result + let client_ref = unsafe { &mut (*client).0 }; + client_ref.unmount_image(mount_path).await }); match res { @@ -432,11 +372,8 @@ pub unsafe extern "C" fn image_mounter_query_developer_mode_status( } let res: Result = RUNTIME.block_on(async move { - let mut client_box = unsafe { Box::from_raw(client) }; - let client_ref = &mut client_box.0; - let result = client_ref.query_developer_mode_status().await; - std::mem::forget(client_box); - result + let client_ref = unsafe { &mut (*client).0 }; + client_ref.query_developer_mode_status().await }); match res { @@ -478,13 +415,10 @@ pub unsafe extern "C" fn image_mounter_mount_developer( let signature_slice = unsafe { std::slice::from_raw_parts(signature, signature_len) }; let res: Result<(), IdeviceError> = RUNTIME.block_on(async move { - let mut client_box = unsafe { Box::from_raw(client) }; - let client_ref = &mut client_box.0; - let result = client_ref + let client_ref = unsafe { &mut (*client).0 }; + client_ref .mount_developer(image_slice, signature_slice.to_vec()) - .await; - std::mem::forget(client_box); - result + .await }); match res { @@ -531,13 +465,10 @@ pub unsafe extern "C" fn image_mounter_query_personalization_manifest( let signature_slice = unsafe { std::slice::from_raw_parts(signature, signature_len) }; let res: Result, IdeviceError> = RUNTIME.block_on(async move { - let mut client_box = unsafe { Box::from_raw(client) }; - let client_ref = &mut client_box.0; - let result = client_ref + let client_ref = unsafe { &mut (*client).0 }; + client_ref .query_personalization_manifest(image_type, signature_slice.to_vec()) - .await; - std::mem::forget(client_box); - result + .await }); match res { @@ -590,11 +521,8 @@ pub unsafe extern "C" fn image_mounter_query_nonce( }; let res: Result, IdeviceError> = RUNTIME.block_on(async move { - let mut client_box = unsafe { Box::from_raw(client) }; - let client_ref = &mut client_box.0; - let result = client_ref.query_nonce(image_type).await; - std::mem::forget(client_box); - result + let client_ref = unsafe { &mut (*client).0 }; + client_ref.query_nonce(image_type).await }); match res { @@ -645,13 +573,10 @@ pub unsafe extern "C" fn image_mounter_query_personalization_identifiers( }; let res: Result = RUNTIME.block_on(async move { - let mut client_box = unsafe { Box::from_raw(client) }; - let client_ref = &mut client_box.0; - let result = client_ref + let client_ref = unsafe { &mut (*client).0 }; + client_ref .query_personalization_identifiers(image_type) - .await; - std::mem::forget(client_box); - result + .await }); match res { @@ -679,11 +604,8 @@ pub unsafe extern "C" fn image_mounter_roll_personalization_nonce( client: *mut ImageMounterHandle, ) -> IdeviceErrorCode { let res: Result<(), IdeviceError> = RUNTIME.block_on(async move { - let mut client_box = unsafe { Box::from_raw(client) }; - let client_ref = &mut client_box.0; - let result = client_ref.roll_personalization_nonce().await; - std::mem::forget(client_box); - result + let client_ref = unsafe { &mut (*client).0 }; + client_ref.roll_personalization_nonce().await }); match res { @@ -707,11 +629,8 @@ pub unsafe extern "C" fn image_mounter_roll_cryptex_nonce( client: *mut ImageMounterHandle, ) -> IdeviceErrorCode { let res: Result<(), IdeviceError> = RUNTIME.block_on(async move { - let mut client_box = unsafe { Box::from_raw(client) }; - let client_ref = &mut client_box.0; - let result = client_ref.roll_cryptex_nonce().await; - std::mem::forget(client_box); - result + let client_ref = unsafe { &mut (*client).0 }; + client_ref.roll_cryptex_nonce().await }); match res { @@ -739,10 +658,11 @@ pub unsafe extern "C" fn image_mounter_roll_cryptex_nonce( /// /// # Safety /// All pointers must be valid (except optional ones which can be null) +#[cfg(feature = "tss")] #[unsafe(no_mangle)] -pub unsafe extern "C" fn image_mounter_mount_personalized_usbmuxd( +pub unsafe extern "C" fn image_mounter_mount_personalized( client: *mut ImageMounterHandle, - provider: *mut UsbmuxdProviderHandle, + provider: *mut IdeviceProviderHandle, image: *const u8, image_len: libc::size_t, trust_cache: *const u8, @@ -772,11 +692,9 @@ pub unsafe extern "C" fn image_mounter_mount_personalized_usbmuxd( }; let res: Result<(), IdeviceError> = RUNTIME.block_on(async move { - let mut client_box = unsafe { Box::from_raw(client) }; - let provider_box = unsafe { Box::from_raw(provider) }; - let client_ref = &mut client_box.0; - let provider_ref = &provider_box.0; - let result = client_ref + let client_ref = unsafe { &mut (*client).0 }; + let provider_ref: &dyn IdeviceProvider = unsafe { &*(*provider).0 }; + client_ref .mount_personalized( provider_ref, image_slice.to_vec(), @@ -785,87 +703,7 @@ pub unsafe extern "C" fn image_mounter_mount_personalized_usbmuxd( info_plist, unique_chip_id, ) - .await; - std::mem::forget(client_box); - std::mem::forget(provider_box); - result - }); - - match res { - Ok(_) => IdeviceErrorCode::IdeviceSuccess, - Err(e) => e.into(), - } -} - -/// Mounts a personalized developer image -/// -/// # Arguments -/// * [`client`] - A valid ImageMounter handle -/// * [`provider`] - A valid provider handle -/// * [`image`] - Pointer to the image data -/// * [`image_len`] - Length of the image data -/// * [`trust_cache`] - Pointer to the trust cache data -/// * [`trust_cache_len`] - Length of the trust cache data -/// * [`build_manifest`] - Pointer to the build manifest data -/// * [`build_manifest_len`] - Length of the build manifest data -/// * [`info_plist`] - Pointer to info plist (optional) -/// * [`unique_chip_id`] - The device's unique chip ID -/// -/// # Returns -/// An error code indicating success or failure -/// -/// # Safety -/// All pointers must be valid (except optional ones which can be null) -#[unsafe(no_mangle)] -pub unsafe extern "C" fn image_mounter_mount_personalized_tcp( - client: *mut ImageMounterHandle, - provider: *mut TcpProviderHandle, - image: *const u8, - image_len: libc::size_t, - trust_cache: *const u8, - trust_cache_len: libc::size_t, - build_manifest: *const u8, - build_manifest_len: libc::size_t, - info_plist: *const c_void, - unique_chip_id: u64, -) -> IdeviceErrorCode { - if provider.is_null() || image.is_null() || trust_cache.is_null() || build_manifest.is_null() { - return IdeviceErrorCode::InvalidArg; - } - - let image_slice = unsafe { std::slice::from_raw_parts(image, image_len) }; - let trust_cache_slice = unsafe { std::slice::from_raw_parts(trust_cache, trust_cache_len) }; - let build_manifest_slice = - unsafe { std::slice::from_raw_parts(build_manifest, build_manifest_len) }; - - let info_plist = if !info_plist.is_null() { - Some( - unsafe { Box::from_raw(info_plist as *mut Value) } - .as_ref() - .clone(), - ) - } else { - None - }; - - let res: Result<(), IdeviceError> = RUNTIME.block_on(async move { - let mut client_box = unsafe { Box::from_raw(client) }; - let provider_box = unsafe { Box::from_raw(provider) }; - let client_ref = &mut client_box.0; - let provider_ref = &provider_box.0; - let result = client_ref - .mount_personalized( - provider_ref, - image_slice.to_vec(), - trust_cache_slice.to_vec(), - build_manifest_slice, - info_plist, - unique_chip_id, - ) - .await; - std::mem::forget(client_box); - std::mem::forget(provider_box); - result + .await }); match res { @@ -895,10 +733,11 @@ pub unsafe extern "C" fn image_mounter_mount_personalized_tcp( /// /// # Safety /// All pointers must be valid (except optional ones which can be null) +#[cfg(feature = "tss")] #[unsafe(no_mangle)] -pub unsafe extern "C" fn image_mounter_mount_personalized_usbmuxd_with_callback( +pub unsafe extern "C" fn image_mounter_mount_personalized_with_callback( client: *mut ImageMounterHandle, - provider: *mut UsbmuxdProviderHandle, + provider: *mut IdeviceProviderHandle, image: *const u8, image_len: libc::size_t, trust_cache: *const u8, @@ -930,16 +769,14 @@ pub unsafe extern "C" fn image_mounter_mount_personalized_usbmuxd_with_callback( }; let res: Result<(), IdeviceError> = RUNTIME.block_on(async move { - let mut client_box = unsafe { Box::from_raw(client) }; - let provider_box = unsafe { Box::from_raw(provider) }; - let client_ref = &mut client_box.0; - let provider_ref = &provider_box.0; + let client_ref = unsafe { &mut (*client).0 }; + let provider_ref: &dyn IdeviceProvider = unsafe { &*(*provider).0 }; let callback_wrapper = |((progress, total), context)| async move { callback(progress, total, context); }; - let result = client_ref + client_ref .mount_personalized_with_callback( provider_ref, image_slice.to_vec(), @@ -950,98 +787,7 @@ pub unsafe extern "C" fn image_mounter_mount_personalized_usbmuxd_with_callback( callback_wrapper, context, ) - .await; - std::mem::forget(client_box); - std::mem::forget(provider_box); - result - }); - - match res { - Ok(_) => IdeviceErrorCode::IdeviceSuccess, - Err(e) => e.into(), - } -} - -/// Mounts a personalized developer image with progress callback -/// -/// # Arguments -/// * [`client`] - A valid ImageMounter handle -/// * [`provider`] - A valid provider handle -/// * [`image`] - Pointer to the image data -/// * [`image_len`] - Length of the image data -/// * [`trust_cache`] - Pointer to the trust cache data -/// * [`trust_cache_len`] - Length of the trust cache data -/// * [`build_manifest`] - Pointer to the build manifest data -/// * [`build_manifest_len`] - Length of the build manifest data -/// * [`info_plist`] - Pointer to info plist (optional) -/// * [`unique_chip_id`] - The device's unique chip ID -/// * [`callback`] - Progress callback function -/// * [`context`] - User context to pass to callback -/// -/// # Returns -/// An error code indicating success or failure -/// -/// # Safety -/// All pointers must be valid (except optional ones which can be null) -#[unsafe(no_mangle)] -pub unsafe extern "C" fn image_mounter_mount_personalized_tcp_with_callback( - client: *mut ImageMounterHandle, - provider: *mut TcpProviderHandle, - image: *const u8, - image_len: libc::size_t, - trust_cache: *const u8, - trust_cache_len: libc::size_t, - build_manifest: *const u8, - build_manifest_len: libc::size_t, - info_plist: *const c_void, - unique_chip_id: u64, - callback: extern "C" fn(progress: libc::size_t, total: libc::size_t, context: *mut c_void), - context: *mut c_void, -) -> IdeviceErrorCode { - if provider.is_null() || image.is_null() || trust_cache.is_null() || build_manifest.is_null() { - return IdeviceErrorCode::InvalidArg; - } - - let image_slice = unsafe { std::slice::from_raw_parts(image, image_len) }; - let trust_cache_slice = unsafe { std::slice::from_raw_parts(trust_cache, trust_cache_len) }; - let build_manifest_slice = - unsafe { std::slice::from_raw_parts(build_manifest, build_manifest_len) }; - - let info_plist = if !info_plist.is_null() { - Some( - unsafe { Box::from_raw(info_plist as *mut Value) } - .as_ref() - .clone(), - ) - } else { - None - }; - - let res: Result<(), IdeviceError> = RUNTIME.block_on(async move { - let mut client_box = unsafe { Box::from_raw(client) }; - let provider_box = unsafe { Box::from_raw(provider) }; - let client_ref = &mut client_box.0; - let provider_ref = &provider_box.0; - - let callback_wrapper = |((progress, total), context)| async move { - callback(progress, total, context); - }; - - let result = client_ref - .mount_personalized_with_callback( - provider_ref, - image_slice.to_vec(), - trust_cache_slice.to_vec(), - build_manifest_slice, - info_plist, - unique_chip_id, - callback_wrapper, - context, - ) - .await; - std::mem::forget(client_box); - std::mem::forget(provider_box); - result + .await }); match res { diff --git a/ffi/src/process_control.rs b/ffi/src/process_control.rs index d8eae35..ef69ad0 100644 --- a/ffi/src/process_control.rs +++ b/ffi/src/process_control.rs @@ -2,13 +2,13 @@ use std::ffi::{CStr, c_char}; -use idevice::{dvt::process_control::ProcessControlClient, tcp::adapter::Adapter}; +use idevice::{ReadWrite, dvt::process_control::ProcessControlClient}; use plist::{Dictionary, Value}; -use crate::{IdeviceErrorCode, RUNTIME, remote_server::RemoteServerAdapterHandle}; +use crate::{IdeviceErrorCode, RUNTIME, remote_server::RemoteServerHandle}; /// Opaque handle to a ProcessControlClient -pub struct ProcessControlAdapterHandle<'a>(pub ProcessControlClient<'a, Adapter>); +pub struct ProcessControlHandle<'a>(pub ProcessControlClient<'a, Box>); /// Creates a new ProcessControlClient from a RemoteServerClient /// @@ -24,8 +24,8 @@ pub struct ProcessControlAdapterHandle<'a>(pub ProcessControlClient<'a, Adapter> /// `handle` must be a valid pointer to a location where the handle will be stored #[unsafe(no_mangle)] pub unsafe extern "C" fn process_control_new( - server: *mut RemoteServerAdapterHandle, - handle: *mut *mut ProcessControlAdapterHandle<'static>, + server: *mut RemoteServerHandle, + handle: *mut *mut ProcessControlHandle<'static>, ) -> IdeviceErrorCode { if server.is_null() || handle.is_null() { return IdeviceErrorCode::InvalidArg; @@ -36,7 +36,7 @@ pub unsafe extern "C" fn process_control_new( match res { Ok(client) => { - let boxed = Box::new(ProcessControlAdapterHandle(client)); + let boxed = Box::new(ProcessControlHandle(client)); unsafe { *handle = Box::into_raw(boxed) }; IdeviceErrorCode::IdeviceSuccess } @@ -52,7 +52,7 @@ pub unsafe extern "C" fn process_control_new( /// # Safety /// `handle` must be a valid pointer to a handle allocated by this library or NULL #[unsafe(no_mangle)] -pub unsafe extern "C" fn process_control_free(handle: *mut ProcessControlAdapterHandle<'static>) { +pub unsafe extern "C" fn process_control_free(handle: *mut ProcessControlHandle<'static>) { if !handle.is_null() { let _ = unsafe { Box::from_raw(handle) }; } @@ -76,7 +76,7 @@ pub unsafe extern "C" fn process_control_free(handle: *mut ProcessControlAdapter /// All pointers must be valid or NULL where appropriate #[unsafe(no_mangle)] pub unsafe extern "C" fn process_control_launch_app( - handle: *mut ProcessControlAdapterHandle<'static>, + handle: *mut ProcessControlHandle<'static>, bundle_id: *const c_char, env_vars: *const *const c_char, env_vars_count: usize, @@ -159,7 +159,7 @@ pub unsafe extern "C" fn process_control_launch_app( /// `handle` must be a valid pointer to a handle allocated by this library #[unsafe(no_mangle)] pub unsafe extern "C" fn process_control_kill_app( - handle: *mut ProcessControlAdapterHandle<'static>, + handle: *mut ProcessControlHandle<'static>, pid: u64, ) -> IdeviceErrorCode { if handle.is_null() { @@ -188,7 +188,7 @@ pub unsafe extern "C" fn process_control_kill_app( /// `handle` must be a valid pointer to a handle allocated by this library #[unsafe(no_mangle)] pub unsafe extern "C" fn process_control_disable_memory_limit( - handle: *mut ProcessControlAdapterHandle<'static>, + handle: *mut ProcessControlHandle<'static>, pid: u64, ) -> IdeviceErrorCode { if handle.is_null() { diff --git a/ffi/src/provider.rs b/ffi/src/provider.rs index 19dacac..0fe624b 100644 --- a/ffi/src/provider.rs +++ b/ffi/src/provider.rs @@ -1,13 +1,12 @@ // Jackson Coxson -use idevice::provider::{TcpProvider, UsbmuxdProvider}; +use idevice::provider::{IdeviceProvider, TcpProvider, UsbmuxdProvider}; use std::ffi::CStr; use std::os::raw::c_char; use crate::{IdeviceErrorCode, usbmuxd::UsbmuxdAddrHandle, util}; -pub struct TcpProviderHandle(pub TcpProvider); -pub struct UsbmuxdProviderHandle(pub UsbmuxdProvider); +pub struct IdeviceProviderHandle(pub Box); /// Creates a TCP provider for idevice /// @@ -25,13 +24,18 @@ pub struct UsbmuxdProviderHandle(pub UsbmuxdProvider); /// `pairing_file` must never be used again /// `label` must be a valid Cstr /// `provider` must be a valid, non-null pointer to a location where the handle will be stored +#[cfg(feature = "tcp")] #[unsafe(no_mangle)] pub unsafe extern "C" fn idevice_tcp_provider_new( ip: *const libc::sockaddr, pairing_file: *mut crate::pairing_file::IdevicePairingFile, label: *const c_char, - provider: *mut *mut TcpProviderHandle, + provider: *mut *mut IdeviceProviderHandle, ) -> IdeviceErrorCode { + if ip.is_null() || label.is_null() || provider.is_null() { + return IdeviceErrorCode::InvalidArg; + } + let addr = match util::c_addr_to_rust(ip) { Ok(i) => i, Err(e) => { @@ -53,23 +57,23 @@ pub unsafe extern "C" fn idevice_tcp_provider_new( label, }; - let boxed = Box::new(TcpProviderHandle(t)); + let boxed = Box::new(IdeviceProviderHandle(Box::new(t))); unsafe { *provider = Box::into_raw(boxed) }; IdeviceErrorCode::IdeviceSuccess } -/// Frees a TcpProvider handle +/// Frees an IdeviceProvider handle /// /// # Arguments /// * [`provider`] - The provider handle to free /// /// # Safety -/// `provider` must be a valid pointer to a TcpProvider handle that was allocated by this library, -/// or NULL (in which case this function does nothing) +/// `provider` must be a valid pointer to a IdeviceProvider handle that was allocated this library +/// or NULL (in which case this function does nothing) #[unsafe(no_mangle)] -pub unsafe extern "C" fn tcp_provider_free(provider: *mut TcpProviderHandle) { +pub unsafe extern "C" fn idevice_provider_free(provider: *mut IdeviceProviderHandle) { if !provider.is_null() { - log::debug!("Freeing TCP provider"); + log::debug!("Freeing provider"); unsafe { drop(Box::from_raw(provider)) }; } } @@ -81,7 +85,6 @@ pub unsafe extern "C" fn tcp_provider_free(provider: *mut TcpProviderHandle) { /// * [`tag`] - The tag returned in usbmuxd responses /// * [`udid`] - The UDID of the device to connect to /// * [`device_id`] - The muxer ID of the device to connect to -/// * [`pairing_file`] - The pairing file handle to use /// * [`label`] - The label to use with the connection /// * [`provider`] - A pointer to a newly allocated provider /// @@ -91,7 +94,6 @@ pub unsafe extern "C" fn tcp_provider_free(provider: *mut TcpProviderHandle) { /// # Safety /// `addr` must be a valid pointer to UsbmuxdAddrHandle created by this library, and never used again /// `udid` must be a valid CStr -/// `pairing_file` must never be used again /// `label` must be a valid Cstr /// `provider` must be a valid, non-null pointer to a location where the handle will be stored #[unsafe(no_mangle)] @@ -101,21 +103,25 @@ pub unsafe extern "C" fn usbmuxd_provider_new( udid: *const c_char, device_id: u32, label: *const c_char, - provider: *mut *mut UsbmuxdProviderHandle, + provider: *mut *mut IdeviceProviderHandle, ) -> IdeviceErrorCode { + if addr.is_null() || udid.is_null() || label.is_null() || provider.is_null() { + return IdeviceErrorCode::InvalidArg; + } + let udid = match unsafe { CStr::from_ptr(udid) }.to_str() { Ok(u) => u.to_string(), Err(e) => { log::error!("Invalid UDID string: {e:?}"); - return IdeviceErrorCode::InvalidArgument; + return IdeviceErrorCode::InvalidString; } }; let label = match unsafe { CStr::from_ptr(label) }.to_str() { Ok(l) => l.to_string(), Err(e) => { - log::error!("Invalid UDID string: {e:?}"); - return IdeviceErrorCode::InvalidArgument; + log::error!("Invalid label string: {e:?}"); + return IdeviceErrorCode::InvalidArg; } }; @@ -129,23 +135,8 @@ pub unsafe extern "C" fn usbmuxd_provider_new( label, }; - let boxed = Box::new(UsbmuxdProviderHandle(p)); + let boxed = Box::new(IdeviceProviderHandle(Box::new(p))); unsafe { *provider = Box::into_raw(boxed) }; IdeviceErrorCode::IdeviceSuccess } - -/// Frees a UsbmuxdProvider handle -/// -/// # Arguments -/// * [`provider`] - The provider handle to free -/// -/// # Safety -/// `provider` must be a valid pointer to a UsbmuxdProvider handle that was allocated by this library, -/// or NULL (in which case this function does nothing) -#[unsafe(no_mangle)] -pub unsafe extern "C" fn usbmuxd_provider_free(provider: *mut UsbmuxdProviderHandle) { - if !provider.is_null() { - unsafe { drop(Box::from_raw(provider)) }; - } -} diff --git a/ffi/src/remote_server.rs b/ffi/src/remote_server.rs index f39615c..1570dd5 100644 --- a/ffi/src/remote_server.rs +++ b/ffi/src/remote_server.rs @@ -1,46 +1,45 @@ // Jackson Coxson -use crate::core_device_proxy::AdapterHandle; use crate::{IdeviceErrorCode, RUNTIME}; -use idevice::IdeviceError; use idevice::dvt::remote_server::RemoteServerClient; -use idevice::tcp::adapter::Adapter; +use idevice::{IdeviceError, ReadWrite}; /// Opaque handle to a RemoteServerClient -pub struct RemoteServerAdapterHandle(pub RemoteServerClient); +pub struct RemoteServerHandle(pub RemoteServerClient>); /// Creates a new RemoteServerClient from a ReadWrite connection /// /// # Arguments -/// * [`connection`] - The connection to use for communication +/// * [`socket`] - The connection to use for communication, an object that implements ReadWrite /// * [`handle`] - Pointer to store the newly created RemoteServerClient handle /// /// # Returns /// An error code indicating success or failure /// /// # Safety -/// `connection` must be a valid pointer to a handle allocated by this library +/// `socket` must be a valid pointer to a handle allocated by this library /// `handle` must be a valid pointer to a location where the handle will be stored #[unsafe(no_mangle)] -pub unsafe extern "C" fn remote_server_adapter_new( - adapter: *mut crate::core_device_proxy::AdapterHandle, - handle: *mut *mut RemoteServerAdapterHandle, +pub unsafe extern "C" fn remote_server_new( + socket: *mut Box, + handle: *mut *mut RemoteServerHandle, ) -> IdeviceErrorCode { - if adapter.is_null() { + if socket.is_null() { return IdeviceErrorCode::InvalidArg; } - let connection = unsafe { Box::from_raw(adapter) }; + let connection = unsafe { Box::from_raw(socket) }; - let res: Result, IdeviceError> = RUNTIME.block_on(async move { - let mut client = RemoteServerClient::new(connection.0); - client.read_message(0).await?; // Until Message has bindings, we'll do the first read - Ok(client) - }); + let res: Result>, IdeviceError> = + RUNTIME.block_on(async move { + let mut client = RemoteServerClient::new(*connection); + client.read_message(0).await?; // Until Message has bindings, we'll do the first read + Ok(client) + }); match res { Ok(client) => { - let boxed = Box::new(RemoteServerAdapterHandle(client)); + let boxed = Box::new(RemoteServerHandle(client)); unsafe { *handle = Box::into_raw(boxed) }; IdeviceErrorCode::IdeviceSuccess } @@ -56,35 +55,8 @@ pub unsafe extern "C" fn remote_server_adapter_new( /// # Safety /// `handle` must be a valid pointer to a handle allocated by this library or NULL #[unsafe(no_mangle)] -pub unsafe extern "C" fn remote_server_free(handle: *mut RemoteServerAdapterHandle) { +pub unsafe extern "C" fn remote_server_free(handle: *mut RemoteServerHandle) { if !handle.is_null() { let _ = unsafe { Box::from_raw(handle) }; } } - -/// Returns the underlying connection from a RemoteServerClient -/// -/// # Arguments -/// * [`handle`] - The handle to get the connection from -/// * [`connection`] - The newly allocated ConnectionHandle -/// -/// # Returns -/// An error code indicating success or failure -/// -/// # Safety -/// `handle` must be a valid pointer to a handle allocated by this library or NULL, and never used again -#[unsafe(no_mangle)] -pub unsafe extern "C" fn remote_server_adapter_into_inner( - handle: *mut RemoteServerAdapterHandle, - connection: *mut *mut AdapterHandle, -) -> IdeviceErrorCode { - if handle.is_null() || connection.is_null() { - return IdeviceErrorCode::InvalidArg; - } - - let server = unsafe { Box::from_raw(handle) }; - let connection_obj = server.0.into_inner(); - let boxed = Box::new(AdapterHandle(connection_obj)); - unsafe { *connection = Box::into_raw(boxed) }; - IdeviceErrorCode::IdeviceSuccess -} diff --git a/ffi/src/remotexpc.rs b/ffi/src/remotexpc.rs deleted file mode 100644 index dcd7f8e..0000000 --- a/ffi/src/remotexpc.rs +++ /dev/null @@ -1,288 +0,0 @@ -// Jackson Coxson - -use std::ffi::{CStr, CString, c_char}; - -use idevice::{tcp::adapter::Adapter, xpc::XPCDevice}; - -use crate::{IdeviceErrorCode, RUNTIME, core_device_proxy::AdapterHandle}; - -/// Opaque handle to an XPCDevice -pub struct XPCDeviceAdapterHandle(pub XPCDevice); - -/// Opaque handle to an XPCService -#[repr(C)] -pub struct XPCServiceHandle { - pub entitlement: *mut c_char, - pub port: u16, - pub uses_remote_xpc: bool, - pub features: *mut *mut c_char, - pub features_count: usize, - pub service_version: i64, -} - -impl XPCServiceHandle { - pub fn new( - entitlement: *mut c_char, - port: u16, - uses_remote_xpc: bool, - features: *mut *mut c_char, - features_count: usize, - service_version: i64, - ) -> Self { - Self { - entitlement, - port, - uses_remote_xpc, - features, - features_count, - service_version, - } - } -} - -/// Creates a new XPCDevice from an adapter -/// -/// # Arguments -/// * [`adapter`] - The adapter to use for communication -/// * [`device`] - Pointer to store the newly created XPCDevice handle -/// -/// # Returns -/// An error code indicating success or failure -/// -/// # Safety -/// `adapter` must be a valid pointer to a handle allocated by this library -/// `device` must be a valid pointer to a location where the handle will be stored -#[unsafe(no_mangle)] -pub unsafe extern "C" fn xpc_device_new( - adapter: *mut AdapterHandle, - device: *mut *mut XPCDeviceAdapterHandle, -) -> IdeviceErrorCode { - if adapter.is_null() || device.is_null() { - return IdeviceErrorCode::InvalidArg; - } - - let adapter = unsafe { Box::from_raw(adapter) }; - let res = RUNTIME.block_on(async move { XPCDevice::new(adapter.0).await }); - - match res { - // we have to unwrap res to avoid just getting a reference - Ok(_) => { - let boxed = Box::new(XPCDeviceAdapterHandle(res.unwrap())); - unsafe { *device = Box::into_raw(boxed) }; - IdeviceErrorCode::IdeviceSuccess - } - Err(e) => e.into(), - } -} - -/// Frees an XPCDevice handle -/// -/// # Arguments -/// * [`handle`] - The handle to free -/// -/// # Safety -/// `handle` must be a valid pointer to a handle allocated by this library or NULL -#[unsafe(no_mangle)] -pub unsafe extern "C" fn xpc_device_free(handle: *mut XPCDeviceAdapterHandle) { - if !handle.is_null() { - let _ = unsafe { Box::from_raw(handle) }; - } -} - -/// Gets a service by name from the XPCDevice -/// -/// # Arguments -/// * [`handle`] - The XPCDevice handle -/// * [`service_name`] - The name of the service to get -/// * [`service`] - Pointer to store the newly created XPCService handle -/// -/// # Returns -/// An error code indicating success or failure -/// -/// # Safety -/// `handle` must be a valid pointer to a handle allocated by this library -/// `service_name` must be a valid null-terminated C string -/// `service` must be a valid pointer to a location where the handle will be stored -#[unsafe(no_mangle)] -pub unsafe extern "C" fn xpc_device_get_service( - handle: *mut XPCDeviceAdapterHandle, - service_name: *const c_char, - service: *mut *mut XPCServiceHandle, -) -> IdeviceErrorCode { - if handle.is_null() || service_name.is_null() || service.is_null() { - return IdeviceErrorCode::InvalidArg; - } - - let device = unsafe { &(*handle).0 }; - let service_name_cstr = unsafe { CStr::from_ptr(service_name) }; - let service_name = match service_name_cstr.to_str() { - Ok(s) => s, - Err(_) => return IdeviceErrorCode::InvalidArg, - }; - - let xpc_service = match device.services.get(service_name) { - Some(s) => s, - None => return IdeviceErrorCode::ServiceNotFound, - }; - - // Convert features to C array if they exist - let (features_ptr, features_count) = if let Some(features) = &xpc_service.features { - let mut features_vec: Vec<*mut c_char> = features - .iter() - .map(|f| CString::new(f.as_str()).unwrap().into_raw()) - .collect(); - features_vec.shrink_to_fit(); - - let mut features_vec = Box::new(features_vec); - let features_ptr = features_vec.as_mut_ptr(); - let features_len = features_vec.len(); - - Box::leak(features_vec); - (features_ptr, features_len) - } else { - (std::ptr::null_mut(), 0) - }; - - let boxed = Box::new(XPCServiceHandle { - entitlement: CString::new(xpc_service.entitlement.as_str()) - .unwrap() - .into_raw(), - port: xpc_service.port, - uses_remote_xpc: xpc_service.uses_remote_xpc, - features: features_ptr, - features_count, - service_version: xpc_service.service_version.unwrap_or(-1), - }); - - unsafe { *service = Box::into_raw(boxed) }; - IdeviceErrorCode::IdeviceSuccess -} - -/// Returns the adapter in the RemoteXPC Device -/// -/// # Arguments -/// * [`handle`] - The handle to get the adapter from -/// * [`adapter`] - The newly allocated AdapterHandle -/// -/// # Safety -/// `handle` must be a valid pointer to a handle allocated by this library or NULL, and never used again -#[unsafe(no_mangle)] -pub unsafe extern "C" fn xpc_device_adapter_into_inner( - handle: *mut XPCDeviceAdapterHandle, - adapter: *mut *mut AdapterHandle, -) -> IdeviceErrorCode { - if handle.is_null() { - return IdeviceErrorCode::InvalidArg; - } - let device = unsafe { Box::from_raw(handle).0 }; - let adapter_obj = device.into_inner(); - let boxed = Box::new(AdapterHandle(adapter_obj)); - unsafe { *adapter = Box::into_raw(boxed) }; - IdeviceErrorCode::IdeviceSuccess -} - -/// Frees an XPCService handle -/// -/// # Arguments -/// * [`handle`] - The handle to free -/// -/// # Safety -/// `handle` must be a valid pointer to a handle allocated by this library or NULL -#[unsafe(no_mangle)] -pub unsafe extern "C" fn xpc_service_free(handle: *mut XPCServiceHandle) { - if !handle.is_null() { - let handle = unsafe { Box::from_raw(handle) }; - - // Free the entitlement string - if !handle.entitlement.is_null() { - let _ = unsafe { CString::from_raw(handle.entitlement) }; - } - - // Free the features array - if !handle.features.is_null() { - for i in 0..handle.features_count { - let feature_ptr = unsafe { *handle.features.add(i) }; - if !feature_ptr.is_null() { - let _ = unsafe { CString::from_raw(feature_ptr) }; - } - } - let _ = unsafe { - Vec::from_raw_parts( - handle.features, - handle.features_count, - handle.features_count, - ) - }; - } - } -} - -/// Gets the list of available service names -/// -/// # Arguments -/// * [`handle`] - The XPCDevice handle -/// * [`names`] - Pointer to store the array of service names -/// * [`count`] - Pointer to store the number of services -/// -/// # Returns -/// An error code indicating success or failure -/// -/// # Safety -/// `handle` must be a valid pointer to a handle allocated by this library -/// `names` must be a valid pointer to a location where the array will be stored -/// `count` must be a valid pointer to a location where the count will be stored -#[unsafe(no_mangle)] -pub unsafe extern "C" fn xpc_device_get_service_names( - handle: *mut XPCDeviceAdapterHandle, - names: *mut *mut *mut c_char, - count: *mut usize, -) -> IdeviceErrorCode { - if handle.is_null() || names.is_null() || count.is_null() { - return IdeviceErrorCode::InvalidArg; - } - - let device = unsafe { &(*handle).0 }; - let service_names: Vec = device - .services - .keys() - .map(|k| CString::new(k.as_str()).unwrap()) - .collect(); - - let mut name_ptrs: Vec<*mut c_char> = service_names.into_iter().map(|s| s.into_raw()).collect(); - - if name_ptrs.is_empty() { - unsafe { - *names = std::ptr::null_mut(); - *count = 0; - } - } else { - name_ptrs.shrink_to_fit(); - unsafe { - *names = name_ptrs.as_mut_ptr(); - *count = name_ptrs.len(); - } - std::mem::forget(name_ptrs); - } - - IdeviceErrorCode::IdeviceSuccess -} - -/// Frees a list of service names -/// -/// # Arguments -/// * [`names`] - The array of service names to free -/// * [`count`] - The number of services in the array -/// -/// # Safety -/// `names` must be a valid pointer to an array of `count` C strings -#[unsafe(no_mangle)] -pub unsafe extern "C" fn xpc_device_free_service_names(names: *mut *mut c_char, count: usize) { - if !names.is_null() && count > 0 { - let names_vec = unsafe { Vec::from_raw_parts(names, count, count) }; - for name in names_vec { - if !name.is_null() { - let _ = unsafe { CString::from_raw(name) }; - } - } - } -} diff --git a/ffi/src/rsd.rs b/ffi/src/rsd.rs new file mode 100644 index 0000000..44c41ec --- /dev/null +++ b/ffi/src/rsd.rs @@ -0,0 +1,502 @@ +//! Remote Service Discovery (RSD) Client Bindings +//! +//! Provides C-compatible bindings for RSD handshake and service discovery on iOS devices. + +use std::ffi::{CStr, CString}; +use std::ptr; + +use idevice::{ReadWrite, rsd::RsdHandshake}; + +use crate::{IdeviceErrorCode, RUNTIME}; + +/// Opaque handle to an RsdHandshake +pub struct RsdHandshakeHandle(pub RsdHandshake); + +/// C-compatible representation of an RSD service +#[repr(C)] +pub struct CRsdService { + /// Service name (null-terminated string) + pub name: *mut libc::c_char, + /// Required entitlement (null-terminated string) + pub entitlement: *mut libc::c_char, + /// Port number + pub port: u16, + /// Whether service uses remote XPC + pub uses_remote_xpc: bool, + /// Number of features + pub features_count: libc::size_t, + /// Array of feature strings + pub features: *mut *mut libc::c_char, + /// Service version (-1 if not present) + pub service_version: i64, +} + +/// Array of RSD services returned by rsd_get_services +#[repr(C)] +pub struct CRsdServiceArray { + /// Array of services + pub services: *mut CRsdService, + /// Number of services in array + pub count: libc::size_t, +} + +/// Creates a new RSD handshake from a ReadWrite connection +/// +/// # Arguments +/// * [`socket`] - The connection to use for communication +/// * [`handle`] - Pointer to store the newly created RsdHandshake handle +/// +/// # Returns +/// An error code indicating success or failure +/// +/// # Safety +/// `socket` must be a valid pointer to a ReadWrite handle allocated by this library +/// `handle` must be a valid pointer to a location where the handle will be stored +#[unsafe(no_mangle)] +pub unsafe extern "C" fn rsd_handshake_new( + socket: *mut Box, + handle: *mut *mut RsdHandshakeHandle, +) -> IdeviceErrorCode { + if socket.is_null() || handle.is_null() { + return IdeviceErrorCode::InvalidArg; + } + + let connection = unsafe { Box::from_raw(socket) }; + + let res = RUNTIME.block_on(async move { RsdHandshake::new(*connection).await }); + + match res { + Ok(handshake) => { + let boxed = Box::new(RsdHandshakeHandle(handshake)); + unsafe { *handle = Box::into_raw(boxed) }; + IdeviceErrorCode::IdeviceSuccess + } + Err(e) => e.into(), + } +} + +/// Gets the protocol version from the RSD handshake +/// +/// # Arguments +/// * [`handle`] - A valid RsdHandshake handle +/// * [`version`] - Pointer to store the protocol version +/// +/// # Returns +/// An error code indicating success or failure +/// +/// # Safety +/// `handle` must be a valid pointer to a handle allocated by this library +/// `version` must be a valid pointer to store the version +#[unsafe(no_mangle)] +pub unsafe extern "C" fn rsd_get_protocol_version( + handle: *mut RsdHandshakeHandle, + version: *mut libc::size_t, +) -> IdeviceErrorCode { + if handle.is_null() || version.is_null() { + return IdeviceErrorCode::InvalidArg; + } + + unsafe { + *version = (*handle).0.protocol_version; + } + IdeviceErrorCode::IdeviceSuccess +} + +/// Gets the UUID from the RSD handshake +/// +/// # Arguments +/// * [`handle`] - A valid RsdHandshake handle +/// * [`uuid`] - Pointer to store the UUID string (caller must free with rsd_free_string) +/// +/// # Returns +/// An error code indicating success or failure +/// +/// # Safety +/// `handle` must be a valid pointer to a handle allocated by this library +/// `uuid` must be a valid pointer to store the string pointer +#[unsafe(no_mangle)] +pub unsafe extern "C" fn rsd_get_uuid( + handle: *mut RsdHandshakeHandle, + uuid: *mut *mut libc::c_char, +) -> IdeviceErrorCode { + if handle.is_null() || uuid.is_null() { + return IdeviceErrorCode::InvalidArg; + } + + let uuid_str = &unsafe { &*handle }.0.uuid; + match CString::new(uuid_str.as_str()) { + Ok(c_str) => { + unsafe { *uuid = c_str.into_raw() }; + IdeviceErrorCode::IdeviceSuccess + } + Err(_) => IdeviceErrorCode::InvalidString, + } +} + +/// Gets all available services from the RSD handshake +/// +/// # Arguments +/// * [`handle`] - A valid RsdHandshake handle +/// * [`services`] - Pointer to store the services array +/// +/// # Returns +/// An error code indicating success or failure +/// +/// # Safety +/// `handle` must be a valid pointer to a handle allocated by this library +/// `services` must be a valid pointer to store the services array +/// Caller must free the returned array with rsd_free_services +#[unsafe(no_mangle)] +pub unsafe extern "C" fn rsd_get_services( + handle: *mut RsdHandshakeHandle, + services: *mut *mut CRsdServiceArray, +) -> IdeviceErrorCode { + if handle.is_null() || services.is_null() { + return IdeviceErrorCode::InvalidArg; + } + + let handshake = unsafe { &*handle }; + let service_map = &handshake.0.services; + + let count = service_map.len(); + let mut c_services = Vec::with_capacity(count); + + for (name, service) in service_map.iter() { + // Convert name + let c_name = match CString::new(name.as_str()) { + Ok(s) => s.into_raw(), + Err(_) => continue, + }; + + // Convert entitlement + let c_entitlement = match CString::new(service.entitlement.as_str()) { + Ok(s) => s.into_raw(), + Err(_) => { + unsafe { + let _ = CString::from_raw(c_name); + } + continue; + } + }; + + // Convert features + let (features_ptr, features_count) = match &service.features { + Some(features) => { + let mut c_features = Vec::with_capacity(features.len()); + + for feature in features { + match CString::new(feature.as_str()) { + Ok(s) => c_features.push(s.into_raw()), + Err(_) => { + // Clean up already allocated features + for f in c_features { + unsafe { + let _ = CString::from_raw(f); + } + } + // Return early to avoid the move below + return IdeviceErrorCode::InvalidString; + } + } + } + + // All features converted successfully + let boxed = c_features.into_boxed_slice(); + let ptr = Box::into_raw(boxed) as *mut *mut libc::c_char; + (ptr, features.len()) + } + None => (ptr::null_mut(), 0), + }; + + let c_service = CRsdService { + name: c_name, + entitlement: c_entitlement, + port: service.port, + uses_remote_xpc: service.uses_remote_xpc, + features_count, + features: features_ptr, + service_version: service.service_version.unwrap_or(-1), + }; + + c_services.push(c_service); + } + + let boxed_services = c_services.into_boxed_slice(); + let array = Box::new(CRsdServiceArray { + services: Box::into_raw(boxed_services) as *mut CRsdService, + count, + }); + + unsafe { *services = Box::into_raw(array) }; + IdeviceErrorCode::IdeviceSuccess +} + +/// Checks if a specific service is available +/// +/// # Arguments +/// * [`handle`] - A valid RsdHandshake handle +/// * [`service_name`] - Name of the service to check for +/// * [`available`] - Pointer to store the availability result +/// +/// # Returns +/// An error code indicating success or failure +/// +/// # Safety +/// `handle` must be a valid pointer to a handle allocated by this library +/// `service_name` must be a valid C string +/// `available` must be a valid pointer to store the boolean result +#[unsafe(no_mangle)] +pub unsafe extern "C" fn rsd_service_available( + handle: *mut RsdHandshakeHandle, + service_name: *const libc::c_char, + available: *mut bool, +) -> IdeviceErrorCode { + if handle.is_null() || service_name.is_null() || available.is_null() { + return IdeviceErrorCode::InvalidArg; + } + + let name = match unsafe { CStr::from_ptr(service_name) }.to_str() { + Ok(s) => s, + Err(_) => return IdeviceErrorCode::InvalidArg, + }; + + let handshake = unsafe { &*handle }; + unsafe { *available = handshake.0.services.contains_key(name) }; + + IdeviceErrorCode::IdeviceSuccess +} + +/// Gets information about a specific service +/// +/// # Arguments +/// * [`handle`] - A valid RsdHandshake handle +/// * [`service_name`] - Name of the service to get info for +/// * [`service_info`] - Pointer to store the service information +/// +/// # Returns +/// An error code indicating success or failure +/// +/// # Safety +/// `handle` must be a valid pointer to a handle allocated by this library +/// `service_name` must be a valid C string +/// `service_info` must be a valid pointer to store the service info +/// Caller must free the returned service with rsd_free_service +#[unsafe(no_mangle)] +pub unsafe extern "C" fn rsd_get_service_info( + handle: *mut RsdHandshakeHandle, + service_name: *const libc::c_char, + service_info: *mut *mut CRsdService, +) -> IdeviceErrorCode { + if handle.is_null() || service_name.is_null() || service_info.is_null() { + return IdeviceErrorCode::InvalidArg; + } + + let name = match unsafe { CStr::from_ptr(service_name) }.to_str() { + Ok(s) => s, + Err(_) => return IdeviceErrorCode::InvalidArg, + }; + + let handshake = unsafe { &*handle }; + let service = match handshake.0.services.get(name) { + Some(s) => s, + None => return IdeviceErrorCode::ServiceNotFound, + }; + + // Convert service to C representation (similar to rsd_get_services logic) + let c_name = match CString::new(name) { + Ok(s) => s.into_raw(), + Err(_) => return IdeviceErrorCode::InvalidString, + }; + + let c_entitlement = match CString::new(service.entitlement.as_str()) { + Ok(s) => s.into_raw(), + Err(_) => { + unsafe { + let _ = CString::from_raw(c_name); + } + return IdeviceErrorCode::InvalidString; + } + }; + + // Convert features + let (features_ptr, features_count) = match &service.features { + Some(features) => { + let mut c_features = Vec::with_capacity(features.len()); + for feature in features { + match CString::new(feature.as_str()) { + Ok(s) => c_features.push(s.into_raw()), + Err(_) => { + // Clean up already allocated features + for f in c_features { + unsafe { + let _ = CString::from_raw(f); + } + } + // Clean up name and entitlement + unsafe { + let _ = CString::from_raw(c_name); + let _ = CString::from_raw(c_entitlement); + } + return IdeviceErrorCode::InvalidString; + } + } + } + let boxed = c_features.into_boxed_slice(); + ( + Box::into_raw(boxed) as *mut *mut libc::c_char, + features.len(), + ) + } + None => (ptr::null_mut(), 0), + }; + + let c_service = Box::new(CRsdService { + name: c_name, + entitlement: c_entitlement, + port: service.port, + uses_remote_xpc: service.uses_remote_xpc, + features_count, + features: features_ptr, + service_version: service.service_version.unwrap_or(-1), + }); + + unsafe { *service_info = Box::into_raw(c_service) }; + IdeviceErrorCode::IdeviceSuccess +} + +/// Frees a string returned by RSD functions +/// +/// # Arguments +/// * [`string`] - The string to free +/// +/// # Safety +/// Must only be called with strings returned from RSD functions +#[unsafe(no_mangle)] +pub unsafe extern "C" fn rsd_free_string(string: *mut libc::c_char) { + if !string.is_null() { + unsafe { + let _ = CString::from_raw(string); + } + } +} + +/// Frees a single service returned by rsd_get_service_info +/// +/// # Arguments +/// * [`service`] - The service to free +/// +/// # Safety +/// Must only be called with services returned from rsd_get_service_info +#[unsafe(no_mangle)] +pub unsafe extern "C" fn rsd_free_service(service: *mut CRsdService) { + if service.is_null() { + return; + } + + let service_box = unsafe { Box::from_raw(service) }; + + // Free name + if !service_box.name.is_null() { + unsafe { + let _ = CString::from_raw(service_box.name); + } + } + + // Free entitlement + if !service_box.entitlement.is_null() { + unsafe { + let _ = CString::from_raw(service_box.entitlement); + } + } + + // Free features array + if !service_box.features.is_null() && service_box.features_count > 0 { + let features_slice = unsafe { + std::slice::from_raw_parts_mut(service_box.features, service_box.features_count) + }; + for feature_ptr in features_slice.iter() { + if !feature_ptr.is_null() { + unsafe { + let _ = CString::from_raw(*feature_ptr); + } + } + } + unsafe { + let _ = Box::from_raw(features_slice); + } + } +} + +/// Frees services array returned by rsd_get_services +/// +/// # Arguments +/// * [`services`] - The services array to free +/// +/// # Safety +/// Must only be called with arrays returned from rsd_get_services +#[unsafe(no_mangle)] +pub unsafe extern "C" fn rsd_free_services(services: *mut CRsdServiceArray) { + if services.is_null() { + return; + } + + let services_box = unsafe { Box::from_raw(services) }; + + if !services_box.services.is_null() && services_box.count > 0 { + let services_slice = + unsafe { std::slice::from_raw_parts_mut(services_box.services, services_box.count) }; + + // Free each service + for service in services_slice.iter() { + // Free name + if !service.name.is_null() { + unsafe { + let _ = CString::from_raw(service.name); + } + } + + // Free entitlement + if !service.entitlement.is_null() { + unsafe { + let _ = CString::from_raw(service.entitlement); + } + } + + // Free features array + if !service.features.is_null() && service.features_count > 0 { + let features_slice = unsafe { + std::slice::from_raw_parts_mut(service.features, service.features_count) + }; + for feature_ptr in features_slice.iter() { + if !feature_ptr.is_null() { + unsafe { + let _ = CString::from_raw(*feature_ptr); + } + } + } + unsafe { + let _ = Box::from_raw(features_slice); + } + } + } + + unsafe { + let _ = Box::from_raw(services_slice); + } + } +} + +/// Frees an RSD handshake handle +/// +/// # Arguments +/// * [`handle`] - The handle to free +/// +/// # Safety +/// `handle` must be a valid pointer to a handle allocated by this library, +/// or NULL (in which case this function does nothing) +#[unsafe(no_mangle)] +pub unsafe extern "C" fn rsd_handshake_free(handle: *mut RsdHandshakeHandle) { + if !handle.is_null() { + let _ = unsafe { Box::from_raw(handle) }; + } +} diff --git a/ffi/src/sbservices.rs b/ffi/src/springboardservices.rs similarity index 64% rename from ffi/src/sbservices.rs rename to ffi/src/springboardservices.rs index 657c4d2..67d6533 100644 --- a/ffi/src/sbservices.rs +++ b/ffi/src/springboardservices.rs @@ -1,18 +1,18 @@ use std::ffi::{CStr, c_void}; -use idevice::{IdeviceError, IdeviceService, springboardservices::SpringBoardServicesClient}; - -use crate::{ - IdeviceErrorCode, IdeviceHandle, RUNTIME, - provider::{TcpProviderHandle, UsbmuxdProviderHandle}, +use idevice::{ + IdeviceError, IdeviceService, provider::IdeviceProvider, + springboardservices::SpringBoardServicesClient, }; +use crate::{IdeviceErrorCode, IdeviceHandle, RUNTIME, provider::IdeviceProviderHandle}; + pub struct SpringBoardServicesClientHandle(pub SpringBoardServicesClient); -/// Connects to the Springboard service using a TCP provider +/// Connects to the Springboard service using a provider /// /// # Arguments -/// * [`provider`] - A TcpProvider +/// * [`provider`] - An IdeviceProvider /// * [`client`] - On success, will be set to point to a newly allocated SpringBoardServicesClient handle /// /// # Returns @@ -22,8 +22,8 @@ pub struct SpringBoardServicesClientHandle(pub SpringBoardServicesClient); /// `provider` must be a valid pointer to a handle allocated by this library /// `client` must be a valid, non-null pointer to a location where the handle will be stored #[unsafe(no_mangle)] -pub unsafe extern "C" fn springboard_services_connect_tcp( - provider: *mut TcpProviderHandle, +pub unsafe extern "C" fn springboard_services_connect( + provider: *mut IdeviceProviderHandle, client: *mut *mut SpringBoardServicesClientHandle, ) -> IdeviceErrorCode { if provider.is_null() || client.is_null() { @@ -32,18 +32,8 @@ pub unsafe extern "C" fn springboard_services_connect_tcp( } let res: Result = RUNTIME.block_on(async move { - // Take ownership of the provider (without immediately dropping it) - let provider_box = unsafe { Box::from_raw(provider) }; - - // Get a reference to the inner value - let provider_ref = &provider_box.0; - - // Connect using the reference - let result = SpringBoardServicesClient::connect(provider_ref).await; - - // Explicitly keep the provider_box alive until after connect completes - std::mem::forget(provider_box); - result + let provider_ref: &dyn IdeviceProvider = unsafe { &*(*provider).0 }; + SpringBoardServicesClient::connect(provider_ref).await }); match res { @@ -61,53 +51,6 @@ pub unsafe extern "C" fn springboard_services_connect_tcp( } } -/// Connects to the Springboard service using a usbmuxd provider -/// -/// # Arguments -/// * [`provider`] - A UsbmuxdProvider -/// * [`client`] - On success, will be set to point to a newly allocated SpringBoardServicesClient handle -/// -/// # Returns -/// An error code indicating success or failure -/// -/// # Safety -/// `provider` must be a valid pointer to a handle allocated by this library -/// `client` must be a valid, non-null pointer to a location where the handle will be stored -#[unsafe(no_mangle)] -pub unsafe extern "C" fn springboard_services_connect_usbmuxd( - provider: *mut UsbmuxdProviderHandle, - client: *mut *mut SpringBoardServicesClientHandle, -) -> IdeviceErrorCode { - if provider.is_null() { - log::error!("Provider is null"); - return IdeviceErrorCode::InvalidArg; - } - - let res: Result = RUNTIME.block_on(async move { - // Take ownership of the provider (without immediately dropping it) - let provider_box = unsafe { Box::from_raw(provider) }; - - // Get a reference to the inner value - let provider_ref = &provider_box.0; - - // Connect using the reference - let result = SpringBoardServicesClient::connect(provider_ref).await; - - // Explicitly keep the provider_box alive until after connect completes - std::mem::forget(provider_box); - result - }); - - match res { - Ok(r) => { - let boxed = Box::new(SpringBoardServicesClientHandle(r)); - unsafe { *client = Box::into_raw(boxed) }; - IdeviceErrorCode::IdeviceSuccess - } - Err(e) => e.into(), - } -} - /// Creates a new SpringBoardServices client from an existing Idevice connection /// /// # Arguments @@ -118,7 +61,8 @@ pub unsafe extern "C" fn springboard_services_connect_usbmuxd( /// An error code indicating success or failure /// /// # Safety -/// `socket` must be a valid pointer to a handle allocated by this library +/// `socket` must be a valid pointer to a handle allocated by this library. The socket is consumed, +/// and may not be used again. /// `client` must be a valid, non-null pointer to a location where the handle will be stored #[unsafe(no_mangle)] pub unsafe extern "C" fn springboard_services_new( diff --git a/ffi/src/syslog_relay.rs b/ffi/src/syslog_relay.rs index be30b72..0c00c18 100644 --- a/ffi/src/syslog_relay.rs +++ b/ffi/src/syslog_relay.rs @@ -1,26 +1,26 @@ use std::os::raw::c_char; -use idevice::{syslog_relay::SyslogRelayClient, IdeviceError, IdeviceService}; - -use crate::{ - provider::TcpProviderHandle, IdeviceErrorCode, RUNTIME +use idevice::{ + IdeviceError, IdeviceService, provider::IdeviceProvider, syslog_relay::SyslogRelayClient, }; +use crate::{IdeviceErrorCode, RUNTIME, provider::IdeviceProviderHandle}; + pub struct SyslogRelayClientHandle(pub SyslogRelayClient); /// Automatically creates and connects to syslog relay, returning a client handle /// /// # Arguments -/// * [`provider`] - A TcpProvider +/// * [`provider`] - An IdeviceProvider /// * [`client`] - On success, will be set to point to a newly allocated SyslogRelayClient handle /// /// # Safety /// `provider` must be a valid pointer to a handle allocated by this library /// `client` must be a valid, non-null pointer to a location where the handle will be stored #[unsafe(no_mangle)] -pub extern "C" fn syslog_relay_connect_tcp( - provider: *mut TcpProviderHandle, - client: *mut *mut SyslogRelayClientHandle +pub unsafe extern "C" fn syslog_relay_connect_tcp( + provider: *mut IdeviceProviderHandle, + client: *mut *mut SyslogRelayClientHandle, ) -> IdeviceErrorCode { if provider.is_null() { log::error!("Null pointer provided"); @@ -28,14 +28,8 @@ pub extern "C" fn syslog_relay_connect_tcp( } let res: Result = RUNTIME.block_on(async move { - let provider_box = unsafe { Box::from_raw(provider) }; - - let provider_ref = &provider_box.0; - - let result = SyslogRelayClient::connect(provider_ref).await; - - std::mem::forget(provider_box); - result + let provider_ref: &dyn IdeviceProvider = unsafe { &*(*provider).0 }; + SyslogRelayClient::connect(provider_ref).await }); match res { @@ -62,9 +56,7 @@ pub extern "C" fn syslog_relay_connect_tcp( /// `handle` must be a valid pointer to the handle that was allocated by this library, /// or NULL (in which case this function does nothing) #[unsafe(no_mangle)] -pub extern "C" fn syslog_relay_client_free( - handle: *mut SyslogRelayClientHandle -) { +pub unsafe extern "C" fn syslog_relay_client_free(handle: *mut SyslogRelayClientHandle) { if !handle.is_null() { log::debug!("Freeing syslog relay client"); let _ = unsafe { Box::from_raw(handle) }; @@ -76,12 +68,12 @@ pub extern "C" fn syslog_relay_client_free( /// # Arguments /// * [`client`] - The SyslogRelayClient handle /// * [`log_message`] - On success a newly allocated cstring will be set to point to the log message -/// +/// /// # Safety /// `client` must be a valid pointer to a handle allocated by this library /// `log_message` must be a valid, non-null pointer to a location where the log message will be stored #[unsafe(no_mangle)] -pub extern "C" fn syslog_relay_next( +pub unsafe extern "C" fn syslog_relay_next( client: *mut SyslogRelayClientHandle, log_message: *mut *mut c_char, ) -> IdeviceErrorCode { @@ -89,20 +81,15 @@ pub extern "C" fn syslog_relay_next( return IdeviceErrorCode::InvalidArg; } - let res = RUNTIME.block_on(async { - unsafe { &mut *client } - .0 - .next() - .await - }); + let res = RUNTIME.block_on(async { unsafe { &mut *client }.0.next().await }); match res { Ok(log) => { use std::ffi::CString; - + // null bytes are a curse in C, so just use spaces let safe_log = log.replace('\0', " "); - + match CString::new(safe_log) { Ok(c_string) => { unsafe { *log_message = c_string.into_raw() }; @@ -116,4 +103,4 @@ pub extern "C" fn syslog_relay_next( } Err(e) => e.into(), } -} \ No newline at end of file +} diff --git a/idevice/src/tcp/stream.rs b/idevice/src/tcp/stream.rs index 54dfde7..6de84e4 100644 --- a/idevice/src/tcp/stream.rs +++ b/idevice/src/tcp/stream.rs @@ -31,6 +31,17 @@ impl<'a> AdapterStream<'a> { pub async fn close(&mut self) -> Result<(), std::io::Error> { self.adapter.close(self.host_port).await } + + /// Sends data to the target + pub async fn psh(&mut self, payload: &[u8]) -> Result<(), std::io::Error> { + self.adapter.queue_send(payload, self.host_port)?; + self.adapter.write_buffer_flush().await?; + Ok(()) + } + + pub async fn recv(&mut self) -> Result, std::io::Error> { + self.adapter.recv(self.host_port).await + } } impl AsyncRead for AdapterStream<'_> { From f6cc68cb98ba106ef7eaa8d85643f8cfd53937d4 Mon Sep 17 00:00:00 2001 From: Jackson Coxson Date: Mon, 26 May 2025 16:01:47 -0600 Subject: [PATCH 14/16] Flush after socket write --- idevice/src/services/debug_proxy.rs | 4 ++++ idevice/src/services/dvt/remote_server.rs | 1 + 2 files changed, 5 insertions(+) diff --git a/idevice/src/services/debug_proxy.rs b/idevice/src/services/debug_proxy.rs index 022d5f2..aaa5f25 100644 --- a/idevice/src/services/debug_proxy.rs +++ b/idevice/src/services/debug_proxy.rs @@ -114,6 +114,7 @@ impl DebugProxyClient { // Send the packet self.socket.write_all(packet.as_bytes()).await?; + self.socket.flush().await?; // Read the response let response = self.read_response().await?; @@ -173,6 +174,7 @@ impl DebugProxyClient { /// Returns `IdeviceError` if writing fails pub async fn send_raw(&mut self, bytes: &[u8]) -> Result<(), IdeviceError> { self.socket.write_all(bytes).await?; + self.socket.flush().await?; Ok(()) } @@ -254,6 +256,7 @@ impl DebugProxyClient { /// Returns `IdeviceError` if writing fails pub async fn send_ack(&mut self) -> Result<(), IdeviceError> { self.socket.write_all(b"+").await?; + self.socket.flush().await?; Ok(()) } @@ -263,6 +266,7 @@ impl DebugProxyClient { /// Returns `IdeviceError` if writing fails pub async fn send_noack(&mut self) -> Result<(), IdeviceError> { self.socket.write_all(b"-").await?; + self.socket.flush().await?; Ok(()) } diff --git a/idevice/src/services/dvt/remote_server.rs b/idevice/src/services/dvt/remote_server.rs index 9e91a53..91e9684 100644 --- a/idevice/src/services/dvt/remote_server.rs +++ b/idevice/src/services/dvt/remote_server.rs @@ -202,6 +202,7 @@ impl RemoteServerClient { let message = Message::new(mheader, pheader, aux, data); debug!("Sending message: {message:#?}"); self.idevice.write_all(&message.serialize()).await?; + self.idevice.flush().await?; Ok(()) } From bc6a1c05035db0119ecb053a154d16ccfa3712cf Mon Sep 17 00:00:00 2001 From: Jackson Coxson Date: Mon, 26 May 2025 16:33:58 -0600 Subject: [PATCH 15/16] Update C examples for new FFI --- ffi/examples/CMakeLists.txt | 5 - ffi/examples/afc.c | 8 +- ffi/examples/debug_proxy.c | 88 ++------ ffi/examples/enable_jit.c | 148 ++++++-------- ffi/examples/heartbeat.c | 8 +- ffi/examples/ipa_installer.c | 318 +++++++++++++++-------------- ffi/examples/list_apps.c | 8 +- ffi/examples/location_simulation.c | 56 ++--- ffi/examples/lockdownd.c | 10 +- ffi/examples/mount_personalized.c | 18 +- ffi/examples/mounter.c | 8 +- ffi/examples/process_control.c | 82 ++------ ffi/examples/remotexpc.c | 204 ------------------ 13 files changed, 302 insertions(+), 659 deletions(-) delete mode 100644 ffi/examples/remotexpc.c diff --git a/ffi/examples/CMakeLists.txt b/ffi/examples/CMakeLists.txt index 33c6b3b..6dbf217 100644 --- a/ffi/examples/CMakeLists.txt +++ b/ffi/examples/CMakeLists.txt @@ -11,7 +11,6 @@ set(EXAMPLES_DIR ${CMAKE_SOURCE_DIR}/../examples) # Find all C example files file(GLOB EXAMPLE_SOURCES ${EXAMPLES_DIR}/*.c) -find_package(OpenSSL REQUIRED) # Create an executable for each example file foreach(EXAMPLE_FILE ${EXAMPLE_SOURCES}) @@ -27,14 +26,10 @@ foreach(EXAMPLE_FILE ${EXAMPLE_SOURCES}) # Link the static Rust library target_link_libraries(${EXAMPLE_NAME} PRIVATE ${STATIC_LIB}) - # Link OpenSSL - target_link_libraries(${EXAMPLE_NAME} PRIVATE OpenSSL::SSL OpenSSL::Crypto) - # libplist if( APPLE ) # use static linking - pkg_search_module(PLIST REQUIRED libplist-2.0) find_library( LIBPLIST libplist-2.0.a REQUIRED ) message( STATUS "(Static linking) LIBPLIST " ${LIBPLIST} ) target_link_libraries ( ${EXAMPLE_NAME} PRIVATE ${LIBPLIST} ) diff --git a/ffi/examples/afc.c b/ffi/examples/afc.c index 7c7a5d4..2039861 100644 --- a/ffi/examples/afc.c +++ b/ffi/examples/afc.c @@ -169,7 +169,7 @@ int main(int argc, char **argv) { } // Create TCP provider - TcpProviderHandle *provider = NULL; + IdeviceProviderHandle *provider = NULL; err = idevice_tcp_provider_new((struct sockaddr *)&addr, pairing_file, "ImageMounterTest", &provider); if (err != IdeviceSuccess) { @@ -180,13 +180,13 @@ int main(int argc, char **argv) { // Connect to AFC service AfcClientHandle *client = NULL; - err = afc_client_connect_tcp(provider, &client); + err = afc_client_connect(provider, &client); if (err != IdeviceSuccess) { fprintf(stderr, "Failed to connect to AFC service: %d\n", err); - tcp_provider_free(provider); + idevice_provider_free(provider); return 1; } - tcp_provider_free(provider); + idevice_provider_free(provider); // Process command int success = 1; diff --git a/ffi/examples/debug_proxy.c b/ffi/examples/debug_proxy.c index ee645d7..09d8db0 100644 --- a/ffi/examples/debug_proxy.c +++ b/ffi/examples/debug_proxy.c @@ -17,7 +17,7 @@ void print_usage(const char *program_name) { int main(int argc, char **argv) { // Initialize logger - idevice_init_logger(Debug, Disabled, NULL); + idevice_init_logger(Info, Disabled, NULL); if (argc < 2) { print_usage(argv[0]); @@ -25,7 +25,7 @@ int main(int argc, char **argv) { } const char *device_ip = argv[1]; - const char *pairing_file = argc > 2 ? argv[2] : "pairing_file.plist"; + const char *pairing_file = argc > 2 ? argv[2] : "pairing.plist"; /***************************************************************** * CoreDeviceProxy Setup @@ -51,7 +51,7 @@ int main(int argc, char **argv) { } // Create TCP provider - TcpProviderHandle *tcp_provider = NULL; + IdeviceProviderHandle *tcp_provider = NULL; err = idevice_tcp_provider_new((struct sockaddr *)&addr, pairing, "DebugProxyShell", &tcp_provider); if (err != IdeviceSuccess) { @@ -62,14 +62,13 @@ int main(int argc, char **argv) { // Connect to CoreDeviceProxy CoreDeviceProxyHandle *core_device = NULL; - err = core_device_proxy_connect_tcp(tcp_provider, &core_device); + err = core_device_proxy_connect(tcp_provider, &core_device); if (err != IdeviceSuccess) { fprintf(stderr, "Failed to connect to CoreDeviceProxy: %d\n", err); - tcp_provider_free(tcp_provider); - idevice_pairing_file_free(pairing); + idevice_provider_free(tcp_provider); return 1; } - tcp_provider_free(tcp_provider); + idevice_provider_free(tcp_provider); // Get server RSD port uint16_t rsd_port; @@ -77,7 +76,6 @@ int main(int argc, char **argv) { if (err != IdeviceSuccess) { fprintf(stderr, "Failed to get server RSD port: %d\n", err); core_device_proxy_free(core_device); - idevice_pairing_file_free(pairing); return 1; } printf("Server RSD Port: %d\n", rsd_port); @@ -92,92 +90,45 @@ int main(int argc, char **argv) { if (err != IdeviceSuccess) { fprintf(stderr, "Failed to create TCP adapter: %d\n", err); core_device_proxy_free(core_device); - idevice_pairing_file_free(pairing); return 1; } // Connect to RSD port - err = adapter_connect(adapter, rsd_port); + AdapterStreamHandle *stream = NULL; + err = adapter_connect(adapter, rsd_port, &stream); if (err != IdeviceSuccess) { fprintf(stderr, "Failed to connect to RSD port: %d\n", err); adapter_free(adapter); - core_device_proxy_free(core_device); - idevice_pairing_file_free(pairing); return 1; } printf("Successfully connected to RSD port\n"); /***************************************************************** - * XPC Device Setup + * RSD Handshake *****************************************************************/ - printf("\n=== Setting up XPC Device ===\n"); + printf("\n=== Performing RSD Handshake ===\n"); - XPCDeviceAdapterHandle *xpc_device = NULL; - err = xpc_device_new(adapter, &xpc_device); + RsdHandshakeHandle *handshake = NULL; + err = rsd_handshake_new(stream, &handshake); if (err != IdeviceSuccess) { - fprintf(stderr, "Failed to create XPC device: %d\n", err); + fprintf(stderr, "Failed to perform RSD handshake: %d\n", err); + adapter_close(stream); adapter_free(adapter); - core_device_proxy_free(core_device); - idevice_pairing_file_free(pairing); return 1; } - /***************************************************************** - * Get Debug Proxy Service - *****************************************************************/ - printf("\n=== Getting Debug Proxy Service ===\n"); - - XPCServiceHandle *debug_service = NULL; - err = xpc_device_get_service( - xpc_device, "com.apple.internal.dt.remote.debugproxy", &debug_service); - if (err != IdeviceSuccess) { - fprintf(stderr, "Failed to get debug proxy service: %d\n", err); - xpc_device_free(xpc_device); - adapter_free(adapter); - core_device_proxy_free(core_device); - idevice_pairing_file_free(pairing); - return 1; - } - printf("Debug Proxy Service Port: %d\n", debug_service->port); - /***************************************************************** * Debug Proxy Setup *****************************************************************/ printf("\n=== Setting up Debug Proxy ===\n"); - // Get the adapter back from XPC device - AdapterHandle *debug_adapter = NULL; - err = xpc_device_adapter_into_inner(xpc_device, &debug_adapter); - if (err != IdeviceSuccess) { - fprintf(stderr, "Failed to extract adapter: %d\n", err); - xpc_service_free(debug_service); - xpc_device_free(xpc_device); - core_device_proxy_free(core_device); - idevice_pairing_file_free(pairing); - return 1; - } - - // Connect to debug proxy port - err = adapter_connect(debug_adapter, debug_service->port); - if (err != IdeviceSuccess) { - fprintf(stderr, "Failed to connect to debug proxy port: %d\n", err); - adapter_free(debug_adapter); - xpc_service_free(debug_service); - core_device_proxy_free(core_device); - idevice_pairing_file_free(pairing); - return 1; - } - printf("Successfully connected to debug proxy port\n"); - // Create DebugProxyClient - DebugProxyAdapterHandle *debug_proxy = NULL; - err = debug_proxy_adapter_new(debug_adapter, &debug_proxy); + DebugProxyHandle *debug_proxy = NULL; + err = debug_proxy_connect_rsd(adapter, handshake, &debug_proxy); if (err != IdeviceSuccess) { fprintf(stderr, "Failed to create debug proxy client: %d\n", err); - adapter_free(debug_adapter); - xpc_service_free(debug_service); - core_device_proxy_free(core_device); - idevice_pairing_file_free(pairing); + rsd_handshake_free(handshake); + adapter_free(adapter); return 1; } @@ -264,7 +215,8 @@ int main(int argc, char **argv) { * Cleanup *****************************************************************/ debug_proxy_free(debug_proxy); - xpc_service_free(debug_service); + rsd_handshake_free(handshake); + adapter_free(adapter); printf("\nDebug session ended\n"); return 0; diff --git a/ffi/examples/enable_jit.c b/ffi/examples/enable_jit.c index d1fb0c7..b4f1148 100644 --- a/ffi/examples/enable_jit.c +++ b/ffi/examples/enable_jit.c @@ -15,7 +15,7 @@ void print_usage(const char *program_name) { int main(int argc, char **argv) { // Initialize logger - idevice_init_logger(Debug, Disabled, NULL); + idevice_init_logger(Info, Disabled, NULL); if (argc < 3) { print_usage(argv[0]); @@ -42,17 +42,17 @@ int main(int argc, char **argv) { } // Read pairing file - IdevicePairingFile *pairing = NULL; - IdeviceErrorCode err = idevice_pairing_file_read(pairing_file, &pairing); + struct IdevicePairingFile *pairing = NULL; + enum IdeviceErrorCode err = idevice_pairing_file_read(pairing_file, &pairing); if (err != IdeviceSuccess) { fprintf(stderr, "Failed to read pairing file: %d\n", err); return 1; } // Create TCP provider - TcpProviderHandle *tcp_provider = NULL; + struct IdeviceProviderHandle *provider = NULL; err = idevice_tcp_provider_new((struct sockaddr *)&addr, pairing, - "ProcessDebugTest", &tcp_provider); + "ProcessDebugTest", &provider); if (err != IdeviceSuccess) { fprintf(stderr, "Failed to create TCP provider: %d\n", err); idevice_pairing_file_free(pairing); @@ -60,14 +60,14 @@ int main(int argc, char **argv) { } // Connect to CoreDeviceProxy - CoreDeviceProxyHandle *core_device = NULL; - err = core_device_proxy_connect_tcp(tcp_provider, &core_device); + struct CoreDeviceProxyHandle *core_device = NULL; + err = core_device_proxy_connect(provider, &core_device); if (err != IdeviceSuccess) { fprintf(stderr, "Failed to connect to CoreDeviceProxy: %d\n", err); - tcp_provider_free(tcp_provider); + idevice_provider_free(provider); return 1; } - tcp_provider_free(tcp_provider); + idevice_provider_free(provider); // Get server RSD port uint16_t rsd_port; @@ -84,7 +84,7 @@ int main(int argc, char **argv) { *****************************************************************/ printf("\n=== Creating TCP Tunnel Adapter ===\n"); - AdapterHandle *adapter = NULL; + struct AdapterHandle *adapter = NULL; err = core_device_proxy_create_tcp_adapter(core_device, &adapter); if (err != IdeviceSuccess) { fprintf(stderr, "Failed to create TCP adapter: %d\n", err); @@ -93,46 +93,59 @@ int main(int argc, char **argv) { } // Connect to RSD port - err = adapter_connect(adapter, rsd_port); + struct ReadWriteOpaque *rsd_stream = NULL; + err = adapter_connect(adapter, rsd_port, &rsd_stream); if (err != IdeviceSuccess) { fprintf(stderr, "Failed to connect to RSD port: %d\n", err); adapter_free(adapter); - core_device_proxy_free(core_device); return 1; } printf("Successfully connected to RSD port\n"); + adapter_pcap(adapter, "jit.pcap"); + /***************************************************************** - * XPC Device Setup + * RSD Handshake *****************************************************************/ - printf("\n=== Setting up XPC Device ===\n"); + printf("\n=== Performing RSD Handshake ===\n"); - XPCDeviceAdapterHandle *xpc_device = NULL; - err = xpc_device_new(adapter, &xpc_device); + struct RsdHandshakeHandle *rsd_handshake = NULL; + err = rsd_handshake_new(rsd_stream, &rsd_handshake); if (err != IdeviceSuccess) { - fprintf(stderr, "Failed to create XPC device: %d\n", err); + fprintf(stderr, "Failed to create RSD handshake: %d\n", err); adapter_free(adapter); - core_device_proxy_free(core_device); return 1; } - // Get DebugProxy service - XPCServiceHandle *debug_service = NULL; - err = xpc_device_get_service( - xpc_device, "com.apple.internal.dt.remote.debugproxy", &debug_service); + // Get services + struct CRsdServiceArray *services = NULL; + err = rsd_get_services(rsd_handshake, &services); if (err != IdeviceSuccess) { - fprintf(stderr, "Failed to get debug proxy service: %d\n", err); + fprintf(stderr, "Failed to get RSD services: %d\n", err); + rsd_handshake_free(rsd_handshake); + adapter_free(adapter); return 1; } - // Get ProcessControl service - XPCServiceHandle *pc_service = NULL; - err = xpc_device_get_service(xpc_device, "com.apple.instruments.dtservicehub", - &pc_service); - if (err != IdeviceSuccess) { - fprintf(stderr, "Failed to get process control service: %d\n", err); - xpc_device_free(xpc_device); - core_device_proxy_free(core_device); + // Find debug proxy and process control services + uint16_t debug_port = 0; + uint16_t pc_port = 0; + + for (size_t i = 0; i < services->count; i++) { + struct CRsdService *service = &services->services[i]; + if (strcmp(service->name, "com.apple.internal.dt.remote.debugproxy") == 0) { + debug_port = service->port; + } else if (strcmp(service->name, "com.apple.instruments.dtservicehub") == + 0) { + pc_port = service->port; + } + } + + rsd_free_services(services); + + if (debug_port == 0 || pc_port == 0) { + fprintf(stderr, "Required services not found\n"); + adapter_free(adapter); return 1; } @@ -141,46 +154,31 @@ int main(int argc, char **argv) { *****************************************************************/ printf("\n=== Launching App ===\n"); - // Get the adapter back from XPC device - AdapterHandle *pc_adapter = NULL; - err = xpc_device_adapter_into_inner(xpc_device, &pc_adapter); - if (err != IdeviceSuccess) { - fprintf(stderr, "Failed to extract adapter: %d\n", err); - xpc_device_free(xpc_device); - core_device_proxy_free(core_device); - return 1; - } - // Connect to process control port - err = adapter_connect(pc_adapter, pc_service->port); + struct ReadWriteOpaque *pc_stream = NULL; + err = adapter_connect(adapter, pc_port, &pc_stream); if (err != IdeviceSuccess) { fprintf(stderr, "Failed to connect to process control port: %d\n", err); - adapter_free(pc_adapter); - xpc_service_free(pc_service); - core_device_proxy_free(core_device); + adapter_free(adapter); return 1; } printf("Successfully connected to process control port\n"); // Create RemoteServerClient - RemoteServerAdapterHandle *remote_server = NULL; - err = remote_server_adapter_new(pc_adapter, &remote_server); + struct RemoteServerHandle *remote_server = NULL; + err = remote_server_new(pc_stream, &remote_server); if (err != IdeviceSuccess) { fprintf(stderr, "Failed to create remote server: %d\n", err); - adapter_free(pc_adapter); - xpc_service_free(pc_service); - core_device_proxy_free(core_device); + adapter_free(adapter); return 1; } // Create ProcessControlClient - ProcessControlAdapterHandle *process_control = NULL; + struct ProcessControlHandle *process_control = NULL; err = process_control_new(remote_server, &process_control); if (err != IdeviceSuccess) { fprintf(stderr, "Failed to create process control client: %d\n", err); remote_server_free(remote_server); - xpc_service_free(pc_service); - core_device_proxy_free(core_device); return 1; } @@ -192,8 +190,6 @@ int main(int argc, char **argv) { fprintf(stderr, "Failed to launch app: %d\n", err); process_control_free(process_control); remote_server_free(remote_server); - xpc_service_free(pc_service); - core_device_proxy_free(core_device); return 1; } printf("Successfully launched app with PID: %" PRIu64 "\n", pid); @@ -203,44 +199,24 @@ int main(int argc, char **argv) { *****************************************************************/ printf("\n=== Attaching Debugger ===\n"); - // Get the adapter back from the remote server - AdapterHandle *debug_adapter = NULL; - err = remote_server_adapter_into_inner(remote_server, &debug_adapter); - if (err != IdeviceSuccess) { - fprintf(stderr, "Failed to extract adapter: %d\n", err); - xpc_service_free(debug_service); - process_control_free(process_control); - remote_server_free(remote_server); - xpc_service_free(pc_service); - core_device_proxy_free(core_device); - return 1; - } - // Connect to debug proxy port - err = adapter_connect(debug_adapter, debug_service->port); + struct ReadWriteOpaque *debug_stream = NULL; + err = adapter_connect(adapter, debug_port, &debug_stream); if (err != IdeviceSuccess) { fprintf(stderr, "Failed to connect to debug proxy port: %d\n", err); - adapter_free(debug_adapter); - xpc_service_free(debug_service); process_control_free(process_control); remote_server_free(remote_server); - xpc_service_free(pc_service); - core_device_proxy_free(core_device); return 1; } printf("Successfully connected to debug proxy port\n"); // Create DebugProxyClient - DebugProxyAdapterHandle *debug_proxy = NULL; - err = debug_proxy_adapter_new(debug_adapter, &debug_proxy); + struct DebugProxyHandle *debug_proxy = NULL; + err = debug_proxy_connect_rsd(adapter, rsd_handshake, &debug_proxy); if (err != IdeviceSuccess) { fprintf(stderr, "Failed to create debug proxy client: %d\n", err); - adapter_free(debug_adapter); - xpc_service_free(debug_service); process_control_free(process_control); remote_server_free(remote_server); - xpc_service_free(pc_service); - core_device_proxy_free(core_device); return 1; } @@ -248,17 +224,13 @@ int main(int argc, char **argv) { char attach_command[64]; snprintf(attach_command, sizeof(attach_command), "vAttach;%" PRIx64, pid); - DebugserverCommandHandle *attach_cmd = + struct DebugserverCommandHandle *attach_cmd = debugserver_command_new(attach_command, NULL, 0); if (attach_cmd == NULL) { fprintf(stderr, "Failed to create attach command\n"); debug_proxy_free(debug_proxy); - adapter_free(debug_adapter); - xpc_service_free(debug_service); process_control_free(process_control); remote_server_free(remote_server); - xpc_service_free(pc_service); - core_device_proxy_free(core_device); return 1; } @@ -274,7 +246,8 @@ int main(int argc, char **argv) { } // Send detach command - DebugserverCommandHandle *detach_cmd = debugserver_command_new("D", NULL, 0); + struct DebugserverCommandHandle *detach_cmd = + debugserver_command_new("D", NULL, 0); if (detach_cmd == NULL) { fprintf(stderr, "Failed to create detach command\n"); } else { @@ -296,7 +269,10 @@ int main(int argc, char **argv) { * Cleanup *****************************************************************/ debug_proxy_free(debug_proxy); - xpc_service_free(debug_service); + process_control_free(process_control); + remote_server_free(remote_server); + adapter_free(adapter); + rsd_handshake_free(rsd_handshake); printf("\nDebug session completed\n"); return 0; diff --git a/ffi/examples/heartbeat.c b/ffi/examples/heartbeat.c index a876db8..e277494 100644 --- a/ffi/examples/heartbeat.c +++ b/ffi/examples/heartbeat.c @@ -28,7 +28,7 @@ int main() { } // Create TCP provider - TcpProviderHandle *provider = NULL; + IdeviceProviderHandle *provider = NULL; err = idevice_tcp_provider_new((struct sockaddr *)&addr, pairing_file, "ExampleProvider", &provider); if (err != IdeviceSuccess) { @@ -39,13 +39,13 @@ int main() { // Connect to installation proxy HeartbeatClientHandle *client = NULL; - err = heartbeat_connect_tcp(provider, &client); + err = heartbeat_connect(provider, &client); if (err != IdeviceSuccess) { fprintf(stderr, "Failed to connect to installation proxy: %d\n", err); - tcp_provider_free(provider); + idevice_provider_free(provider); return 1; } - tcp_provider_free(provider); + idevice_provider_free(provider); u_int64_t current_interval = 15; while (1) { diff --git a/ffi/examples/ipa_installer.c b/ffi/examples/ipa_installer.c index a652fad..2d838ca 100644 --- a/ffi/examples/ipa_installer.c +++ b/ffi/examples/ipa_installer.c @@ -7,187 +7,189 @@ #include void print_usage() { - printf("Usage: ipa_installer [options] \n"); - printf("Options:\n"); - printf(" --ip IP_ADDRESS Device IP address (default: 10.7.0.2)\n"); - printf(" --pairing FILE Pairing file path (default: pairing_file.plist)\n"); - printf(" --udid UDID Device UDID (optional)\n"); + printf("Usage: ipa_installer [options] \n"); + printf("Options:\n"); + printf(" --ip IP_ADDRESS Device IP address (default: 10.7.0.2)\n"); + printf(" --pairing FILE Pairing file path (default: " + "pairing_file.plist)\n"); + printf(" --udid UDID Device UDID (optional)\n"); } int read_file(const char *filename, uint8_t **data, size_t *length) { - FILE *file = fopen(filename, "rb"); - if (!file) { - perror("Failed to open file"); - return 0; - } + FILE *file = fopen(filename, "rb"); + if (!file) { + perror("Failed to open file"); + return 0; + } - fseek(file, 0, SEEK_END); - *length = ftell(file); - fseek(file, 0, SEEK_SET); - - *data = malloc(*length); - if (!*data) { - perror("Failed to allocate memory"); - fclose(file); - return 0; - } - - if (fread(*data, 1, *length, file) != *length) { - perror("Failed to read file"); - free(*data); - fclose(file); - return 0; - } + fseek(file, 0, SEEK_END); + *length = ftell(file); + fseek(file, 0, SEEK_SET); + *data = malloc(*length); + if (!*data) { + perror("Failed to allocate memory"); fclose(file); - return 1; + return 0; + } + + if (fread(*data, 1, *length, file) != *length) { + perror("Failed to read file"); + free(*data); + fclose(file); + return 0; + } + + fclose(file); + return 1; } int main(int argc, char **argv) { - // Initialize logger - idevice_init_logger(Debug, Disabled, NULL); + // Initialize logger + idevice_init_logger(Debug, Disabled, NULL); - // Default values - char *ip = "10.7.0.2"; - char *pairing_file_path = "pairing_file.plist"; - char *udid = NULL; - char *ipa_path = NULL; + // Default values + char *ip = "10.7.0.2"; + char *pairing_file_path = "pairing_file.plist"; + char *udid = NULL; + char *ipa_path = NULL; - // Parse arguments - for (int i = 1; i < argc; i++) { - if (strcmp(argv[i], "--ip") == 0) { - if (i + 1 >= argc) { - printf("Error: Missing IP address argument\n"); - return 1; - } - ip = argv[++i]; - } else if (strcmp(argv[i], "--pairing") == 0) { - if (i + 1 >= argc) { - printf("Error: Missing pairing file argument\n"); - return 1; - } - pairing_file_path = argv[++i]; - } else if (strcmp(argv[i], "--udid") == 0) { - if (i + 1 >= argc) { - printf("Error: Missing UDID argument\n"); - return 1; - } - udid = argv[++i]; - } else if (strcmp(argv[i], "help") == 0) { - print_usage(); - return 0; - } else { - ipa_path = argv[i]; - break; - } - } - - if (!ipa_path) { - print_usage(); + // Parse arguments + for (int i = 1; i < argc; i++) { + if (strcmp(argv[i], "--ip") == 0) { + if (i + 1 >= argc) { + printf("Error: Missing IP address argument\n"); return 1; - } - - // Create the socket address - struct sockaddr_in addr; - memset(&addr, 0, sizeof(addr)); - addr.sin_family = AF_INET; - addr.sin_port = htons(LOCKDOWN_PORT); - if (inet_pton(AF_INET, ip, &addr.sin_addr) != 1) { - fprintf(stderr, "Invalid IP address\n"); + } + ip = argv[++i]; + } else if (strcmp(argv[i], "--pairing") == 0) { + if (i + 1 >= argc) { + printf("Error: Missing pairing file argument\n"); return 1; - } - - // Read pairing file - IdevicePairingFile *pairing_file = NULL; - IdeviceErrorCode err = idevice_pairing_file_read(pairing_file_path, &pairing_file); - if (err != IdeviceSuccess) { - fprintf(stderr, "Failed to read pairing file: %d\n", err); + } + pairing_file_path = argv[++i]; + } else if (strcmp(argv[i], "--udid") == 0) { + if (i + 1 >= argc) { + printf("Error: Missing UDID argument\n"); return 1; - } - - // Create TCP provider - TcpProviderHandle *provider = NULL; - err = idevice_tcp_provider_new((struct sockaddr *)&addr, pairing_file, - "IPAInstaller", &provider); - if (err != IdeviceSuccess) { - fprintf(stderr, "Failed to create TCP provider: %d\n", err); - idevice_pairing_file_free(pairing_file); - return 1; - } - - // Connect to AFC service - AfcClientHandle *afc_client = NULL; - err = afc_client_connect_tcp(provider, &afc_client); - if (err != IdeviceSuccess) { - fprintf(stderr, "Failed to connect to AFC service: %d\n", err); - tcp_provider_free(provider); - return 1; - } - - // Extract filename from path - char *filename = strrchr(ipa_path, '/'); - if (filename == NULL) { - filename = ipa_path; + } + udid = argv[++i]; + } else if (strcmp(argv[i], "help") == 0) { + print_usage(); + return 0; } else { - filename++; // Skip the '/' + ipa_path = argv[i]; + break; } + } - // Create destination path - char dest_path[256]; - snprintf(dest_path, sizeof(dest_path), "/PublicStaging/%s", filename); + if (!ipa_path) { + print_usage(); + return 1; + } - // Upload IPA file - printf("Uploading %s to %s...\n", ipa_path, dest_path); - uint8_t *data = NULL; - size_t length = 0; - if (!read_file(ipa_path, &data, &length)) { - fprintf(stderr, "Failed to read IPA file\n"); - afc_client_free(afc_client); - return 1; - } + // Create the socket address + struct sockaddr_in addr; + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(LOCKDOWN_PORT); + if (inet_pton(AF_INET, ip, &addr.sin_addr) != 1) { + fprintf(stderr, "Invalid IP address\n"); + return 1; + } - AfcFileHandle *file = NULL; - err = afc_file_open(afc_client, dest_path, AfcWrOnly | AfcCreat, &file); - if (err != IdeviceSuccess) { - fprintf(stderr, "Failed to open destination file: %d\n", err); - free(data); - afc_client_free(afc_client); - return 1; - } + // Read pairing file + IdevicePairingFile *pairing_file = NULL; + IdeviceErrorCode err = + idevice_pairing_file_read(pairing_file_path, &pairing_file); + if (err != IdeviceSuccess) { + fprintf(stderr, "Failed to read pairing file: %d\n", err); + return 1; + } - err = afc_file_write(file, data, length); - free(data); - afc_file_close(file); - - if (err != IdeviceSuccess) { - fprintf(stderr, "Failed to write file: %d\n", err); - afc_client_free(afc_client); - return 1; - } - printf("Upload completed successfully\n"); + // Create TCP provider + IdeviceProviderHandle *provider = NULL; + err = idevice_tcp_provider_new((struct sockaddr *)&addr, pairing_file, + "IPAInstaller", &provider); + if (err != IdeviceSuccess) { + fprintf(stderr, "Failed to create TCP provider: %d\n", err); + idevice_pairing_file_free(pairing_file); + return 1; + } - // Connect to installation proxy - InstallationProxyClientHandle *instproxy_client = NULL; - err = installation_proxy_connect_tcp(provider, &instproxy_client); - if (err != IdeviceSuccess) { - fprintf(stderr, "Failed to connect to installation proxy: %d\n", err); - afc_client_free(afc_client); - return 1; - } + // Connect to AFC service + AfcClientHandle *afc_client = NULL; + err = afc_client_connect(provider, &afc_client); + if (err != IdeviceSuccess) { + fprintf(stderr, "Failed to connect to AFC service: %d\n", err); + idevice_provider_free(provider); + return 1; + } - // Install the uploaded IPA - printf("Installing %s...\n", dest_path); - err = installation_proxy_install(instproxy_client, dest_path, NULL); - if (err != IdeviceSuccess) { - fprintf(stderr, "Failed to install IPA: %d\n", err); - } else { - printf("Installation completed successfully\n"); - } + // Extract filename from path + char *filename = strrchr(ipa_path, '/'); + if (filename == NULL) { + filename = ipa_path; + } else { + filename++; // Skip the '/' + } - // Cleanup - installation_proxy_client_free(instproxy_client); + // Create destination path + char dest_path[256]; + snprintf(dest_path, sizeof(dest_path), "/PublicStaging/%s", filename); + + // Upload IPA file + printf("Uploading %s to %s...\n", ipa_path, dest_path); + uint8_t *data = NULL; + size_t length = 0; + if (!read_file(ipa_path, &data, &length)) { + fprintf(stderr, "Failed to read IPA file\n"); afc_client_free(afc_client); - tcp_provider_free(provider); + return 1; + } - return err == IdeviceSuccess ? 0 : 1; + AfcFileHandle *file = NULL; + err = afc_file_open(afc_client, dest_path, AfcWrOnly, &file); + if (err != IdeviceSuccess) { + fprintf(stderr, "Failed to open destination file: %d\n", err); + free(data); + afc_client_free(afc_client); + return 1; + } + + err = afc_file_write(file, data, length); + free(data); + afc_file_close(file); + + if (err != IdeviceSuccess) { + fprintf(stderr, "Failed to write file: %d\n", err); + afc_client_free(afc_client); + return 1; + } + printf("Upload completed successfully\n"); + + // Connect to installation proxy + InstallationProxyClientHandle *instproxy_client = NULL; + err = installation_proxy_connect_tcp(provider, &instproxy_client); + if (err != IdeviceSuccess) { + fprintf(stderr, "Failed to connect to installation proxy: %d\n", err); + afc_client_free(afc_client); + return 1; + } + + // Install the uploaded IPA + printf("Installing %s...\n", dest_path); + err = installation_proxy_install(instproxy_client, dest_path, NULL); + if (err != IdeviceSuccess) { + fprintf(stderr, "Failed to install IPA: %d\n", err); + } else { + printf("Installation completed successfully\n"); + } + + // Cleanup + installation_proxy_client_free(instproxy_client); + afc_client_free(afc_client); + idevice_provider_free(provider); + + return err == IdeviceSuccess ? 0 : 1; } diff --git a/ffi/examples/list_apps.c b/ffi/examples/list_apps.c index 70f12f9..4c9cb9a 100644 --- a/ffi/examples/list_apps.c +++ b/ffi/examples/list_apps.c @@ -27,7 +27,7 @@ int main() { } // Create TCP provider - TcpProviderHandle *provider = NULL; + IdeviceProviderHandle *provider = NULL; err = idevice_tcp_provider_new((struct sockaddr *)&addr, pairing_file, "ExampleProvider", &provider); if (err != IdeviceSuccess) { @@ -41,7 +41,7 @@ int main() { err = installation_proxy_connect_tcp(provider, &client); if (err != IdeviceSuccess) { fprintf(stderr, "Failed to connect to installation proxy: %d\n", err); - tcp_provider_free(provider); + idevice_provider_free(provider); return 1; } @@ -56,7 +56,7 @@ int main() { if (err != IdeviceSuccess) { fprintf(stderr, "Failed to get apps: %d\n", err); installation_proxy_client_free(client); - tcp_provider_free(provider); + idevice_provider_free(provider); return 1; } @@ -79,7 +79,7 @@ int main() { // Cleanup installation_proxy_client_free(client); - tcp_provider_free(provider); + idevice_provider_free(provider); return 0; } diff --git a/ffi/examples/location_simulation.c b/ffi/examples/location_simulation.c index 7532073..bac25a3 100644 --- a/ffi/examples/location_simulation.c +++ b/ffi/examples/location_simulation.c @@ -40,7 +40,7 @@ int main(int argc, char **argv) { } // Create TCP provider - TcpProviderHandle *tcp_provider = NULL; + IdeviceProviderHandle *tcp_provider = NULL; err = idevice_tcp_provider_new((struct sockaddr *)&addr, pairing, "LocationSimCLI", &tcp_provider); if (err != IdeviceSuccess) { @@ -51,8 +51,8 @@ int main(int argc, char **argv) { // Connect to CoreDeviceProxy CoreDeviceProxyHandle *core_device = NULL; - err = core_device_proxy_connect_tcp(tcp_provider, &core_device); - tcp_provider_free(tcp_provider); + err = core_device_proxy_connect(tcp_provider, &core_device); + idevice_provider_free(tcp_provider); idevice_pairing_file_free(pairing); if (err != IdeviceSuccess) { fprintf(stderr, "Failed to connect to CoreDeviceProxy: %d\n", err); @@ -77,62 +77,36 @@ int main(int argc, char **argv) { return 1; } - err = adapter_connect(adapter, rsd_port); + // Connect to RSD port + AdapterStreamHandle *stream = NULL; + err = adapter_connect(adapter, rsd_port, &stream); if (err != IdeviceSuccess) { fprintf(stderr, "Failed to connect to RSD port: %d\n", err); adapter_free(adapter); return 1; } - // Create XPC device - XPCDeviceAdapterHandle *xpc_device = NULL; - err = xpc_device_new(adapter, &xpc_device); + RsdHandshakeHandle *handshake = NULL; + err = rsd_handshake_new(stream, &handshake); if (err != IdeviceSuccess) { - fprintf(stderr, "Failed to create XPC device: %d\n", err); + fprintf(stderr, "Failed to perform RSD handshake: %d\n", err); + adapter_close(stream); adapter_free(adapter); return 1; } - // Get debug proxy service - XPCServiceHandle *dvt_service = NULL; - err = xpc_device_get_service( - xpc_device, "com.apple.instruments.server.services.LocationSimulation", - &dvt_service); - if (err != IdeviceSuccess) { - fprintf(stderr, "Failed to get DVT service: %d\n", err); - xpc_device_free(xpc_device); - return 1; - } - - // Reuse the adapter and connect to debug proxy port - AdapterHandle *debug_adapter = NULL; - err = xpc_device_adapter_into_inner(xpc_device, &debug_adapter); - xpc_device_free(xpc_device); - xpc_service_free(dvt_service); - if (err != IdeviceSuccess) { - fprintf(stderr, "Failed to extract adapter: %d\n", err); - return 1; - } - - adapter_close(debug_adapter); - err = adapter_connect(debug_adapter, dvt_service->port); - if (err != IdeviceSuccess) { - fprintf(stderr, "Failed to connect to debug proxy port: %d\n", err); - adapter_free(debug_adapter); - return 1; - } - // Create RemoteServerClient - RemoteServerAdapterHandle *remote_server = NULL; - err = remote_server_adapter_new(debug_adapter, &remote_server); + RemoteServerHandle *remote_server = NULL; + err = remote_server_connect_rsd(adapter, handshake, &remote_server); if (err != IdeviceSuccess) { fprintf(stderr, "Failed to create remote server: %d\n", err); - adapter_free(debug_adapter); + adapter_free(adapter); + rsd_handshake_free(handshake); return 1; } // Create LocationSimulationClient - LocationSimulationAdapterHandle *location_sim = NULL; + LocationSimulationHandle *location_sim = NULL; err = location_simulation_new(remote_server, &location_sim); if (err != IdeviceSuccess) { fprintf(stderr, "Failed to create location simulation client: %d\n", err); diff --git a/ffi/examples/lockdownd.c b/ffi/examples/lockdownd.c index 5e17877..7dd9520 100644 --- a/ffi/examples/lockdownd.c +++ b/ffi/examples/lockdownd.c @@ -29,7 +29,7 @@ int main() { } // Create TCP provider - TcpProviderHandle *provider = NULL; + IdeviceProviderHandle *provider = NULL; err = idevice_tcp_provider_new((struct sockaddr *)&addr, pairing_file, "LockdowndTest", &provider); if (err != IdeviceSuccess) { @@ -40,10 +40,10 @@ int main() { // Connect to lockdownd LockdowndClientHandle *client = NULL; - err = lockdownd_connect_tcp(provider, &client); + err = lockdownd_connect(provider, &client); if (err != IdeviceSuccess) { fprintf(stderr, "Failed to connect to lockdownd: %d\n", err); - tcp_provider_free(provider); + idevice_provider_free(provider); return 1; } @@ -60,7 +60,7 @@ int main() { if (err != IdeviceSuccess) { fprintf(stderr, "Failed to start session: %d\n", err); lockdownd_client_free(client); - tcp_provider_free(provider); + idevice_provider_free(provider); return 1; } @@ -159,7 +159,7 @@ int main() { // Cleanup lockdownd_client_free(client); - tcp_provider_free(provider); + idevice_provider_free(provider); return 0; } diff --git a/ffi/examples/mount_personalized.c b/ffi/examples/mount_personalized.c index 97c1447..ed81190 100644 --- a/ffi/examples/mount_personalized.c +++ b/ffi/examples/mount_personalized.c @@ -99,7 +99,7 @@ int main(int argc, char **argv) { } // Create TCP provider - TcpProviderHandle *provider = NULL; + IdeviceProviderHandle *provider = NULL; err = idevice_tcp_provider_new((struct sockaddr *)&addr, pairing_file, "ImageMounterTest", &provider); if (err != IdeviceSuccess) { @@ -124,10 +124,10 @@ int main(int argc, char **argv) { // Connect to lockdownd LockdowndClientHandle *lockdown_client = NULL; - err = lockdownd_connect_tcp(provider, &lockdown_client); + err = lockdownd_connect(provider, &lockdown_client); if (err != IdeviceSuccess) { fprintf(stderr, "Failed to connect to lockdownd: %d\n", err); - tcp_provider_free(provider); + idevice_provider_free(provider); free(image); free(trustcache); free(build_manifest); @@ -139,7 +139,7 @@ int main(int argc, char **argv) { if (err != IdeviceSuccess) { fprintf(stderr, "Failed to start session: %d\n", err); lockdownd_client_free(lockdown_client); - tcp_provider_free(provider); + idevice_provider_free(provider); idevice_pairing_file_free(pairing_file_2); free(image); free(trustcache); @@ -155,7 +155,7 @@ int main(int argc, char **argv) { if (err != IdeviceSuccess) { fprintf(stderr, "Failed to get UniqueChipID: %d\n", err); lockdownd_client_free(lockdown_client); - tcp_provider_free(provider); + idevice_provider_free(provider); free(image); free(trustcache); free(build_manifest); @@ -168,11 +168,11 @@ int main(int argc, char **argv) { // Connect to image mounter ImageMounterHandle *mounter_client = NULL; - err = image_mounter_connect_tcp(provider, &mounter_client); + err = image_mounter_connect(provider, &mounter_client); if (err != IdeviceSuccess) { fprintf(stderr, "Failed to connect to image mounter: %d\n", err); lockdownd_client_free(lockdown_client); - tcp_provider_free(provider); + idevice_provider_free(provider); free(image); free(trustcache); free(build_manifest); @@ -180,7 +180,7 @@ int main(int argc, char **argv) { } // Mount personalized image with progress callback - err = image_mounter_mount_personalized_tcp_with_callback( + err = image_mounter_mount_personalized_with_callback( mounter_client, provider, image, image_len, trustcache, trustcache_len, build_manifest, manifest_len, NULL, // info_plist @@ -195,7 +195,7 @@ int main(int argc, char **argv) { // Cleanup image_mounter_free(mounter_client); lockdownd_client_free(lockdown_client); - tcp_provider_free(provider); + idevice_provider_free(provider); free(image); free(trustcache); free(build_manifest); diff --git a/ffi/examples/mounter.c b/ffi/examples/mounter.c index 3b3710e..d8a4f23 100644 --- a/ffi/examples/mounter.c +++ b/ffi/examples/mounter.c @@ -112,7 +112,7 @@ int main(int argc, char **argv) { } // Create TCP provider - TcpProviderHandle *provider = NULL; + IdeviceProviderHandle *provider = NULL; err = idevice_tcp_provider_new((struct sockaddr *)&addr, pairing_file, "ImageMounterTest", &provider); if (err != IdeviceSuccess) { @@ -123,13 +123,13 @@ int main(int argc, char **argv) { // Connect to image mounter ImageMounterHandle *client = NULL; - err = image_mounter_connect_tcp(provider, &client); + err = image_mounter_connect(provider, &client); if (err != IdeviceSuccess) { fprintf(stderr, "Failed to connect to image mounter: %d\n", err); - tcp_provider_free(provider); + idevice_provider_free(provider); return 1; } - tcp_provider_free(provider); + idevice_provider_free(provider); // Process command int success = 1; diff --git a/ffi/examples/process_control.c b/ffi/examples/process_control.c index 3be8c20..2450e19 100644 --- a/ffi/examples/process_control.c +++ b/ffi/examples/process_control.c @@ -43,7 +43,7 @@ int main(int argc, char **argv) { printf("=== Setting up CoreDeviceProxy ===\n"); // Create TCP provider - TcpProviderHandle *tcp_provider = NULL; + IdeviceProviderHandle *tcp_provider = NULL; err = idevice_tcp_provider_new((struct sockaddr *)&addr, pairing, "ProcessControlTest", &tcp_provider); if (err != IdeviceSuccess) { @@ -54,14 +54,14 @@ int main(int argc, char **argv) { // Connect to CoreDeviceProxy CoreDeviceProxyHandle *core_device = NULL; - err = core_device_proxy_connect_tcp(tcp_provider, &core_device); + err = core_device_proxy_connect(tcp_provider, &core_device); if (err != IdeviceSuccess) { fprintf(stderr, "Failed to connect to CoreDeviceProxy: %d\n", err); - tcp_provider_free(tcp_provider); + idevice_provider_free(tcp_provider); idevice_pairing_file_free(pairing); return 1; } - tcp_provider_free(tcp_provider); + idevice_provider_free(tcp_provider); // Get server RSD port uint16_t rsd_port; @@ -89,89 +89,37 @@ int main(int argc, char **argv) { } // Connect to RSD port - err = adapter_connect(adapter, rsd_port); + AdapterStreamHandle *stream = NULL; + err = adapter_connect(adapter, rsd_port, &stream); if (err != IdeviceSuccess) { fprintf(stderr, "Failed to connect to RSD port: %d\n", err); adapter_free(adapter); - core_device_proxy_free(core_device); - idevice_pairing_file_free(pairing); return 1; } - printf("Successfully connected to RSD port\n"); - /***************************************************************** - * XPC Device Setup - *****************************************************************/ - printf("\n=== Setting up XPC Device ===\n"); - - XPCDeviceAdapterHandle *xpc_device = NULL; - err = xpc_device_new(adapter, &xpc_device); + RsdHandshakeHandle *handshake = NULL; + err = rsd_handshake_new(stream, &handshake); if (err != IdeviceSuccess) { - fprintf(stderr, "Failed to create XPC device: %d\n", err); + fprintf(stderr, "Failed to perform RSD handshake: %d\n", err); + adapter_close(stream); adapter_free(adapter); - core_device_proxy_free(core_device); - idevice_pairing_file_free(pairing); return 1; } - /***************************************************************** - * Get DVT Service - *****************************************************************/ - printf("\n=== Getting Debug Proxy Service ===\n"); - - XPCServiceHandle *dvt_service = NULL; - err = xpc_device_get_service(xpc_device, "com.apple.instruments.dtservicehub", - &dvt_service); - if (err != IdeviceSuccess) { - fprintf(stderr, "Failed to get DVT service: %d\n", err); - xpc_device_free(xpc_device); - return 1; - } - printf("Debug Proxy Service Port: %d\n", dvt_service->port); - - /***************************************************************** - * Remote Server Setup - *****************************************************************/ - printf("\n=== Setting up Remote Server ===\n"); - - // Get the adapter back from XPC device - AdapterHandle *debug_adapter = NULL; - err = xpc_device_adapter_into_inner(xpc_device, &debug_adapter); - if (err != IdeviceSuccess) { - fprintf(stderr, "Failed to extract adapter: %d\n", err); - xpc_service_free(dvt_service); - xpc_device_free(xpc_device); - return 1; - } - - // Connect to debug proxy port - adapter_close(debug_adapter); - err = adapter_connect(debug_adapter, dvt_service->port); - if (err != IdeviceSuccess) { - fprintf(stderr, "Failed to connect to debug proxy port: %d\n", err); - adapter_free(debug_adapter); - xpc_service_free(dvt_service); - return 1; - } - printf("Successfully connected to debug proxy port\n"); - // Create RemoteServerClient - RemoteServerAdapterHandle *remote_server = NULL; - err = remote_server_adapter_new(debug_adapter, &remote_server); + RemoteServerHandle *remote_server = NULL; + err = remote_server_connect_rsd(adapter, handshake, &remote_server); if (err != IdeviceSuccess) { fprintf(stderr, "Failed to create remote server: %d\n", err); - adapter_free(debug_adapter); - xpc_service_free(dvt_service); + adapter_free(adapter); + rsd_handshake_free(handshake); return 1; } - /***************************************************************** - * Process Control Test - *****************************************************************/ printf("\n=== Testing Process Control ===\n"); // Create ProcessControlClient - ProcessControlAdapterHandle *process_control = NULL; + ProcessControlHandle *process_control = NULL; err = process_control_new(remote_server, &process_control); if (err != IdeviceSuccess) { fprintf(stderr, "Failed to create process control client: %d\n", err); diff --git a/ffi/examples/remotexpc.c b/ffi/examples/remotexpc.c deleted file mode 100644 index 2efdc5e..0000000 --- a/ffi/examples/remotexpc.c +++ /dev/null @@ -1,204 +0,0 @@ -// Jackson Coxson - -#include "idevice.h" -#include -#include -#include -#include -#include -#include - -void print_service_details(XPCServiceHandle *service) { - printf(" Service Details:\n"); - printf(" Entitlement: %s\n", service->entitlement); - printf(" Port: %d\n", service->port); - printf(" Uses Remote XPC: %s\n", - service->uses_remote_xpc ? "true" : "false"); - printf(" Service Version: %lld\n", service->service_version); - - if (service->features_count > 0) { - printf(" Features:\n"); - for (size_t i = 0; i < service->features_count; i++) { - printf(" - %s\n", service->features[i]); - } - } -} - -int main() { - // Initialize logger - idevice_init_logger(Debug, Disabled, NULL); - - // Create the socket address (replace with your device's IP) - struct sockaddr_in addr; - memset(&addr, 0, sizeof(addr)); - addr.sin_family = AF_INET; - addr.sin_port = htons(LOCKDOWN_PORT); - inet_pton(AF_INET, "10.7.0.2", &addr.sin_addr); - - // Read pairing file (replace with your pairing file path) - IdevicePairingFile *pairing_file = NULL; - IdeviceErrorCode err = - idevice_pairing_file_read("pairing_file.plist", &pairing_file); - if (err != IdeviceSuccess) { - fprintf(stderr, "Failed to read pairing file: %d\n", err); - return 1; - } - - /***************************************************************** - * TCP Provider and CoreDeviceProxy Test - *****************************************************************/ - printf("=== Testing TCP Provider and CoreDeviceProxy ===\n"); - - // Create TCP provider - TcpProviderHandle *tcp_provider = NULL; - err = idevice_tcp_provider_new((struct sockaddr *)&addr, pairing_file, - "CoreDeviceProxyTest", &tcp_provider); - if (err != IdeviceSuccess) { - fprintf(stderr, "Failed to create TCP provider: %d\n", err); - idevice_pairing_file_free(pairing_file); - return 1; - } - - // Connect to CoreDeviceProxy - CoreDeviceProxyHandle *core_device = NULL; - err = core_device_proxy_connect_tcp(tcp_provider, &core_device); - if (err != IdeviceSuccess) { - fprintf(stderr, "Failed to connect to CoreDeviceProxy: %d\n", err); - tcp_provider_free(tcp_provider); - return 1; - } - tcp_provider_free(tcp_provider); - - // Get client parameters - uint16_t mtu; - char *address = NULL; - char *netmask = NULL; - err = core_device_proxy_get_client_parameters(core_device, &mtu, &address, - &netmask); - if (err != IdeviceSuccess) { - fprintf(stderr, "Failed to get client parameters: %d\n", err); - core_device_proxy_free(core_device); - return 1; - } - printf("Client Parameters:\n"); - printf(" MTU: %d\n", mtu); - printf(" Address: %s\n", address); - printf(" Netmask: %s\n", netmask); - idevice_string_free(address); - idevice_string_free(netmask); - - // Get server address - char *server_address = NULL; - err = core_device_proxy_get_server_address(core_device, &server_address); - if (err != IdeviceSuccess) { - fprintf(stderr, "Failed to get server address: %d\n", err); - core_device_proxy_free(core_device); - return 1; - } - printf("Server Address: %s\n", server_address); - idevice_string_free(server_address); - - // Get server RSD port - uint16_t rsd_port; - err = core_device_proxy_get_server_rsd_port(core_device, &rsd_port); - if (err != IdeviceSuccess) { - fprintf(stderr, "Failed to get server RSD port: %d\n", err); - core_device_proxy_free(core_device); - return 1; - } - printf("Server RSD Port: %d\n", rsd_port); - - // Create TCP tunnel adapter - AdapterHandle *adapter = NULL; - err = core_device_proxy_create_tcp_adapter(core_device, &adapter); - if (err != IdeviceSuccess) { - fprintf(stderr, "Failed to create TCP adapter: %d\n", err); - } else { - printf("Successfully created TCP tunnel adapter\n"); - } - err = adapter_connect(adapter, rsd_port); - if (err != IdeviceSuccess) { - fprintf(stderr, "Failed to connect to RSD port: %d\n", err); - } else { - printf("Successfully connected to RSD port\n"); - } - - /***************************************************************** - * XPC Device Test - *****************************************************************/ - printf("\n=== Testing XPC Device ===\n"); - - // Create XPC device - XPCDeviceAdapterHandle *xpc_device = NULL; - err = xpc_device_new(adapter, &xpc_device); - if (err != IdeviceSuccess) { - fprintf(stderr, "Failed to create XPC device: %d\n", err); - core_device_proxy_free(core_device); - return 1; - } - - // List all services - char **service_names = NULL; - size_t service_count = 0; - err = - xpc_device_get_service_names(xpc_device, &service_names, &service_count); - if (err != IdeviceSuccess) { - fprintf(stderr, "Failed to get service names: %d\n", err); - xpc_device_free(xpc_device); - return 1; - } - - printf("Available Services (%zu):\n", service_count); - for (size_t i = 0; i < service_count; i++) { - printf("- %s\n", service_names[i]); - - // Get service details for each service - XPCServiceHandle *service = NULL; - err = xpc_device_get_service(xpc_device, service_names[i], &service); - if (err == IdeviceSuccess) { - print_service_details(service); - xpc_service_free(service); - } else { - printf(" Failed to get service details: %d\n", err); - } - } - xpc_device_free_service_names(service_names, service_count); - - // Test getting a specific service - const char *test_service_name = "com.apple.internal.dt.remote.debugproxy"; - XPCServiceHandle *test_service = NULL; - err = xpc_device_get_service(xpc_device, test_service_name, &test_service); - if (err == IdeviceSuccess) { - printf("\nSuccessfully retrieved service '%s':\n", test_service_name); - print_service_details(test_service); - xpc_service_free(test_service); - } else { - printf("\nFailed to get service '%s': %d\n", test_service_name, err); - } - - /***************************************************************** - * Adapter return - *****************************************************************/ - AdapterHandle *adapter_return = NULL; - err = xpc_device_adapter_into_inner(xpc_device, &adapter_return); - if (err != IdeviceSuccess) { - fprintf(stderr, "Failed to extract adapter: %d\n", err); - } else { - printf("Successfully extracted adapter\n"); - } - - err = adapter_close(adapter_return); - if (err != IdeviceSuccess) { - fprintf(stderr, "Failed to close adapter port: %d\n", err); - } else { - printf("Successfully closed adapter port\n"); - } - - /***************************************************************** - * Cleanup - *****************************************************************/ - adapter_free(adapter_return); - - printf("\nAll tests completed successfully!\n"); - return 0; -} From b8e2b115a595ad025c15af11bc28307f92c37f7a Mon Sep 17 00:00:00 2001 From: Jackson Coxson Date: Mon, 26 May 2025 16:34:15 -0600 Subject: [PATCH 16/16] Use opaque handle for readwrite objects --- ffi/src/adapter.rs | 16 +++++++--- ffi/src/debug_proxy.rs | 46 ++++++++++++++++++++++++++- ffi/src/lib.rs | 7 +++- ffi/src/provider.rs | 2 +- ffi/src/remote_server.rs | 69 ++++++++++++++++++++++++++++++++++------ ffi/src/rsd.rs | 18 +++++++---- 6 files changed, 134 insertions(+), 24 deletions(-) diff --git a/ffi/src/adapter.rs b/ffi/src/adapter.rs index 20fdb88..306237b 100644 --- a/ffi/src/adapter.rs +++ b/ffi/src/adapter.rs @@ -1,11 +1,11 @@ // Jackson Coxson -use std::ffi::{CString, c_char}; +use std::ffi::{CStr, c_char}; use idevice::tcp::stream::AdapterStream; use crate::core_device_proxy::AdapterHandle; -use crate::{IdeviceErrorCode, RUNTIME}; +use crate::{IdeviceErrorCode, RUNTIME, ReadWriteOpaque}; pub struct AdapterStreamHandle<'a>(pub AdapterStream<'a>); @@ -27,7 +27,7 @@ pub struct AdapterStreamHandle<'a>(pub AdapterStream<'a>); pub unsafe extern "C" fn adapter_connect( adapter_handle: *mut AdapterHandle, port: u16, - stream_handle: *mut *mut AdapterStreamHandle, + stream_handle: *mut *mut ReadWriteOpaque, ) -> IdeviceErrorCode { if adapter_handle.is_null() || stream_handle.is_null() { return IdeviceErrorCode::InvalidArg; @@ -37,7 +37,13 @@ pub unsafe extern "C" fn adapter_connect( let res = RUNTIME.block_on(async move { AdapterStream::connect(adapter, port).await }); match res { - Ok(_) => IdeviceErrorCode::IdeviceSuccess, + Ok(r) => { + let boxed = Box::new(ReadWriteOpaque { + inner: Some(Box::new(r)), + }); + unsafe { *stream_handle = Box::into_raw(boxed) }; + IdeviceErrorCode::IdeviceSuccess + } Err(e) => { log::error!("Adapter connect failed: {}", e); IdeviceErrorCode::AdapterIOFailed @@ -67,7 +73,7 @@ pub unsafe extern "C" fn adapter_pcap( } let adapter = unsafe { &mut (*handle).0 }; - let c_str = unsafe { CString::from_raw(path as *mut c_char) }; + let c_str = unsafe { CStr::from_ptr(path) }; let path_str = match c_str.to_str() { Ok(s) => s, Err(_) => return IdeviceErrorCode::InvalidArg, diff --git a/ffi/src/debug_proxy.rs b/ffi/src/debug_proxy.rs index 800c4dd..cb1fc85 100644 --- a/ffi/src/debug_proxy.rs +++ b/ffi/src/debug_proxy.rs @@ -4,9 +4,12 @@ use std::ffi::{CStr, CString, c_char}; use std::os::raw::c_int; use std::ptr; -use idevice::ReadWrite; use idevice::debug_proxy::{DebugProxyClient, DebugserverCommand}; +use idevice::tcp::stream::AdapterStream; +use idevice::{IdeviceError, ReadWrite, RsdService}; +use crate::core_device_proxy::AdapterHandle; +use crate::rsd::RsdHandshakeHandle; use crate::{IdeviceErrorCode, RUNTIME}; /// Opaque handle to a DebugProxyClient @@ -112,6 +115,47 @@ pub unsafe extern "C" fn debugserver_command_free(command: *mut DebugserverComma } } +/// Creates a new DebugProxyClient +/// +/// # Arguments +/// * [`provider`] - An adapter created by this library +/// * [`handshake`] - An RSD handshake from the same provider +/// +/// # Returns +/// An error code indicating success or failure +/// +/// # Safety +/// `provider` must be a valid pointer to a handle allocated by this library +/// `handshake` must be a valid pointer to a location where the handle will be stored +#[unsafe(no_mangle)] +pub unsafe extern "C" fn debug_proxy_connect_rsd( + provider: *mut AdapterHandle, + handshake: *mut RsdHandshakeHandle, + handle: *mut *mut DebugProxyHandle, +) -> IdeviceErrorCode { + if provider.is_null() || handshake.is_null() || handshake.is_null() { + return IdeviceErrorCode::InvalidArg; + } + let res: Result, IdeviceError> = RUNTIME.block_on(async move { + let provider_ref = unsafe { &mut (*provider).0 }; + let handshake_ref = unsafe { &mut (*handshake).0 }; + + // Connect using the reference + DebugProxyClient::connect_rsd(provider_ref, handshake_ref).await + }); + + match res { + Ok(d) => { + let boxed = Box::new(DebugProxyHandle(DebugProxyClient::new(Box::new( + d.into_inner(), + )))); + unsafe { *handle = Box::into_raw(boxed) }; + IdeviceErrorCode::IdeviceSuccess + } + Err(e) => e.into(), + } +} + /// Creates a new DebugProxyClient /// /// # Arguments diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index abb86bb..14cf92f 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -42,7 +42,7 @@ pub mod util; pub use errors::*; pub use pairing_file::*; -use idevice::{Idevice, IdeviceSocket}; +use idevice::{Idevice, IdeviceSocket, ReadWrite}; use once_cell::sync::Lazy; use std::ffi::{CStr, CString, c_char}; use tokio::runtime::{self, Runtime}; @@ -57,6 +57,11 @@ static RUNTIME: Lazy = Lazy::new(|| { pub const LOCKDOWN_PORT: u16 = 62078; +#[repr(C)] +pub struct ReadWriteOpaque { + pub inner: Option>, +} + /// Opaque C-compatible handle to an Idevice connection pub struct IdeviceHandle(pub Idevice); pub struct IdeviceSocketHandle(IdeviceSocket); diff --git a/ffi/src/provider.rs b/ffi/src/provider.rs index 0fe624b..493c567 100644 --- a/ffi/src/provider.rs +++ b/ffi/src/provider.rs @@ -21,7 +21,7 @@ pub struct IdeviceProviderHandle(pub Box); /// /// # Safety /// `ip` must be a valid sockaddr -/// `pairing_file` must never be used again +/// `pairing_file` is consumed must never be used again /// `label` must be a valid Cstr /// `provider` must be a valid, non-null pointer to a location where the handle will be stored #[cfg(feature = "tcp")] diff --git a/ffi/src/remote_server.rs b/ffi/src/remote_server.rs index 1570dd5..f8385b3 100644 --- a/ffi/src/remote_server.rs +++ b/ffi/src/remote_server.rs @@ -1,8 +1,11 @@ // Jackson Coxson -use crate::{IdeviceErrorCode, RUNTIME}; +use crate::core_device_proxy::AdapterHandle; +use crate::rsd::RsdHandshakeHandle; +use crate::{IdeviceErrorCode, RUNTIME, ReadWriteOpaque}; use idevice::dvt::remote_server::RemoteServerClient; -use idevice::{IdeviceError, ReadWrite}; +use idevice::tcp::stream::AdapterStream; +use idevice::{IdeviceError, ReadWrite, RsdService}; /// Opaque handle to a RemoteServerClient pub struct RemoteServerHandle(pub RemoteServerClient>); @@ -17,25 +20,29 @@ pub struct RemoteServerHandle(pub RemoteServerClient>); /// An error code indicating success or failure /// /// # Safety -/// `socket` must be a valid pointer to a handle allocated by this library +/// `socket` must be a valid pointer to a handle allocated by this library. It is consumed and may +/// not be used again. /// `handle` must be a valid pointer to a location where the handle will be stored #[unsafe(no_mangle)] pub unsafe extern "C" fn remote_server_new( - socket: *mut Box, + socket: *mut ReadWriteOpaque, handle: *mut *mut RemoteServerHandle, ) -> IdeviceErrorCode { if socket.is_null() { return IdeviceErrorCode::InvalidArg; } - let connection = unsafe { Box::from_raw(socket) }; + let wrapper = unsafe { &mut *socket }; let res: Result>, IdeviceError> = - RUNTIME.block_on(async move { - let mut client = RemoteServerClient::new(*connection); - client.read_message(0).await?; // Until Message has bindings, we'll do the first read - Ok(client) - }); + match wrapper.inner.take() { + Some(stream) => RUNTIME.block_on(async move { + let mut client = RemoteServerClient::new(stream); + client.read_message(0).await?; + Ok(client) + }), + None => return IdeviceErrorCode::InvalidArg, + }; match res { Ok(client) => { @@ -47,6 +54,48 @@ pub unsafe extern "C" fn remote_server_new( } } +/// Creates a new RemoteServerClient from a handshake and adapter +/// +/// # Arguments +/// * [`provider`] - An adapter created by this library +/// * [`handshake`] - An RSD handshake from the same provider +/// +/// # Returns +/// An error code indicating success or failure +/// +/// # Safety +/// `provider` must be a valid pointer to a handle allocated by this library +/// `handshake` must be a valid pointer to a location where the handle will be stored +#[unsafe(no_mangle)] +pub unsafe extern "C" fn remote_server_connect_rsd( + provider: *mut AdapterHandle, + handshake: *mut RsdHandshakeHandle, + handle: *mut *mut RemoteServerHandle, +) -> IdeviceErrorCode { + if provider.is_null() || handshake.is_null() || handshake.is_null() { + return IdeviceErrorCode::InvalidArg; + } + let res: Result, IdeviceError> = + RUNTIME.block_on(async move { + let provider_ref = unsafe { &mut (*provider).0 }; + let handshake_ref = unsafe { &mut (*handshake).0 }; + + // Connect using the reference + RemoteServerClient::connect_rsd(provider_ref, handshake_ref).await + }); + + match res { + Ok(d) => { + let boxed = Box::new(RemoteServerHandle(RemoteServerClient::new(Box::new( + d.into_inner(), + )))); + unsafe { *handle = Box::into_raw(boxed) }; + IdeviceErrorCode::IdeviceSuccess + } + Err(e) => e.into(), + } +} + /// Frees a RemoteServerClient handle /// /// # Arguments diff --git a/ffi/src/rsd.rs b/ffi/src/rsd.rs index 44c41ec..83d7e5c 100644 --- a/ffi/src/rsd.rs +++ b/ffi/src/rsd.rs @@ -5,9 +5,9 @@ use std::ffi::{CStr, CString}; use std::ptr; -use idevice::{ReadWrite, rsd::RsdHandshake}; +use idevice::rsd::RsdHandshake; -use crate::{IdeviceErrorCode, RUNTIME}; +use crate::{IdeviceErrorCode, RUNTIME, ReadWriteOpaque}; /// Opaque handle to an RsdHandshake pub struct RsdHandshakeHandle(pub RsdHandshake); @@ -50,20 +50,26 @@ pub struct CRsdServiceArray { /// An error code indicating success or failure /// /// # Safety -/// `socket` must be a valid pointer to a ReadWrite handle allocated by this library +/// `socket` must be a valid pointer to a ReadWrite handle allocated by this library. It is +/// consumed and may not be used again. /// `handle` must be a valid pointer to a location where the handle will be stored #[unsafe(no_mangle)] pub unsafe extern "C" fn rsd_handshake_new( - socket: *mut Box, + socket: *mut ReadWriteOpaque, handle: *mut *mut RsdHandshakeHandle, ) -> IdeviceErrorCode { if socket.is_null() || handle.is_null() { return IdeviceErrorCode::InvalidArg; } - let connection = unsafe { Box::from_raw(socket) }; + let wrapper = unsafe { &mut *socket }; - let res = RUNTIME.block_on(async move { RsdHandshake::new(*connection).await }); + let res = match wrapper.inner.take() { + Some(mut w) => RUNTIME.block_on(async move { RsdHandshake::new(w.as_mut()).await }), + None => { + return IdeviceErrorCode::InvalidArg; + } + }; match res { Ok(handshake) => {