mirror of
https://github.com/jkcoxson/idevice.git
synced 2026-03-02 14:36:16 +01:00
Implement usbmuxd protocol
This commit is contained in:
@@ -37,6 +37,7 @@ core_device_proxy = ["dep:serde_json", "dep:json", "dep:byteorder"]
|
|||||||
heartbeat = []
|
heartbeat = []
|
||||||
installation_proxy = []
|
installation_proxy = []
|
||||||
mounter = []
|
mounter = []
|
||||||
|
usbmuxd = []
|
||||||
xpc = [
|
xpc = [
|
||||||
"tokio/full",
|
"tokio/full",
|
||||||
"dep:indexmap",
|
"dep:indexmap",
|
||||||
@@ -49,5 +50,6 @@ full = [
|
|||||||
"heartbeat",
|
"heartbeat",
|
||||||
"installation_proxy",
|
"installation_proxy",
|
||||||
"mounter",
|
"mounter",
|
||||||
|
"usbmuxd",
|
||||||
"xpc",
|
"xpc",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ pub mod lockdownd;
|
|||||||
#[cfg(feature = "mounter")]
|
#[cfg(feature = "mounter")]
|
||||||
pub mod mounter;
|
pub mod mounter;
|
||||||
pub mod pairing_file;
|
pub mod pairing_file;
|
||||||
|
#[cfg(feature = "usbmuxd")]
|
||||||
|
pub mod usbmuxd;
|
||||||
#[cfg(feature = "xpc")]
|
#[cfg(feature = "xpc")]
|
||||||
pub mod xpc;
|
pub mod xpc;
|
||||||
|
|
||||||
|
|||||||
@@ -66,6 +66,12 @@ impl PairingFile {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn from_value(v: &plist::Value) -> Result<Self, crate::IdeviceError> {
|
||||||
|
let raw: RawPairingFile = plist::from_value(v)?;
|
||||||
|
let p = raw.try_into()?;
|
||||||
|
Ok(p)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<RawPairingFile> for PairingFile {
|
impl TryFrom<RawPairingFile> for PairingFile {
|
||||||
|
|||||||
227
idevice/src/usbmuxd/mod.rs
Normal file
227
idevice/src/usbmuxd/mod.rs
Normal file
@@ -0,0 +1,227 @@
|
|||||||
|
// Jackson Coxson
|
||||||
|
|
||||||
|
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
use std::net::{Ipv4Addr, SocketAddrV4};
|
||||||
|
|
||||||
|
use log::debug;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||||
|
|
||||||
|
use crate::{pairing_file::PairingFile, IdeviceError, ReadWrite};
|
||||||
|
|
||||||
|
mod raw_packet;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum Connection {
|
||||||
|
Usb,
|
||||||
|
Network(IpAddr),
|
||||||
|
Unknown(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct UsbmuxdDevice {
|
||||||
|
pub connection_type: Connection,
|
||||||
|
pub udid: String,
|
||||||
|
pub device_id: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct UsbmuxdConnection {
|
||||||
|
socket: Box<dyn ReadWrite>,
|
||||||
|
tag: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct ListDevicesResponse {
|
||||||
|
#[serde(rename = "DeviceList")]
|
||||||
|
device_list: Vec<DeviceListResponse>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct DeviceListResponse {
|
||||||
|
#[serde(rename = "DeviceID")]
|
||||||
|
device_id: u32,
|
||||||
|
#[serde(rename = "Properties")]
|
||||||
|
properties: DevicePropertiesResponse,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct DevicePropertiesResponse {
|
||||||
|
#[serde(rename = "ConnectionType")]
|
||||||
|
connection_type: String,
|
||||||
|
#[serde(rename = "NetworkAddress")]
|
||||||
|
network_address: Option<plist::Data>,
|
||||||
|
#[serde(rename = "SerialNumber")]
|
||||||
|
serial_number: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UsbmuxdConnection {
|
||||||
|
pub const DEFAULT_PORT: u16 = 27015;
|
||||||
|
pub const SOCKET_FILE: &str = "/var/run/usbmuxd";
|
||||||
|
|
||||||
|
pub const BINARY_PLIST_VERSION: u32 = 0;
|
||||||
|
pub const XML_PLIST_VERSION: u32 = 1;
|
||||||
|
|
||||||
|
pub const RESULT_MESSAGE_TYPE: u32 = 1;
|
||||||
|
pub const PLIST_MESSAGE_TYPE: u32 = 8;
|
||||||
|
|
||||||
|
pub async fn default() -> Result<Self, IdeviceError> {
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
let socket = tokio::net::TcpStream::connect(SocketAddrV4::new(
|
||||||
|
Ipv4Addr::new(127, 0, 0, 1),
|
||||||
|
Self::DEFAULT_PORT,
|
||||||
|
))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
#[cfg(any(target_os = "macos", target_os = "linux"))]
|
||||||
|
let socket = tokio::net::UnixStream::connect(Self::SOCKET_FILE).await?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
socket: Box::new(socket),
|
||||||
|
tag: 0,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn new(socket: Box<dyn ReadWrite>, tag: u32) -> Self {
|
||||||
|
Self { socket, tag }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_devices(&mut self) -> Result<Vec<UsbmuxdDevice>, IdeviceError> {
|
||||||
|
let mut req = plist::Dictionary::new();
|
||||||
|
req.insert("MessageType".into(), "ListDevices".into());
|
||||||
|
req.insert("ClientVersionString".into(), "idevice-rs".into());
|
||||||
|
req.insert("kLibUSBMuxVersion".into(), 3.into());
|
||||||
|
self.write_plist(req).await?;
|
||||||
|
let res = self.read_plist().await?;
|
||||||
|
let res = plist::to_value(&res)?;
|
||||||
|
let res = plist::from_value::<ListDevicesResponse>(&res)?;
|
||||||
|
|
||||||
|
let mut devs = Vec::new();
|
||||||
|
for dev in res.device_list {
|
||||||
|
let connection_type = match dev.properties.connection_type.as_str() {
|
||||||
|
"Network" => {
|
||||||
|
if let Some(addr) = dev.properties.network_address {
|
||||||
|
let addr = &Into::<Vec<u8>>::into(addr);
|
||||||
|
if addr.len() < 8 {
|
||||||
|
return Err(IdeviceError::UnexpectedResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
let addr = match addr[0] {
|
||||||
|
0x02 => {
|
||||||
|
// ipv4
|
||||||
|
IpAddr::V4(Ipv4Addr::new(addr[4], addr[5], addr[6], addr[7]))
|
||||||
|
}
|
||||||
|
0x1E => {
|
||||||
|
// ipv6
|
||||||
|
if addr.len() < 24 {
|
||||||
|
return Err(IdeviceError::UnexpectedResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
IpAddr::V6(Ipv6Addr::new(
|
||||||
|
u16::from_le_bytes([addr[8], addr[9]]),
|
||||||
|
u16::from_le_bytes([addr[10], addr[11]]),
|
||||||
|
u16::from_le_bytes([addr[12], addr[13]]),
|
||||||
|
u16::from_le_bytes([addr[14], addr[15]]),
|
||||||
|
u16::from_le_bytes([addr[16], addr[17]]),
|
||||||
|
u16::from_le_bytes([addr[18], addr[19]]),
|
||||||
|
u16::from_le_bytes([addr[20], addr[21]]),
|
||||||
|
u16::from_le_bytes([addr[22], addr[23]]),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
return Err(IdeviceError::UnexpectedResponse);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Connection::Network(addr)
|
||||||
|
} else {
|
||||||
|
return Err(IdeviceError::UnexpectedResponse);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"USB" => Connection::Usb,
|
||||||
|
_ => Connection::Unknown(dev.properties.connection_type),
|
||||||
|
};
|
||||||
|
devs.push(UsbmuxdDevice {
|
||||||
|
connection_type,
|
||||||
|
udid: dev.properties.serial_number,
|
||||||
|
device_id: dev.device_id,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(devs)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_pair_record(&mut self, udid: &str) -> Result<PairingFile, IdeviceError> {
|
||||||
|
let mut req = plist::Dictionary::new();
|
||||||
|
req.insert("MessageType".into(), "ReadPairRecord".into());
|
||||||
|
req.insert("PairRecordID".into(), udid.into());
|
||||||
|
self.write_plist(req).await?;
|
||||||
|
let res = self.read_plist().await?;
|
||||||
|
|
||||||
|
match res.get("PairRecordData") {
|
||||||
|
Some(plist::Value::Data(d)) => PairingFile::from_bytes(d),
|
||||||
|
_ => Err(IdeviceError::UnexpectedResponse),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_buid(&mut self) -> Result<String, IdeviceError> {
|
||||||
|
let mut req = plist::Dictionary::new();
|
||||||
|
req.insert("MessageType".into(), "ReadBUID".into());
|
||||||
|
self.write_plist(req).await?;
|
||||||
|
let mut res = self.read_plist().await?;
|
||||||
|
|
||||||
|
match res.remove("BUID") {
|
||||||
|
Some(plist::Value::String(s)) => Ok(s),
|
||||||
|
_ => Err(IdeviceError::UnexpectedResponse),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn connect_to_device(
|
||||||
|
mut self,
|
||||||
|
device_id: u32,
|
||||||
|
port: u16,
|
||||||
|
) -> Result<Box<dyn ReadWrite>, IdeviceError> {
|
||||||
|
let mut req = plist::Dictionary::new();
|
||||||
|
req.insert("MessageType".into(), "Connect".into());
|
||||||
|
req.insert("DeviceID".into(), device_id.into());
|
||||||
|
req.insert("PortNumber".into(), port.into());
|
||||||
|
self.write_plist(req).await?;
|
||||||
|
match self.read_plist().await?.get("Number") {
|
||||||
|
Some(plist::Value::Integer(i)) => match i.as_unsigned() {
|
||||||
|
Some(0) => Ok(self.socket),
|
||||||
|
_ => Err(IdeviceError::UnexpectedResponse),
|
||||||
|
},
|
||||||
|
_ => Err(IdeviceError::UnexpectedResponse),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn write_plist(&mut self, req: plist::Dictionary) -> Result<(), IdeviceError> {
|
||||||
|
let raw = raw_packet::RawPacket::new(
|
||||||
|
req,
|
||||||
|
Self::XML_PLIST_VERSION,
|
||||||
|
Self::PLIST_MESSAGE_TYPE,
|
||||||
|
self.tag,
|
||||||
|
);
|
||||||
|
|
||||||
|
let raw: Vec<u8> = raw.into();
|
||||||
|
self.socket.write_all(&raw).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn read_plist(&mut self) -> Result<plist::Dictionary, IdeviceError> {
|
||||||
|
let mut header_buffer = [0; 16];
|
||||||
|
self.socket.read_exact(&mut header_buffer).await?;
|
||||||
|
|
||||||
|
// We are safe to unwrap as it only panics if the buffer isn't 4
|
||||||
|
let packet_size = u32::from_le_bytes(header_buffer[..4].try_into().unwrap()) - 16;
|
||||||
|
debug!("Reading {packet_size} bytes from muxer");
|
||||||
|
|
||||||
|
let mut body_buffer = vec![0; packet_size as usize];
|
||||||
|
self.socket.read_exact(&mut body_buffer).await?;
|
||||||
|
|
||||||
|
let res = plist::from_bytes(&body_buffer)?;
|
||||||
|
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
}
|
||||||
123
idevice/src/usbmuxd/raw_packet.rs
Normal file
123
idevice/src/usbmuxd/raw_packet.rs
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
// Jackson Coxson
|
||||||
|
|
||||||
|
use log::warn;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct RawPacket {
|
||||||
|
pub size: u32,
|
||||||
|
pub version: u32,
|
||||||
|
pub message: u32,
|
||||||
|
pub tag: u32,
|
||||||
|
pub plist: plist::Dictionary,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn plist_to_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()
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RawPacket {
|
||||||
|
pub fn new(plist: plist::Dictionary, version: u32, message: u32, tag: u32) -> RawPacket {
|
||||||
|
let plist_bytes = plist_to_bytes(&plist);
|
||||||
|
let size = plist_bytes.len() as u32 + 16;
|
||||||
|
RawPacket {
|
||||||
|
size,
|
||||||
|
version,
|
||||||
|
message,
|
||||||
|
tag,
|
||||||
|
plist,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<RawPacket> for Vec<u8> {
|
||||||
|
fn from(raw_packet: RawPacket) -> Vec<u8> {
|
||||||
|
let mut packet = vec![];
|
||||||
|
packet.extend_from_slice(&raw_packet.size.to_le_bytes());
|
||||||
|
packet.extend_from_slice(&raw_packet.version.to_le_bytes());
|
||||||
|
packet.extend_from_slice(&raw_packet.message.to_le_bytes());
|
||||||
|
packet.extend_from_slice(&raw_packet.tag.to_le_bytes());
|
||||||
|
packet.extend_from_slice(&plist_to_bytes(&raw_packet.plist));
|
||||||
|
packet
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<&mut Vec<u8>> for RawPacket {
|
||||||
|
type Error = ();
|
||||||
|
fn try_from(packet: &mut Vec<u8>) -> Result<Self, Self::Error> {
|
||||||
|
let packet: &[u8] = packet;
|
||||||
|
packet.try_into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<&[u8]> for RawPacket {
|
||||||
|
type Error = ();
|
||||||
|
fn try_from(packet: &[u8]) -> Result<Self, ()> {
|
||||||
|
// Determine if we have enough data to parse
|
||||||
|
if packet.len() < 16 {
|
||||||
|
warn!("Not enough data to parse a raw packet header");
|
||||||
|
return Err(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let packet_size = &packet[0..4];
|
||||||
|
let packet_size = u32::from_le_bytes(match packet_size.try_into() {
|
||||||
|
Ok(packet_size) => packet_size,
|
||||||
|
Err(_) => {
|
||||||
|
warn!("Failed to parse packet size");
|
||||||
|
return Err(());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Determine if we have enough data to parse
|
||||||
|
if packet.len() < packet_size as usize {
|
||||||
|
warn!("Not enough data to parse a raw packet body");
|
||||||
|
return Err(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let packet_version = &packet[4..8];
|
||||||
|
let packet_version = u32::from_le_bytes(match packet_version.try_into() {
|
||||||
|
Ok(packet_version) => packet_version,
|
||||||
|
Err(_) => {
|
||||||
|
warn!("Failed to parse packet version");
|
||||||
|
return Err(());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let message = &packet[8..12];
|
||||||
|
let message = u32::from_le_bytes(match message.try_into() {
|
||||||
|
Ok(message) => message,
|
||||||
|
Err(_) => {
|
||||||
|
warn!("Failed to parse packet message");
|
||||||
|
return Err(());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let packet_tag = &packet[12..16];
|
||||||
|
let packet_tag = u32::from_le_bytes(match packet_tag.try_into() {
|
||||||
|
Ok(packet_tag) => packet_tag,
|
||||||
|
Err(_) => {
|
||||||
|
warn!("Failed to parse packet tag");
|
||||||
|
return Err(());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let plist = &packet[16..packet_size as usize];
|
||||||
|
let plist = if let Ok(p) = plist::from_bytes(plist) {
|
||||||
|
p
|
||||||
|
} else {
|
||||||
|
warn!("Failed to parse packet plist");
|
||||||
|
return Err(());
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(RawPacket {
|
||||||
|
size: packet_size,
|
||||||
|
version: packet_version,
|
||||||
|
message,
|
||||||
|
tag: packet_tag,
|
||||||
|
plist,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -29,6 +29,10 @@ path = "src/mounter.rs"
|
|||||||
name = "core_device_proxy_tun"
|
name = "core_device_proxy_tun"
|
||||||
path = "src/core_device_proxy_tun.rs"
|
path = "src/core_device_proxy_tun.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "idevice_id"
|
||||||
|
path = "src/idevice_id.rs"
|
||||||
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
idevice = { path = "../idevice", features = ["full"] }
|
idevice = { path = "../idevice", features = ["full"] }
|
||||||
|
|||||||
13
tools/src/idevice_id.rs
Normal file
13
tools/src/idevice_id.rs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
// Jackson Coxson
|
||||||
|
// Gets the devices from the muxer
|
||||||
|
|
||||||
|
use idevice::usbmuxd::UsbmuxdConnection;
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
env_logger::init();
|
||||||
|
|
||||||
|
let mut muxer = UsbmuxdConnection::default().await.unwrap();
|
||||||
|
let res = muxer.get_devices().await.unwrap();
|
||||||
|
println!("{res:#?}");
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user