From 3e647bcb6d26e24664e1e909c5ce880d5759e8fd Mon Sep 17 00:00:00 2001 From: Jackson Coxson Date: Fri, 24 Jan 2025 16:50:48 -0700 Subject: [PATCH] Import DebianArch xpc library --- Cargo.lock | 32 ++++ Cargo.toml | 5 + src/http2/error.rs | 62 +++++++ src/http2/h2.rs | 287 ++++++++++++++++++++++++++++ src/http2/mod.rs | 218 ++++++++++++++++++++++ src/http2/padded.rs | 12 ++ src/lib.rs | 4 + src/xpc/cdtunnel.rs | 28 +++ src/xpc/error.rs | 103 +++++++++++ src/xpc/format.rs | 441 ++++++++++++++++++++++++++++++++++++++++++++ src/xpc/mod.rs | 150 +++++++++++++++ 11 files changed, 1342 insertions(+) create mode 100644 src/http2/error.rs create mode 100644 src/http2/h2.rs create mode 100644 src/http2/mod.rs create mode 100644 src/http2/padded.rs create mode 100644 src/xpc/cdtunnel.rs create mode 100644 src/xpc/error.rs create mode 100644 src/xpc/format.rs create mode 100644 src/xpc/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 1294ec6..010f80c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -75,6 +75,17 @@ dependencies = [ "windows-sys 0.59.0", ] +[[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", +] + [[package]] name = "autocfg" version = "1.4.0" @@ -426,7 +437,11 @@ dependencies = [ name = "idevice" version = "0.1.7" dependencies = [ + "async-recursion", + "base64", "env_logger", + "indexmap", + "json", "log", "openssl", "plist", @@ -436,6 +451,7 @@ dependencies = [ "tokio", "tokio-openssl", "ureq", + "uuid", ] [[package]] @@ -467,6 +483,7 @@ checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" dependencies = [ "equivalent", "hashbrown", + "serde", ] [[package]] @@ -481,6 +498,12 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" +[[package]] +name = "json" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "078e285eafdfb6c4b434e0d31e8cfcb5115b651496faca5749b88fafd4f23bfd" + [[package]] name = "libc" version = "0.2.169" @@ -1044,6 +1067,15 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "uuid" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3758f5e68192bb96cc8f9b7e2c2cfdabb435499a28499a42f8f984092adad4b" +dependencies = [ + "serde", +] + [[package]] name = "vcpkg" version = "0.2.15" diff --git a/Cargo.toml b/Cargo.toml index cb310f1..a8635a1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,6 +36,11 @@ thiserror = { version = "2" } log = { version = "0.4" } env_logger = { version = "0.11" } openssl = { version = "0.10" } +json = { version = "0.12" } +indexmap = { version = "2.7", features = ["serde"] } +uuid = { version = "1.12", features = ["serde"] } +async-recursion = { version = "1.1" } +base64 = { version = "0.22" } # Binary dependencies sha2 = { version = "0.10", optional = true } diff --git a/src/http2/error.rs b/src/http2/error.rs new file mode 100644 index 0000000..f067aaf --- /dev/null +++ b/src/http2/error.rs @@ -0,0 +1,62 @@ +// 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/src/http2/h2.rs b/src/http2/h2.rs new file mode 100644 index 0000000..b57e387 --- /dev/null +++ b/src/http2/h2.rs @@ -0,0 +1,287 @@ +// 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 new(stream_id: u32, flags: u8, frame_type: FrameType) -> Self { + Self { + stream_id, + flags, + frame_type, + body: Vec::new(), + } + } + + pub fn set_body(&mut self, body: Vec) { + self.body = body; + } + + 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 HEADER_TABLE_SIZE: u16 = 0x01; + pub const ENABLE_PUSH: u16 = 0x02; + pub const MAX_CONCURRENT_STREAMS: u16 = 0x03; + pub const INITIAL_WINDOW_SIZE: u16 = 0x04; + pub const MAX_FRAME_SIZE: u16 = 0x05; + pub const MAX_HEADER_LIST_SIZE: u16 = 0x06; + pub const ENABLE_CONNECT_PROTOCOL: u16 = 0x08; + + 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, + pub window_increment: u32, +} + +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(), + }, + window_increment, + } + } +} + +impl Framable for WindowUpdateFrame { + fn serialize(&self) -> Vec { + self.frame.serialize() + } +} + +impl From for WindowUpdateFrame { + fn from(value: Frame) -> Self { + let body = value.body.clone(); + Self { + frame: value, + window_increment: u32::from_be_bytes(body.try_into().unwrap()), + } + } +} + +pub struct HeadersFrame { + frame: Frame, +} + +impl HeadersFrame { + pub const END_STREAM: u8 = 0x01; + 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) } + } +} + +// impl Drop for Connection { +// fn drop(&mut self) { + +// } +// } diff --git a/src/http2/mod.rs b/src/http2/mod.rs new file mode 100644 index 0000000..6b4a71a --- /dev/null +++ b/src/http2/mod.rs @@ -0,0 +1,218 @@ +// DebianArch + +use async_recursion::async_recursion; +use error::Http2Error; +use std::collections::HashMap; + +use tokio::{ + io::{AsyncReadExt, AsyncWriteExt}, + sync::mpsc::{self, Receiver, Sender}, +}; + +pub mod error; +pub mod h2; +pub mod padded; + +use h2::{ + DataFrame, Framable, Frame, FrameType, HeadersFrame, SettingsFrame, WindowUpdateFrame, + HTTP2_MAGIC, +}; + +pub type Channels = HashMap>, Receiver>)>; + +pub struct Connection { + stream: crate::IdeviceSocket, + channels: Channels, + window_size: u32, +} + +impl Connection { + pub const INIT_STREAM: u32 = 0; + pub const ROOT_CHANNEL: u32 = 1; + pub const REPLY_CHANNEL: u32 = 3; + + pub async fn new(mut stream: crate::IdeviceSocket) -> Result { + stream.write_all(HTTP2_MAGIC).await?; + Ok(Self { + stream, + channels: HashMap::new(), + window_size: 1048576, + }) + } + + 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( + &mut self, + 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?; + 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 + } + }, + None => { + self.read_data().await?; + self.read_streamid(stream_id).await + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[tokio::test] + async fn it_works() { + // let frame: Frame = Frame::deserialize( + // &BASE64_STANDARD + // .decode("AAAECAAAAAAAAA8AAQ==" /*"AAAABAEAAAAA"*/) + // .unwrap(), + // ) + // .unwrap() + // .into(); + // println!("supposed: {:x?}", frame.frame_type); + // return; + let mut client = Connection::new(Box::new( + tokio::net::TcpStream::connect("0.0.0.0:1010") + .await + .unwrap(), + )) + .await + .unwrap(); + + // apart of spec, settings frame must be immediately sent after. Can be empty but must exist. + client + .send_frame(SettingsFrame::new( + [ + (SettingsFrame::MAX_CONCURRENT_STREAMS, 100), + (SettingsFrame::INITIAL_WINDOW_SIZE, 1048576), + ] + .into_iter() + .collect(), + Default::default(), + )) + .await + .unwrap(); + + // apart of spec we are allowed to send frames before reading any from the server. + // 'INIT_STREAM'/0 applies to all stream_ids. + client + .send_frame(WindowUpdateFrame::new(Connection::INIT_STREAM, 983041)) + .await + .unwrap(); + + // We create stream_id '1' by sending Header frame. + let mut frame = Frame::new(Connection::ROOT_CHANNEL, 5, FrameType::Headers); + frame.set_body( + [ + 0x41, 0x89, 0x2, 0xe0, 0x5c, 0xb, 0x82, 0xe0, 0x40, 0x10, 0x7f, 0x82, 0x84, 0x86, + 0x50, 0x83, 0x9b, 0xd9, 0xab, 0x7a, 0x8d, 0xc4, 0x75, 0xa7, 0x4a, 0x6b, 0x58, 0x94, + 0x18, 0xb5, 0x25, 0x81, 0x2e, 0xf, + ] + .to_vec(), + ); + + // when server sends 'Settings' on a streamId that the client hasn't sent one on. + // then we must send them back one. + client + .send_frame(Frame::new(Connection::ROOT_CHANNEL, 1, FrameType::Settings)) + .await + .unwrap(); + + client + .write_streamid(Connection::ROOT_CHANNEL, b"nibba\x00".to_vec()) + .await + .unwrap(); + // 'END_HEADERS' is sent before data. + + println!( + "response: {:?}", + String::from_utf8_lossy(&client.read_streamid(1).await.unwrap()) + ); + } +} diff --git a/src/http2/padded.rs b/src/http2/padded.rs new file mode 100644 index 0000000..272688d --- /dev/null +++ b/src/http2/padded.rs @@ -0,0 +1,12 @@ +// DebianArch + +// use crate::h2::Frame; + +// pub struct PaddedFrame {} + +// impl PaddedFrame { +// pub fn new(body: &[u8]) -> Self { +// let pad_len = body[0]; +// let stream_dependency = body[0..4]; +// } +// } diff --git a/src/lib.rs b/src/lib.rs index a335cc1..fa1882b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,10 +1,12 @@ // Jackson Coxson pub mod heartbeat; +pub mod http2; pub mod installation_proxy; pub mod lockdownd; pub mod mounter; pub mod pairing_file; +pub mod xpc; use log::{debug, error}; use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode}; @@ -15,6 +17,8 @@ use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; pub trait ReadWrite: AsyncRead + AsyncWrite + Unpin + Send + Sync + std::fmt::Debug {} impl ReadWrite for T {} +pub type IdeviceSocket = Box; + pub struct Idevice { socket: Option>, // in a box for now to use the ReadWrite trait for further uses label: String, diff --git a/src/xpc/cdtunnel.rs b/src/xpc/cdtunnel.rs new file mode 100644 index 0000000..bd21501 --- /dev/null +++ b/src/xpc/cdtunnel.rs @@ -0,0 +1,28 @@ +// DebianArch + +use json::JsonValue; +pub struct CDTunnel {} + +impl CDTunnel { + const MAGIC: &'static [u8; 8] = b"CDTunnel"; + pub fn decode(data: &[u8]) -> Result> { + let magic_len = CDTunnel::MAGIC.len(); + if &data[0..magic_len] != CDTunnel::MAGIC { + Err("Invalid Magic")?; + } + + let size = u16::from_be_bytes(data[magic_len..magic_len + 2].try_into()?) as usize; + let content = &data[magic_len + 2..magic_len + 2 + size]; + Ok(json::parse(&String::from_utf8(content.to_vec())?)?) + } + + pub fn encode(value: JsonValue) -> Result, Box> { + let mut buf = Vec::new(); + let json_str = value.dump(); + + buf.extend_from_slice(CDTunnel::MAGIC); + buf.extend_from_slice(&u16::to_be_bytes(json_str.len().try_into()?)); + buf.extend_from_slice(json_str.as_bytes()); + Ok(buf) + } +} diff --git a/src/xpc/error.rs b/src/xpc/error.rs new file mode 100644 index 0000000..85f89e3 --- /dev/null +++ b/src/xpc/error.rs @@ -0,0 +1,103 @@ +// DebianArch + +use crate::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/src/xpc/format.rs b/src/xpc/format.rs new file mode 100644 index 0000000..c183523 --- /dev/null +++ b/src/xpc/format.rs @@ -0,0 +1,441 @@ +// 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_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/src/xpc/mod.rs b/src/xpc/mod.rs new file mode 100644 index 0000000..05e4b48 --- /dev/null +++ b/src/xpc/mod.rs @@ -0,0 +1,150 @@ +// Thanks DebianArch + +use crate::http2::{ + self, + h2::{SettingsFrame, WindowUpdateFrame}, +}; +use error::XPCError; +use format::{XPCFlag, XPCMessage, XPCObject}; +use log::debug; +use tokio::net::{TcpStream, ToSocketAddrs}; + +pub mod cdtunnel; +pub mod error; +pub mod format; + +pub struct XPCConnection { + inner: http2::Connection, +} + +impl XPCConnection { + pub const ROOT_CHANNEL: u32 = http2::Connection::ROOT_CHANNEL; + pub const REPLY_CHANNEL: u32 = http2::Connection::REPLY_CHANNEL; + const INIT_STREAM: u32 = http2::Connection::INIT_STREAM; + + pub async fn connect(addr: A) -> Result { + Self::new(Box::new(TcpStream::connect(addr).await?)).await + } + + pub async fn new(stream: crate::IdeviceSocket) -> Result { + let mut client = http2::Connection::new(stream).await?; + client + .send_frame(SettingsFrame::new( + [ + (SettingsFrame::MAX_CONCURRENT_STREAMS, 100), + (SettingsFrame::INITIAL_WINDOW_SIZE, 1048576), + ] + .into_iter() + .collect(), + Default::default(), + )) + .await?; + client + .send_frame(WindowUpdateFrame::new(Self::INIT_STREAM, 983041)) + .await?; + let mut xpc_client = Self { inner: client }; + xpc_client + .send_recv_message( + Self::ROOT_CHANNEL, + XPCMessage::new( + Some(XPCFlag::AlwaysSet), + Some(XPCObject::Dictionary(Default::default())), + None, + ), + ) + .await?; + + // we are here. we send data to stream_id 3 yet we get data from stream 1 ??? + 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) + } + + 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 + } + + pub async fn send_message( + &mut self, + stream_id: u32, + message: XPCMessage, + ) -> Result<(), XPCError> { + self.inner + .write_streamid(stream_id, message.encode(0)?) + .await + .map_err(|err| err.into()) + } + + 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); + return Ok(decoded); + } + Err(err) => { + log::error!("Error decoding message: {:?}", err); + buf.extend_from_slice(&self.inner.read_streamid(stream_id).await?); + } + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[tokio::test] + async fn it_works() { + // assert_eq!( + // XPCFlag::InitHandshake | XPCFlag::AlwaysSet, + // XPCFlag::Custom(0x00400000 | 0x00000001) + // ); + + // let mut buf = Vec::new(); + // let plst = XPCMessage::decode(&buf) + // .unwrap() + // .message + // .unwrap() + // .to_plist(); + + // plst.to_file_xml("rayan.bin").unwrap(); + // return; + let mut client = XPCConnection::new(Box::new( + TcpStream::connect(("fd35:d15d:9272::1", 64634)) + .await + .unwrap(), + )) + .await + .unwrap(); + + let data = client + .read_message(http2::Connection::ROOT_CHANNEL) + .await + .unwrap(); + println!("ayo: {:?}", data); + } +}