diff --git a/idevice/src/usbmuxd/des.rs b/idevice/src/usbmuxd/des.rs index d6ca703..737c6f6 100644 --- a/idevice/src/usbmuxd/des.rs +++ b/idevice/src/usbmuxd/des.rs @@ -1,7 +1,15 @@ // Jackson Coxson +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; + +use log::{debug, warn}; use serde::Deserialize; +use crate::{ + IdeviceError, + usbmuxd::{Connection, UsbmuxdDevice}, +}; + #[derive(Deserialize)] pub struct ListDevicesResponse { #[serde(rename = "DeviceList")] @@ -25,3 +33,69 @@ pub struct DevicePropertiesResponse { #[serde(rename = "SerialNumber")] pub serial_number: String, } + +impl DeviceListResponse { + pub fn into_usbmuxd_dev(self) -> Result { + self.try_into() + } +} + +impl TryFrom for UsbmuxdDevice { + type Error = IdeviceError; + + fn try_from(dev: DeviceListResponse) -> Result { + let connection_type = match dev.properties.connection_type.as_str() { + "Network" => { + if let Some(addr) = dev.properties.network_address { + let addr = &Into::>::into(addr); + if addr.len() < 8 { + warn!("Device address bytes len < 8"); + return Err(IdeviceError::UnexpectedResponse); + } + + match addr[0] { + 0x02 => { + // IPv4 + Connection::Network(IpAddr::V4(Ipv4Addr::new( + addr[4], addr[5], addr[6], addr[7], + ))) + } + 0x1E => { + // IPv6 + if addr.len() < 24 { + warn!("IPv6 address is less than 24 bytes"); + return Err(IdeviceError::UnexpectedResponse); + } + + Connection::Network(IpAddr::V6(Ipv6Addr::new( + u16::from_be_bytes([addr[8], addr[9]]), + u16::from_be_bytes([addr[10], addr[11]]), + u16::from_be_bytes([addr[12], addr[13]]), + u16::from_be_bytes([addr[14], addr[15]]), + u16::from_be_bytes([addr[16], addr[17]]), + u16::from_be_bytes([addr[18], addr[19]]), + u16::from_be_bytes([addr[20], addr[21]]), + u16::from_be_bytes([addr[22], addr[23]]), + ))) + } + _ => { + warn!("Unknown IP address protocol: {:02X}", addr[0]); + Connection::Unknown(format!("Network {:02X}", addr[0])) + } + } + } else { + warn!("Device is network attached, but has no network info"); + return Err(IdeviceError::UnexpectedResponse); + } + } + "USB" => Connection::Usb, + _ => Connection::Unknown(dev.properties.connection_type), + }; + debug!("Connection type: {connection_type:?}"); + Ok(UsbmuxdDevice { + connection_type, + udid: dev.properties.serial_number, + device_id: dev.device_id, + }) + } +} diff --git a/idevice/src/usbmuxd/mod.rs b/idevice/src/usbmuxd/mod.rs index 9953feb..4fab0ea 100644 --- a/idevice/src/usbmuxd/mod.rs +++ b/idevice/src/usbmuxd/mod.rs @@ -4,18 +4,21 @@ //! connections to iOS devices over USB and network and pairing files use std::{ - net::{AddrParseError, IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}, + net::{AddrParseError, IpAddr, SocketAddr}, + pin::Pin, str::FromStr, }; #[cfg(not(unix))] use std::net::SocketAddrV4; +use futures::Stream; use log::{debug, warn}; use tokio::io::{AsyncReadExt, AsyncWriteExt}; use crate::{ Idevice, IdeviceError, ReadWrite, pairing_file::PairingFile, provider::UsbmuxdProvider, + usbmuxd::des::DeviceListResponse, }; mod des; @@ -43,6 +46,14 @@ pub struct UsbmuxdDevice { pub device_id: u32, } +/// Listen events from the socket +#[derive(Debug, Clone)] +pub enum UsbmuxdListenEvent { + Connected(UsbmuxdDevice), + /// The mux ID + Disconnected(u32), +} + /// Active connection to the usbmuxd service pub struct UsbmuxdConnection { socket: Box, @@ -187,62 +198,11 @@ impl UsbmuxdConnection { let res = plist::to_value(&res)?; let res = plist::from_value::(&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::>::into(addr); - if addr.len() < 8 { - warn!("Device address bytes len < 8"); - return Err(IdeviceError::UnexpectedResponse); - } - - match addr[0] { - 0x02 => { - // IPv4 - Connection::Network(IpAddr::V4(Ipv4Addr::new( - addr[4], addr[5], addr[6], addr[7], - ))) - } - 0x1E => { - // IPv6 - if addr.len() < 24 { - warn!("IPv6 address is less than 24 bytes"); - return Err(IdeviceError::UnexpectedResponse); - } - - Connection::Network(IpAddr::V6(Ipv6Addr::new( - u16::from_be_bytes([addr[8], addr[9]]), - u16::from_be_bytes([addr[10], addr[11]]), - u16::from_be_bytes([addr[12], addr[13]]), - u16::from_be_bytes([addr[14], addr[15]]), - u16::from_be_bytes([addr[16], addr[17]]), - u16::from_be_bytes([addr[18], addr[19]]), - u16::from_be_bytes([addr[20], addr[21]]), - u16::from_be_bytes([addr[22], addr[23]]), - ))) - } - _ => { - warn!("Unknown IP address protocol: {:02X}", addr[0]); - Connection::Unknown(format!("Network {:02X}", addr[0])) - } - } - } else { - warn!("Device is network attached, but has no network info"); - return Err(IdeviceError::UnexpectedResponse); - } - } - "USB" => Connection::Usb, - _ => Connection::Unknown(dev.properties.connection_type), - }; - debug!("Connection type: {connection_type:?}"); - devs.push(UsbmuxdDevice { - connection_type, - udid: dev.properties.serial_number, - device_id: dev.device_id, - }) - } + let devs = res + .device_list + .into_iter() + .flat_map(|x| x.into_usbmuxd_dev()) + .collect::>(); Ok(devs) } @@ -361,6 +321,90 @@ impl UsbmuxdConnection { } } + pub async fn listen<'a>( + &'a mut self, + ) -> Result< + Pin> + 'a>>, + IdeviceError, + > { + let req = crate::plist!(dict { + "MessageType": "Listen", + }); + self.write_plist(req).await?; + + // First, read the handshake response to confirm the "Listen" request was successful + let res = self.read_plist().await?; + match res.get("Number").and_then(|x| x.as_unsigned_integer()) { + Some(0) => { + // Success, now create the stream + let stream = futures::stream::try_unfold(self, |conn| async move { + // This loop is to skip non-Attach/Detach messages + loop { + // Read the next packet. This will propagate IO errors. + let msg = conn.read_plist().await?; + + if let Some(plist::Value::String(s)) = msg.get("MessageType") { + match s.as_str() { + "Attached" => { + if let Ok(props) = plist::from_value::( + &plist::Value::Dictionary(msg), + ) { + let dev: UsbmuxdDevice = match props.into_usbmuxd_dev() { + Ok(d) => d, + Err(e) => { + warn!( + "Failed to convert props into usbmuxd device: {e:?}" + ); + continue; + } + }; + + let res = UsbmuxdListenEvent::Connected(dev); + + // Yield the device and the next state + return Ok(Some((res, conn))); + } else { + warn!( + "Received malformed message during listen (no device props and ID)" + ); + } + } + "Detached" => { + // Log it and continue the loop to wait for the next message + if let Some(id) = + msg.get("DeviceID").and_then(|v| v.as_unsigned_integer()) + { + let res = UsbmuxdListenEvent::Disconnected(id as u32); + return Ok(Some((res, conn))); + } else { + debug!("Device detached (unknown ID)"); + } + // Continue loop + } + _ => { + // Unexpected message type, log and continue + warn!("Received unexpected message type during listen: {}", s); + // Continue loop + } + } + } else { + // Malformed message, log and continue + warn!("Received malformed message during listen (no MessageType)"); + // Continue loop + } + } + }); + + // Box and Pin the stream + Ok(Box::pin(stream)) + } + _ => { + // "Listen" request failed + Err(IdeviceError::UnexpectedResponse) + } + } + } + /// Writes a PLIST message to usbmuxd async fn write_plist(&mut self, req: plist::Dictionary) -> Result<(), IdeviceError> { let raw = raw_packet::RawPacket::new( diff --git a/tools/src/idevice_id.rs b/tools/src/idevice_id.rs index 1791e10..8ccb1d3 100644 --- a/tools/src/idevice_id.rs +++ b/tools/src/idevice_id.rs @@ -1,6 +1,7 @@ // Jackson Coxson // Gets the devices from the muxer +use futures_util::StreamExt; use idevice::usbmuxd::UsbmuxdConnection; #[tokio::main] @@ -10,4 +11,13 @@ async fn main() { let mut muxer = UsbmuxdConnection::default().await.unwrap(); let res = muxer.get_devices().await.unwrap(); println!("{res:#?}"); + + let args: Vec = std::env::args().collect(); + if args.len() > 1 && args[1] == "-l" { + let mut s = muxer.listen().await.expect("listen failed"); + while let Some(dev) = s.next().await { + let dev = dev.expect("failed to read from stream"); + println!("{dev:#?}"); + } + } }