fix(lib): implement proper pairing logic and spawn heartbeat task after device is initialized

This commit is contained in:
uncor3
2026-04-05 09:59:35 +00:00
parent ed8f958f6c
commit 3b2fe2698f
+147 -101
View File
@@ -6,7 +6,8 @@ use idevice::{
heartbeat,
lockdown::LockdownClient,
pairing_file::PairingFile,
provider::TcpProvider,
provider::{IdeviceProvider, TcpProvider},
IdeviceError,
usbmuxd::{Connection, UsbmuxdAddr, UsbmuxdConnection, UsbmuxdListenEvent},
};
use std::sync::atomic::{AtomicBool, Ordering};
@@ -42,7 +43,7 @@ const APP_LABEL: &str = "iDescriptor";
const EV_CONNECTED: u32 = 1;
const EV_DISCONNECTED: u32 = 2;
const EV_PAIRING_PENDING: u32 = 3;
const EV_FAIL_KIND: u32 = 4;
const EV_FAIL: u32 = 4;
#[derive(Clone)]
pub struct DeviceServices {
@@ -162,7 +163,7 @@ impl qobject::Core {
device_map.insert(device_id, udid.clone());
let qt_thread = qt_t.clone();
tokio::spawn(async move {
RUNTIME.spawn(async move {
let already_paired = {
let mut u2 =
match UsbmuxdConnection::default().await {
@@ -241,14 +242,15 @@ impl qobject::Core {
udid: String,
reason : &str,
) {
let reason_clone = reason.to_string();
// let reason_clone = reason.to_string();
//TODO: listen for this event
qt_thread
.queue(move |core_qobj| {
core_qobj.device_event(
EV_FAIL_KIND,
EV_FAIL,
&QString::from(udid),
// FIXME: reason is not info
&QString::from(reason_clone),
&QString::from(""),
);
})
.ok();
@@ -303,7 +305,7 @@ impl qobject::Core {
}
};
let mut buid = match uc2.get_buid().await {
let buid = match uc2.get_buid().await {
Ok(b) => b,
Err(_) => {
let udid_for_event = udid.clone();
@@ -312,29 +314,46 @@ impl qobject::Core {
}
};
if let Some(first) = buid.chars().next() {
let mut chars: Vec<char> =
buid.chars().collect();
chars[0] = if first == 'F' { 'A' } else { 'F' };
buid = chars.into_iter().collect();
}
let host_id =
uuid::Uuid::new_v4().to_string().to_uppercase();
let mut pf = match lc
.pair(host_id, buid, None)
.await
{
Ok(p) => p,
Err(_) => {
let udid_for_event = udid.clone();
// FIXME: we may not want to emit here
// because if user doesnt accept pairing in time, it will be considered a failure
emit_pairing_failed(qt_thread.clone(), udid_for_event, "Failed to pair device");
return;
}
println!(
"Pairing with device {}, host_id: {}, buid: {}",
udid, host_id, buid
);
let mut pf = loop {
match lc.pair(host_id.clone(), buid.clone(), None).await {
Ok(p) => {
println!("Pairing successful with device {}, host_id: {}, buid: {}", udid, host_id, buid);
break p;
}
Err(IdeviceError::PairingDialogResponsePending) => {
/* wait */
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
continue;
}
Err(IdeviceError::PasswordProtected) => {
/* wait */
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
continue;
}
Err(IdeviceError::InvalidHostID) =>{
/* wait */
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
continue;
}
// TODO: we can also check for CanceledByUser or UserDeniedPairing
Err(e) => {
// Actual error occurred
eprintln!("Pairing failed for device {}: {e:?}", udid);
let udid_for_event = udid.clone();
emit_pairing_failed(qt_thread.clone(), udid_for_event, "Failed to pair device");
return;
}
}
};
println!("Paired with device {}, pairing file obtained", udid);
pf.udid = Some(udid.clone());
let bytes = match pf.serialize() {
@@ -354,6 +373,10 @@ impl qobject::Core {
return;
}
println!(
"Pairing record saved to usbmuxd for device {}. Emitting connected event.",
udid
);
emit_connected(qt_thread.clone(), udid).await;
});
}
@@ -561,7 +584,6 @@ async fn clean_device_from_app_state(udid: &str) {
}
}
//FIXME: dont spawn hb if init fails
async fn init_idescriptor_device<
T: idevice::provider::IdeviceProvider + Send + Sync + Clone + 'static,
>(
@@ -580,12 +602,6 @@ async fn init_idescriptor_device<
};
eprintln!("init_idescriptor_device: Pairing file obtained.");
let mut hb_task = None;
let mut hb = None;
let provider_for_hb = provider.clone();
let qt_thread_for_hb = qt_thread.clone();
let mut lc = match LockdownClient::connect(&provider).await {
Ok(lc) => lc,
Err(e) => {
@@ -621,10 +637,11 @@ async fn init_idescriptor_device<
eprintln!("init_idescriptor_device: UDID is empty.");
return None;
}
let mut hb = None;
if is_wireless {
eprintln!("init_idescriptor_device: Attempting to connect to HeartbeatClient.");
hb = match heartbeat::HeartbeatClient::connect(&provider_for_hb).await {
hb = match heartbeat::HeartbeatClient::connect(&provider).await {
Ok(h) => Some(h),
Err(e) => {
eprintln!("heartbeat: connect failed: {e:?}");
@@ -634,71 +651,6 @@ async fn init_idescriptor_device<
eprintln!("init_idescriptor_device: Connected to HeartbeatClient.");
}
if is_wireless {
let mut hb_for_task = hb.take().unwrap();
let udid_for_hb = udid.clone();
hb_task = Some(Arc::new(RUNTIME.spawn(async move {
eprintln!("heartbeat: starting heartbeat task ");
let mut interval = 15u64;
let mut fails = 0;
loop {
eprintln!("heartbeat: Getting marco (interval: {interval}s)");
match hb_for_task.get_marco(interval).await {
Ok(next) => {
interval = next;
fails = 0; // Reset failures on success
eprintln!("heartbeat: Received marco, new interval: {interval}s");
}
Err(e) => {
fails += 1;
eprintln!("heartbeat: get_marco failed (fail count: {fails}): {e:?}");
if fails >= 3 {
eprintln!("heartbeat: too many failures for giving up");
clean_device_from_app_state(&udid_for_hb).await;
let udid_for_event = udid_for_hb.clone();
let _ = qt_thread_for_hb.queue(move |core_qobj| {
core_qobj.device_event(
EV_DISCONNECTED,
&QString::from(udid_for_event),
&QString::from(""),
);
});
break;
}
continue;
}
}
eprintln!("heartbeat: Sending polo.");
if let Err(e) = hb_for_task.send_polo().await {
fails += 1;
eprintln!("heartbeat: send_polo failed (fail count: {fails}): {e:?}");
if fails >= 3 {
eprintln!("heartbeat: too many failures for , giving up");
clean_device_from_app_state(&udid_for_hb).await;
let udid_for_event = udid_for_hb.clone();
let _ = qt_thread_for_hb.queue(move |core_qobj| {
core_qobj.device_event(
EV_DISCONNECTED,
&QString::from(udid_for_event),
&QString::from(""),
);
});
break;
}
continue;
}
eprintln!("heartbeat: Polo sent successfully.");
interval += 5;
}
eprintln!("heartbeat: heartbeat task ended.");
})));
}
// FIXME: this cannot be done when paired wirelessly
// lockdownd_set_value(device->lockdown, "EnableWifiConnections",
// // value, "com.apple.mobile.wireless_lockdown");
@@ -798,13 +750,13 @@ async fn init_idescriptor_device<
);
}
//Store device services in AppDeviceState
eprintln!("init_idescriptor_device: Storing device services.");
let device_services = DeviceServices {
afc: Arc::new(Mutex::new(afc_client)),
afc2,
diag: Arc::new(Mutex::new(diag_relay)),
heartbeat_task: hb_task,
heartbeat_task: None,
video_streams: Arc::new(Mutex::new(HashMap::new())),
provider: Arc::new(Mutex::new(Box::new(provider))),
lockdown: Arc::new(Mutex::new(lc)),
@@ -815,6 +767,32 @@ async fn init_idescriptor_device<
.await
.insert(udid.to_string(), device_services);
if is_wireless {
match hb {
Some(hb_client) => {
eprintln!("init_idescriptor_device: Spawning heartbeat task.");
match spawn_heartbeat_task(hb_client, qt_thread.clone(), udid.clone()).await {
Ok(task) => {
let mut state = APP_DEVICE_STATE.lock().await;
if let Some(svc) = state.get_mut(&udid) {
svc.heartbeat_task = Some(task);
}
}
Err(()) => {
eprintln!("init_idescriptor_device: Failed to spawn heartbeat task.");
return None;
}
}
}
None => {
eprintln!(
"init_idescriptor_device: Heartbeat client is None, cannot spawn heartbeat task."
);
return None;
}
}
}
let mut buf = Vec::new();
if def_vals.to_writer_xml(&mut buf).is_err() {
eprintln!("init_idescriptor_device: Failed to serialize default values to XML.");
@@ -825,3 +803,71 @@ async fn init_idescriptor_device<
Some((udid, info))
}
async fn spawn_heartbeat_task(
mut hb_client: heartbeat::HeartbeatClient,
qt_thread: cxx_qt::CxxQtThread<Core>,
udid: String,
) -> Result<Arc<JoinHandle<()>>, ()> {
let udid_for_hb = udid.clone();
Ok(Arc::new(RUNTIME.spawn(async move {
eprintln!("heartbeat: starting heartbeat task ");
let mut interval = 15u64;
let mut fails = 0;
loop {
eprintln!("heartbeat: Getting marco (interval: {interval}s)");
match hb_client.get_marco(interval).await {
Ok(next) => {
interval = next;
fails = 0;
eprintln!("heartbeat: Received marco, new interval: {interval}s");
}
Err(e) => {
fails += 1;
eprintln!("heartbeat: get_marco failed (fail count: {fails}): {e:?}");
if fails >= 3 {
eprintln!("heartbeat: too many failures for giving up");
clean_device_from_app_state(&udid_for_hb).await;
let udid_for_event = udid_for_hb.clone();
let _ = qt_thread.queue(move |core_qobj| {
core_qobj.device_event(
EV_DISCONNECTED,
&QString::from(udid_for_event),
&QString::from(""),
);
});
break;
}
continue;
}
}
eprintln!("heartbeat: Sending polo.");
if let Err(e) = hb_client.send_polo().await {
fails += 1;
eprintln!("heartbeat: send_polo failed (fail count: {fails}): {e:?}");
if fails >= 3 {
eprintln!("heartbeat: too many failures for , giving up");
clean_device_from_app_state(&udid_for_hb).await;
let udid_for_event = udid_for_hb.clone();
let _ = qt_thread.queue(move |core_qobj| {
core_qobj.device_event(
EV_DISCONNECTED,
&QString::from(udid_for_event),
&QString::from(""),
);
});
break;
}
continue;
}
eprintln!("heartbeat: Polo sent successfully.");
interval += 5;
}
eprintln!("heartbeat: heartbeat task ended.");
})))
}