use std::{future::Future, pin::Pin, sync::Arc};
use plist::Value;
use tracing::{debug, warn};
use crate::{Idevice, IdeviceError};
pub const FDR_CTRL_PORT: u16 = 0x43A;
const CTRL_CMD: &[u8] = b"BeginCtrl\0";
const HELLO_CMD: &[u8] = b"HelloConn\0";
const CTRL_PROTO_VERSION: i64 = 2;
const FDR_SYNC_MSG: u16 = 0x1;
const FDR_PROXY_MSG: u16 = 0x105;
const FDR_PLIST_MSG: u16 = 0xBBAA;
const CHUNK_SIZE: u32 = 1 << 20;
pub trait FdrConnector: Send + Sync {
fn connect_device_port(
&self,
port: u16,
) -> Pin<Box<dyn Future<Output = Result<Idevice, IdeviceError>> + Send>>;
}
#[derive(Debug)]
pub struct FdrClient {
idevice: Idevice,
}
impl FdrClient {
pub fn new(idevice: Idevice) -> Self {
Self { idevice }
}
pub async fn ctrl_handshake(&mut self) -> Result<u16, IdeviceError> {
self.idevice.send_raw(CTRL_CMD).await?;
let req = crate::plist!({
"Command": Value::Data(CTRL_CMD.to_vec()),
"CtrlProtoVersion": CTRL_PROTO_VERSION,
});
let resp = self.send_recv_plist(&req).await?;
resp.get("ConnPort")
.and_then(Value::as_unsigned_integer)
.map(|p| p as u16)
.ok_or_else(|| {
IdeviceError::UnexpectedResponse("FDR ctrl reply missing ConnPort".into())
})
}
pub async fn sync_handshake(&mut self) -> Result<(), IdeviceError> {
self.idevice.send_raw(HELLO_CMD).await?;
let reply = self.recv_plist().await?;
match reply.get("Command").and_then(Value::as_string) {
Some("HelloConn") => Ok(()),
other => Err(IdeviceError::UnexpectedResponse(format!(
"expected HelloConn, got {other:?}"
))),
}
}
async fn recv_plist(&mut self) -> Result<plist::Dictionary, IdeviceError> {
let len = self.idevice.read_raw(4).await?;
let len = u32::from_le_bytes([len[0], len[1], len[2], len[3]]) as usize;
let body = self.idevice.read_raw(len).await?;
Ok(plist::from_bytes(&body)?)
}
async fn send_plist(&mut self, value: &Value) -> Result<(), IdeviceError> {
let mut body = Vec::new();
value
.to_writer_binary(&mut body)
.map_err(IdeviceError::Plist)?;
self.idevice
.send_raw(&(body.len() as u32).to_le_bytes())
.await?;
self.idevice.send_raw(&body).await
}
async fn send_recv_plist(&mut self, value: &Value) -> Result<plist::Dictionary, IdeviceError> {
self.send_plist(value).await?;
self.recv_plist().await
}
async fn read_message_tag(&mut self) -> Result<u16, IdeviceError> {
let b = self.idevice.read_raw(2).await?;
Ok(u16::from_le_bytes([b[0], b[1]]))
}
async fn handle_plist(&mut self) -> Result<(), IdeviceError> {
let d = self.recv_plist().await?;
match d.get("Command").and_then(Value::as_string) {
Some("Ping") => {
let _ = self
.send_recv_plist(&crate::plist!({ "Pong": true }))
.await?;
}
other => warn!("FDR: unknown plist command {other:?}"),
}
Ok(())
}
async fn handle_proxy(mut self) -> Result<(), IdeviceError> {
let buf = self.idevice.read_any(CHUNK_SIZE).await?;
debug!("FDR proxy command with {} bytes", buf.len());
self.idevice.send_raw(&5u16.to_le_bytes()).await?;
if buf.len() < 3 {
debug!("FDR proxy command too short");
return Ok(());
}
self.idevice.send_raw(&buf).await?;
if buf[0] != 0 || buf[1] != 3 {
return Ok(());
}
let hostlen = buf[2] as usize;
if 3 + hostlen + 2 > buf.len() {
warn!("FDR proxy connect request truncated");
return Ok(());
}
let host = String::from_utf8_lossy(&buf[3..3 + hostlen]).to_string();
let port = u16::from_be_bytes([buf[buf.len() - 2], buf[buf.len() - 1]]);
debug!("FDR proxy connect to {host}:{port}");
use tokio::io::AsyncWriteExt;
let host_stream = tokio::net::TcpStream::connect((host.as_str(), port)).await?;
let device = self
.idevice
.get_socket()
.ok_or(IdeviceError::NoEstablishedConnection)?;
let (mut dr, mut dw) = tokio::io::split(device);
let (mut hr, mut hw) = tokio::io::split(host_stream);
let s2h = async {
let _ = tokio::io::copy(&mut dr, &mut hw).await;
let _ = hw.shutdown().await;
};
let h2s = async {
let _ = tokio::io::copy(&mut hr, &mut dw).await;
let _ = dw.shutdown().await;
};
tokio::select! {
_ = s2h => {},
_ = h2s => {},
}
Ok(())
}
}
pub fn run_fdr_listener(
mut client: FdrClient,
connector: Arc<dyn FdrConnector>,
conn_port: u16,
) -> Pin<Box<dyn Future<Output = Result<(), IdeviceError>> + Send>> {
Box::pin(async move {
loop {
let tag = client.read_message_tag().await?;
match tag {
FDR_SYNC_MSG => {
let _ = client.idevice.read_raw(2).await?;
let connector = connector.clone();
tokio::spawn(async move {
match connector.connect_device_port(conn_port).await {
Ok(idevice) => {
let mut conn = FdrClient::new(idevice);
if let Err(e) = conn.sync_handshake().await {
warn!("FDR conn handshake failed: {e}");
return;
}
if let Err(e) = run_fdr_listener(conn, connector, conn_port).await {
debug!("FDR conn listener ended: {e}");
}
}
Err(e) => warn!("FDR conn connect failed: {e}"),
}
});
}
FDR_PROXY_MSG => {
return client.handle_proxy().await;
}
FDR_PLIST_MSG => client.handle_plist().await?,
other => warn!("FDR: ignoring message tag {other:#x}"),
}
}
})
}