idevice

repository·master·Indexed 19 days ago

https://github.com/jkcoxson/idevice

A pure Rust library for interfacing with iOS services, providing a modern alternative to libimobiledevice. It enables file access, app management, and XCTest execution via lockdownd, usbmuxd, and RSD. The project includes idevice-tools for CLI XCTest and WebDriverAgent (WDA) launching, as well as idevice-ffi for C-compatible management of adapter connections and data streams.

Tokens
33.7K
Snippets
132
Records
151
Agent score
64%

What's inside idevice

  1. How Lockdown and RemoteXPC/RSD work

    master

    idevice interacts with multiple stacked layers of Apple's protocols:

    Lockdown Protocol

    1. A lockdown service is accessible via a specific port.
    2. Lockdown is accessible via USB or TCP using TLS.
    3. USB access is facilitated via usbmuxd.
    4. usbmuxd is accessed through a Unix socket using its own protocol.

    RemoteXPC / RSD (Remote Service Discovery)

    1. An RSD service is discovered through a RemoteXPC handshake response.
    2. RemoteXPC is transferred over non-compliant HTTP/2.
    3. This HTTP/2 connection is accessed through an NCM USB interface or CoreDeviceProxy.
    4. CoreDeviceProxy is itself a lockdown service.
  2. Install and use idevice in Rust

    master

    idevice is a pure Rust library for interacting with iOS services via lockdownd, usbmuxd, and RSD.

    Important: The library is in active development. Breaking changes occur at each point release until version 0.2.0. You should pin your Cargo.toml to a specific version to avoid breakage.

    To use the library, you must enable specific features in your Cargo.toml (e.g., usbmuxd) because functionality is gated by features to minimize dependency bloat.

    // Example Cargo.toml dependency
    idevice = { version = "0.1.65", features = ["usbmuxd"] }
  3. Manage open files using FileDescriptor and OwnedFileDescriptor

    master

    The AFC (Apple File Conduit) service provides two types of handles for interacting with open files on a device:

    1. FileDescriptor<'a>: A borrowed handle that holds a reference to an AfcClient. Use this when you want to perform file operations without taking ownership of the client.
    2. OwnedFileDescriptor: An owned handle that contains the AfcClient. Use this when you want the file handle to manage the lifecycle of the client.

    CRITICAL: Manual Resource Management Neither FileDescriptor nor OwnedFileDescriptor will automatically close the file descriptor on the device when they are dropped in Rust. To prevent leaking file descriptors on the device, you must explicitly call .close().await.

    If you do not call .close(), the file descriptor will only be reclaimed automatically when the entire AFC session ends.

    // Example of using FileDescriptor (borrowed)
    // Note: .close() must be called to release the device-side FD
    let mut fd = FileDescriptor::new(&mut afc_client, raw_fd, path_string);
    fd.write_entire(b"data").await?;
    fd.close().await?;
  4. Understand the iOS Instruments protocol message format

    master

    The iOS instruments protocol uses a structured message format for communication. A complete message consists of a 32-byte MessageHeader, a 16-byte PayloadHeader, an optional auxiliary data section (with its own AuxHeader), and an optional payload data section (typically encoded as NSKeyedArchive).

    Message Structure

    +---------------------+
    |   MessageHeader     | 32 bytes
    +---------------------+
    |   PayloadHeader     | 16 bytes
    +---------------------+
    |   AuxHeader         | 16 bytes (if aux present)
    |   Aux data          | variable length
    +---------------------+
    |   Payload data      | variable length (NSKeyedArchive)
    +---------------------+
    // See structure diagram in source for visual representation
  5. Use the LockdownClient for device management

    master

    The LockdownClient is the primary interface for interacting with the iOS lockdown service. It is used for device management tasks such as retrieving device information, managing settings, service discovery, and session management.

    To use it, you can either connect via a provider or wrap an existing Idevice connection.

    // Connecting via a provider
    let mut client = LockdownClient::connect(&provider).await?;
    
    // Or wrapping an existing Idevice
    let client = LockdownClient::new(idevice);
  6. Use the IdeviceProvider trait to connect to iOS devices

    master

    The IdeviceProvider trait provides a unified asynchronous interface for establishing connections to iOS devices across different transport mechanisms like TCP or USB (via usbmuxd).

    To connect to a device, implement or use a type that satisfies IdeviceProvider and call the connect method with the target service port. This returns an Idevice handle which can be used for communication.

    Key methods:

    • connect(port: u16): Returns a future that resolves to an Idevice connection handle.
    • label(): Returns a string identifying the provider or connection.
    • get_pairing_file(): Returns a future that resolves to the PairingFile required for secure communication with the device.
    // Example conceptual usage of an IdeviceProvider
    async fn connect_to_device<P: IdeviceProvider>(provider: P, port: u16) -> Result<Idevice, IdeviceError> {
        provider.connect(port).await
    }
  7. How CoreDevice display streaming works

    master

    The com.apple.coredevice.displayservice manages the control plane and media negotiation for device screen mirroring.

    Control Plane

    Control operations (status, start, stop) are handled via plain RemoteXPC.

    Media Negotiation

    Media is negotiated as an AVConference session:

    1. The client sends a negotiatorOffer (a zlib+protobuf blob).
    2. The device responds to the offer.
    3. The device streams plaintext RTP/HEVC to the receiver address specified in the parameters.

    Session Lifecycle

    To successfully establish a screen-sharing session, the client should follow this sequence:

    1. Start Audio: Use build_start_audio_parameters and start_media_stream. This establishes the session rules.
    2. Start Video: Use build_start_video_parameters and start_media_stream using the same client_session_id used for audio.
    3. Stop: Use stop_media_stream to terminate active streams.
  8. Understand the ReadWrite trait for device sockets

    master

    The ReadWrite trait is a convenience abstraction for any type that can act as an asynchronous read/write socket for device communication. It combines AsyncRead, AsyncWrite, Unpin, Send, Sync, and std::fmt::Debug. This allows the library to use different underlying transport mechanisms (like TcpStream or UnixStream) via dynamic dispatch using the IdeviceSocket type alias.

    /// Type alias for boxed device connection sockets
    pub type IdeviceSocket = Box<dyn ReadWrite>;
  9. Use the ImageMounter client for iOS disk images

    master

    The ImageMounter client provides functionality for mounting various disk images on iOS devices, including Developer disk images, Personalized images, and Cryptex images. It manages the full workflow: looking up signatures, uploading image data, and executing the mount command.

    Important: After establishing an ImageMounter client, you must establish and query a lockdown client, otherwise the device may stop responding to requests.

    // Example initialization
    let mounter = ImageMounter::new(idevice_connection);
  10. Connect to the Installation Proxy

    master

    You can establish a connection to the Installation Proxy using one of three methods depending on your available handles:

    1. Via IdeviceProvider: Use installation_proxy_connect with a valid IdeviceProviderHandle.
    2. Via RSD (Remote Service Discovery): Use installation_proxy_connect_rsd if you have an AdapterHandle and an RsdHandshakeHandle (requires core_device_proxy and rsd features).
    3. Via IdeviceSocket: Use installation_proxy_new with an IdeviceHandle. Note that this consumes the socket and it cannot be used again.

    All connection functions return a pointer to an IdeviceFfiError on failure, or NULL on success. On success, the client pointer will be updated to a new InstallationProxyClientHandle.

    // Example: Connecting via provider
    InstallationProxyClientHandle* client = NULL;
    IdeviceFfiError* err = installation_proxy_connect(provider_handle, &client);
    if (err == NULL) {
        // Success
    }
  11. Use idevice-rs-tools CLI

    master

    The idevice-rs-tools CLI is used to manage and manipulate iOS devices. It supports connecting via USB (using usbmuxd) or over a network (TCP).

    Global Flags

    All subcommands can use the following flags to specify the target device and connection method:

    • --pairing-file <PATH>: The path to the pairing file to use.
    • --host <IP_ADDRESS>: The host IP address to connect to (used with --pairing-file).
    • --udid <UDID>: The unique device identifier (UDID) to use.

    Connection Modes

    1. USB Mode: If no --host is provided, the tool attempts to connect via usbmuxd. If --udid is provided, it targets that specific device. If no UDID is provided, it selects the first available USB device (falling back to the first available device if no USB devices are found).
    2. Network Mode: If both --host and --pairing-file are provided, the tool connects via TCP to the specified host.

    Environment Variables

    • USBMUXD_SOCKET_ADDRESS: Allows specifying a custom socket address for usbmuxd connection.
    # Example: Connect to a specific device via USB
    idevice-rs-tools --udid 00008030-001A246E12345678 subcommand
    
    # Example: Connect to a device over the network
    idevice-rs-tools --host 192.168.1.50 --pairing-file ./my_device.plist subcommand
  12. Connect to an iOS device using usbmuxd and a provider

    master

    To interact with a device, you typically follow this workflow:

    1. Connect to the usbmuxd daemon.
    2. Retrieve a list of connected devices.
    3. Create a provider using to_provider. The provider abstracts the complexity of opening multiple connections required by various services.
    4. Use the provider to connect to specific clients, such as LockdowndClient.

    Note: to_provider requires a UsbmuxdAddr, a port, and a program name.

    use idevice::{lockdown::LockdowndClient, IdeviceService};
    use idevice::usbmuxd::{UsbmuxdAddr, UsbmuxdConnection};
    
    #[tokio::main]
    async fn main() {
        // 1. Connect to usbmuxd
        let mut usbmuxd = UsbmuxdConnection::default()
            .await
            .expect("Unable to connect to usbmuxd");
        
        let devs = usbmuxd.get_devices().unwrap();
        if devs.is_empty() {
            eprintln!("No devices connected!");
            return;
        }
    
        // 2. Create a provider for the first device
        let provider = devs[0].to_provider(UsbmuxdAddr::from_env_var().unwrap(), 0, "example-program");
    
        // 3. Connect to the lockdown client
        let mut lockdown_client = match LockdowndClient::connect(&provider).await {
            Ok(l) => l,
            Err(e) => {
                eprintln!("Unable to connect to lockdown: {e:?}");
                return;
            }
        };
    
        // 4. Perform actions
        println!("{:?}", lockdown_client.get_value("ProductVersion").await);
    }