From 6d9f0987c1581d6df568297f67fb252e80febb96 Mon Sep 17 00:00:00 2001 From: Jackson Coxson Date: Wed, 31 Dec 2025 21:21:37 -0700 Subject: [PATCH] Migrate to plist_macro crate for utils --- Cargo.lock | 11 + idevice/Cargo.toml | 1 + idevice/src/cursor.rs | 334 ++++++++ idevice/src/lib.rs | 11 +- idevice/src/obfuscation.rs | 15 + idevice/src/plist_macro.rs | 714 ------------------ .../src/services/core_device/app_service.rs | 3 +- idevice/src/tss.rs | 5 +- idevice/src/usbmuxd/raw_packet.rs | 2 +- idevice/src/util.rs | 142 ---- idevice/src/utils/installation/helpers.rs | 5 +- idevice/src/xpc/format.rs | 3 +- tools/Cargo.toml | 1 + tools/src/companion_proxy.rs | 4 +- tools/src/lockdown.rs | 3 +- tools/src/mounter.rs | 6 +- tools/src/restore_service.rs | 3 +- 17 files changed, 385 insertions(+), 878 deletions(-) create mode 100644 idevice/src/cursor.rs create mode 100644 idevice/src/obfuscation.rs delete mode 100644 idevice/src/plist_macro.rs delete mode 100644 idevice/src/util.rs diff --git a/Cargo.lock b/Cargo.lock index 543979d..295cf8e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1076,6 +1076,7 @@ dependencies = [ "obfstr", "openssl", "plist", + "plist-macro", "rand 0.9.2", "reqwest", "rsa", @@ -1122,6 +1123,7 @@ dependencies = [ "idevice", "ns-keyed-archive", "plist", + "plist-macro", "sha2", "tokio", "tracing", @@ -1730,6 +1732,15 @@ dependencies = [ "time", ] +[[package]] +name = "plist-macro" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb72007326fe20721ef27304fcf2d1bd5877b92d13dbd8df735fd33407e31c2a" +dependencies = [ + "plist", +] + [[package]] name = "plist_ffi" version = "0.1.6" diff --git a/idevice/Cargo.toml b/idevice/Cargo.toml index ab47bdf..20605c2 100644 --- a/idevice/Cargo.toml +++ b/idevice/Cargo.toml @@ -21,6 +21,7 @@ tokio-openssl = { version = "0.6", optional = true } openssl = { version = "0.10", optional = true } plist = { version = "1.8" } +plist-macro = { version = "0.1" } serde = { version = "1", features = ["derive"] } ns-keyed-archive = { version = "0.1.4", optional = true } crossfire = { version = "2.1", optional = true } diff --git a/idevice/src/cursor.rs b/idevice/src/cursor.rs new file mode 100644 index 0000000..20d0df4 --- /dev/null +++ b/idevice/src/cursor.rs @@ -0,0 +1,334 @@ +// Jackson Coxson + +#[derive(Clone, Debug)] +pub struct Cursor<'a> { + inner: &'a [u8], + pos: usize, +} + +impl<'a> Cursor<'a> { + /// Creates a new cursor + pub fn new(inner: &'a [u8]) -> Self { + Self { inner, pos: 0 } + } + + pub fn len(&self) -> usize { + self.inner.len() + } + + pub fn is_empty(&self) -> bool { + self.inner.is_empty() + } + + pub fn at_end(&self) -> bool { + self.pos == self.inner.len() + } + + pub fn read(&mut self, to_read: usize) -> Option<&'a [u8]> { + // Check if the end of the slice (self.pos + to_read) is beyond the buffer length + if self + .pos + .checked_add(to_read) + .is_none_or(|end_pos| end_pos > self.inner.len()) + { + return None; + } + + // The end of the slice is self.pos + to_read + let end_pos = self.pos + to_read; + let res = Some(&self.inner[self.pos..end_pos]); + self.pos = end_pos; + res + } + + pub fn back(&mut self, to_back: usize) { + let to_back = if to_back > self.pos { + self.pos + } else { + to_back + }; + + self.pos -= to_back; + } + + /// True if actually all zeroes + pub fn read_assert_zero(&mut self, to_read: usize) -> Option<()> { + let bytes = self.read(to_read)?; + + #[cfg(debug_assertions)] + for b in bytes.iter() { + if *b > 0 { + eprintln!("Zero read contained non-zero values!"); + eprintln!("{bytes:02X?}"); + return None; + } + } + + Some(()) + } + + pub fn read_to(&mut self, end: usize) -> Option<&'a [u8]> { + if end > self.inner.len() { + return None; + } + let res = Some(&self.inner[self.pos..end]); + self.pos = end; + res + } + + pub fn peek_to(&mut self, end: usize) -> Option<&'a [u8]> { + if end > self.inner.len() { + return None; + } + Some(&self.inner[self.pos..end]) + } + + pub fn peek(&self, to_read: usize) -> Option<&'a [u8]> { + if self + .pos + .checked_add(to_read) + .is_none_or(|end_pos| end_pos > self.inner.len()) + { + return None; + } + + let end_pos = self.pos + to_read; + Some(&self.inner[self.pos..end_pos]) + } + + pub fn reveal(&self, surrounding: usize) { + let len = self.inner.len(); + + if self.pos > len { + println!("Cursor is past end of buffer"); + return; + } + + let start = self.pos.saturating_sub(surrounding); + let end = (self.pos + surrounding + 1).min(len); + + // HEADER + println!("Reveal around pos {} ({} bytes):", self.pos, surrounding); + + // --- HEX LINE --- + print!("Hex: "); + for i in start..end { + if i == self.pos { + print!("[{:02X}] ", self.inner[i]); + } else { + print!("{:02X} ", self.inner[i]); + } + } + println!(); + + // --- ASCII LINE --- + print!("Ascii: "); + for i in start..end { + let b = self.inner[i]; + let c = if b.is_ascii_graphic() || b == b' ' { + b as char + } else { + '.' + }; + + if i == self.pos { + print!("[{}] ", c); + } else { + print!("{} ", c); + } + } + println!(); + + // --- OFFSET LINE --- + print!("Offset: "); + for i in start..end { + let off = i as isize - self.pos as isize; + if i == self.pos { + print!("[{}] ", off); + } else { + print!("{:<3} ", off); + } + } + println!(); + } + + pub fn remaining(&mut self) -> &'a [u8] { + let res = &self.inner[self.pos..]; + self.pos = self.inner.len(); + res + } + + pub fn read_u8(&mut self) -> Option { + if self.pos == self.inner.len() { + return None; + } + let res = Some(self.inner[self.pos]); + self.pos += 1; + res + } + + pub fn read_le_u16(&mut self) -> Option { + const SIZE: usize = 2; + let bytes = self.read(SIZE)?; + let bytes: [u8; SIZE] = bytes.try_into().unwrap(); + Some(u16::from_le_bytes(bytes)) + } + + pub fn read_be_u16(&mut self) -> Option { + const SIZE: usize = 2; + let bytes = self.read(SIZE)?; + let bytes: [u8; SIZE] = bytes.try_into().unwrap(); + Some(u16::from_be_bytes(bytes)) + } + + pub fn read_le_u32(&mut self) -> Option { + const SIZE: usize = 4; + let bytes = self.read(SIZE)?; + let bytes: [u8; SIZE] = bytes.try_into().unwrap(); + Some(u32::from_le_bytes(bytes)) + } + + pub fn read_be_u32(&mut self) -> Option { + const SIZE: usize = 4; + let bytes = self.read(SIZE)?; + let bytes: [u8; SIZE] = bytes.try_into().unwrap(); + Some(u32::from_be_bytes(bytes)) + } + + pub fn read_le_u64(&mut self) -> Option { + const SIZE: usize = 8; + let bytes = self.read(SIZE)?; + let bytes: [u8; SIZE] = bytes.try_into().unwrap(); + Some(u64::from_le_bytes(bytes)) + } + + pub fn read_be_u64(&mut self) -> Option { + const SIZE: usize = 8; + let bytes = self.read(SIZE)?; + let bytes: [u8; SIZE] = bytes.try_into().unwrap(); + Some(u64::from_be_bytes(bytes)) + } + + pub fn read_le_u128(&mut self) -> Option { + const SIZE: usize = 16; + let bytes = self.read(SIZE)?; + let bytes: [u8; SIZE] = bytes.try_into().unwrap(); + Some(u128::from_le_bytes(bytes)) + } + + pub fn read_be_u128(&mut self) -> Option { + const SIZE: usize = 16; + let bytes = self.read(SIZE)?; + let bytes: [u8; SIZE] = bytes.try_into().unwrap(); + Some(u128::from_be_bytes(bytes)) + } + + pub fn read_le_f32(&mut self) -> Option { + const SIZE: usize = 4; + let bytes = self.read(SIZE)?; + let bytes: [u8; SIZE] = bytes.try_into().unwrap(); + Some(f32::from_le_bytes(bytes)) + } + + pub fn read_be_f32(&mut self) -> Option { + const SIZE: usize = 4; + let bytes = self.read(SIZE)?; + let bytes: [u8; SIZE] = bytes.try_into().unwrap(); + Some(f32::from_be_bytes(bytes)) + } + + pub fn read_i8(&mut self) -> Option { + if self.pos == self.inner.len() { + return None; + } + let res = Some(self.inner[self.pos]).map(|x| x as i8); + self.pos += 1; + res + } + + pub fn read_le_i16(&mut self) -> Option { + const SIZE: usize = 2; + let bytes = self.read(SIZE)?; + let bytes: [u8; SIZE] = bytes.try_into().unwrap(); + Some(i16::from_le_bytes(bytes)) + } + + pub fn read_be_i16(&mut self) -> Option { + const SIZE: usize = 2; + let bytes = self.read(SIZE)?; + let bytes: [u8; SIZE] = bytes.try_into().unwrap(); + Some(i16::from_be_bytes(bytes)) + } + + pub fn read_le_i32(&mut self) -> Option { + const SIZE: usize = 4; + let bytes = self.read(SIZE)?; + let bytes: [u8; SIZE] = bytes.try_into().unwrap(); + Some(i32::from_le_bytes(bytes)) + } + + pub fn read_be_i32(&mut self) -> Option { + const SIZE: usize = 4; + let bytes = self.read(SIZE)?; + let bytes: [u8; SIZE] = bytes.try_into().unwrap(); + Some(i32::from_be_bytes(bytes)) + } + + pub fn read_le_i64(&mut self) -> Option { + const SIZE: usize = 8; + let bytes = self.read(SIZE)?; + let bytes: [u8; SIZE] = bytes.try_into().unwrap(); + Some(i64::from_le_bytes(bytes)) + } + + pub fn read_be_i64(&mut self) -> Option { + const SIZE: usize = 8; + let bytes = self.read(SIZE)?; + let bytes: [u8; SIZE] = bytes.try_into().unwrap(); + Some(i64::from_be_bytes(bytes)) + } + + pub fn read_le_i128(&mut self) -> Option { + const SIZE: usize = 16; + let bytes = self.read(SIZE)?; + let bytes: [u8; SIZE] = bytes.try_into().unwrap(); + Some(i128::from_le_bytes(bytes)) + } + + pub fn read_be_i128(&mut self) -> Option { + const SIZE: usize = 16; + let bytes = self.read(SIZE)?; + let bytes: [u8; SIZE] = bytes.try_into().unwrap(); + Some(i128::from_be_bytes(bytes)) + } + + pub fn take_2(&mut self) -> Option<[u8; 2]> { + let bytes = self.read(2)?; + Some(bytes.to_owned().try_into().unwrap()) + } + + pub fn take_3(&mut self) -> Option<[u8; 3]> { + let bytes = self.read(3)?; + Some(bytes.to_owned().try_into().unwrap()) + } + + pub fn take_4(&mut self) -> Option<[u8; 4]> { + let bytes = self.read(4)?; + Some(bytes.to_owned().try_into().unwrap()) + } + + pub fn take_8(&mut self) -> Option<[u8; 8]> { + let bytes = self.read(8)?; + Some(bytes.to_owned().try_into().unwrap()) + } + + pub fn take_20(&mut self) -> Option<[u8; 20]> { + let bytes = self.read(20)?; + Some(bytes.to_owned().try_into().unwrap()) + } + + pub fn take_32(&mut self) -> Option<[u8; 32]> { + let bytes = self.read(32)?; + Some(bytes.to_owned().try_into().unwrap()) + } +} diff --git a/idevice/src/lib.rs b/idevice/src/lib.rs index cb62deb..5aa23d6 100644 --- a/idevice/src/lib.rs +++ b/idevice/src/lib.rs @@ -5,8 +5,9 @@ #[cfg(all(feature = "pair", feature = "rustls"))] mod ca; +pub mod cursor; +mod obfuscation; pub mod pairing_file; -pub mod plist_macro; pub mod provider; #[cfg(feature = "rustls")] mod sni; @@ -18,7 +19,6 @@ pub mod tss; pub mod tunneld; #[cfg(feature = "usbmuxd")] pub mod usbmuxd; -mod util; pub mod utils; #[cfg(feature = "xpc")] pub mod xpc; @@ -29,6 +29,7 @@ pub use services::*; #[cfg(feature = "xpc")] pub use xpc::RemoteXpcClient; +use plist_macro::{plist, pretty_print_dictionary, pretty_print_plist}; use provider::{IdeviceProvider, RsdProvider}; #[cfg(feature = "rustls")] use rustls::{crypto::CryptoProvider, pki_types::ServerName}; @@ -40,8 +41,6 @@ use thiserror::Error; use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; use tracing::{debug, trace}; -pub use util::{pretty_print_dictionary, pretty_print_plist}; - use crate::services::lockdown::LockdownClient; /// A trait combining all required characteristics for a device communication socket @@ -192,7 +191,7 @@ impl Idevice { /// # Errors /// Returns `IdeviceError` if communication fails or response is invalid pub async fn get_type(&mut self) -> Result { - let req = crate::plist!({ + let req = plist!({ "Label": self.label.clone(), "Request": "QueryType", }); @@ -212,7 +211,7 @@ impl Idevice { /// # Errors /// Returns `IdeviceError` if the protocol sequence isn't followed correctly pub async fn rsd_checkin(&mut self) -> Result<(), IdeviceError> { - let req = crate::plist!({ + let req = plist!({ "Label": self.label.clone(), "ProtocolVersion": "2", "Request": "RSDCheckin", diff --git a/idevice/src/obfuscation.rs b/idevice/src/obfuscation.rs new file mode 100644 index 0000000..d101c09 --- /dev/null +++ b/idevice/src/obfuscation.rs @@ -0,0 +1,15 @@ +// Jackson Coxson + +#[macro_export] +macro_rules! obf { + ($lit:literal) => {{ + #[cfg(feature = "obfuscate")] + { + std::borrow::Cow::Owned(obfstr::obfstr!($lit).to_string()) + } + #[cfg(not(feature = "obfuscate"))] + { + std::borrow::Cow::Borrowed($lit) + } + }}; +} diff --git a/idevice/src/plist_macro.rs b/idevice/src/plist_macro.rs deleted file mode 100644 index 3453b43..0000000 --- a/idevice/src/plist_macro.rs +++ /dev/null @@ -1,714 +0,0 @@ -// Jackson Coxson -// Ported from serde's json! - -/// Construct a `plist::Value` from a JSON-like literal. -/// -/// ``` -/// # use idevice::plist; -/// # -/// let value = plist!({ -/// "code": 200, -/// "success": true, -/// "payload": { -/// "features": [ -/// "serde", -/// "plist" -/// ], -/// "homepage": null -/// } -/// }); -/// ``` -/// -/// Variables or expressions can be interpolated into the plist literal. Any type -/// interpolated into an array element or object value must implement `Into`. -/// If the conversion fails, the `plist!` macro will panic. -/// -/// ``` -/// # use idevice::plist; -/// # -/// let code = 200; -/// let features = vec!["serde", "plist"]; -/// -/// let value = plist!({ -/// "code": code, -/// "success": code == 200, -/// "payload": { -/// features[0]: features[1] -/// } -/// }); -/// ``` -/// -/// Trailing commas are allowed inside both arrays and objects. -/// -/// ``` -/// # use idevice::plist; -/// # -/// let value = plist!([ -/// "notice", -/// "the", -/// "trailing", -/// "comma -->", -/// ]); -/// ``` -#[macro_export] -macro_rules! plist { - // Force: dictionary out - (dict { $($tt:tt)+ }) => {{ - let mut object = plist::Dictionary::new(); - $crate::plist_internal!(@object object () ($($tt)+) ($($tt)+)); - object - }}; - - // Force: value out (explicit, though default already does this) - (value { $($tt:tt)+ }) => { - $crate::plist_internal!({ $($tt)+ }) - }; - - // Force: raw vec of plist::Value out - (array [ $($tt:tt)+ ]) => { - $crate::plist_internal!(@array [] $($tt)+) - }; - - // Hide distracting implementation details from the generated rustdoc. - ($($plist:tt)+) => { - $crate::plist_internal!($($plist)+) - }; -} - -#[macro_export] -#[doc(hidden)] -macro_rules! plist_internal { - ////////////////////////////////////////////////////////////////////////// - // TT muncher for parsing the inside of an array [...]. Produces a vec![...] - // of the elements. - // - // Must be invoked as: plist_internal!(@array [] $($tt)*) - ////////////////////////////////////////////////////////////////////////// - - // Done with trailing comma. - (@array [$($elems:expr,)*]) => { - vec![$($elems,)*] - }; - - // Done without trailing comma. - (@array [$($elems:expr),*]) => { - vec![$($elems),*] - }; - - // Next element is `null`. - (@array [$($elems:expr,)*] null $($rest:tt)*) => { - $crate::plist_internal!(@array [$($elems,)* $crate::plist_internal!(null)] $($rest)*) - }; - - // Next element is `true`. - (@array [$($elems:expr,)*] true $($rest:tt)*) => { - $crate::plist_internal!(@array [$($elems,)* $crate::plist_internal!(true)] $($rest)*) - }; - - // Next element is `false`. - (@array [$($elems:expr,)*] false $($rest:tt)*) => { - $crate::plist_internal!(@array [$($elems,)* $crate::plist_internal!(false)] $($rest)*) - }; - - // Next element is an array. - (@array [$($elems:expr,)*] [$($array:tt)*] $($rest:tt)*) => { - $crate::plist_internal!(@array [$($elems,)* $crate::plist_internal!([$($array)*])] $($rest)*) - }; - - // Next element is a map. - (@array [$($elems:expr,)*] {$($map:tt)*} $($rest:tt)*) => { - $crate::plist_internal!(@array [$($elems,)* $crate::plist_internal!({$($map)*})] $($rest)*) - }; - - // Next element is an expression followed by comma. - (@array [$($elems:expr,)*] $next:expr, $($rest:tt)*) => { - $crate::plist_internal!(@array [$($elems,)* $crate::plist_internal!($next),] $($rest)*) - }; - - // Last element is an expression with no trailing comma. - (@array [$($elems:expr,)*] $last:expr) => { - $crate::plist_internal!(@array [$($elems,)* $crate::plist_internal!($last)]) - }; - - // Comma after the most recent element. - (@array [$($elems:expr),*] , $($rest:tt)*) => { - $crate::plist_internal!(@array [$($elems,)*] $($rest)*) - }; - - // Unexpected token after most recent element. - (@array [$($elems:expr),*] $unexpected:tt $($rest:tt)*) => { - $crate::plist_unexpected!($unexpected) - }; - - (@array [$($elems:expr,)*] ? $maybe:expr , $($rest:tt)*) => { - if let Some(__v) = $crate::plist_macro::plist_maybe($maybe) { - $crate::plist_internal!(@array [$($elems,)* __v,] $($rest)*) - } else { - $crate::plist_internal!(@array [$($elems,)*] $($rest)*) - } - }; - (@array [$($elems:expr,)*] ? $maybe:expr) => { - if let Some(__v) = $crate::plist_macro::plist_maybe($maybe) { - $crate::plist_internal!(@array [$($elems,)* __v]) - } else { - $crate::plist_internal!(@array [$($elems,)*]) - } - }; - - ////////////////////////////////////////////////////////////////////////// - // TT muncher for parsing the inside of an object {...}. Each entry is - // inserted into the given map variable. - // - // Must be invoked as: plist_internal!(@object $map () ($($tt)*) ($($tt)*)) - // - // We require two copies of the input tokens so that we can match on one - // copy and trigger errors on the other copy. - ////////////////////////////////////////////////////////////////////////// - - // Done. - (@object $object:ident () () ()) => {}; - - // Insert the current entry followed by trailing comma. - (@object $object:ident [$($key:tt)+] ($value:expr) , $($rest:tt)*) => { - let _ = $object.insert(($($key)+).into(), $value); - $crate::plist_internal!(@object $object () ($($rest)*) ($($rest)*)); - }; - - // Current entry followed by unexpected token. - (@object $object:ident [$($key:tt)+] ($value:expr) $unexpected:tt $($rest:tt)*) => { - $crate::plist_unexpected!($unexpected); - }; - - // Insert the last entry without trailing comma. - (@object $object:ident [$($key:tt)+] ($value:expr)) => { - let _ = $object.insert(($($key)+).into(), $value); - }; - - // Next value is `null`. - (@object $object:ident ($($key:tt)+) (: null $($rest:tt)*) $copy:tt) => { - $crate::plist_internal!(@object $object [$($key)+] ($crate::plist_internal!(null)) $($rest)*); - }; - - // Next value is `true`. - (@object $object:ident ($($key:tt)+) (: true $($rest:tt)*) $copy:tt) => { - $crate::plist_internal!(@object $object [$($key)+] ($crate::plist_internal!(true)) $($rest)*); - }; - - // Next value is `false`. - (@object $object:ident ($($key:tt)+) (: false $($rest:tt)*) $copy:tt) => { - $crate::plist_internal!(@object $object [$($key)+] ($crate::plist_internal!(false)) $($rest)*); - }; - - // Next value is an array. - (@object $object:ident ($($key:tt)+) (: [$($array:tt)*] $($rest:tt)*) $copy:tt) => { - $crate::plist_internal!(@object $object [$($key)+] ($crate::plist_internal!([$($array)*])) $($rest)*); - }; - - // Next value is a map. - (@object $object:ident ($($key:tt)+) (: {$($map:tt)*} $($rest:tt)*) $copy:tt) => { - $crate::plist_internal!(@object $object [$($key)+] ($crate::plist_internal!({$($map)*})) $($rest)*); - }; - - // Optional insert with trailing comma: key?: expr, - (@object $object:ident ($($key:tt)+) (:? $value:expr , $($rest:tt)*) $copy:tt) => { - if let Some(__v) = $crate::plist_macro::plist_maybe($value) { - let _ = $object.insert(($($key)+).into(), __v); - } - $crate::plist_internal!(@object $object () ($($rest)*) ($($rest)*)); - }; - - // Optional insert, last entry: key?: expr - (@object $object:ident ($($key:tt)+) (:? $value:expr) $copy:tt) => { - if let Some(__v) = $crate::plist_macro::plist_maybe($value) { - let _ = $object.insert(($($key)+).into(), __v); - } - }; - - (@object $object:ident () ( :< $value:expr , $($rest:tt)*) $copy:tt) => { - { - let __v = $crate::plist_internal!($value); - let __dict = $crate::plist_macro::IntoPlistDict::into_plist_dict(__v); - for (__k, __val) in __dict { - let _ = $object.insert(__k, __val); - } - } - $crate::plist_internal!(@object $object () ($($rest)*) ($($rest)*)); - }; - - // Merge: last entry `:< expr` - (@object $object:ident () ( :< $value:expr ) $copy:tt) => { - { - let __v = $crate::plist_internal!($value); - let __dict = $crate::plist_macro::IntoPlistDict::into_plist_dict(__v); - for (__k, __val) in __dict { - let _ = $object.insert(__k, __val); - } - } - }; - - // Optional merge: `:< ? expr,` — only merge if Some(...) - (@object $object:ident () ( :< ? $value:expr , $($rest:tt)*) $copy:tt) => { - if let Some(__dict) = $crate::plist_macro::maybe_into_dict($value) { - for (__k, __val) in __dict { - let _ = $object.insert(__k, __val); - } - } - $crate::plist_internal!(@object $object () ($($rest)*) ($($rest)*)); - }; - - // Optional merge: last entry `:< ? expr` - (@object $object:ident () ( :< ? $value:expr ) $copy:tt) => { - if let Some(__dict) = $crate::plist_macro::maybe_into_dict($value) { - for (__k, __val) in __dict { - let _ = $object.insert(__k, __val); - } - } - }; - - // Next value is an expression followed by comma. - (@object $object:ident ($($key:tt)+) (: $value:expr , $($rest:tt)*) $copy:tt) => { - $crate::plist_internal!(@object $object [$($key)+] ($crate::plist_internal!($value)) , $($rest)*); - }; - - // Last value is an expression with no trailing comma. - (@object $object:ident ($($key:tt)+) (: $value:expr) $copy:tt) => { - $crate::plist_internal!(@object $object [$($key)+] ($crate::plist_internal!($value))); - }; - - // Missing value for last entry. Trigger a reasonable error message. - (@object $object:ident ($($key:tt)+) (:) $copy:tt) => { - // "unexpected end of macro invocation" - $crate::plist_internal!(); - }; - - // Missing colon and value for last entry. Trigger a reasonable error - // message. - (@object $object:ident ($($key:tt)+) () $copy:tt) => { - // "unexpected end of macro invocation" - $crate::plist_internal!(); - }; - - // Misplaced colon. Trigger a reasonable error message. - (@object $object:ident () (: $($rest:tt)*) ($colon:tt $($copy:tt)*)) => { - // Takes no arguments so "no rules expected the token `:`". - $crate::plist_unexpected!($colon); - }; - - // Found a comma inside a key. Trigger a reasonable error message. - (@object $object:ident ($($key:tt)*) (, $($rest:tt)*) ($comma:tt $($copy:tt)*)) => { - // Takes no arguments so "no rules expected the token `,`". - $crate::plist_unexpected!($comma); - }; - - // Key is fully parenthesized. This avoids clippy double_parens false - // positives because the parenthesization may be necessary here. - (@object $object:ident () (($key:expr) : $($rest:tt)*) $copy:tt) => { - $crate::plist_internal!(@object $object ($key) (: $($rest)*) (: $($rest)*)); - }; - - // Refuse to absorb colon token into key expression. - (@object $object:ident ($($key:tt)*) (: $($unexpected:tt)+) $copy:tt) => { - $crate::plist_expect_expr_comma!($($unexpected)+); - }; - - // Munch a token into the current key. - (@object $object:ident ($($key:tt)*) ($tt:tt $($rest:tt)*) $copy:tt) => { - $crate::plist_internal!(@object $object ($($key)* $tt) ($($rest)*) ($($rest)*)); - }; - - ////////////////////////////////////////////////////////////////////////// - // The main implementation. - // - // Must be invoked as: plist_internal!($($plist)+) - ////////////////////////////////////////////////////////////////////////// - - (null) => { - plist::Value::String("".to_string()) // plist doesn't have null, use empty string or consider other representation - }; - - (true) => { - plist::Value::Boolean(true) - }; - - (false) => { - plist::Value::Boolean(false) - }; - - ([]) => { - plist::Value::Array(vec![]) - }; - - ([ $($tt:tt)+ ]) => { - plist::Value::Array($crate::plist_internal!(@array [] $($tt)+)) - }; - - ({}) => { - plist::Value::Dictionary(plist::Dictionary::new()) - }; - - ({ $($tt:tt)+ }) => { - plist::Value::Dictionary({ - let mut object = plist::Dictionary::new(); - $crate::plist_internal!(@object object () ($($tt)+) ($($tt)+)); - object - }) - }; - - // Any type that can be converted to plist::Value: numbers, strings, variables etc. - // Must be below every other rule. - ($other:expr) => { - $crate::plist_macro::plist_to_value($other) - }; -} - -#[macro_export] -#[doc(hidden)] -macro_rules! plist_unexpected { - () => {}; -} - -#[macro_export] -#[doc(hidden)] -macro_rules! plist_expect_expr_comma { - ($e:expr , $($tt:tt)*) => {}; -} - -// Helper function to convert various types to plist::Value -#[doc(hidden)] -pub fn plist_to_value(value: T) -> plist::Value { - value.to_plist_value() -} - -// Trait for types that can be converted to plist::Value -pub trait PlistConvertible { - fn to_plist_value(self) -> plist::Value; -} - -// Implementations for common types -impl PlistConvertible for plist::Value { - fn to_plist_value(self) -> plist::Value { - self - } -} - -impl PlistConvertible for String { - fn to_plist_value(self) -> plist::Value { - plist::Value::String(self) - } -} - -impl PlistConvertible for &str { - fn to_plist_value(self) -> plist::Value { - plist::Value::String(self.to_string()) - } -} - -impl PlistConvertible for i16 { - fn to_plist_value(self) -> plist::Value { - plist::Value::Integer(self.into()) - } -} - -impl PlistConvertible for i32 { - fn to_plist_value(self) -> plist::Value { - plist::Value::Integer(self.into()) - } -} - -impl PlistConvertible for i64 { - fn to_plist_value(self) -> plist::Value { - plist::Value::Integer(self.into()) - } -} - -impl PlistConvertible for u16 { - fn to_plist_value(self) -> plist::Value { - plist::Value::Integer((self as i64).into()) - } -} - -impl PlistConvertible for u32 { - fn to_plist_value(self) -> plist::Value { - plist::Value::Integer((self as i64).into()) - } -} - -impl PlistConvertible for u64 { - fn to_plist_value(self) -> plist::Value { - plist::Value::Integer((self as i64).into()) - } -} - -impl PlistConvertible for f32 { - fn to_plist_value(self) -> plist::Value { - plist::Value::Real(self as f64) - } -} - -impl PlistConvertible for f64 { - fn to_plist_value(self) -> plist::Value { - plist::Value::Real(self) - } -} - -impl PlistConvertible for bool { - fn to_plist_value(self) -> plist::Value { - plist::Value::Boolean(self) - } -} - -impl<'a> PlistConvertible for std::borrow::Cow<'a, str> { - fn to_plist_value(self) -> plist::Value { - plist::Value::String(self.into_owned()) - } -} -impl PlistConvertible for Vec { - fn to_plist_value(self) -> plist::Value { - plist::Value::Data(self) - } -} -impl PlistConvertible for &[u8] { - fn to_plist_value(self) -> plist::Value { - plist::Value::Data(self.to_vec()) - } -} -impl PlistConvertible for std::time::SystemTime { - fn to_plist_value(self) -> plist::Value { - plist::Value::Date(self.into()) - } -} - -impl PlistConvertible for Vec { - fn to_plist_value(self) -> plist::Value { - plist::Value::Array(self.into_iter().map(|item| item.to_plist_value()).collect()) - } -} - -impl PlistConvertible for &[T] { - fn to_plist_value(self) -> plist::Value { - plist::Value::Array( - self.iter() - .map(|item| item.clone().to_plist_value()) - .collect(), - ) - } -} - -impl PlistConvertible for [T; N] { - fn to_plist_value(self) -> plist::Value { - plist::Value::Array(self.into_iter().map(|item| item.to_plist_value()).collect()) - } -} - -impl PlistConvertible for &[T; N] { - fn to_plist_value(self) -> plist::Value { - plist::Value::Array( - self.iter() - .map(|item| item.clone().to_plist_value()) - .collect(), - ) - } -} - -impl PlistConvertible for plist::Dictionary { - fn to_plist_value(self) -> plist::Value { - plist::Value::Dictionary(self) - } -} - -impl PlistConvertible for std::collections::HashMap -where - K: Into, - V: PlistConvertible, -{ - fn to_plist_value(self) -> plist::Value { - let mut dict = plist::Dictionary::new(); - for (key, value) in self { - dict.insert(key.into(), value.to_plist_value()); - } - plist::Value::Dictionary(dict) - } -} - -impl PlistConvertible for std::collections::BTreeMap -where - K: Into, - V: PlistConvertible, -{ - fn to_plist_value(self) -> plist::Value { - let mut dict = plist::Dictionary::new(); - for (key, value) in self { - dict.insert(key.into(), value.to_plist_value()); - } - plist::Value::Dictionary(dict) - } -} - -// Treat plain T as Some(T) and Option as-is. -pub trait MaybePlist { - fn into_option_value(self) -> Option; -} - -impl MaybePlist for T { - fn into_option_value(self) -> Option { - Some(self.to_plist_value()) - } -} - -impl MaybePlist for Option { - fn into_option_value(self) -> Option { - self.map(|v| v.to_plist_value()) - } -} - -#[doc(hidden)] -pub fn plist_maybe(v: T) -> Option { - v.into_option_value() -} - -// Convert things into a Dictionary we can merge. -pub trait IntoPlistDict { - fn into_plist_dict(self) -> plist::Dictionary; -} - -impl IntoPlistDict for plist::Dictionary { - fn into_plist_dict(self) -> plist::Dictionary { - self - } -} - -impl IntoPlistDict for plist::Value { - fn into_plist_dict(self) -> plist::Dictionary { - match self { - plist::Value::Dictionary(d) => d, - other => panic!("plist :< expects a dictionary, got {other:?}"), - } - } -} - -impl IntoPlistDict for std::collections::HashMap -where - K: Into, - V: PlistConvertible, -{ - fn into_plist_dict(self) -> plist::Dictionary { - let mut d = plist::Dictionary::new(); - for (k, v) in self { - d.insert(k.into(), v.to_plist_value()); - } - d - } -} - -impl IntoPlistDict for std::collections::BTreeMap -where - K: Into, - V: PlistConvertible, -{ - fn into_plist_dict(self) -> plist::Dictionary { - let mut d = plist::Dictionary::new(); - for (k, v) in self { - d.insert(k.into(), v.to_plist_value()); - } - d - } -} - -// Optional version: T or Option. -pub trait MaybeIntoPlistDict { - fn into_option_plist_dict(self) -> Option; -} -impl MaybeIntoPlistDict for T { - fn into_option_plist_dict(self) -> Option { - Some(self.into_plist_dict()) - } -} -impl MaybeIntoPlistDict for Option { - fn into_option_plist_dict(self) -> Option { - self.map(|t| t.into_plist_dict()) - } -} - -#[doc(hidden)] -pub fn maybe_into_dict(v: T) -> Option { - v.into_option_plist_dict() -} - -#[cfg(test)] -mod tests { - #[test] - fn test_plist_macro_basic() { - let value = plist!({ - "name": "test", - "count": 42, - "active": true, - "items": ["a", ?"b", "c"] - }); - - if let plist::Value::Dictionary(dict) = value { - assert_eq!( - dict.get("name"), - Some(&plist::Value::String("test".to_string())) - ); - assert_eq!(dict.get("count"), Some(&plist::Value::Integer(42.into()))); - assert_eq!(dict.get("active"), Some(&plist::Value::Boolean(true))); - } else { - panic!("Expected dictionary"); - } - } - - #[test] - fn test_plist_macro_with_variables() { - let name = "dynamic"; - let count = 100; - let items = vec!["x", "y"]; - let none: Option = None; - - let to_merge = plist!({ - "reee": "cool beans" - }); - let maybe_merge = Some(plist!({ - "yeppers": "what did I say about yeppers", - "replace me": 2, - })); - let value = plist!({ - "name": name, - "count": count, - "items": items, - "omit me":? none, - "keep me":? Some(123), - "replace me": 1, - :< to_merge, - : AppServiceClient { "user": { "active": true, }, - "platformSpecificOptions": plist::Value::Data(crate::util::plist_to_xml_bytes(&platform_options.unwrap_or_default())), + "platformSpecificOptions": plist::Value::Data(plist_to_xml_bytes(&platform_options.unwrap_or_default())), }, }); diff --git a/idevice/src/tss.rs b/idevice/src/tss.rs index 1e02e35..89de161 100644 --- a/idevice/src/tss.rs +++ b/idevice/src/tss.rs @@ -6,9 +6,10 @@ //! - Handle cryptographic signing operations use plist::Value; +use plist_macro::plist_to_xml_bytes; use tracing::{debug, warn}; -use crate::{IdeviceError, util::plist_to_xml_bytes}; +use crate::IdeviceError; /// TSS client version string sent in requests const TSS_CLIENT_VERSION_STRING: &str = "libauthinstall-1033.0.2"; @@ -30,7 +31,7 @@ impl TSSRequest { /// - Client version string /// - Random UUID for request identification pub fn new() -> Self { - let inner = crate::plist!(dict { + let inner = plist_macro::plist!(dict { "@HostPlatformInfo": "mac", "@VersionInfo": TSS_CLIENT_VERSION_STRING, "@UUID": uuid::Uuid::new_v4().to_string().to_uppercase() diff --git a/idevice/src/usbmuxd/raw_packet.rs b/idevice/src/usbmuxd/raw_packet.rs index 0a9aaba..7bc85a4 100644 --- a/idevice/src/usbmuxd/raw_packet.rs +++ b/idevice/src/usbmuxd/raw_packet.rs @@ -1,6 +1,6 @@ // Jackson Coxson -use crate::util::plist_to_xml_bytes; +use plist_macro::plist_to_xml_bytes; use tracing::warn; #[derive(Debug)] diff --git a/idevice/src/util.rs b/idevice/src/util.rs deleted file mode 100644 index 4e69565..0000000 --- a/idevice/src/util.rs +++ /dev/null @@ -1,142 +0,0 @@ -//! Utility Functions -//! -//! Provides helper functions for working with Apple's Property List (PLIST) format, -//! including serialization and pretty-printing utilities. -#![allow(dead_code)] // functions might not be used by all features - -use plist::Value; - -/// Converts a PLIST dictionary to XML-formatted bytes -/// -/// # Arguments -/// * `p` - The PLIST dictionary to serialize -/// -/// # Returns -/// A byte vector containing the XML representation -/// -/// # Panics -/// Will panic if serialization fails (should only happen with invalid data) -/// -/// # Example -/// ```rust -/// let mut dict = plist::Dictionary::new(); -/// dict.insert("key".into(), "value".into()); -/// let xml_bytes = plist_to_xml_bytes(&dict); -/// ``` -pub fn plist_to_xml_bytes(p: &plist::Dictionary) -> Vec { - let buf = Vec::new(); - let mut writer = std::io::BufWriter::new(buf); - plist::to_writer_xml(&mut writer, &p).unwrap(); - - writer.into_inner().unwrap() -} - -/// Pretty-prints a PLIST value with indentation -/// -/// # Arguments -/// * `p` - The PLIST value to format -/// -/// # Returns -/// A formatted string representation -pub fn pretty_print_plist(p: &Value) -> String { - print_plist(p, 0) -} - -/// Pretty-prints a PLIST dictionary with key-value pairs -/// -/// # Arguments -/// * `dict` - The dictionary to format -/// -/// # Returns -/// A formatted string representation with newlines and indentation -/// -/// # Example -/// ```rust -/// let mut dict = plist::Dictionary::new(); -/// dict.insert("name".into(), "John".into()); -/// dict.insert("age".into(), 30.into()); -/// println!("{}", pretty_print_dictionary(&dict)); -/// ``` -pub fn pretty_print_dictionary(dict: &plist::Dictionary) -> String { - let items: Vec = dict - .iter() - .map(|(k, v)| format!("{}: {}", k, print_plist(v, 2))) - .collect(); - format!("{{\n{}\n}}", items.join(",\n")) -} - -/// Internal recursive function for printing PLIST values with indentation -/// -/// # Arguments -/// * `p` - The PLIST value to format -/// * `indentation` - Current indentation level -/// -/// # Returns -/// Formatted string representation -fn print_plist(p: &Value, indentation: usize) -> String { - let indent = " ".repeat(indentation); - match p { - Value::Array(vec) => { - let items: Vec = vec - .iter() - .map(|v| { - format!( - "{}{}", - " ".repeat(indentation + 2), - print_plist(v, indentation + 2) - ) - }) - .collect(); - format!("[\n{}\n{}]", items.join(",\n"), indent) - } - Value::Dictionary(dict) => { - let items: Vec = dict - .iter() - .map(|(k, v)| { - format!( - "{}{}: {}", - " ".repeat(indentation + 2), - k, - print_plist(v, indentation + 2) - ) - }) - .collect(); - format!("{{\n{}\n{}}}", items.join(",\n"), indent) - } - Value::Boolean(b) => format!("{b}"), - Value::Data(vec) => { - let len = vec.len(); - let preview: String = vec - .iter() - .take(20) - .map(|b| format!("{b:02X}")) - .collect::>() - .join(" "); - if len > 20 { - format!("Data({preview}... Len: {len})") - } else { - format!("Data({preview} Len: {len})") - } - } - Value::Date(date) => format!("Date({})", date.to_xml_format()), - Value::Real(f) => format!("{f}"), - Value::Integer(i) => format!("{i}"), - Value::String(s) => format!("\"{s}\""), - Value::Uid(_uid) => "Uid(?)".to_string(), - _ => "Unknown".to_string(), - } -} - -#[macro_export] -macro_rules! obf { - ($lit:literal) => {{ - #[cfg(feature = "obfuscate")] - { - std::borrow::Cow::Owned(obfstr::obfstr!($lit).to_string()) - } - #[cfg(not(feature = "obfuscate"))] - { - std::borrow::Cow::Borrowed($lit) - } - }}; -} diff --git a/idevice/src/utils/installation/helpers.rs b/idevice/src/utils/installation/helpers.rs index f88ce0d..8e6057f 100644 --- a/idevice/src/utils/installation/helpers.rs +++ b/idevice/src/utils/installation/helpers.rs @@ -1,13 +1,12 @@ -use std::{io::Cursor, path::Path}; - use async_zip::base::read::seek::ZipFileReader; use futures::AsyncReadExt as _; +use plist_macro::plist; +use std::{io::Cursor, path::Path}; use tokio::io::{AsyncBufRead, AsyncSeek, BufReader}; use crate::{ IdeviceError, IdeviceService, afc::{AfcClient, opcode::AfcFopenMode}, - plist, provider::IdeviceProvider, }; diff --git a/idevice/src/xpc/format.rs b/idevice/src/xpc/format.rs index f280656..5803be5 100644 --- a/idevice/src/xpc/format.rs +++ b/idevice/src/xpc/format.rs @@ -1,3 +1,4 @@ +use plist_macro::plist; use std::{ ffi::CString, io::{BufRead, Cursor, Read}, @@ -169,7 +170,7 @@ impl XPCObject { plist::Value::Dictionary(dict) } Self::FileTransfer { msg_id, data } => { - crate::plist!({ + plist!({ "msg_id": *msg_id, "data": data.to_plist(), }) diff --git a/tools/Cargo.toml b/tools/Cargo.toml index 9beaa5d..106977b 100644 --- a/tools/Cargo.toml +++ b/tools/Cargo.toml @@ -157,6 +157,7 @@ sha2 = { version = "0.10" } ureq = { version = "3" } clap = { version = "4.5" } plist = { version = "1.7" } +plist-macro = { version = "0.1" } ns-keyed-archive = "0.1.2" uuid = "1.16" futures-util = { version = "0.3" } diff --git a/tools/src/companion_proxy.rs b/tools/src/companion_proxy.rs index f37f345..8e02c99 100644 --- a/tools/src/companion_proxy.rs +++ b/tools/src/companion_proxy.rs @@ -3,9 +3,9 @@ use clap::{Arg, Command, arg}; use idevice::{ IdeviceService, RsdService, companion_proxy::CompanionProxy, - core_device_proxy::CoreDeviceProxy, pretty_print_dictionary, pretty_print_plist, - rsd::RsdHandshake, + core_device_proxy::CoreDeviceProxy, rsd::RsdHandshake, }; +use plist_macro::{pretty_print_dictionary, pretty_print_plist}; mod common; diff --git a/tools/src/lockdown.rs b/tools/src/lockdown.rs index 2fcf138..8f654f1 100644 --- a/tools/src/lockdown.rs +++ b/tools/src/lockdown.rs @@ -1,8 +1,9 @@ // Jackson Coxson use clap::{Arg, Command, arg}; -use idevice::{IdeviceService, lockdown::LockdownClient, pretty_print_plist}; +use idevice::{IdeviceService, lockdown::LockdownClient}; use plist::Value; +use plist_macro::pretty_print_plist; mod common; diff --git a/tools/src/mounter.rs b/tools/src/mounter.rs index 8b67056..baeb4ee 100644 --- a/tools/src/mounter.rs +++ b/tools/src/mounter.rs @@ -4,10 +4,8 @@ use std::{io::Write, path::PathBuf}; use clap::{Arg, Command, arg, value_parser}; -use idevice::{ - IdeviceService, lockdown::LockdownClient, mobile_image_mounter::ImageMounter, - pretty_print_plist, -}; +use idevice::{IdeviceService, lockdown::LockdownClient, mobile_image_mounter::ImageMounter}; +use plist_macro::pretty_print_plist; mod common; diff --git a/tools/src/restore_service.rs b/tools/src/restore_service.rs index e2a2efd..37b5a93 100644 --- a/tools/src/restore_service.rs +++ b/tools/src/restore_service.rs @@ -2,9 +2,10 @@ use clap::{Arg, Command}; use idevice::{ - IdeviceService, RsdService, core_device_proxy::CoreDeviceProxy, pretty_print_dictionary, + IdeviceService, RsdService, core_device_proxy::CoreDeviceProxy, restore_service::RestoreServiceClient, rsd::RsdHandshake, }; +use plist_macro::pretty_print_dictionary; mod common;