mirror of
https://github.com/jkcoxson/idevice.git
synced 2026-03-02 06:26:15 +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 = []
|
||||
installation_proxy = []
|
||||
mounter = []
|
||||
usbmuxd = []
|
||||
xpc = [
|
||||
"tokio/full",
|
||||
"dep:indexmap",
|
||||
@@ -49,5 +50,6 @@ full = [
|
||||
"heartbeat",
|
||||
"installation_proxy",
|
||||
"mounter",
|
||||
"usbmuxd",
|
||||
"xpc",
|
||||
]
|
||||
|
||||
@@ -12,6 +12,8 @@ pub mod lockdownd;
|
||||
#[cfg(feature = "mounter")]
|
||||
pub mod mounter;
|
||||
pub mod pairing_file;
|
||||
#[cfg(feature = "usbmuxd")]
|
||||
pub mod usbmuxd;
|
||||
#[cfg(feature = "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 {
|
||||
|
||||
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"
|
||||
path = "src/core_device_proxy_tun.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "idevice_id"
|
||||
path = "src/idevice_id.rs"
|
||||
|
||||
|
||||
[dependencies]
|
||||
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