mirror of
https://github.com/jkcoxson/idevice.git
synced 2026-03-02 06:26:15 +01:00
Migrate to plist_macro crate for utils
This commit is contained in:
11
Cargo.lock
generated
11
Cargo.lock
generated
@@ -1076,6 +1076,7 @@ dependencies = [
|
|||||||
"obfstr",
|
"obfstr",
|
||||||
"openssl",
|
"openssl",
|
||||||
"plist",
|
"plist",
|
||||||
|
"plist-macro",
|
||||||
"rand 0.9.2",
|
"rand 0.9.2",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"rsa",
|
"rsa",
|
||||||
@@ -1122,6 +1123,7 @@ dependencies = [
|
|||||||
"idevice",
|
"idevice",
|
||||||
"ns-keyed-archive",
|
"ns-keyed-archive",
|
||||||
"plist",
|
"plist",
|
||||||
|
"plist-macro",
|
||||||
"sha2",
|
"sha2",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tracing",
|
"tracing",
|
||||||
@@ -1730,6 +1732,15 @@ dependencies = [
|
|||||||
"time",
|
"time",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "plist-macro"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cb72007326fe20721ef27304fcf2d1bd5877b92d13dbd8df735fd33407e31c2a"
|
||||||
|
dependencies = [
|
||||||
|
"plist",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "plist_ffi"
|
name = "plist_ffi"
|
||||||
version = "0.1.6"
|
version = "0.1.6"
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ tokio-openssl = { version = "0.6", optional = true }
|
|||||||
openssl = { version = "0.10", optional = true }
|
openssl = { version = "0.10", optional = true }
|
||||||
|
|
||||||
plist = { version = "1.8" }
|
plist = { version = "1.8" }
|
||||||
|
plist-macro = { version = "0.1" }
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
ns-keyed-archive = { version = "0.1.4", optional = true }
|
ns-keyed-archive = { version = "0.1.4", optional = true }
|
||||||
crossfire = { version = "2.1", optional = true }
|
crossfire = { version = "2.1", optional = true }
|
||||||
|
|||||||
334
idevice/src/cursor.rs
Normal file
334
idevice/src/cursor.rs
Normal file
@@ -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<u8> {
|
||||||
|
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<u16> {
|
||||||
|
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<u16> {
|
||||||
|
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<u32> {
|
||||||
|
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<u32> {
|
||||||
|
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<u64> {
|
||||||
|
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<u64> {
|
||||||
|
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<u128> {
|
||||||
|
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<u128> {
|
||||||
|
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<f32> {
|
||||||
|
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<f32> {
|
||||||
|
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<i8> {
|
||||||
|
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<i16> {
|
||||||
|
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<i16> {
|
||||||
|
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<i32> {
|
||||||
|
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<i32> {
|
||||||
|
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<i64> {
|
||||||
|
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<i64> {
|
||||||
|
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<i128> {
|
||||||
|
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<i128> {
|
||||||
|
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())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,8 +5,9 @@
|
|||||||
|
|
||||||
#[cfg(all(feature = "pair", feature = "rustls"))]
|
#[cfg(all(feature = "pair", feature = "rustls"))]
|
||||||
mod ca;
|
mod ca;
|
||||||
|
pub mod cursor;
|
||||||
|
mod obfuscation;
|
||||||
pub mod pairing_file;
|
pub mod pairing_file;
|
||||||
pub mod plist_macro;
|
|
||||||
pub mod provider;
|
pub mod provider;
|
||||||
#[cfg(feature = "rustls")]
|
#[cfg(feature = "rustls")]
|
||||||
mod sni;
|
mod sni;
|
||||||
@@ -18,7 +19,6 @@ pub mod tss;
|
|||||||
pub mod tunneld;
|
pub mod tunneld;
|
||||||
#[cfg(feature = "usbmuxd")]
|
#[cfg(feature = "usbmuxd")]
|
||||||
pub mod usbmuxd;
|
pub mod usbmuxd;
|
||||||
mod util;
|
|
||||||
pub mod utils;
|
pub mod utils;
|
||||||
#[cfg(feature = "xpc")]
|
#[cfg(feature = "xpc")]
|
||||||
pub mod xpc;
|
pub mod xpc;
|
||||||
@@ -29,6 +29,7 @@ pub use services::*;
|
|||||||
#[cfg(feature = "xpc")]
|
#[cfg(feature = "xpc")]
|
||||||
pub use xpc::RemoteXpcClient;
|
pub use xpc::RemoteXpcClient;
|
||||||
|
|
||||||
|
use plist_macro::{plist, pretty_print_dictionary, pretty_print_plist};
|
||||||
use provider::{IdeviceProvider, RsdProvider};
|
use provider::{IdeviceProvider, RsdProvider};
|
||||||
#[cfg(feature = "rustls")]
|
#[cfg(feature = "rustls")]
|
||||||
use rustls::{crypto::CryptoProvider, pki_types::ServerName};
|
use rustls::{crypto::CryptoProvider, pki_types::ServerName};
|
||||||
@@ -40,8 +41,6 @@ use thiserror::Error;
|
|||||||
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
|
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
|
||||||
use tracing::{debug, trace};
|
use tracing::{debug, trace};
|
||||||
|
|
||||||
pub use util::{pretty_print_dictionary, pretty_print_plist};
|
|
||||||
|
|
||||||
use crate::services::lockdown::LockdownClient;
|
use crate::services::lockdown::LockdownClient;
|
||||||
|
|
||||||
/// A trait combining all required characteristics for a device communication socket
|
/// A trait combining all required characteristics for a device communication socket
|
||||||
@@ -192,7 +191,7 @@ impl Idevice {
|
|||||||
/// # Errors
|
/// # Errors
|
||||||
/// Returns `IdeviceError` if communication fails or response is invalid
|
/// Returns `IdeviceError` if communication fails or response is invalid
|
||||||
pub async fn get_type(&mut self) -> Result<String, IdeviceError> {
|
pub async fn get_type(&mut self) -> Result<String, IdeviceError> {
|
||||||
let req = crate::plist!({
|
let req = plist!({
|
||||||
"Label": self.label.clone(),
|
"Label": self.label.clone(),
|
||||||
"Request": "QueryType",
|
"Request": "QueryType",
|
||||||
});
|
});
|
||||||
@@ -212,7 +211,7 @@ impl Idevice {
|
|||||||
/// # Errors
|
/// # Errors
|
||||||
/// Returns `IdeviceError` if the protocol sequence isn't followed correctly
|
/// Returns `IdeviceError` if the protocol sequence isn't followed correctly
|
||||||
pub async fn rsd_checkin(&mut self) -> Result<(), IdeviceError> {
|
pub async fn rsd_checkin(&mut self) -> Result<(), IdeviceError> {
|
||||||
let req = crate::plist!({
|
let req = plist!({
|
||||||
"Label": self.label.clone(),
|
"Label": self.label.clone(),
|
||||||
"ProtocolVersion": "2",
|
"ProtocolVersion": "2",
|
||||||
"Request": "RSDCheckin",
|
"Request": "RSDCheckin",
|
||||||
|
|||||||
15
idevice/src/obfuscation.rs
Normal file
15
idevice/src/obfuscation.rs
Normal file
@@ -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)
|
||||||
|
}
|
||||||
|
}};
|
||||||
|
}
|
||||||
@@ -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<plist::Value>`.
|
|
||||||
/// 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<T: PlistConvertible>(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<u8> {
|
|
||||||
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<T: PlistConvertible> PlistConvertible for Vec<T> {
|
|
||||||
fn to_plist_value(self) -> plist::Value {
|
|
||||||
plist::Value::Array(self.into_iter().map(|item| item.to_plist_value()).collect())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: PlistConvertible + Clone> PlistConvertible for &[T] {
|
|
||||||
fn to_plist_value(self) -> plist::Value {
|
|
||||||
plist::Value::Array(
|
|
||||||
self.iter()
|
|
||||||
.map(|item| item.clone().to_plist_value())
|
|
||||||
.collect(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: PlistConvertible + Clone, const N: usize> 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<T: PlistConvertible + Clone, const N: usize> 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<K, V> PlistConvertible for std::collections::HashMap<K, V>
|
|
||||||
where
|
|
||||||
K: Into<String>,
|
|
||||||
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<K, V> PlistConvertible for std::collections::BTreeMap<K, V>
|
|
||||||
where
|
|
||||||
K: Into<String>,
|
|
||||||
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<T> as-is.
|
|
||||||
pub trait MaybePlist {
|
|
||||||
fn into_option_value(self) -> Option<plist::Value>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: PlistConvertible> MaybePlist for T {
|
|
||||||
fn into_option_value(self) -> Option<plist::Value> {
|
|
||||||
Some(self.to_plist_value())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: PlistConvertible> MaybePlist for Option<T> {
|
|
||||||
fn into_option_value(self) -> Option<plist::Value> {
|
|
||||||
self.map(|v| v.to_plist_value())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[doc(hidden)]
|
|
||||||
pub fn plist_maybe<T: MaybePlist>(v: T) -> Option<plist::Value> {
|
|
||||||
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<K, V> IntoPlistDict for std::collections::HashMap<K, V>
|
|
||||||
where
|
|
||||||
K: Into<String>,
|
|
||||||
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<K, V> IntoPlistDict for std::collections::BTreeMap<K, V>
|
|
||||||
where
|
|
||||||
K: Into<String>,
|
|
||||||
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<T>.
|
|
||||||
pub trait MaybeIntoPlistDict {
|
|
||||||
fn into_option_plist_dict(self) -> Option<plist::Dictionary>;
|
|
||||||
}
|
|
||||||
impl<T: IntoPlistDict> MaybeIntoPlistDict for T {
|
|
||||||
fn into_option_plist_dict(self) -> Option<plist::Dictionary> {
|
|
||||||
Some(self.into_plist_dict())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<T: IntoPlistDict> MaybeIntoPlistDict for Option<T> {
|
|
||||||
fn into_option_plist_dict(self) -> Option<plist::Dictionary> {
|
|
||||||
self.map(|t| t.into_plist_dict())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[doc(hidden)]
|
|
||||||
pub fn maybe_into_dict<T: MaybeIntoPlistDict>(v: T) -> Option<plist::Dictionary> {
|
|
||||||
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<u64> = 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,
|
|
||||||
:<? maybe_merge
|
|
||||||
});
|
|
||||||
|
|
||||||
if let plist::Value::Dictionary(dict) = value {
|
|
||||||
assert_eq!(
|
|
||||||
dict.get("name"),
|
|
||||||
Some(&plist::Value::String("dynamic".to_string()))
|
|
||||||
);
|
|
||||||
assert_eq!(dict.get("count"), Some(&plist::Value::Integer(100.into())));
|
|
||||||
assert!(dict.get("omit me").is_none());
|
|
||||||
assert_eq!(
|
|
||||||
dict.get("keep me"),
|
|
||||||
Some(&plist::Value::Integer(123.into()))
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
dict.get("reee"),
|
|
||||||
Some(&plist::Value::String("cool beans".to_string()))
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
dict.get("yeppers"),
|
|
||||||
Some(&plist::Value::String(
|
|
||||||
"what did I say about yeppers".to_string()
|
|
||||||
))
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
dict.get("replace me"),
|
|
||||||
Some(&plist::Value::Integer(2.into()))
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
panic!("Expected dictionary");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
// Jackson Coxson
|
// Jackson Coxson
|
||||||
|
|
||||||
|
use plist_macro::plist_to_xml_bytes;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use tracing::warn;
|
use tracing::warn;
|
||||||
|
|
||||||
@@ -216,7 +217,7 @@ impl<R: ReadWrite> AppServiceClient<R> {
|
|||||||
"user": {
|
"user": {
|
||||||
"active": true,
|
"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())),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -6,9 +6,10 @@
|
|||||||
//! - Handle cryptographic signing operations
|
//! - Handle cryptographic signing operations
|
||||||
|
|
||||||
use plist::Value;
|
use plist::Value;
|
||||||
|
use plist_macro::plist_to_xml_bytes;
|
||||||
use tracing::{debug, warn};
|
use tracing::{debug, warn};
|
||||||
|
|
||||||
use crate::{IdeviceError, util::plist_to_xml_bytes};
|
use crate::IdeviceError;
|
||||||
|
|
||||||
/// TSS client version string sent in requests
|
/// TSS client version string sent in requests
|
||||||
const TSS_CLIENT_VERSION_STRING: &str = "libauthinstall-1033.0.2";
|
const TSS_CLIENT_VERSION_STRING: &str = "libauthinstall-1033.0.2";
|
||||||
@@ -30,7 +31,7 @@ impl TSSRequest {
|
|||||||
/// - Client version string
|
/// - Client version string
|
||||||
/// - Random UUID for request identification
|
/// - Random UUID for request identification
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let inner = crate::plist!(dict {
|
let inner = plist_macro::plist!(dict {
|
||||||
"@HostPlatformInfo": "mac",
|
"@HostPlatformInfo": "mac",
|
||||||
"@VersionInfo": TSS_CLIENT_VERSION_STRING,
|
"@VersionInfo": TSS_CLIENT_VERSION_STRING,
|
||||||
"@UUID": uuid::Uuid::new_v4().to_string().to_uppercase()
|
"@UUID": uuid::Uuid::new_v4().to_string().to_uppercase()
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// Jackson Coxson
|
// Jackson Coxson
|
||||||
|
|
||||||
use crate::util::plist_to_xml_bytes;
|
use plist_macro::plist_to_xml_bytes;
|
||||||
use tracing::warn;
|
use tracing::warn;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|||||||
@@ -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<u8> {
|
|
||||||
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<String> = 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<String> = 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<String> = 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::<Vec<String>>()
|
|
||||||
.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)
|
|
||||||
}
|
|
||||||
}};
|
|
||||||
}
|
|
||||||
@@ -1,13 +1,12 @@
|
|||||||
use std::{io::Cursor, path::Path};
|
|
||||||
|
|
||||||
use async_zip::base::read::seek::ZipFileReader;
|
use async_zip::base::read::seek::ZipFileReader;
|
||||||
use futures::AsyncReadExt as _;
|
use futures::AsyncReadExt as _;
|
||||||
|
use plist_macro::plist;
|
||||||
|
use std::{io::Cursor, path::Path};
|
||||||
use tokio::io::{AsyncBufRead, AsyncSeek, BufReader};
|
use tokio::io::{AsyncBufRead, AsyncSeek, BufReader};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
IdeviceError, IdeviceService,
|
IdeviceError, IdeviceService,
|
||||||
afc::{AfcClient, opcode::AfcFopenMode},
|
afc::{AfcClient, opcode::AfcFopenMode},
|
||||||
plist,
|
|
||||||
provider::IdeviceProvider,
|
provider::IdeviceProvider,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use plist_macro::plist;
|
||||||
use std::{
|
use std::{
|
||||||
ffi::CString,
|
ffi::CString,
|
||||||
io::{BufRead, Cursor, Read},
|
io::{BufRead, Cursor, Read},
|
||||||
@@ -169,7 +170,7 @@ impl XPCObject {
|
|||||||
plist::Value::Dictionary(dict)
|
plist::Value::Dictionary(dict)
|
||||||
}
|
}
|
||||||
Self::FileTransfer { msg_id, data } => {
|
Self::FileTransfer { msg_id, data } => {
|
||||||
crate::plist!({
|
plist!({
|
||||||
"msg_id": *msg_id,
|
"msg_id": *msg_id,
|
||||||
"data": data.to_plist(),
|
"data": data.to_plist(),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -157,6 +157,7 @@ sha2 = { version = "0.10" }
|
|||||||
ureq = { version = "3" }
|
ureq = { version = "3" }
|
||||||
clap = { version = "4.5" }
|
clap = { version = "4.5" }
|
||||||
plist = { version = "1.7" }
|
plist = { version = "1.7" }
|
||||||
|
plist-macro = { version = "0.1" }
|
||||||
ns-keyed-archive = "0.1.2"
|
ns-keyed-archive = "0.1.2"
|
||||||
uuid = "1.16"
|
uuid = "1.16"
|
||||||
futures-util = { version = "0.3" }
|
futures-util = { version = "0.3" }
|
||||||
|
|||||||
@@ -3,9 +3,9 @@
|
|||||||
use clap::{Arg, Command, arg};
|
use clap::{Arg, Command, arg};
|
||||||
use idevice::{
|
use idevice::{
|
||||||
IdeviceService, RsdService, companion_proxy::CompanionProxy,
|
IdeviceService, RsdService, companion_proxy::CompanionProxy,
|
||||||
core_device_proxy::CoreDeviceProxy, pretty_print_dictionary, pretty_print_plist,
|
core_device_proxy::CoreDeviceProxy, rsd::RsdHandshake,
|
||||||
rsd::RsdHandshake,
|
|
||||||
};
|
};
|
||||||
|
use plist_macro::{pretty_print_dictionary, pretty_print_plist};
|
||||||
|
|
||||||
mod common;
|
mod common;
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
// Jackson Coxson
|
// Jackson Coxson
|
||||||
|
|
||||||
use clap::{Arg, Command, arg};
|
use clap::{Arg, Command, arg};
|
||||||
use idevice::{IdeviceService, lockdown::LockdownClient, pretty_print_plist};
|
use idevice::{IdeviceService, lockdown::LockdownClient};
|
||||||
use plist::Value;
|
use plist::Value;
|
||||||
|
use plist_macro::pretty_print_plist;
|
||||||
|
|
||||||
mod common;
|
mod common;
|
||||||
|
|
||||||
|
|||||||
@@ -4,10 +4,8 @@
|
|||||||
use std::{io::Write, path::PathBuf};
|
use std::{io::Write, path::PathBuf};
|
||||||
|
|
||||||
use clap::{Arg, Command, arg, value_parser};
|
use clap::{Arg, Command, arg, value_parser};
|
||||||
use idevice::{
|
use idevice::{IdeviceService, lockdown::LockdownClient, mobile_image_mounter::ImageMounter};
|
||||||
IdeviceService, lockdown::LockdownClient, mobile_image_mounter::ImageMounter,
|
use plist_macro::pretty_print_plist;
|
||||||
pretty_print_plist,
|
|
||||||
};
|
|
||||||
|
|
||||||
mod common;
|
mod common;
|
||||||
|
|
||||||
|
|||||||
@@ -2,9 +2,10 @@
|
|||||||
|
|
||||||
use clap::{Arg, Command};
|
use clap::{Arg, Command};
|
||||||
use idevice::{
|
use idevice::{
|
||||||
IdeviceService, RsdService, core_device_proxy::CoreDeviceProxy, pretty_print_dictionary,
|
IdeviceService, RsdService, core_device_proxy::CoreDeviceProxy,
|
||||||
restore_service::RestoreServiceClient, rsd::RsdHandshake,
|
restore_service::RestoreServiceClient, rsd::RsdHandshake,
|
||||||
};
|
};
|
||||||
|
use plist_macro::pretty_print_dictionary;
|
||||||
|
|
||||||
mod common;
|
mod common;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user