Files
idevice/idevice/src/services/crashreportcopymobile.rs
2025-08-13 08:06:58 -06:00

154 lines
4.8 KiB
Rust

//! iOS Crash Logs
//!
//! Provides functionality for managing crash logs on a connected iOS device.
//!
//! This module enables clients to list, pull, and remove crash logs via the
//! `CrashReportCopyMobile` service using the AFC protocol. It also includes a
//! function to trigger a flush of crash logs from system storage into the
//! crash reports directory by connecting to the `com.apple.crashreportmover` service.
use log::{debug, warn};
use crate::{Idevice, IdeviceError, IdeviceService, afc::AfcClient, lockdown::LockdownClient, obf};
/// Client for managing crash logs on an iOS device.
///
/// This client wraps access to the `com.apple.crashreportcopymobile` service,
/// which exposes crash logs through the Apple File Conduit (AFC).
pub struct CrashReportCopyMobileClient {
/// The underlying AFC client connected to the crash logs directory.
pub afc_client: AfcClient,
}
impl IdeviceService for CrashReportCopyMobileClient {
/// Returns the name of the CrashReportCopyMobile service.
fn service_name() -> std::borrow::Cow<'static, str> {
obf!("com.apple.crashreportcopymobile")
}
async fn from_stream(idevice: Idevice) -> Result<Self, crate::IdeviceError> {
Ok(Self::new(idevice))
}
}
impl CrashReportCopyMobileClient {
/// Creates a new client from an existing AFC-capable device connection.
///
/// # Arguments
/// * `idevice` - A pre-established connection to the device.
pub fn new(idevice: Idevice) -> Self {
Self {
afc_client: AfcClient::new(idevice),
}
}
/// Lists crash report files in the root of the crash logs directory.
///
/// # Arguments
/// * `dir_path` - The directory to pull logs from. Default is /
///
/// # Returns
/// A list of filenames.
///
/// # Errors
/// Returns `IdeviceError` if listing the directory fails.
pub async fn ls(&mut self, dir_path: Option<&str>) -> Result<Vec<String>, IdeviceError> {
let path = dir_path.unwrap_or("/");
let mut res = self.afc_client.list_dir(path).await?;
if res.len() > 2 {
if &res[0] == "." {
res.swap_remove(0);
}
if &res[1] == ".." {
res.swap_remove(1);
}
}
Ok(res)
}
/// Retrieves the contents of a specified crash log file.
///
/// # Arguments
/// * `log` - Name of the log file to retrieve.
///
/// # Returns
/// A byte vector containing the file contents.
///
/// # Errors
/// Returns `IdeviceError` if the file cannot be opened or read.
pub async fn pull(&mut self, log: impl Into<String>) -> Result<Vec<u8>, IdeviceError> {
let log = log.into();
let mut f = self
.afc_client
.open(format!("/{log}"), crate::afc::opcode::AfcFopenMode::RdOnly)
.await?;
f.read().await
}
/// Removes a specified crash log file from the device.
///
/// # Arguments
/// * `log` - Name of the log file to remove.
///
/// # Errors
/// Returns `IdeviceError` if the file could not be deleted.
pub async fn remove(&mut self, log: impl Into<String>) -> Result<(), IdeviceError> {
let log = log.into();
self.afc_client.remove(format!("/{log}")).await
}
/// Consumes this client and returns the inner AFC client.
pub fn to_afc_client(self) -> AfcClient {
self.afc_client
}
}
const EXPECTED_FLUSH: [u8; 4] = [0x70, 0x69, 0x6E, 0x67]; // 'ping'
/// Triggers a flush of crash logs from system storage.
///
/// This connects to the `com.apple.crashreportmover` service,
/// which moves crash logs into the AFC-accessible directory.
///
/// # Arguments
/// * `provider` - The device provider used for connection and pairing info.
///
/// # Returns
/// `Ok(())` if the service responds with a valid flush indicator.
/// `Err(IdeviceError)` if the service responds with unexpected data
/// or the connection fails.
pub async fn flush_reports(
provider: &dyn crate::provider::IdeviceProvider,
) -> Result<(), IdeviceError> {
let mut lockdown = LockdownClient::connect(provider).await?;
lockdown
.start_session(&provider.get_pairing_file().await?)
.await?;
let (port, ssl) = lockdown
.start_service(obf!("com.apple.crashreportmover"))
.await?;
let mut idevice = provider.connect(port).await?;
if ssl {
idevice
.start_session(&provider.get_pairing_file().await?)
.await?;
}
let res = idevice.read_raw(4).await?;
debug!(
"Flush reports response: {:?}",
String::from_utf8_lossy(&res)
);
if res[..4] == EXPECTED_FLUSH {
Ok(())
} else {
warn!("crashreportmover sent wrong bytes: {res:02X?}");
Err(IdeviceError::CrashReportMoverBadResponse(res))
}
}