mirror of
https://github.com/jkcoxson/idevice.git
synced 2026-03-02 14:36:16 +01:00
Write new XPC implementation
This commit is contained in:
@@ -509,6 +509,34 @@ pub enum IdeviceError {
|
|||||||
#[error("Unintialized stream ID")]
|
#[error("Unintialized stream ID")]
|
||||||
UninitializedStreamId,
|
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")]
|
#[cfg(feature = "dvt")]
|
||||||
#[error("NSKeyedArchive error")]
|
#[error("NSKeyedArchive error")]
|
||||||
NsKeyedArchiveError(#[from] ns_keyed_archive::ConverterError),
|
NsKeyedArchiveError(#[from] ns_keyed_archive::ConverterError),
|
||||||
|
|||||||
467
idevice/src/services/xpc/format.rs
Normal file
467
idevice/src/services/xpc/format.rs
Normal file
@@ -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<XPCFlag> 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<u32> for XPCType {
|
||||||
|
type Error = IdeviceError;
|
||||||
|
|
||||||
|
fn try_from(value: u32) -> Result<Self, Self::Error> {
|
||||||
|
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<String, XPCObject>;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub enum XPCObject {
|
||||||
|
Bool(bool),
|
||||||
|
Dictionary(Dictionary),
|
||||||
|
Array(Vec<XPCObject>),
|
||||||
|
|
||||||
|
Int64(i64),
|
||||||
|
UInt64(u64),
|
||||||
|
|
||||||
|
String(String),
|
||||||
|
Data(Vec<u8>),
|
||||||
|
Uuid(uuid::Uuid),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<plist::Value> 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<Vec<u8>, 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<u8>) -> 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<Self, IdeviceError> {
|
||||||
|
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<Self, IdeviceError> {
|
||||||
|
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<Self>> {
|
||||||
|
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<i64> {
|
||||||
|
match self {
|
||||||
|
XPCObject::String(s) => s.parse().ok(),
|
||||||
|
XPCObject::Int64(v) => Some(*v),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_unsigned_integer(&self) -> Option<u64> {
|
||||||
|
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<Dictionary> for XPCObject {
|
||||||
|
fn from(value: Dictionary) -> Self {
|
||||||
|
XPCObject::Dictionary(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct XPCMessage {
|
||||||
|
pub flags: u32,
|
||||||
|
pub message: Option<XPCObject>,
|
||||||
|
pub message_id: Option<u64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl XPCMessage {
|
||||||
|
pub fn new(
|
||||||
|
flags: Option<XPCFlag>,
|
||||||
|
message: Option<XPCObject>,
|
||||||
|
message_id: Option<u64>,
|
||||||
|
) -> XPCMessage {
|
||||||
|
XPCMessage {
|
||||||
|
flags: flags.unwrap_or(XPCFlag::AlwaysSet).into(),
|
||||||
|
message,
|
||||||
|
message_id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn decode(data: &[u8]) -> Result<XPCMessage, IdeviceError> {
|
||||||
|
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<Vec<u8>, 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,6 +7,8 @@ pub trait HttpFrame {
|
|||||||
fn serialize(&self) -> Vec<u8>;
|
fn serialize(&self) -> Vec<u8>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
#[allow(dead_code)] // we don't care about frames from the device
|
||||||
pub enum Frame {
|
pub enum Frame {
|
||||||
Settings(SettingsFrame),
|
Settings(SettingsFrame),
|
||||||
WindowUpdate(WindowUpdateFrame),
|
WindowUpdate(WindowUpdateFrame),
|
||||||
@@ -15,10 +17,10 @@ pub enum Frame {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Frame {
|
impl Frame {
|
||||||
pub async fn next(socket: &mut impl ReadWrite) -> Result<Self, IdeviceError> {
|
pub async fn next(mut socket: &mut impl ReadWrite) -> Result<Self, IdeviceError> {
|
||||||
// Read the len of the frame
|
// Read the len of the frame
|
||||||
let mut buf = [0u8; 3];
|
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]]);
|
let frame_len = u32::from_be_bytes([0x00, buf[0], buf[1], buf[2]]);
|
||||||
|
|
||||||
// Read the fields
|
// Read the fields
|
||||||
@@ -26,8 +28,8 @@ impl Frame {
|
|||||||
let flags = socket.read_u8().await?;
|
let flags = socket.read_u8().await?;
|
||||||
let stream_id = socket.read_u32().await?;
|
let stream_id = socket.read_u32().await?;
|
||||||
|
|
||||||
let body = vec![0; frame_len as usize];
|
let mut body = vec![0; frame_len as usize];
|
||||||
socket.read_exact(&mut buf).await?;
|
socket.read_exact(&mut body).await?;
|
||||||
|
|
||||||
Ok(match frame_type {
|
Ok(match frame_type {
|
||||||
0x00 => {
|
0x00 => {
|
||||||
@@ -41,6 +43,7 @@ impl Frame {
|
|||||||
// headers
|
// headers
|
||||||
Self::Headers(HeadersFrame { stream_id })
|
Self::Headers(HeadersFrame { stream_id })
|
||||||
}
|
}
|
||||||
|
0x03 => return Err(IdeviceError::HttpStreamReset),
|
||||||
0x04 => {
|
0x04 => {
|
||||||
// settings
|
// settings
|
||||||
let mut body = std::io::Cursor::new(body);
|
let mut body = std::io::Cursor::new(body);
|
||||||
@@ -67,6 +70,14 @@ impl Frame {
|
|||||||
flags,
|
flags,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
0x07 => {
|
||||||
|
let msg = if body.len() < 8 {
|
||||||
|
"<MISSING>".to_string()
|
||||||
|
} else {
|
||||||
|
String::from_utf8_lossy(&body[8..]).to_string()
|
||||||
|
};
|
||||||
|
return Err(IdeviceError::HttpGoAway(msg));
|
||||||
|
}
|
||||||
0x08 => {
|
0x08 => {
|
||||||
// window update
|
// window update
|
||||||
if body.len() != 4 {
|
if body.len() != 4 {
|
||||||
@@ -86,23 +97,19 @@ impl Frame {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
pub struct SettingsFrame {
|
pub struct SettingsFrame {
|
||||||
pub settings: Vec<Setting>,
|
pub settings: Vec<Setting>,
|
||||||
pub stream_id: u32,
|
pub stream_id: u32,
|
||||||
pub flags: u8,
|
pub flags: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
pub enum Setting {
|
pub enum Setting {
|
||||||
MaxConcurrentStreams(u32),
|
MaxConcurrentStreams(u32),
|
||||||
InitialWindowSize(u32),
|
InitialWindowSize(u32),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SettingsFrame {
|
|
||||||
pub fn ack(&mut self) {
|
|
||||||
self.flags = 1; // this seems to be the only http flag used
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Setting {
|
impl Setting {
|
||||||
fn serialize(&self) -> Vec<u8> {
|
fn serialize(&self) -> Vec<u8> {
|
||||||
match self {
|
match self {
|
||||||
@@ -142,6 +149,7 @@ impl HttpFrame for SettingsFrame {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
pub struct WindowUpdateFrame {
|
pub struct WindowUpdateFrame {
|
||||||
pub increment_size: u32,
|
pub increment_size: u32,
|
||||||
pub stream_id: 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.
|
/// We don't actually care about this frame according to spec. This is just to open new channels.
|
||||||
pub struct HeadersFrame {
|
pub struct HeadersFrame {
|
||||||
pub stream_id: u32,
|
pub stream_id: u32,
|
||||||
@@ -169,6 +178,7 @@ impl HttpFrame for HeadersFrame {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
pub struct DataFrame {
|
pub struct DataFrame {
|
||||||
pub stream_id: u32,
|
pub stream_id: u32,
|
||||||
pub payload: Vec<u8>,
|
pub payload: Vec<u8>,
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
// Jackson Coxson
|
// Jackson Coxson
|
||||||
|
|
||||||
use frame::HttpFrame;
|
use frame::HttpFrame;
|
||||||
use log::warn;
|
use log::{debug, warn};
|
||||||
use std::collections::{HashMap, VecDeque};
|
use std::collections::{HashMap, VecDeque};
|
||||||
use tokio::io::AsyncWriteExt;
|
use tokio::io::AsyncWriteExt;
|
||||||
|
|
||||||
use crate::{IdeviceError, ReadWrite};
|
use crate::{IdeviceError, ReadWrite};
|
||||||
|
|
||||||
mod frame;
|
pub mod frame;
|
||||||
pub use frame::Setting;
|
pub use frame::Setting;
|
||||||
|
|
||||||
const HTTP2_MAGIC: &[u8] = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n".as_bytes();
|
const HTTP2_MAGIC: &[u8] = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n".as_bytes();
|
||||||
@@ -40,6 +40,7 @@ impl<R: ReadWrite> Http2Client<R> {
|
|||||||
}
|
}
|
||||||
.serialize();
|
.serialize();
|
||||||
self.inner.write_all(&frame).await?;
|
self.inner.write_all(&frame).await?;
|
||||||
|
self.inner.flush().await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,6 +55,7 @@ impl<R: ReadWrite> Http2Client<R> {
|
|||||||
}
|
}
|
||||||
.serialize();
|
.serialize();
|
||||||
self.inner.write_all(&frame).await?;
|
self.inner.write_all(&frame).await?;
|
||||||
|
self.inner.flush().await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,25 +63,34 @@ impl<R: ReadWrite> Http2Client<R> {
|
|||||||
self.cache.insert(stream_id, VecDeque::new());
|
self.cache.insert(stream_id, VecDeque::new());
|
||||||
let frame = frame::HeadersFrame { stream_id }.serialize();
|
let frame = frame::HeadersFrame { stream_id }.serialize();
|
||||||
self.inner.write_all(&frame).await?;
|
self.inner.write_all(&frame).await?;
|
||||||
|
self.inner.flush().await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn send(&mut self, payload: Vec<u8>, stream_id: u32) -> Result<(), IdeviceError> {
|
||||||
|
let frame = frame::DataFrame { stream_id, payload }.serialize();
|
||||||
|
self.inner.write_all(&frame).await?;
|
||||||
|
self.inner.flush().await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn read(&mut self, stream_id: u32) -> Result<Vec<u8>, IdeviceError> {
|
pub async fn read(&mut self, stream_id: u32) -> Result<Vec<u8>, IdeviceError> {
|
||||||
// See if we already have a cached message from another read
|
// See if we already have a cached message from another read
|
||||||
let c = match self.cache.get_mut(&stream_id) {
|
match self.cache.get_mut(&stream_id) {
|
||||||
Some(c) => c,
|
Some(c) => {
|
||||||
|
if let Some(d) = c.pop_front() {
|
||||||
|
return Ok(d);
|
||||||
|
}
|
||||||
|
}
|
||||||
None => {
|
None => {
|
||||||
warn!("Requested stream ID is not in cache");
|
self.cache.insert(stream_id, VecDeque::new());
|
||||||
return Err(IdeviceError::UninitializedStreamId);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if let Some(d) = c.pop_front() {
|
|
||||||
return Ok(d);
|
|
||||||
}
|
|
||||||
|
|
||||||
// handle packets until we get what we want
|
// handle packets until we get what we want
|
||||||
loop {
|
loop {
|
||||||
let frame = frame::Frame::next(&mut self.inner).await?;
|
let frame = frame::Frame::next(&mut self.inner).await?;
|
||||||
|
// debug!("Got frame: {frame:#?}");
|
||||||
match frame {
|
match frame {
|
||||||
frame::Frame::Settings(settings_frame) => {
|
frame::Frame::Settings(settings_frame) => {
|
||||||
if settings_frame.flags != 1 {
|
if settings_frame.flags != 1 {
|
||||||
@@ -91,16 +102,25 @@ impl<R: ReadWrite> Http2Client<R> {
|
|||||||
}
|
}
|
||||||
.serialize();
|
.serialize();
|
||||||
self.inner.write_all(&frame).await?;
|
self.inner.write_all(&frame).await?;
|
||||||
|
self.inner.flush().await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
frame::Frame::Data(data_frame) => {
|
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 {
|
if data_frame.stream_id == stream_id {
|
||||||
return Ok(data_frame.payload);
|
return Ok(data_frame.payload);
|
||||||
} else {
|
} else {
|
||||||
let c = match self.cache.get_mut(&data_frame.stream_id) {
|
let c = match self.cache.get_mut(&data_frame.stream_id) {
|
||||||
Some(c) => c,
|
Some(c) => c,
|
||||||
None => {
|
None => {
|
||||||
warn!("Received message for stream ID not in cache");
|
warn!(
|
||||||
|
"Received message for stream ID {} not in cache",
|
||||||
|
data_frame.stream_id
|
||||||
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,118 @@
|
|||||||
// Jackson Coxson
|
// Jackson Coxson
|
||||||
|
|
||||||
|
use http2::Setting;
|
||||||
|
use log::debug;
|
||||||
|
|
||||||
|
use crate::{IdeviceError, ReadWrite};
|
||||||
|
|
||||||
|
mod format;
|
||||||
mod http2;
|
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<R: ReadWrite> {
|
||||||
|
h2_client: http2::Http2Client<R>,
|
||||||
|
root_id: u64,
|
||||||
|
reply_id: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R: ReadWrite> RemoteXpcClient<R> {
|
||||||
|
pub async fn new(socket: R) -> Result<Self, IdeviceError> {
|
||||||
|
Ok(Self {
|
||||||
|
h2_client: http2::Http2Client::new(socket).await?,
|
||||||
|
root_id: 1,
|
||||||
|
reply_id: 1,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn do_handshake(&mut self) -> Result<plist::Value, IdeviceError> {
|
||||||
|
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<plist::Value, IdeviceError> {
|
||||||
|
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(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ async fn main() {
|
|||||||
println!("rsd port: {}", tun_proxy.handshake.server_rsd_port);
|
println!("rsd port: {}", tun_proxy.handshake.server_rsd_port);
|
||||||
println!("-----------------------------");
|
println!("-----------------------------");
|
||||||
|
|
||||||
let mut buf = vec![0; 1500];
|
let mut buf = vec![0; 20_000]; // XPC is big lol
|
||||||
loop {
|
loop {
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
Ok(len) = async_dev.recv(&mut buf) => {
|
Ok(len) = async_dev.recv(&mut buf) => {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
// Print out all the RemoteXPC services
|
// Print out all the RemoteXPC services
|
||||||
|
|
||||||
use clap::{Arg, Command};
|
use clap::{Arg, Command};
|
||||||
use idevice::{core_device_proxy::CoreDeviceProxy, xpc::XPCDevice, IdeviceService};
|
use idevice::{core_device_proxy::CoreDeviceProxy, xpc::RemoteXpcClient, IdeviceService};
|
||||||
|
|
||||||
mod common;
|
mod common;
|
||||||
|
|
||||||
@@ -66,7 +66,7 @@ async fn main() {
|
|||||||
adapter.connect(rsd_port).await.expect("no RSD connect");
|
adapter.connect(rsd_port).await.expect("no RSD connect");
|
||||||
|
|
||||||
// Make the connection to RemoteXPC
|
// 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);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user