Unify IdeviceService creation behavior with trait

This commit is contained in:
Jackson Coxson
2025-08-11 13:56:09 -06:00
parent 0a0899cd8a
commit c80512f37f
16 changed files with 59 additions and 455 deletions

View File

@@ -16,10 +16,6 @@ jobs:
run: |
brew install just
- name: Install libplist
run: |
brew install libplist
- name: Cache Cargo
uses: actions/cache@v4
with:
@@ -73,7 +69,7 @@ jobs:
- name: Install build dependencies
run: |
sudo apt-get update && sudo apt-get install -y build-essential cmake libplist
sudo apt-get update && sudo apt-get install -y build-essential cmake
- name: Install just
run: |

View File

@@ -37,6 +37,8 @@ use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
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
///
/// This serves as a convenience trait for any type that can be used as an asynchronous
@@ -61,9 +63,25 @@ pub trait IdeviceService: Sized {
///
/// # Arguments
/// * `provider` - The device provider that can supply connections
fn connect(
provider: &dyn IdeviceProvider,
) -> impl std::future::Future<Output = Result<Self, IdeviceError>> + Send;
async fn connect(provider: &dyn IdeviceProvider) -> Result<Self, IdeviceError> {
let mut lockdown = LockdownClient::connect(provider).await?;
lockdown
.start_session(&provider.get_pairing_file().await?)
.await?;
let (port, ssl) = lockdown.start_service(Self::service_name()).await?;
let mut idevice = provider.connect(port).await?;
if ssl {
idevice
.start_session(&provider.get_pairing_file().await?)
.await?;
}
Self::from_stream(idevice).await
}
async fn from_stream(idevice: Idevice) -> Result<Self, IdeviceError>;
}
#[cfg(feature = "rsd")]

View File

