Add listen command to usbmuxd connection

This commit is contained in:
Jackson Coxson
2025-10-18 18:10:53 -06:00
parent aff3ef589f
commit 2ffeb0f25c
3 changed files with 185 additions and 57 deletions

View File

@@ -1,7 +1,15 @@
// Jackson Coxson // Jackson Coxson
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use log::{debug, warn};
use serde::Deserialize; use serde::Deserialize;
use crate::{
IdeviceError,
usbmuxd::{Connection, UsbmuxdDevice},
};
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct ListDevicesResponse { pub struct ListDevicesResponse {
#[serde(rename = "DeviceList")] #[serde(rename = "DeviceList")]
@@ -25,3 +33,69 @@ pub struct DevicePropertiesResponse {
#[serde(rename = "SerialNumber")] #[serde(rename = "SerialNumber")]
pub serial_number: String, pub serial_number: String,
} }
impl DeviceListResponse {
pub fn into_usbmuxd_dev(self) -> Result<UsbmuxdDevice, IdeviceError> {
self.try_into()
}
}
impl TryFrom<DeviceListResponse> for UsbmuxdDevice {
type Error = IdeviceError;
fn try_from(dev: DeviceListResponse) -> Result<Self, Self::Error> {
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 {
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,
})
}
}

View File

@@ -4,18 +4,21 @@
//! connections to iOS devices over USB and network and pairing files //! connections to iOS devices over USB and network and pairing files
use std::{ use std::{
net::{AddrParseError, IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}, net::{AddrParseError, IpAddr, SocketAddr},
pin::Pin,
str::FromStr, str::FromStr,
}; };
#[cfg(not(unix))] #[cfg(not(unix))]
use std::net::SocketAddrV4; use std::net::SocketAddrV4;
use futures::Stream;
use log::{debug, warn}; use log::{debug, warn};
use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio::io::{AsyncReadExt, AsyncWriteExt};
use crate::{ use crate::{
Idevice, IdeviceError, ReadWrite, pairing_file::PairingFile, provider::UsbmuxdProvider, Idevice, IdeviceError, ReadWrite, pairing_file::PairingFile, provider::UsbmuxdProvider,
usbmuxd::des::DeviceListResponse,
}; };
mod des; mod des;
@@ -43,6 +46,14 @@ pub struct UsbmuxdDevice {
pub device_id: u32, 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 /// Active connection to the usbmuxd service
pub struct UsbmuxdConnection { pub struct UsbmuxdConnection {
socket: Box<dyn ReadWrite>, socket: Box<dyn ReadWrite>,
@@ -187,62 +198,11 @@ impl UsbmuxdConnection {
let res = plist::to_value(&res)?; let res = plist::to_value(&res)?;
let res = plist::from_value::<des::ListDevicesResponse>(&res)?; let res = plist::from_value::<des::ListDevicesResponse>(&res)?;
let mut devs = Vec::new(); let devs = res
for dev in res.device_list { .device_list
let connection_type = match dev.properties.connection_type.as_str() { .into_iter()
"Network" => { .flat_map(|x| x.into_usbmuxd_dev())
if let Some(addr) = dev.properties.network_address { .collect::<Vec<UsbmuxdDevice>>();
let addr = &Into::<Vec<u8>>::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,
})
}
Ok(devs) Ok(devs)
} }
@@ -361,6 +321,90 @@ impl UsbmuxdConnection {
} }
} }
pub async fn listen<'a>(
&'a mut self,
) -> Result<
Pin<Box<dyn Stream<Item = Result<UsbmuxdListenEvent, IdeviceError>> + '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::<DeviceListResponse>(
&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 /// Writes a PLIST message to usbmuxd
async fn write_plist(&mut self, req: plist::Dictionary) -> Result<(), IdeviceError> { async fn write_plist(&mut self, req: plist::Dictionary) -> Result<(), IdeviceError> {
let raw = raw_packet::RawPacket::new( let raw = raw_packet::RawPacket::new(

View File

@@ -1,6 +1,7 @@
// Jackson Coxson // Jackson Coxson
// Gets the devices from the muxer // Gets the devices from the muxer
use futures_util::StreamExt;
use idevice::usbmuxd::UsbmuxdConnection; use idevice::usbmuxd::UsbmuxdConnection;
#[tokio::main] #[tokio::main]
@@ -10,4 +11,13 @@ async fn main() {
let mut muxer = UsbmuxdConnection::default().await.unwrap(); let mut muxer = UsbmuxdConnection::default().await.unwrap();
let res = muxer.get_devices().await.unwrap(); let res = muxer.get_devices().await.unwrap();
println!("{res:#?}"); println!("{res:#?}");
let args: Vec<String> = 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:#?}");
}
}
} }