feat: impl parts of diagnostics and mobilebackup2 (#20)

* feat: add udid cache to idevice

* feat: impl diagnostics

* feat: impl mobilebackup2

* docs: update README.md

* fix: make clippy happy

* fix: make linux clippy happy

* fix: make linux clippy happy again

* fix: make clippy happy again

* fix: small updates
This commit is contained in:
Ylarod
2025-08-13 21:41:48 +08:00
committed by GitHub
parent 5477571a80
commit 0bb5deada8
9 changed files with 2234 additions and 5 deletions

View File

@@ -75,6 +75,7 @@ installation_proxy = []
springboardservices = []
misagent = []
mobile_image_mounter = ["dep:sha2"]
mobilebackup2 = []
location_simulation = []
pair = ["chrono/default", "tokio/time", "dep:sha2", "dep:rsa", "dep:x509-cert"]
obfuscate = ["dep:obfstr"]
@@ -109,6 +110,7 @@ full = [
"location_simulation",
"misagent",
"mobile_image_mounter",
"mobilebackup2",
"pair",
"restore_service",
"rsd",
@@ -123,4 +125,4 @@ full = [
]
[package.metadata.docs.rs]
all-features = true
all-features = true

View File

@@ -74,6 +74,11 @@ pub trait IdeviceService: Sized {
lockdown
.start_session(&provider.get_pairing_file().await?)
.await?;
// Best-effort fetch UDID for downstream defaults (e.g., MobileBackup2 Target/Source identifiers)
let udid_value = match lockdown.get_value(Some("UniqueDeviceID"), None).await {
Ok(v) => v.as_string().map(|s| s.to_string()),
Err(_) => None,
};
let (port, ssl) = lockdown.start_service(Self::service_name()).await?;
@@ -84,6 +89,10 @@ pub trait IdeviceService: Sized {
.await?;
}
if let Some(udid) = udid_value {
idevice.set_udid(udid);
}
Self::from_stream(idevice).await
}
@@ -123,6 +132,8 @@ pub struct Idevice {
socket: Option<Box<dyn ReadWrite>>,
/// Unique label identifying this connection
label: String,
/// Cached device UDID for convenience in higher-level protocols
udid: Option<String>,
}
impl Idevice {
@@ -135,6 +146,7 @@ impl Idevice {
Self {
socket: Some(socket),
label: label.into(),
udid: None,
}
}
@@ -142,6 +154,16 @@ impl Idevice {
self.socket
}
/// Sets cached UDID
pub fn set_udid(&mut self, udid: impl Into<String>) {
self.udid = Some(udid.into());
}
/// Returns cached UDID if available
pub fn udid(&self) -> Option<&str> {
self.udid.as_deref()
}
/// Queries the device type
///
/// Sends a QueryType request and parses the response

View File

@@ -20,7 +20,7 @@ impl IdeviceService for DiagnosticsRelayClient {
}
impl DiagnosticsRelayClient {
/// Creates a new client from an existing device connection
/// Creates a new client from an existing device connection
///
/// # Arguments
/// * `idevice` - Pre-established device connection
@@ -74,7 +74,223 @@ impl DiagnosticsRelayClient {
.and_then(|x| x.into_dictionary())
.and_then(|mut x| x.remove("IORegistry"))
.and_then(|x| x.into_dictionary());
Ok(res)
}
}
/// Requests MobileGestalt information from the device
///
/// # Arguments
/// * `keys` - Optional list of specific keys to request. If None, requests all available keys
///
/// # Returns
/// A dictionary containing the requested MobileGestalt information
pub async fn mobilegestalt(
&mut self,
keys: Option<Vec<String>>,
) -> Result<Option<plist::Dictionary>, IdeviceError> {
let mut req = plist::Dictionary::new();
req.insert("Request".into(), "MobileGestalt".into());
if let Some(keys) = keys {
let keys_array: Vec<plist::Value> = keys.into_iter().map(|k| k.into()).collect();
req.insert("MobileGestaltKeys".into(), plist::Value::Array(keys_array));
}
self.idevice
.send_plist(plist::Value::Dictionary(req))
.await?;
let mut res = self.idevice.read_plist().await?;
match res.get("Status").and_then(|x| x.as_string()) {
Some("Success") => {}
_ => {
return Err(IdeviceError::UnexpectedResponse);
}
}
let res = res
.remove("Diagnostics")
.and_then(|x| x.into_dictionary());
Ok(res)
}
/// Requests gas gauge information from the device
///
/// # Returns
/// A dictionary containing gas gauge (battery) information
pub async fn gasguage(&mut self) -> Result<Option<plist::Dictionary>, IdeviceError> {
let mut req = plist::Dictionary::new();
req.insert("Request".into(), "GasGauge".into());
self.idevice
.send_plist(plist::Value::Dictionary(req))
.await?;
let mut res = self.idevice.read_plist().await?;
match res.get("Status").and_then(|x| x.as_string()) {
Some("Success") => {}
_ => {
return Err(IdeviceError::UnexpectedResponse);
}
}
let res = res
.remove("Diagnostics")
.and_then(|x| x.into_dictionary());
Ok(res)
}
/// Requests NAND information from the device
///
/// # Returns
/// A dictionary containing NAND flash information
pub async fn nand(&mut self) -> Result<Option<plist::Dictionary>, IdeviceError> {
let mut req = plist::Dictionary::new();
req.insert("Request".into(), "NAND".into());
self.idevice
.send_plist(plist::Value::Dictionary(req))
.await?;
let mut res = self.idevice.read_plist().await?;
match res.get("Status").and_then(|x| x.as_string()) {
Some("Success") => {}
_ => {
return Err(IdeviceError::UnexpectedResponse);
}
}
let res = res
.remove("Diagnostics")
.and_then(|x| x.into_dictionary());
Ok(res)
}
/// Requests all available diagnostics information
///
/// # Returns
/// A dictionary containing all diagnostics information
pub async fn all(&mut self) -> Result<Option<plist::Dictionary>, IdeviceError> {
let mut req = plist::Dictionary::new();
req.insert("Request".into(), "All".into());
self.idevice
.send_plist(plist::Value::Dictionary(req))
.await?;
let mut res = self.idevice.read_plist().await?;
match res.get("Status").and_then(|x| x.as_string()) {
Some("Success") => {}
_ => {
return Err(IdeviceError::UnexpectedResponse);
}
}
let res = res
.remove("Diagnostics")
.and_then(|x| x.into_dictionary());
Ok(res)
}
/// Restarts the device
///
/// # Returns
/// Result indicating success or failure
pub async fn restart(&mut self) -> Result<(), IdeviceError> {
let mut req = plist::Dictionary::new();
req.insert("Request".into(), "Restart".into());
self.idevice
.send_plist(plist::Value::Dictionary(req))
.await?;
let res = self.idevice.read_plist().await?;
match res.get("Status").and_then(|x| x.as_string()) {
Some("Success") => Ok(()),
_ => Err(IdeviceError::UnexpectedResponse),
}
}
/// Shuts down the device
///
/// # Returns
/// Result indicating success or failure
pub async fn shutdown(&mut self) -> Result<(), IdeviceError> {
let mut req = plist::Dictionary::new();
req.insert("Request".into(), "Shutdown".into());
self.idevice
.send_plist(plist::Value::Dictionary(req))
.await?;
let res = self.idevice.read_plist().await?;
match res.get("Status").and_then(|x| x.as_string()) {
Some("Success") => Ok(()),
_ => Err(IdeviceError::UnexpectedResponse),
}
}
/// Puts the device to sleep
///
/// # Returns
/// Result indicating success or failure
pub async fn sleep(&mut self) -> Result<(), IdeviceError> {
let mut req = plist::Dictionary::new();
req.insert("Request".into(), "Sleep".into());
self.idevice
.send_plist(plist::Value::Dictionary(req))
.await?;
let res = self.idevice.read_plist().await?;
match res.get("Status").and_then(|x| x.as_string()) {
Some("Success") => Ok(()),
_ => Err(IdeviceError::UnexpectedResponse),
}
}
/// Requests WiFi diagnostics from the device
pub async fn wifi(&mut self) -> Result<Option<plist::Dictionary>, IdeviceError> {
let mut req = plist::Dictionary::new();
req.insert("Request".into(), "WiFi".into());
self.idevice
.send_plist(plist::Value::Dictionary(req))
.await?;
let mut res = self.idevice.read_plist().await?;
match res.get("Status").and_then(|x| x.as_string()) {
Some("Success") => {}
_ => {
return Err(IdeviceError::UnexpectedResponse);
}
}
let res = res
.remove("Diagnostics")
.and_then(|x| x.into_dictionary());
Ok(res)
}
/// Sends Goodbye request signaling end of communication
pub async fn goodbye(&mut self) -> Result<(), IdeviceError> {
let mut req = plist::Dictionary::new();
req.insert("Request".into(), "Goodbye".into());
self.idevice
.send_plist(plist::Value::Dictionary(req))
.await?;
let res = self.idevice.read_plist().await?;
match res.get("Status").and_then(|x| x.as_string()) {
Some("Success") => Ok(()),
Some("UnknownRequest") => Err(IdeviceError::UnexpectedResponse),
_ => Err(IdeviceError::UnexpectedResponse),
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -27,6 +27,8 @@ pub mod lockdown;
pub mod misagent;
#[cfg(feature = "mobile_image_mounter")]
pub mod mobile_image_mounter;
#[cfg(feature = "mobilebackup2")]
pub mod mobilebackup2;
#[cfg(feature = "syslog_relay")]
pub mod os_trace_relay;
#[cfg(feature = "restore_service")]