mirror of
https://github.com/jkcoxson/idevice.git
synced 2026-03-02 14:36:16 +01:00
Add listen command to usbmuxd connection
This commit is contained in:
@@ -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,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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:#?}");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user