@@ -11,7 +11,7 @@ use log::warn;
use opcode::{AfcFopenMode, AfcOpcode};
use packet::{AfcPacket, AfcPacketHeader};
use crate::{lockdown::LockdownClient, obf, Idevice, IdeviceError, IdeviceService};
use crate::{obf, Idevice, IdeviceError, IdeviceService};
pub mod errors;
pub mod file;
@@ -65,30 +65,7 @@ impl IdeviceService for AfcClient {
obf!("com.apple.afc")
}
/// Connects to the AFC service on the device
///
/// # Arguments
/// * `provider` - The iDevice provider to use for the connection
///
/// # Returns
/// A new `AfcClient` instance on success
async fn connect(
provider: &dyn crate::provider::IdeviceProvider,
) -> Result<Self, IdeviceError> {
let mut lockdown = LockdownClient::connect(provider).await?;
lockdown
.start_session(&provider.get_pairing_file().await?)
.await?;
let (port, ssl) = lockdown.start_service(Self::service_name()).await?;
let mut idevice = provider.connect(port).await?;
if ssl {
idevice
.start_session(&provider.get_pairing_file().await?)
.await?;
}
async fn from_stream(idevice: Idevice) -> Result<Self, IdeviceError> {
Ok(Self {
idevice,
package_number: 0,

View File

@@ -2,7 +2,7 @@
use plist::Dictionary;
use crate::{lockdown::LockdownClient, obf, Idevice, IdeviceError, IdeviceService};
use crate::{obf, Idevice, IdeviceError, IdeviceService};
/// Client for interacting with the AMFI service on the device
pub struct AmfiClient {
@@ -16,41 +16,8 @@ impl IdeviceService for AmfiClient {
obf!("com.apple.amfi.lockdown")
}
/// Establishes a connection to the amfi service
///
/// # Arguments
/// * `provider` - Device connection provider
///
/// # Returns
/// A connected `AmfiClient` instance
///
/// # Errors
/// Returns `IdeviceError` if any step of the connection process fails
///
/// # Process
/// 1. Connects to lockdownd service
/// 2. Starts a lockdown session
/// 3. Requests the amfi service port
/// 4. Establishes connection to the amfi port
/// 5. Optionally starts TLS if required by service
async fn connect(
provider: &dyn crate::provider::IdeviceProvider,
) -> Result<Self, IdeviceError> {
let mut lockdown = LockdownClient::connect(provider).await?;
lockdown
.start_session(&provider.get_pairing_file().await?)
.await?;
let (port, ssl) = lockdown.start_service(Self::service_name()).await?;
let mut idevice = provider.connect(port).await?;
if ssl {
idevice
.start_session(&provider.get_pairing_file().await?)
.await?;
}
Ok(Self { idevice })
async fn from_stream(idevice: Idevice) -> Result<Self, crate::IdeviceError> {
Ok(Self::new(idevice))
}
}

View File

@@ -13,7 +13,7 @@
//! # Features
//! - `tunnel_tcp_stack`: Enables software TCP/IP tunnel creation using a virtual adapter. See the tcp moduel.
use crate::{lockdown::LockdownClient, obf, Idevice, IdeviceError, IdeviceService};
use crate::{obf, Idevice, IdeviceError, IdeviceService};
use byteorder::{BigEndian, WriteBytesExt};
use serde::{Deserialize, Serialize};
@@ -97,34 +97,7 @@ impl IdeviceService for CoreDeviceProxy {
obf!("com.apple.internal.devicecompute.CoreDeviceProxy")
}
/// Connects to the CoreDeviceProxy service
///
/// # Arguments
///
/// * `provider` - An implementation of `IdeviceProvider` that supplies
/// pairing data and socket connections.
///
/// # Returns
///
/// * `Ok(CoreDeviceProxy)` if connection and handshake succeed.
/// * `Err(IdeviceError)` if any step fails.
async fn connect(
provider: &dyn crate::provider::IdeviceProvider,
) -> Result<Self, IdeviceError> {
let mut lockdown = LockdownClient::connect(provider).await?;
lockdown
.start_session(&provider.get_pairing_file().await?)
.await?;
let (port, ssl) = lockdown.start_service(Self::service_name()).await?;
let mut idevice = provider.connect(port).await?;
if ssl {
idevice
.start_session(&provider.get_pairing_file().await?)
.await?;
}
async fn from_stream(idevice: Idevice) -> Result<Self, crate::IdeviceError> {
Self::new(idevice).await
}
}

View File

@@ -26,43 +26,8 @@ impl IdeviceService for CrashReportCopyMobileClient {
obf!("com.apple.crashreportcopymobile")
}
/// Connects to the CrashReportCopyMobile service on the device.
///
/// # Arguments
/// * `provider` - The provider used to access the device and pairing info.
///
/// # Returns
/// A connected `CrashReportCopyMobileClient`.
///
/// # Errors
/// Returns `IdeviceError` if the connection fails at any stage.
///
/// # Process
/// 1. Connects to the lockdownd service.
/// 2. Starts a lockdown session.
/// 3. Requests the CrashReportCopyMobile service.
/// 4. Establishes a connection to the service.
/// 5. Performs SSL handshake if required.
async fn connect(
provider: &dyn crate::provider::IdeviceProvider,
) -> Result<Self, IdeviceError> {
let mut lockdown = LockdownClient::connect(provider).await?;
lockdown
.start_session(&provider.get_pairing_file().await?)
.await?;
let (port, ssl) = lockdown.start_service(Self::service_name()).await?;
let mut idevice = provider.connect(port).await?;
if ssl {
idevice
.start_session(&provider.get_pairing_file().await?)
.await?;
}
Ok(Self {
afc_client: AfcClient::new(idevice),
})
async fn from_stream(idevice: Idevice) -> Result<Self, crate::IdeviceError> {
Ok(Self::new(idevice))
}
}

View File

@@ -1,6 +1,6 @@
//! Diagnostics Relay
use crate::{lockdown::LockdownClient, obf, Idevice, IdeviceError, IdeviceService};
use crate::{obf, Idevice, IdeviceError, IdeviceService};
/// Client for interacting with the Diagnostics Relay
pub struct DiagnosticsRelayClient {
@@ -14,41 +14,8 @@ impl IdeviceService for DiagnosticsRelayClient {
obf!("com.apple.mobile.diagnostics_relay")
}
/// Establishes a connection to the service
///
/// # Arguments
/// * `provider` - Device connection provider
///
/// # Returns
/// A connected `DiagnosticsRelayClient` instance
///
/// # Errors
/// Returns `IdeviceError` if any step of the connection process fails
///
/// # Process
/// 1. Connects to lockdownd service
/// 2. Starts a lockdown session
/// 3. Requests the service port
/// 4. Establishes connection to the port
/// 5. Optionally starts TLS if required by service
async fn connect(
provider: &dyn crate::provider::IdeviceProvider,
) -> Result<Self, IdeviceError> {
let mut lockdown = LockdownClient::connect(provider).await?;
lockdown
.start_session(&provider.get_pairing_file().await?)
.await?;
let (port, ssl) = lockdown.start_service(Self::service_name()).await?;
let mut idevice = provider.connect(port).await?;
if ssl {
idevice
.start_session(&provider.get_pairing_file().await?)
.await?;
}
Ok(Self { idevice })
async fn from_stream(idevice: Idevice) -> Result<Self, crate::IdeviceError> {
Ok(Self::new(idevice))
}
}

View File

@@ -3,7 +3,7 @@
//! iOS automatically closes service connections if there is no heartbeat client connected and
//! responding.
use crate::{lockdown::LockdownClient, obf, Idevice, IdeviceError, IdeviceService};
use crate::{obf, Idevice, IdeviceError, IdeviceService};
/// Client for interacting with the iOS device heartbeat service
///
@@ -22,42 +22,8 @@ impl IdeviceService for HeartbeatClient {
fn service_name() -> std::borrow::Cow<'static, str> {
obf!("com.apple.mobile.heartbeat")
}
/// Establishes a connection to the heartbeat service
///
/// # Arguments
/// * `provider` - Device connection provider
///
/// # Returns
/// A connected `HeartbeatClient` instance
///
/// # Errors
/// Returns `IdeviceError` if any step of the connection process fails
///
/// # Process
/// 1. Connects to lockdownd service
/// 2. Starts a lockdown session
/// 3. Requests the heartbeat service port
/// 4. Establishes connection to the heartbeat port
/// 5. Optionally starts TLS if required by service
async fn connect(
provider: &dyn crate::provider::IdeviceProvider,
) -> Result<Self, IdeviceError> {
let mut lockdown = LockdownClient::connect(provider).await?;
lockdown
.start_session(&provider.get_pairing_file().await?)
.await?;
let (port, ssl) = lockdown.start_service(Self::service_name()).await?;
let mut idevice = provider.connect(port).await?;
if ssl {
idevice
.start_session(&provider.get_pairing_file().await?)
.await?;
}
Ok(Self { idevice })
async fn from_stream(idevice: Idevice) -> Result<Self, crate::IdeviceError> {
Ok(Self::new(idevice))
}
}

View File

@@ -6,7 +6,7 @@
use plist::{Dictionary, Value};
use crate::{lockdown::LockdownClient, obf, Idevice, IdeviceError, IdeviceService};
use crate::{obf, Idevice, IdeviceError, IdeviceService};
use super::afc::AfcClient;
@@ -25,41 +25,8 @@ impl IdeviceService for HouseArrestClient {
obf!("com.apple.mobile.house_arrest")
}
/// Establishes a connection to the HouseArrest service
///
/// # Arguments
/// * `provider` - Device connection provider
///
/// # Returns
/// A connected `HouseArrestClient` instance
///
/// # Errors
/// Returns `IdeviceError` if any step of the connection process fails
///
/// # Process
/// 1. Connect to the lockdownd service
/// 2. Start a lockdown session
/// 3. Request the HouseArrest service
/// 4. Connect to the returned service port
/// 5. Start TLS if required by the service
async fn connect(
provider: &dyn crate::provider::IdeviceProvider,
) -> Result<Self, IdeviceError> {
let mut lockdown = LockdownClient::connect(provider).await?;
lockdown
.start_session(&provider.get_pairing_file().await?)
.await?;
let (port, ssl) = lockdown.start_service(Self::service_name()).await?;
let mut idevice = provider.connect(port).await?;
if ssl {
idevice
.start_session(&provider.get_pairing_file().await?)
.await?;
}
Ok(Self { idevice })
async fn from_stream(idevice: Idevice) -> Result<Self, crate::IdeviceError> {
Ok(Self::new(idevice))
}
}

View File

@@ -8,7 +8,7 @@ use std::collections::HashMap;
use log::warn;
use plist::Dictionary;
use crate::{lockdown::LockdownClient, obf, Idevice, IdeviceError, IdeviceService};
use crate::{obf, Idevice, IdeviceError, IdeviceService};
/// Client for interacting with the iOS installation proxy service
///
@@ -25,39 +25,7 @@ impl IdeviceService for InstallationProxyClient {
obf!("com.apple.mobile.installation_proxy")
}
/// Establishes a connection to the installation proxy service
///
/// # Arguments
/// * `provider` - Device connection provider
///
/// # Returns
/// A connected `InstallationProxyClient` instance
///
/// # Errors
/// Returns `IdeviceError` if any step of the connection process fails
///
/// # Process
/// 1. Connects to lockdownd service
/// 2. Starts a lockdown session
/// 3. Requests the installation proxy service port
/// 4. Establishes connection to the service port
/// 5. Optionally starts TLS if required by service
async fn connect(
provider: &dyn crate::provider::IdeviceProvider,
) -> Result<Self, IdeviceError> {
let mut lockdown = LockdownClient::connect(provider).await?;
lockdown
.start_session(&provider.get_pairing_file().await?)
.await?;
let (port, ssl) = lockdown.start_service(Self::service_name()).await?;
let mut idevice = provider.connect(port).await?;
if ssl {
idevice
.start_session(&provider.get_pairing_file().await?)
.await?;
}
async fn from_stream(idevice: Idevice) -> Result<Self, crate::IdeviceError> {
Ok(Self::new(idevice))
}
}

View File

@@ -42,6 +42,10 @@ impl IdeviceService for LockdownClient {
let idevice = provider.connect(Self::LOCKDOWND_PORT).await?;
Ok(Self::new(idevice))
}
async fn from_stream(idevice: Idevice) -> Result<Self, crate::IdeviceError> {
Ok(Self::new(idevice))
}
}
/// Internal structure for lockdown protocol requests

View File

@@ -6,7 +6,7 @@
use log::warn;
use plist::Dictionary;
use crate::{lockdown::LockdownClient, obf, Idevice, IdeviceError, IdeviceService, RsdService};
use crate::{obf, Idevice, IdeviceError, IdeviceService, RsdService};
/// Client for interacting with the iOS misagent service
///
@@ -37,39 +37,7 @@ impl IdeviceService for MisagentClient {
obf!("com.apple.misagent")
}
/// Establishes a connection to the misagent service
///
/// # Arguments
/// * `provider` - Device connection provider
///
/// # Returns
/// A connected `MisagentClient` instance
///
/// # Errors
/// Returns `IdeviceError` if any step of the connection process fails
///
/// # Process
/// 1. Connects to lockdownd service
/// 2. Starts a lockdown session
/// 3. Requests the misagent service port
/// 4. Establishes connection to the service port
/// 5. Optionally starts TLS if required by service
async fn connect(
provider: &dyn crate::provider::IdeviceProvider,
) -> Result<Self, IdeviceError> {
let mut lockdown = LockdownClient::connect(provider).await?;
lockdown
.start_session(&provider.get_pairing_file().await?)
.await?;
let (port, ssl) = lockdown.start_service(Self::service_name()).await?;
let mut idevice = provider.connect(port).await?;
if ssl {
idevice
.start_session(&provider.get_pairing_file().await?)
.await?;
}
async fn from_stream(idevice: Idevice) -> Result<Self, crate::IdeviceError> {
Ok(Self::new(idevice))
}
}

View File

@@ -9,7 +9,7 @@
use log::debug;
use crate::{lockdown::LockdownClient, obf, Idevice, IdeviceError, IdeviceService};
use crate::{obf, Idevice, IdeviceError, IdeviceService};
use sha2::{Digest, Sha384};
#[cfg(feature = "tss")]
@@ -33,41 +33,8 @@ impl IdeviceService for ImageMounter {
obf!("com.apple.mobile.mobile_image_mounter")
}
/// Establishes a connection to the image mounter service
///
/// # Arguments
/// * `provider` - Device connection provider
///
/// # Returns
/// A connected `ImageMounter` instance
///
/// # Errors
/// Returns `IdeviceError` if any step of the connection process fails
///
/// # Process
/// 1. Connects to lockdownd service
/// 2. Starts a lockdown session
/// 3. Requests the image mounter service port
/// 4. Establishes connection to the service port
/// 5. Optionally starts TLS if required by service
async fn connect(
provider: &dyn crate::provider::IdeviceProvider,
) -> Result<Self, IdeviceError> {
let mut lockdown = LockdownClient::connect(provider).await?;
lockdown
.start_session(&provider.get_pairing_file().await?)
.await?;
let (port, ssl) = lockdown.start_service(Self::service_name()).await?;
let mut idevice = provider.connect(port).await?;
if ssl {
idevice
.start_session(&provider.get_pairing_file().await?)
.await?;
}
Ok(Self { idevice })
async fn from_stream(idevice: Idevice) -> Result<Self, crate::IdeviceError> {
Ok(Self::new(idevice))
}
}

View File

@@ -7,7 +7,7 @@ use chrono::{DateTime, NaiveDateTime};
use plist::Dictionary;
use tokio::io::AsyncWriteExt;
use crate::{lockdown::LockdownClient, obf, Idevice, IdeviceError, IdeviceService};
use crate::{obf, Idevice, IdeviceError, IdeviceService};
/// Client for interacting with the iOS device OsTraceRelay service
pub struct OsTraceRelayClient {
@@ -21,40 +21,7 @@ impl IdeviceService for OsTraceRelayClient {
obf!("com.apple.os_trace_relay")
}
/// Establishes a connection to the OsTraceRelay service
///
/// # Arguments
/// * `provider` - Device connection provider
///
/// # Returns
/// A connected `OsTraceRelayClient` instance
///
/// # Errors
/// Returns `IdeviceError` if any step of the connection process fails
///
/// # Process
/// 1. Connects to lockdownd service
/// 2. Starts a lockdown session
/// 3. Requests the OsTraceRelay service port
/// 4. Establishes connection to the OsTraceRelay port
/// 5. Optionally starts TLS if required by service
async fn connect(
provider: &dyn crate::provider::IdeviceProvider,
) -> Result<Self, IdeviceError> {
let mut lockdown = LockdownClient::connect(provider).await?;
lockdown
.start_session(&provider.get_pairing_file().await?)
.await?;
let (port, ssl) = lockdown.start_service(Self::service_name()).await?;
let mut idevice = provider.connect(port).await?;
if ssl {
idevice
.start_session(&provider.get_pairing_file().await?)
.await?;
}
async fn from_stream(idevice: Idevice) -> Result<Self, crate::IdeviceError> {
Ok(Self { idevice })
}
}

View File

@@ -3,7 +3,7 @@
//! Provides functionality for interacting with the SpringBoard services on iOS devices,
//! which manages home screen and app icon related operations.
use crate::{lockdown::LockdownClient, obf, Idevice, IdeviceError, IdeviceService};
use crate::{obf, Idevice, IdeviceError, IdeviceService};
/// Client for interacting with the iOS SpringBoard services
///
@@ -20,41 +20,8 @@ impl IdeviceService for SpringBoardServicesClient {
obf!("com.apple.springboardservices")
}
/// Establishes a connection to the SpringBoard services
///
/// # Arguments
/// * `provider` - Device connection provider
///
/// # Returns
/// A connected `SpringBoardServicesClient` instance
///
/// # Errors
/// Returns `IdeviceError` if any step of the connection process fails
///
/// # Process
/// 1. Connects to lockdownd service
/// 2. Starts a lockdown session
/// 3. Requests the SpringBoard services port
/// 4. Establishes connection to the service port
/// 5. Optionally starts TLS if required by service
async fn connect(
provider: &dyn crate::provider::IdeviceProvider,
) -> Result<Self, IdeviceError> {
let mut lockdown = LockdownClient::connect(provider).await?;
lockdown
.start_session(&provider.get_pairing_file().await?)
.await?;
let (port, ssl) = lockdown.start_service(Self::service_name()).await?;
let mut idevice = provider.connect(port).await?;
if ssl {
idevice
.start_session(&provider.get_pairing_file().await?)
.await?;
}
Ok(Self { idevice })
async fn from_stream(idevice: Idevice) -> Result<Self, crate::IdeviceError> {
Ok(Self::new(idevice))
}
}

View File

@@ -1,6 +1,6 @@
//! iOS Device SyslogRelay Service Abstraction
use crate::{lockdown::LockdownClient, obf, Idevice, IdeviceError, IdeviceService};
use crate::{obf, Idevice, IdeviceError, IdeviceService};
/// Client for interacting with the iOS device SyslogRelay service
pub struct SyslogRelayClient {
@@ -14,41 +14,8 @@ impl IdeviceService for SyslogRelayClient {
obf!("com.apple.syslog_relay")
}
/// Establishes a connection to the SyslogRelay service
///
/// # Arguments
/// * `provider` - Device connection provider
///
/// # Returns
/// A connected `SyslogRelayClient` instance
///
/// # Errors
/// Returns `IdeviceError` if any step of the connection process fails
///
/// # Process
/// 1. Connects to lockdownd service
/// 2. Starts a lockdown session
/// 3. Requests the SyslogRelay service port
/// 4. Establishes connection to the SyslogRelay port
/// 5. Optionally starts TLS if required by service
async fn connect(
provider: &dyn crate::provider::IdeviceProvider,
) -> Result<Self, IdeviceError> {
let mut lockdown = LockdownClient::connect(provider).await?;
lockdown
.start_session(&provider.get_pairing_file().await?)
.await?;
let (port, ssl) = lockdown.start_service(Self::service_name()).await?;
let mut idevice = provider.connect(port).await?;
if ssl {
idevice
.start_session(&provider.get_pairing_file().await?)
.await?;
}
Ok(Self { idevice })
async fn from_stream(idevice: Idevice) -> Result<Self, crate::IdeviceError> {
Ok(Self::new(idevice))
}
}