1use std::collections::HashMap;
2use std::net::Ipv6Addr;
3use std::path::Path;
4use std::pin::Pin;
5use std::str::FromStr;
6use std::sync::Arc;
7use std::task::{Context, Poll};
8use std::time::{Duration, Instant};
9
10use crate::lockdown::pair_record::{default_pair_record_dir, PairRecord};
11use crate::lockdown::pairing::{
12 build_verify_start_tlv, build_verify_step2_tlv, HostIdentity, VerifyPairSession,
13};
14use crate::lockdown::protocol::{recv_lockdown, send_lockdown};
15use crate::lockdown::session::{
16 start_lockdown_session, start_service, wrap_service_tls, CORE_DEVICE_PROXY,
17};
18use crate::lockdown::LOCKDOWN_PORT;
19use crate::mux::MuxClient;
20use crate::proto::tlv::TlvBuffer;
21use crate::tunnel::{
22 forward::forward_packets,
23 manager::{TunMode, TunnelHandle},
24 tun::{kernel::KernelTunDevice, userspace::UserspaceTunDevice},
25};
26use crate::xpc::{
27 message::XpcValue,
28 rsd::{handshake as rsd_handshake, RsdHandshake, ServiceDescriptor},
29 XpcClient,
30};
31use base64::{engine::general_purpose::STANDARD as BASE64_STANDARD, Engine as _};
32use chacha20poly1305::{aead::Aead, KeyInit};
33use indexmap::IndexMap;
34use rand::RngCore;
35use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
36use tokio::net::TcpStream;
37use tokio_stream::StreamExt;
38
39use crate::credentials::{PersistedCredentials, RemotePairingRecord};
40use crate::discovery::{
41 browse_mobdev2, browse_remotepairing, mobdev2_wifi_mac, BonjourService, DeviceInfo, MdnsDevice,
42};
43use crate::error::CoreError;
44
45#[derive(Debug, Clone, Default)]
48pub struct ConnectOptions {
49 pub tun_mode: TunMode,
50 pub pair_record_path: Option<std::path::PathBuf>,
51 pub skip_tunnel: bool,
53}
54
55#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
56pub struct InternationalConfiguration {
57 pub language: String,
58 pub locale: String,
59 pub supported_locales: Vec<String>,
60 pub supported_languages: Vec<String>,
61}
62
63pub type ServiceStream = Box<dyn ServiceStreamTrait>;
67
68pub trait ServiceStreamTrait: AsyncRead + AsyncWrite + Unpin + Send {}
69impl<T: AsyncRead + AsyncWrite + Unpin + Send> ServiceStreamTrait for T {}
70
71const TUNNEL_HANDSHAKE_TIMEOUT: Duration = Duration::from_secs(5);
72const MOBDEV2_DISCOVERY_TIMEOUT: Duration = Duration::from_secs(3);
73const DIRECT_RSD_DISCOVERY_TIMEOUT: Duration = Duration::from_secs(3);
74const DIRECT_PAIRING_TYPE_PUBLIC_KEY: u8 = 0x03;
76const DIRECT_PAIRING_TYPE_ERROR: u8 = 0x07;
78const DIRECT_CONTROL_CHANNEL_ENVELOPE_TYPE: &str = "RemotePairing.ControlChannelMessageEnvelope";
79const DIRECT_CONTROL_CHANNEL_ORIGIN: &str = "host";
80
81pub struct ConnectedDevice {
84 pub info: DeviceInfo,
85 pub tunnel: Option<Arc<TunnelHandle>>,
86 pub rsd: Option<RsdHandshake>,
88 pair_record: Option<Arc<PairRecord>>,
89 lockdown_transport: LockdownTransport,
90}
91
92#[derive(Debug, Clone, PartialEq, Eq)]
93pub struct PairedMobdev2Device {
94 pub udid: String,
95 pub host: String,
96}
97
98#[derive(Debug, Clone, Copy, PartialEq, Eq)]
99enum TunnelConnectionTarget {
100 UserspaceProxy {
101 proxy_port: u16,
102 remote_addr: Ipv6Addr,
103 },
104 DirectIpv6 {
105 remote_addr: Ipv6Addr,
106 },
107}
108
109#[derive(Debug, Clone, PartialEq, Eq)]
110enum LockdownTransport {
111 Usbmux { device_id: u32 },
112 Tcp { host: String },
113}
114
115fn should_strip_service_ssl(service_name: &str) -> bool {
116 matches!(
117 service_name,
118 "com.apple.instruments.remoteserver" | "com.apple.accessibility.axAuditDaemon.remoteserver"
119 )
120}
121
122impl ConnectedDevice {
123 pub fn server_address(&self) -> Option<&str> {
124 self.tunnel.as_ref().map(|t| t.info.server_address.as_str())
125 }
126
127 pub fn userspace_port(&self) -> Option<u16> {
128 self.tunnel.as_ref().and_then(|t| t.userspace_port)
129 }
130
131 pub fn rsd_port(&self) -> Option<u16> {
132 self.tunnel.as_ref().map(|t| t.info.server_rsd_port)
133 }
134
135 fn pair_record(&self) -> Result<&Arc<PairRecord>, CoreError> {
136 self.pair_record
137 .as_ref()
138 .ok_or_else(|| CoreError::Unsupported("no pair record loaded".into()))
139 }
140
141 async fn lockdown_client(&self) -> Result<crate::lockdown::LockdownClient, CoreError> {
142 let pair_record = self.pair_record()?;
143 let stream = connect_lockdown_port(
144 &self.info.udid,
145 &self.lockdown_transport,
146 LOCKDOWN_PORT,
147 true,
148 )
149 .await?;
150 crate::lockdown::LockdownClient::connect_with_stream(stream, pair_record)
151 .await
152 .map_err(CoreError::from)
153 }
154
155 pub async fn connect_service(&self, service_name: &str) -> Result<ServiceStream, CoreError> {
157 let pair_record = self.pair_record()?;
158 let lockdown_stream = connect_lockdown_port(
159 &self.info.udid,
160 &self.lockdown_transport,
161 LOCKDOWN_PORT,
162 true,
163 )
164 .await?;
165
166 let (_session_id, mut tls_reader, mut tls_writer) =
167 start_lockdown_session(lockdown_stream, pair_record).await?;
168
169 let (port, enable_ssl) =
170 start_service(&mut tls_reader, &mut tls_writer, service_name).await?;
171
172 let svc_stream =
173 connect_lockdown_port(&self.info.udid, &self.lockdown_transport, port, false).await?;
174
175 if enable_ssl {
176 let tls = wrap_service_tls(svc_stream, pair_record)
177 .await
178 .map_err(|e| CoreError::Other(e.to_string()))?;
179 if should_strip_service_ssl(service_name) {
180 let stream = crate::lockdown::session::strip_service_tls(tls)
181 .map_err(|e| CoreError::Other(e.to_string()))?;
182 Ok(Box::new(stream))
183 } else {
184 Ok(Box::new(tls))
185 }
186 } else {
187 Ok(Box::new(svc_stream))
188 }
189 }
190
191 pub async fn product_version(&self) -> Result<semver::Version, CoreError> {
193 let mut client = self.lockdown_client().await?;
194 let ver = client.product_version().await?;
195 Ok(ver)
196 }
197
198 pub async fn lockdown_get_value(&self, key: Option<&str>) -> Result<plist::Value, CoreError> {
200 self.lockdown_get_value_in_domain(None, key).await
201 }
202
203 pub async fn lockdown_get_value_in_domain(
205 &self,
206 domain: Option<&str>,
207 key: Option<&str>,
208 ) -> Result<plist::Value, CoreError> {
209 let mut client = self.lockdown_client().await?;
210 client
211 .get_value(domain, key)
212 .await
213 .map_err(|e| CoreError::Other(e.to_string()))
214 }
215
216 pub async fn lockdown_set_value(
218 &self,
219 key: Option<&str>,
220 value: plist::Value,
221 ) -> Result<(), CoreError> {
222 self.lockdown_set_value_in_domain(None, key, value).await
223 }
224
225 pub async fn lockdown_set_value_in_domain(
227 &self,
228 domain: Option<&str>,
229 key: Option<&str>,
230 value: plist::Value,
231 ) -> Result<(), CoreError> {
232 let mut client = self.lockdown_client().await?;
233 client
234 .set_value(domain, key, value)
235 .await
236 .map_err(|e| CoreError::Other(e.to_string()))
237 }
238
239 pub async fn lockdown_remove_value(&self, key: Option<&str>) -> Result<(), CoreError> {
241 self.lockdown_remove_value_in_domain(None, key).await
242 }
243
244 pub async fn lockdown_remove_value_in_domain(
246 &self,
247 domain: Option<&str>,
248 key: Option<&str>,
249 ) -> Result<(), CoreError> {
250 let mut client = self.lockdown_client().await?;
251 client
252 .remove_value(domain, key)
253 .await
254 .map_err(|e| CoreError::Other(e.to_string()))
255 }
256
257 pub async fn lockdown_international_configuration(
259 &self,
260 ) -> Result<InternationalConfiguration, CoreError> {
261 const INTERNATIONAL_DOMAIN: &str = "com.apple.international";
262
263 let mut client = self.lockdown_client().await?;
264 let language = client
265 .get_value(Some(INTERNATIONAL_DOMAIN), Some("Language"))
266 .await
267 .map_err(|e| CoreError::Other(e.to_string()))?;
268 let locale = client
269 .get_value(Some(INTERNATIONAL_DOMAIN), Some("Locale"))
270 .await
271 .map_err(|e| CoreError::Other(e.to_string()))?;
272 let supported_locales = client
273 .get_value(Some(INTERNATIONAL_DOMAIN), Some("SupportedLocales"))
274 .await
275 .map_err(|e| CoreError::Other(e.to_string()))?;
276 let supported_languages = client
277 .get_value(Some(INTERNATIONAL_DOMAIN), Some("SupportedLanguages"))
278 .await
279 .map_err(|e| CoreError::Other(e.to_string()))?;
280
281 Ok(InternationalConfiguration {
282 language: plist_value_to_string(&language, "Language")?,
283 locale: plist_value_to_string(&locale, "Locale")?,
284 supported_locales: plist_value_to_string_vec(&supported_locales, "SupportedLocales")?,
285 supported_languages: plist_value_to_string_vec(
286 &supported_languages,
287 "SupportedLanguages",
288 )?,
289 })
290 }
291
292 pub async fn connect_rsd_service(
298 &self,
299 service_name: &str,
300 ) -> Result<ServiceStream, CoreError> {
301 let (resolved_service_name, port) =
302 self.resolve_rsd_service_with_retry(service_name).await?;
303
304 let mut stream = self.connect_tunnel_port(port).await?;
305 if resolved_service_name.ends_with(".shim.remote") {
306 rsd_checkin(&mut stream).await?;
307 }
308 Ok(stream)
309 }
310
311 pub async fn connect_xpc_service(&self, service_name: &str) -> Result<XpcClient, CoreError> {
316 let (_resolved_service_name, port) =
317 self.resolve_rsd_service_with_retry(service_name).await?;
318 let stream = self.connect_tunnel_port(port).await?;
319
320 XpcClient::connect_stream(stream)
321 .await
322 .map_err(|e| CoreError::Other(e.to_string()))
323 }
324
325 async fn resolve_rsd_service_with_retry(
326 &self,
327 service_name: &str,
328 ) -> Result<(String, u16), CoreError> {
329 if let Some(rsd) = self.rsd.as_ref() {
330 return resolve_rsd_service(rsd, service_name).ok_or_else(|| {
331 CoreError::Unsupported(format!(
332 "service '{service_name}' not found in RSD directory"
333 ))
334 });
335 }
336
337 let rsd = self.resolve_rsd_with_retry().await?;
338 resolve_rsd_service(&rsd, service_name).ok_or_else(|| {
339 CoreError::Unsupported(format!(
340 "service '{service_name}' not found in RSD directory"
341 ))
342 })
343 }
344
345 async fn resolve_rsd_with_retry(&self) -> Result<RsdHandshake, CoreError> {
346 const MAX_ATTEMPTS: usize = 5;
347
348 if self.tunnel.is_none() {
349 return Err(CoreError::Unsupported(
350 "RSD not available (no tunnel or iOS <17)".into(),
351 ));
352 }
353
354 for attempt in 0..MAX_ATTEMPTS {
355 if attempt > 0 {
356 tokio::time::sleep(std::time::Duration::from_millis(500)).await;
357 }
358
359 if let Some(rsd) = self.attempt_rsd_from_tunnel().await? {
360 return Ok(rsd);
361 }
362
363 tracing::debug!(
364 "RSD handshake attempt {}/{} failed, retrying...",
365 attempt + 1,
366 MAX_ATTEMPTS
367 );
368 }
369
370 Err(CoreError::Unsupported(
371 "RSD handshake failed after retries".into(),
372 ))
373 }
374
375 async fn attempt_rsd_from_tunnel(&self) -> Result<Option<RsdHandshake>, CoreError> {
376 let server_addr = self
377 .server_address()
378 .ok_or_else(|| CoreError::Unsupported("no server address".into()))?;
379 let rsd_port = self
380 .rsd_port()
381 .ok_or_else(|| CoreError::Unsupported("no RSD port from tunnel info".into()))?;
382
383 Ok(match self.userspace_port() {
384 Some(proxy_port) => attempt_rsd_via_proxy(proxy_port, server_addr, rsd_port).await,
385 None => attempt_rsd(server_addr, rsd_port).await,
386 })
387 }
388
389 fn tunnel_connection_target(&self) -> Result<TunnelConnectionTarget, CoreError> {
390 let server_addr = self
391 .server_address()
392 .ok_or_else(|| CoreError::Unsupported("no server address".into()))?;
393
394 resolve_tunnel_connection_target(server_addr, self.userspace_port())
395 }
396
397 async fn connect_tunnel_port(&self, port: u16) -> Result<ServiceStream, CoreError> {
398 use tokio::io::AsyncWriteExt;
399 use tokio::net::TcpStream;
400
401 match self.tunnel_connection_target()? {
402 TunnelConnectionTarget::UserspaceProxy {
403 proxy_port,
404 remote_addr,
405 } => {
406 let mut proxy = TcpStream::connect(format!("127.0.0.1:{proxy_port}")).await?;
407 proxy.write_all(&remote_addr.octets()).await?;
408 proxy.write_all(&(port as u32).to_le_bytes()).await?;
409 Ok(Box::new(proxy))
410 }
411 TunnelConnectionTarget::DirectIpv6 { remote_addr } => {
412 let addr =
413 std::net::SocketAddr::V6(std::net::SocketAddrV6::new(remote_addr, port, 0, 0));
414 Ok(Box::new(TcpStream::connect(addr).await?))
415 }
416 }
417 }
418}
419
420#[derive(serde::Serialize)]
421#[serde(rename_all = "PascalCase")]
422struct RsdCheckinRequest {
423 label: &'static str,
424 protocol_version: &'static str,
425 request: &'static str,
426}
427
428fn resolve_rsd_service(rsd: &RsdHandshake, requested_service: &str) -> Option<(String, u16)> {
429 if let Some(ServiceDescriptor { port }) = rsd.services.get(requested_service) {
430 return Some((requested_service.to_string(), *port));
431 }
432
433 let shim_service = format!("{requested_service}.shim.remote");
434 rsd.services
435 .get(&shim_service)
436 .map(|ServiceDescriptor { port }| (shim_service, *port))
437}
438
439fn resolve_tunnel_connection_target(
440 server_addr: &str,
441 userspace_port: Option<u16>,
442) -> Result<TunnelConnectionTarget, CoreError> {
443 let remote_addr = Ipv6Addr::from_str(server_addr)
444 .map_err(|e| CoreError::Other(format!("invalid IPv6 addr: {e}")))?;
445
446 Ok(match userspace_port {
447 Some(proxy_port) => TunnelConnectionTarget::UserspaceProxy {
448 proxy_port,
449 remote_addr,
450 },
451 None => TunnelConnectionTarget::DirectIpv6 { remote_addr },
452 })
453}
454
455fn validate_rsd_checkin_response(
456 response: plist::Value,
457 expected_request: &str,
458 context: &str,
459) -> Result<(), CoreError> {
460 let response = response.as_dictionary().ok_or_else(|| {
461 CoreError::Other(format!(
462 "{context} expected plist dictionary response, got {:?}",
463 response
464 ))
465 })?;
466
467 let actual_request = response
468 .get("Request")
469 .and_then(plist::Value::as_string)
470 .ok_or_else(|| {
471 CoreError::Other(format!(
472 "{context} missing Request field in response: {:?}",
473 response
474 ))
475 })?;
476
477 if actual_request != expected_request {
478 return Err(CoreError::Other(format!(
479 "{context} expected Request={expected_request}, got {actual_request}"
480 )));
481 }
482
483 if let Some(error) = response.get("Error") {
484 return Err(CoreError::Other(format!(
485 "{context} failed with Error={:?}",
486 error
487 )));
488 }
489
490 Ok(())
491}
492
493async fn rsd_checkin<S>(stream: &mut S) -> Result<(), CoreError>
494where
495 S: AsyncRead + AsyncWrite + Unpin,
496{
497 send_lockdown(
498 stream,
499 &RsdCheckinRequest {
500 label: "ios-rs",
501 protocol_version: "2",
502 request: "RSDCheckin",
503 },
504 )
505 .await
506 .map_err(|e| CoreError::Other(e.to_string()))?;
507
508 let checkin_response: plist::Value = recv_lockdown(stream)
509 .await
510 .map_err(|e| CoreError::Other(e.to_string()))?;
511 validate_rsd_checkin_response(checkin_response, "RSDCheckin", "RSD check-in response")?;
512
513 let start_service_response: plist::Value = recv_lockdown(stream)
514 .await
515 .map_err(|e| CoreError::Other(e.to_string()))?;
516 validate_rsd_checkin_response(
517 start_service_response,
518 "StartService",
519 "RSD start-service response",
520 )?;
521 Ok(())
522}
523
524pub async fn connect(udid: &str, opts: ConnectOptions) -> Result<ConnectedDevice, CoreError> {
527 let mut mux = MuxClient::connect().await?;
528 let devices = mux.list_devices().await?;
529 let dev = select_mux_device(devices, udid)
530 .ok_or_else(|| CoreError::DeviceNotFound(udid.to_string()))?;
531
532 let info = DeviceInfo {
533 udid: dev.serial_number.clone(),
534 device_id: dev.device_id,
535 connection_type: dev.connection_type.clone(),
536 product_id: dev.product_id,
537 };
538
539 let pair_record = load_pair_record(udid, opts.pair_record_path.as_deref())?;
540 connect_via_lockdown_transport(
541 info,
542 pair_record,
543 LockdownTransport::Usbmux {
544 device_id: dev.device_id,
545 },
546 opts,
547 )
548 .await
549}
550
551pub async fn connect_direct_usb_tunnel(
552 udid: &str,
553 rsd_ip: Option<&str>,
554 opts: ConnectOptions,
555) -> Result<ConnectedDevice, CoreError> {
556 let mut mux = MuxClient::connect().await?;
557 let devices = mux.list_devices().await?;
558 let dev = select_mux_device(devices, udid)
559 .ok_or_else(|| CoreError::DeviceNotFound(udid.to_string()))?;
560 let pair_record = try_load_pair_record(udid, opts.pair_record_path.as_deref());
561 let info = DeviceInfo {
562 udid: dev.serial_number.clone(),
563 device_id: dev.device_id,
564 connection_type: dev.connection_type.clone(),
565 product_id: dev.product_id,
566 };
567 let lockdown_transport = LockdownTransport::Usbmux {
568 device_id: dev.device_id,
569 };
570
571 if opts.skip_tunnel {
572 let pair_record =
573 require_pair_record(pair_record, udid, "direct USB lockdown access requires")?;
574 return Ok(ConnectedDevice {
575 info,
576 tunnel: None,
577 rsd: None,
578 pair_record: Some(pair_record),
579 lockdown_transport,
580 });
581 }
582
583 let targets = discover_direct_rsd_targets(udid, rsd_ip).await?;
584 if targets.is_empty() {
585 return Err(CoreError::Unsupported(format!(
586 "no _remoted target matched udid={udid} ip={rsd_ip:?}"
587 )));
588 }
589
590 let mut last_error = None;
591 for target in targets {
592 match connect_via_direct_rsd_target(
593 info.clone(),
594 pair_record.clone(),
595 lockdown_transport.clone(),
596 opts.clone(),
597 target,
598 )
599 .await
600 {
601 Ok(device) => return Ok(device),
602 Err(err) => last_error = Some(err),
603 }
604 }
605
606 Err(last_error.unwrap_or_else(|| {
607 CoreError::Unsupported(format!(
608 "no direct RSD target produced a tunnel for udid={udid}"
609 ))
610 }))
611}
612
613pub async fn connect_remote_pairing_tunnel(
614 udid: &str,
615 host: Option<&str>,
616 opts: ConnectOptions,
617) -> Result<ConnectedDevice, CoreError> {
618 let pair_record = try_load_pair_record(udid, opts.pair_record_path.as_deref());
619 let info = DeviceInfo {
620 udid: udid.to_string(),
621 device_id: 0,
622 connection_type: "Network".into(),
623 product_id: 0,
624 };
625
626 if opts.skip_tunnel {
627 let pair_record =
628 require_pair_record(pair_record, udid, "remote pairing lockdown access requires")?;
629 return Ok(ConnectedDevice {
630 info,
631 tunnel: None,
632 rsd: None,
633 pair_record: Some(pair_record),
634 lockdown_transport: LockdownTransport::Tcp {
635 host: host.unwrap_or_default().to_string(),
636 },
637 });
638 }
639
640 let targets = discover_remote_pairing_targets(udid, host).await?;
641 if targets.is_empty() {
642 return Err(CoreError::Unsupported(format!(
643 "no _remotepairing target matched udid={udid} host={host:?}"
644 )));
645 }
646
647 let mut last_error = None;
648 for (remote_host, port) in targets {
649 match connect_via_remote_pairing_target(
650 info.clone(),
651 pair_record.clone(),
652 opts.clone(),
653 udid,
654 &remote_host,
655 port,
656 )
657 .await
658 {
659 Ok(device) => return Ok(device),
660 Err(err) => last_error = Some(err),
661 }
662 }
663
664 Err(last_error.unwrap_or_else(|| {
665 CoreError::Unsupported(format!(
666 "no remote pairing target produced a tunnel for udid={udid}"
667 ))
668 }))
669}
670
671pub async fn connect_tcp_lockdown_tunnel(
672 udid: &str,
673 host: &str,
674 opts: ConnectOptions,
675) -> Result<ConnectedDevice, CoreError> {
676 let pair_record = load_pair_record(udid, opts.pair_record_path.as_deref())?;
677 let info = DeviceInfo {
678 udid: udid.to_string(),
679 device_id: 0,
680 connection_type: "Network".into(),
681 product_id: 0,
682 };
683 connect_via_lockdown_transport(
684 info,
685 pair_record,
686 LockdownTransport::Tcp {
687 host: host.to_string(),
688 },
689 opts,
690 )
691 .await
692}
693
694pub async fn discover_paired_mobdev2_devices() -> Result<Vec<PairedMobdev2Device>, CoreError> {
695 let wifi_mac_to_udid = load_wifi_mac_pairings()?;
696 let services = browse_mobdev2(MOBDEV2_DISCOVERY_TIMEOUT).await?;
697 Ok(match_paired_mobdev2_targets(&services, &wifi_mac_to_udid))
698}
699
700fn select_mux_device(
701 devices: Vec<crate::mux::MuxDevice>,
702 udid: &str,
703) -> Option<crate::mux::MuxDevice> {
704 let mut fallback = None;
705
706 for device in devices {
707 if device.serial_number != udid {
708 continue;
709 }
710
711 let is_usb = device.connection_type.eq_ignore_ascii_case("USB");
712 fallback = Some(device);
713
714 if is_usb {
715 return fallback;
716 }
717 }
718
719 fallback
720}
721
722fn load_pair_record(
723 udid: &str,
724 pair_record_path: Option<&std::path::Path>,
725) -> Result<Arc<PairRecord>, CoreError> {
726 Ok(Arc::new(if let Some(path) = pair_record_path {
727 PairRecord::load_from_path(path, udid)?
728 } else {
729 PairRecord::load(udid)?
730 }))
731}
732
733fn try_load_pair_record(
734 udid: &str,
735 pair_record_path: Option<&std::path::Path>,
736) -> Option<Arc<PairRecord>> {
737 load_pair_record(udid, pair_record_path).ok()
738}
739
740fn require_pair_record(
741 pair_record: Option<Arc<PairRecord>>,
742 udid: &str,
743 context: &str,
744) -> Result<Arc<PairRecord>, CoreError> {
745 pair_record.ok_or_else(|| {
746 CoreError::Unsupported(format!("{context} a lockdown pair record for {udid}"))
747 })
748}
749
750async fn connect_lockdown_port(
751 udid: &str,
752 transport: &LockdownTransport,
753 port: u16,
754 read_pair_record: bool,
755) -> Result<ServiceStream, CoreError> {
756 match transport {
757 LockdownTransport::Usbmux { device_id } => {
758 let mut mux = MuxClient::connect().await?;
759 if read_pair_record {
760 mux.read_pair_record(udid).await?;
761 }
762 let stream = mux.connect_to_port(*device_id, port).await?;
763 Ok(Box::new(stream))
764 }
765 LockdownTransport::Tcp { host, .. } => {
766 let stream = TcpStream::connect((host.as_str(), port)).await?;
767 Ok(Box::new(stream))
768 }
769 }
770}
771
772async fn connect_via_lockdown_transport(
773 info: DeviceInfo,
774 pair_record: Arc<PairRecord>,
775 lockdown_transport: LockdownTransport,
776 opts: ConnectOptions,
777) -> Result<ConnectedDevice, CoreError> {
778 if opts.skip_tunnel {
779 return Ok(ConnectedDevice {
780 info,
781 tunnel: None,
782 rsd: None,
783 pair_record: Some(pair_record),
784 lockdown_transport,
785 });
786 }
787
788 let lockdown_stream =
789 connect_lockdown_port(&info.udid, &lockdown_transport, LOCKDOWN_PORT, true).await?;
790
791 tracing::info!("tunnel connect: starting lockdown session");
792 let (_session_id, mut tls_reader, mut tls_writer) =
793 start_lockdown_session(lockdown_stream, &pair_record).await?;
794 tracing::info!("tunnel connect: lockdown session established");
795
796 tracing::info!("tunnel connect: requesting CoreDeviceProxy");
797 let (service_port, enable_service_ssl) =
798 start_service(&mut tls_reader, &mut tls_writer, CORE_DEVICE_PROXY).await?;
799 tracing::info!(
800 "tunnel connect: CoreDeviceProxy started on port {service_port} (ssl={enable_service_ssl})"
801 );
802
803 let proxy_stream_raw =
804 connect_lockdown_port(&info.udid, &lockdown_transport, service_port, false).await?;
805
806 let mut proxy_stream = if enable_service_ssl {
807 tracing::info!("tunnel connect: wrapping CoreDeviceProxy with TLS");
808 ProxyStream::Tls(Box::new(
809 wrap_service_tls(proxy_stream_raw, &pair_record)
810 .await
811 .map_err(|e| CoreError::Other(e.to_string()))?,
812 ))
813 } else {
814 tracing::info!("tunnel connect: CoreDeviceProxy is plaintext");
815 ProxyStream::Plain(proxy_stream_raw)
816 };
817 tracing::info!("tunnel connect: CoreDeviceProxy stream ready");
818
819 tracing::info!(
820 "tunnel connect: exchanging CDTunnel parameters (timeout={} ms)",
821 TUNNEL_HANDSHAKE_TIMEOUT.as_millis()
822 );
823 let tunnel_info = crate::tunnel::handshake::exchange_tunnel_parameters_with_timeout(
824 &mut proxy_stream,
825 TUNNEL_HANDSHAKE_TIMEOUT,
826 )
827 .await
828 .map_err(CoreError::Tunnel)?;
829 tracing::info!("tunnel connect: CDTunnel parameters received");
830 tracing::info!(
831 "tunnel_info: server={} rsd_port={} client={} mtu={}",
832 tunnel_info.server_address,
833 tunnel_info.server_rsd_port,
834 tunnel_info.client_address,
835 tunnel_info.client_mtu
836 );
837
838 match opts.tun_mode {
839 TunMode::Kernel => {
840 let (handle, cancel_rx) =
841 TunnelHandle::new(info.udid.clone(), tunnel_info.clone(), None);
842 let tun = KernelTunDevice::create(&tunnel_info.client_address, tunnel_info.client_mtu)
843 .await
844 .map_err(CoreError::Tunnel)?;
845 let mtu = tunnel_info.client_mtu;
846 tokio::spawn(async move {
847 if let Err(e) = forward_packets(proxy_stream, tun, mtu, cancel_rx).await {
848 tracing::error!("kernel TUN forward: {e}");
849 }
850 });
851 let rsd = attempt_rsd(&tunnel_info.server_address, tunnel_info.server_rsd_port).await;
852 Ok(ConnectedDevice {
853 info,
854 tunnel: Some(Arc::new(handle)),
855 rsd,
856 pair_record: Some(pair_record),
857 lockdown_transport,
858 })
859 }
860 TunMode::Userspace => {
861 let userspace = UserspaceTunDevice::start(
862 &tunnel_info.client_address,
863 &tunnel_info.server_address,
864 tunnel_info.client_mtu,
865 proxy_stream,
866 )
867 .await
868 .map_err(CoreError::Tunnel)?;
869
870 let proxy_port = userspace.local_port;
871 let handle =
872 TunnelHandle::new_userspace(info.udid.clone(), tunnel_info.clone(), userspace);
873 let rsd = attempt_rsd_via_proxy(
874 proxy_port,
875 &tunnel_info.server_address,
876 tunnel_info.server_rsd_port,
877 )
878 .await;
879 Ok(ConnectedDevice {
880 info,
881 tunnel: Some(Arc::new(handle)),
882 rsd,
883 pair_record: Some(pair_record),
884 lockdown_transport,
885 })
886 }
887 }
888}
889
890struct GuardedTunnelStream<G> {
891 stream: tokio_openssl::SslStream<TcpStream>,
892 _guard: G,
893}
894
895impl<G> Unpin for GuardedTunnelStream<G> {}
896
897impl<G> AsyncRead for GuardedTunnelStream<G> {
898 fn poll_read(
899 self: Pin<&mut Self>,
900 cx: &mut Context<'_>,
901 buf: &mut ReadBuf<'_>,
902 ) -> Poll<std::io::Result<()>> {
903 Pin::new(&mut self.get_mut().stream).poll_read(cx, buf)
904 }
905}
906
907impl<G> AsyncWrite for GuardedTunnelStream<G> {
908 fn poll_write(
909 self: Pin<&mut Self>,
910 cx: &mut Context<'_>,
911 buf: &[u8],
912 ) -> Poll<std::io::Result<usize>> {
913 Pin::new(&mut self.get_mut().stream).poll_write(cx, buf)
914 }
915
916 fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<std::io::Result<()>> {
917 Pin::new(&mut self.get_mut().stream).poll_flush(cx)
918 }
919
920 fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<std::io::Result<()>> {
921 Pin::new(&mut self.get_mut().stream).poll_shutdown(cx)
922 }
923}
924
925struct LoadedRemotePairingCredentials {
926 host_identity: HostIdentity,
927}
928
929struct RemotePairingControlChannel {
930 stream: TcpStream,
931}
932
933impl RemotePairingControlChannel {
934 async fn connect(host: &str, port: u16) -> Result<Self, CoreError> {
935 Ok(Self {
936 stream: TcpStream::connect((host, port)).await?,
937 })
938 }
939
940 async fn send(&mut self, payload: &serde_json::Value) -> Result<(), CoreError> {
941 use tokio::io::AsyncWriteExt;
942
943 let body = serde_json::to_vec(payload)
944 .map_err(|e| CoreError::Other(format!("remote pairing JSON encode failed: {e}")))?;
945 if body.len() > u16::MAX as usize {
946 return Err(CoreError::Other(format!(
947 "remote pairing payload too large: {} bytes",
948 body.len()
949 )));
950 }
951
952 self.stream.write_all(b"RPPairing").await?;
953 self.stream
954 .write_all(&(body.len() as u16).to_be_bytes())
955 .await?;
956 self.stream.write_all(&body).await?;
957 self.stream.flush().await?;
958 Ok(())
959 }
960
961 async fn recv(&mut self) -> Result<serde_json::Value, CoreError> {
962 use tokio::io::AsyncReadExt;
963
964 let mut magic = [0u8; 9];
965 self.stream.read_exact(&mut magic).await?;
966 if &magic != b"RPPairing" {
967 return Err(CoreError::Other(format!(
968 "invalid RPPairing magic: {magic:?}"
969 )));
970 }
971
972 let mut length = [0u8; 2];
973 self.stream.read_exact(&mut length).await?;
974 let body_len = u16::from_be_bytes(length) as usize;
975 let mut body = vec![0u8; body_len];
976 self.stream.read_exact(&mut body).await?;
977 serde_json::from_slice(&body)
978 .map_err(|e| CoreError::Other(format!("remote pairing JSON decode failed: {e}")))
979 }
980}
981
982async fn discover_direct_rsd_targets(
983 udid: &str,
984 ip_filter: Option<&str>,
985) -> Result<Vec<MdnsDevice>, CoreError> {
986 let stream = crate::discovery::discover_mdns().await?;
987 tokio::pin!(stream);
988
989 let deadline = Instant::now() + DIRECT_RSD_DISCOVERY_TIMEOUT;
990 let mut targets = Vec::new();
991 let mut seen = std::collections::HashSet::new();
992
993 loop {
994 let remaining = deadline.saturating_duration_since(Instant::now());
995 if remaining.is_zero() {
996 break;
997 }
998
999 match tokio::time::timeout(remaining, stream.next()).await {
1000 Ok(Some(device)) => {
1001 let ip = device.ipv6.to_string();
1002 if ip_filter.map(|filter| filter != ip).unwrap_or(false) {
1003 continue;
1004 }
1005
1006 let key = (device.ipv6, device.rsd_port);
1007 if !seen.insert(key) {
1008 continue;
1009 }
1010
1011 targets.push(device);
1012 }
1013 Ok(None) | Err(_) => break,
1014 }
1015 }
1016
1017 targets.sort_by_key(|device| {
1018 if device.udid == udid {
1019 0
1020 } else if device.udid.is_empty() {
1021 1
1022 } else {
1023 2
1024 }
1025 });
1026 Ok(targets)
1027}
1028
1029async fn discover_remote_pairing_targets(
1030 udid: &str,
1031 host_filter: Option<&str>,
1032) -> Result<Vec<(String, u16)>, CoreError> {
1033 let services = browse_remotepairing(MOBDEV2_DISCOVERY_TIMEOUT).await?;
1034 let mut targets = Vec::new();
1035 let mut seen = std::collections::HashSet::new();
1036
1037 for service in services {
1038 let Some(host) = preferred_lockdown_address(&service.addresses) else {
1039 continue;
1040 };
1041 if host_filter.map(|filter| filter != host).unwrap_or(false) {
1042 continue;
1043 }
1044
1045 let key = (host.to_string(), service.port);
1046 if seen.insert(key.clone()) {
1047 targets.push(key);
1048 }
1049 }
1050
1051 if targets.is_empty() {
1052 return Err(CoreError::Unsupported(format!(
1053 "no browse_remotepairing target matched udid={udid} host={host_filter:?}"
1054 )));
1055 }
1056
1057 Ok(targets)
1058}
1059
1060async fn connect_via_direct_rsd_target(
1061 info: DeviceInfo,
1062 pair_record: Option<Arc<PairRecord>>,
1063 lockdown_transport: LockdownTransport,
1064 opts: ConnectOptions,
1065 target: MdnsDevice,
1066) -> Result<ConnectedDevice, CoreError> {
1067 let rsd = rsd_handshake(target.ipv6, target.rsd_port)
1068 .await
1069 .map_err(|e| CoreError::Other(format!("direct RSD handshake failed: {e}")))?;
1070 if rsd.udid != info.udid {
1071 return Err(CoreError::Other(format!(
1072 "direct RSD target {} resolved to unexpected udid {}",
1073 target.ipv6, rsd.udid
1074 )));
1075 }
1076
1077 let service_port = rsd
1078 .get_port(crate::pairing_transport::UNTRUSTED_SERVICE_NAME)
1079 .ok_or_else(|| {
1080 CoreError::Unsupported(format!(
1081 "direct RSD target {} does not expose {}",
1082 target.ipv6,
1083 crate::pairing_transport::UNTRUSTED_SERVICE_NAME
1084 ))
1085 })?;
1086 let mut direct_stream = establish_direct_tunnel_stream(target.ipv6, service_port).await?;
1087
1088 let tunnel_info = crate::tunnel::handshake::exchange_tunnel_parameters_with_timeout(
1089 &mut direct_stream,
1090 TUNNEL_HANDSHAKE_TIMEOUT,
1091 )
1092 .await
1093 .map_err(CoreError::Tunnel)?;
1094
1095 match opts.tun_mode {
1096 TunMode::Kernel => {
1097 let (handle, cancel_rx) =
1098 TunnelHandle::new(info.udid.clone(), tunnel_info.clone(), None);
1099 let tun = KernelTunDevice::create(&tunnel_info.client_address, tunnel_info.client_mtu)
1100 .await
1101 .map_err(CoreError::Tunnel)?;
1102 let mtu = tunnel_info.client_mtu;
1103 tokio::spawn(async move {
1104 if let Err(err) = forward_packets(direct_stream, tun, mtu, cancel_rx).await {
1105 tracing::error!("direct kernel TUN forward: {err}");
1106 }
1107 });
1108 let rsd = attempt_rsd(&tunnel_info.server_address, tunnel_info.server_rsd_port).await;
1109 Ok(ConnectedDevice {
1110 info,
1111 tunnel: Some(Arc::new(handle)),
1112 rsd,
1113 pair_record,
1114 lockdown_transport,
1115 })
1116 }
1117 TunMode::Userspace => {
1118 let userspace = UserspaceTunDevice::start(
1119 &tunnel_info.client_address,
1120 &tunnel_info.server_address,
1121 tunnel_info.client_mtu,
1122 direct_stream,
1123 )
1124 .await
1125 .map_err(CoreError::Tunnel)?;
1126
1127 let proxy_port = userspace.local_port;
1128 let handle =
1129 TunnelHandle::new_userspace(info.udid.clone(), tunnel_info.clone(), userspace);
1130 let rsd = attempt_rsd_via_proxy(
1131 proxy_port,
1132 &tunnel_info.server_address,
1133 tunnel_info.server_rsd_port,
1134 )
1135 .await;
1136 Ok(ConnectedDevice {
1137 info,
1138 tunnel: Some(Arc::new(handle)),
1139 rsd,
1140 pair_record,
1141 lockdown_transport,
1142 })
1143 }
1144 }
1145}
1146
1147async fn connect_via_remote_pairing_target(
1148 info: DeviceInfo,
1149 pair_record: Option<Arc<PairRecord>>,
1150 opts: ConnectOptions,
1151 remote_identifier: &str,
1152 host: &str,
1153 port: u16,
1154) -> Result<ConnectedDevice, CoreError> {
1155 let mut remote_stream =
1156 establish_remote_pairing_tunnel_stream(remote_identifier, host, port).await?;
1157
1158 let tunnel_info = crate::tunnel::handshake::exchange_tunnel_parameters_with_timeout(
1159 &mut remote_stream,
1160 TUNNEL_HANDSHAKE_TIMEOUT,
1161 )
1162 .await
1163 .map_err(CoreError::Tunnel)?;
1164
1165 match opts.tun_mode {
1166 TunMode::Kernel => {
1167 let (handle, cancel_rx) =
1168 TunnelHandle::new(info.udid.clone(), tunnel_info.clone(), None);
1169 let tun = KernelTunDevice::create(&tunnel_info.client_address, tunnel_info.client_mtu)
1170 .await
1171 .map_err(CoreError::Tunnel)?;
1172 let mtu = tunnel_info.client_mtu;
1173 tokio::spawn(async move {
1174 if let Err(err) = forward_packets(remote_stream, tun, mtu, cancel_rx).await {
1175 tracing::error!("remote pairing kernel TUN forward: {err}");
1176 }
1177 });
1178 let rsd = attempt_rsd(&tunnel_info.server_address, tunnel_info.server_rsd_port).await;
1179 Ok(ConnectedDevice {
1180 info,
1181 tunnel: Some(Arc::new(handle)),
1182 rsd,
1183 pair_record,
1184 lockdown_transport: LockdownTransport::Tcp {
1185 host: host.to_string(),
1186 },
1187 })
1188 }
1189 TunMode::Userspace => {
1190 let userspace = UserspaceTunDevice::start(
1191 &tunnel_info.client_address,
1192 &tunnel_info.server_address,
1193 tunnel_info.client_mtu,
1194 remote_stream,
1195 )
1196 .await
1197 .map_err(CoreError::Tunnel)?;
1198
1199 let proxy_port = userspace.local_port;
1200 let handle =
1201 TunnelHandle::new_userspace(info.udid.clone(), tunnel_info.clone(), userspace);
1202 let rsd = attempt_rsd_via_proxy(
1203 proxy_port,
1204 &tunnel_info.server_address,
1205 tunnel_info.server_rsd_port,
1206 )
1207 .await;
1208 Ok(ConnectedDevice {
1209 info,
1210 tunnel: Some(Arc::new(handle)),
1211 rsd,
1212 pair_record,
1213 lockdown_transport: LockdownTransport::Tcp {
1214 host: host.to_string(),
1215 },
1216 })
1217 }
1218 }
1219}
1220
1221async fn establish_direct_tunnel_stream(
1222 rsd_addr: Ipv6Addr,
1223 service_port: u16,
1224) -> Result<GuardedTunnelStream<XpcClient>, CoreError> {
1225 let mut client = XpcClient::connect(rsd_addr, service_port)
1226 .await
1227 .map_err(|e| CoreError::Other(format!("direct tunnelservice connect failed: {e}")))?;
1228 let mut sequence_number = 0u64;
1229
1230 client
1231 .send(build_direct_handshake_request(sequence_number))
1232 .await
1233 .map_err(|e| CoreError::Other(format!("direct handshake request failed: {e}")))?;
1234 sequence_number += 1;
1235
1236 let handshake = client
1237 .recv()
1238 .await
1239 .map_err(|e| CoreError::Other(format!("direct handshake response failed: {e}")))?;
1240 let remote_identifier = extract_direct_remote_identifier(
1241 handshake
1242 .body
1243 .as_ref()
1244 .ok_or_else(|| CoreError::Other("direct handshake response missing body".into()))?,
1245 )?;
1246
1247 let loaded = load_remote_pairing_credentials(&remote_identifier)?;
1248
1249 let mut our_secret = [0u8; 32];
1250 rand::thread_rng().fill_bytes(&mut our_secret);
1251 let static_secret = x25519_dalek::StaticSecret::from(our_secret);
1252 let our_public = x25519_dalek::PublicKey::from(&static_secret).to_bytes();
1253
1254 client
1255 .send(build_direct_pairing_event(
1256 &build_verify_start_tlv(&our_public),
1257 "verifyManualPairing",
1258 true,
1259 None,
1260 sequence_number,
1261 ))
1262 .await
1263 .map_err(|e| CoreError::Other(format!("verifyManualPairing start failed: {e}")))?;
1264 sequence_number += 1;
1265
1266 let verify_start = client
1267 .recv()
1268 .await
1269 .map_err(|e| CoreError::Other(format!("verifyManualPairing start response failed: {e}")))?;
1270 let verify_start_tlv = extract_direct_pairing_tlv(
1271 verify_start
1272 .body
1273 .as_ref()
1274 .ok_or_else(|| CoreError::Other("verifyManualPairing start missing body".into()))?,
1275 )?;
1276 let verify_start_fields = TlvBuffer::decode(&verify_start_tlv);
1277 if let Some(error) = verify_start_fields.get(&DIRECT_PAIRING_TYPE_ERROR) {
1278 send_pair_verify_failed(&mut client, sequence_number).await?;
1279 return Err(CoreError::Other(format!(
1280 "verifyManualPairing start rejected: {error:?}"
1281 )));
1282 }
1283
1284 let device_public: [u8; 32] = verify_start_fields
1285 .get(&DIRECT_PAIRING_TYPE_PUBLIC_KEY)
1286 .ok_or_else(|| {
1287 CoreError::Other("verifyManualPairing start missing device public key".into())
1288 })?
1289 .as_ref()
1290 .try_into()
1291 .map_err(|_| {
1292 CoreError::Other("verifyManualPairing device public key must be 32 bytes".into())
1293 })?;
1294
1295 let verify_session = build_verify_step2_tlv(
1296 our_secret,
1297 &our_public,
1298 &device_public,
1299 &loaded.host_identity,
1300 )
1301 .map_err(|e| CoreError::Other(format!("verifyManualPairing finish build failed: {e}")))?;
1302
1303 client
1304 .send(build_direct_pairing_event(
1305 &verify_session.tlv,
1306 "verifyManualPairing",
1307 false,
1308 None,
1309 sequence_number,
1310 ))
1311 .await
1312 .map_err(|e| CoreError::Other(format!("verifyManualPairing finish failed: {e}")))?;
1313 sequence_number += 1;
1314
1315 let verify_finish = client.recv().await.map_err(|e| {
1316 CoreError::Other(format!("verifyManualPairing finish response failed: {e}"))
1317 })?;
1318 let verify_finish_tlv = extract_direct_pairing_tlv(
1319 verify_finish
1320 .body
1321 .as_ref()
1322 .ok_or_else(|| CoreError::Other("verifyManualPairing finish missing body".into()))?,
1323 )?;
1324 let verify_finish_fields = TlvBuffer::decode(&verify_finish_tlv);
1325 if let Some(error) = verify_finish_fields.get(&DIRECT_PAIRING_TYPE_ERROR) {
1326 send_pair_verify_failed(&mut client, sequence_number).await?;
1327 return Err(CoreError::Other(format!(
1328 "verifyManualPairing finish rejected: {error:?}"
1329 )));
1330 }
1331
1332 let listener_port =
1333 create_direct_tcp_listener(&mut client, &verify_session, sequence_number).await?;
1334 let stream = crate::psk_tls::connect_psk_tls(
1335 &rsd_addr.to_string(),
1336 listener_port,
1337 &verify_session.encryption_key,
1338 )
1339 .await
1340 .map_err(|e| CoreError::Other(format!("direct TLS-PSK listener connect failed: {e}")))?;
1341
1342 Ok(GuardedTunnelStream {
1343 stream,
1344 _guard: client,
1345 })
1346}
1347
1348async fn establish_remote_pairing_tunnel_stream(
1349 remote_identifier: &str,
1350 host: &str,
1351 port: u16,
1352) -> Result<GuardedTunnelStream<RemotePairingControlChannel>, CoreError> {
1353 let loaded = load_remote_pairing_credentials(remote_identifier)?;
1354 let mut control = RemotePairingControlChannel::connect(host, port).await?;
1355 let mut sequence_number = 0u64;
1356
1357 control
1358 .send(&build_remote_pairing_handshake_request(sequence_number))
1359 .await?;
1360 sequence_number += 1;
1361 let _handshake = control.recv().await?;
1362
1363 let mut our_secret = [0u8; 32];
1364 rand::thread_rng().fill_bytes(&mut our_secret);
1365 let static_secret = x25519_dalek::StaticSecret::from(our_secret);
1366 let our_public = x25519_dalek::PublicKey::from(&static_secret).to_bytes();
1367
1368 control
1369 .send(&build_remote_pairing_pairing_event(
1370 &build_verify_start_tlv(&our_public),
1371 "verifyManualPairing",
1372 true,
1373 None,
1374 sequence_number,
1375 ))
1376 .await?;
1377 sequence_number += 1;
1378
1379 let verify_start = control.recv().await?;
1380 let verify_start_tlv = extract_remote_pairing_tlv(&verify_start)?;
1381 let verify_start_fields = TlvBuffer::decode(&verify_start_tlv);
1382 if let Some(error) = verify_start_fields.get(&DIRECT_PAIRING_TYPE_ERROR) {
1383 control
1384 .send(&build_remote_pairing_pair_verify_failed_event(
1385 sequence_number,
1386 ))
1387 .await?;
1388 return Err(CoreError::Other(format!(
1389 "remote pairing verify start rejected: {error:?}"
1390 )));
1391 }
1392
1393 let device_public: [u8; 32] = verify_start_fields
1394 .get(&DIRECT_PAIRING_TYPE_PUBLIC_KEY)
1395 .ok_or_else(|| {
1396 CoreError::Other("remote pairing verify start missing device public key".into())
1397 })?
1398 .as_ref()
1399 .try_into()
1400 .map_err(|_| {
1401 CoreError::Other("remote pairing device public key must be 32 bytes".into())
1402 })?;
1403
1404 let verify_session = build_verify_step2_tlv(
1405 our_secret,
1406 &our_public,
1407 &device_public,
1408 &loaded.host_identity,
1409 )
1410 .map_err(|e| CoreError::Other(format!("remote pairing verify finish build failed: {e}")))?;
1411
1412 control
1413 .send(&build_remote_pairing_pairing_event(
1414 &verify_session.tlv,
1415 "verifyManualPairing",
1416 false,
1417 None,
1418 sequence_number,
1419 ))
1420 .await?;
1421 sequence_number += 1;
1422
1423 let verify_finish = control.recv().await?;
1424 let verify_finish_tlv = extract_remote_pairing_tlv(&verify_finish)?;
1425 let verify_finish_fields = TlvBuffer::decode(&verify_finish_tlv);
1426 if let Some(error) = verify_finish_fields.get(&DIRECT_PAIRING_TYPE_ERROR) {
1427 control
1428 .send(&build_remote_pairing_pair_verify_failed_event(
1429 sequence_number,
1430 ))
1431 .await?;
1432 return Err(CoreError::Other(format!(
1433 "remote pairing verify finish rejected: {error:?}"
1434 )));
1435 }
1436
1437 let listener_port =
1438 create_remote_pairing_tcp_listener(&mut control, &verify_session, sequence_number).await?;
1439 let stream =
1440 crate::psk_tls::connect_psk_tls(host, listener_port, &verify_session.encryption_key)
1441 .await
1442 .map_err(|e| {
1443 CoreError::Other(format!(
1444 "remote pairing TLS-PSK listener connect failed: {e}"
1445 ))
1446 })?;
1447
1448 Ok(GuardedTunnelStream {
1449 stream,
1450 _guard: control,
1451 })
1452}
1453
1454async fn send_pair_verify_failed(
1455 client: &mut XpcClient,
1456 sequence_number: u64,
1457) -> Result<(), CoreError> {
1458 client
1459 .send(build_direct_pair_verify_failed_event(sequence_number))
1460 .await
1461 .map_err(|e| CoreError::Other(format!("pairVerifyFailed send failed: {e}")))
1462}
1463
1464fn load_remote_pairing_credentials(
1465 remote_identifier: &str,
1466) -> Result<LoadedRemotePairingCredentials, CoreError> {
1467 load_remote_pairing_credentials_from_dirs(
1468 remote_identifier,
1469 &PersistedCredentials::default_dir(),
1470 &PersistedCredentials::pymobiledevice3_dir(),
1471 ¤t_hostname(),
1472 )
1473}
1474
1475fn load_remote_pairing_credentials_from_dirs(
1476 remote_identifier: &str,
1477 ios_rs_dir: &Path,
1478 pymobiledevice3_dir: &Path,
1479 hostname: &str,
1480) -> Result<LoadedRemotePairingCredentials, CoreError> {
1481 if let Some(remote_pair_record) =
1482 RemotePairingRecord::load_for_identifier(ios_rs_dir, remote_identifier)
1483 {
1484 if let Some(persisted) = find_persisted_host_identity(ios_rs_dir, remote_identifier) {
1485 return load_ios_rs_remote_pairing_credentials(
1486 remote_identifier,
1487 remote_pair_record,
1488 persisted,
1489 );
1490 }
1491 }
1492
1493 if let Some(remote_pair_record) =
1494 RemotePairingRecord::load_for_identifier(pymobiledevice3_dir, remote_identifier)
1495 {
1496 return load_pymobiledevice3_remote_pairing_credentials(
1497 remote_identifier,
1498 hostname,
1499 remote_pair_record,
1500 pymobiledevice3_dir,
1501 );
1502 }
1503
1504 if RemotePairingRecord::load_for_identifier(ios_rs_dir, remote_identifier).is_some() {
1505 return Err(CoreError::Unsupported(format!(
1506 "missing persisted host identity for remote identifier {remote_identifier}"
1507 )));
1508 }
1509
1510 Err(CoreError::Unsupported(format!(
1511 "missing remote pairing record for {remote_identifier} in {} or {}",
1512 ios_rs_dir.display(),
1513 pymobiledevice3_dir.display()
1514 )))
1515}
1516
1517fn find_persisted_host_identity(
1518 creds_dir: &Path,
1519 remote_identifier: &str,
1520) -> Option<PersistedCredentials> {
1521 PersistedCredentials::list(creds_dir)
1522 .into_iter()
1523 .find(|creds| creds.remote_identifier.as_deref() == Some(remote_identifier))
1524}
1525
1526fn load_ios_rs_remote_pairing_credentials(
1527 remote_identifier: &str,
1528 remote_pair_record: RemotePairingRecord,
1529 persisted: PersistedCredentials,
1530) -> Result<LoadedRemotePairingCredentials, CoreError> {
1531 let host_private_key = remote_pair_record.private_key.clone();
1532 let host_identity =
1533 HostIdentity::from_private_key_bytes(persisted.host_identifier, &host_private_key)
1534 .map_err(|e| CoreError::Other(format!("invalid persisted host identity: {e}")))?;
1535
1536 if host_identity.public_key_bytes() != remote_pair_record.public_key {
1537 return Err(CoreError::Other(format!(
1538 "persisted host key mismatch for remote identifier {remote_identifier}"
1539 )));
1540 }
1541
1542 if let Some(host_private_key_hex) = persisted.host_private_key_hex {
1543 let persisted_private_key = hex::decode(host_private_key_hex)
1544 .map_err(|e| CoreError::Other(format!("invalid host private key hex: {e}")))?;
1545 if persisted_private_key != remote_pair_record.private_key {
1546 return Err(CoreError::Other(format!(
1547 "persisted host private key mismatch for remote identifier {remote_identifier}"
1548 )));
1549 }
1550 }
1551
1552 Ok(LoadedRemotePairingCredentials { host_identity })
1553}
1554
1555fn load_pymobiledevice3_remote_pairing_credentials(
1556 remote_identifier: &str,
1557 hostname: &str,
1558 remote_pair_record: RemotePairingRecord,
1559 creds_dir: &Path,
1560) -> Result<LoadedRemotePairingCredentials, CoreError> {
1561 let host_identifier = pymobiledevice3_host_identifier(hostname);
1562 let host_identity =
1563 HostIdentity::from_private_key_bytes(host_identifier, &remote_pair_record.private_key)
1564 .map_err(|e| {
1565 CoreError::Other(format!(
1566 "invalid pymobiledevice3 remote pairing identity for {remote_identifier}: {e}"
1567 ))
1568 })?;
1569
1570 if host_identity.public_key_bytes() != remote_pair_record.public_key {
1571 return Err(CoreError::Other(format!(
1572 "pymobiledevice3 host key mismatch for remote identifier {remote_identifier} in {}",
1573 creds_dir.display()
1574 )));
1575 }
1576
1577 Ok(LoadedRemotePairingCredentials { host_identity })
1578}
1579
1580fn current_hostname() -> String {
1581 std::env::var_os("COMPUTERNAME")
1582 .or_else(|| std::env::var_os("HOSTNAME"))
1583 .unwrap_or_default()
1584 .to_string_lossy()
1585 .into_owned()
1586}
1587
1588fn pymobiledevice3_host_identifier(hostname: &str) -> String {
1589 const NAMESPACE_DNS: [u8; 16] = [
1590 0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30,
1591 0xc8,
1592 ];
1593
1594 let mut input = Vec::with_capacity(NAMESPACE_DNS.len() + hostname.len());
1595 input.extend_from_slice(&NAMESPACE_DNS);
1596 input.extend_from_slice(hostname.as_bytes());
1597
1598 let mut bytes = md5::compute(&input).0.to_vec();
1599 bytes[6] = (bytes[6] & 0x0f) | 0x30;
1600 bytes[8] = (bytes[8] & 0x3f) | 0x80;
1601
1602 format!(
1603 "{:02x}{:02x}{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
1604 bytes[0],
1605 bytes[1],
1606 bytes[2],
1607 bytes[3],
1608 bytes[4],
1609 bytes[5],
1610 bytes[6],
1611 bytes[7],
1612 bytes[8],
1613 bytes[9],
1614 bytes[10],
1615 bytes[11],
1616 bytes[12],
1617 bytes[13],
1618 bytes[14],
1619 bytes[15]
1620 )
1621 .to_uppercase()
1622}
1623
1624fn build_direct_handshake_request(sequence_number: u64) -> XpcValue {
1625 build_direct_control_envelope(
1626 xpc_dict(&[(
1627 "plain",
1628 xpc_dict(&[(
1629 "_0",
1630 xpc_dict(&[(
1631 "request",
1632 xpc_dict(&[(
1633 "_0",
1634 xpc_dict(&[(
1635 "handshake",
1636 xpc_dict(&[(
1637 "_0",
1638 xpc_dict(&[
1639 (
1640 "hostOptions",
1641 xpc_dict(&[("attemptPairVerify", XpcValue::Bool(true))]),
1642 ),
1643 ("wireProtocolVersion", XpcValue::Int64(19)),
1644 ]),
1645 )]),
1646 )]),
1647 )]),
1648 )]),
1649 )]),
1650 )]),
1651 sequence_number,
1652 )
1653}
1654
1655fn build_direct_pairing_event(
1656 tlv_data: &[u8],
1657 kind: &str,
1658 start_new_session: bool,
1659 sending_host: Option<&str>,
1660 sequence_number: u64,
1661) -> XpcValue {
1662 let mut pairs = vec![
1663 (
1664 "data",
1665 XpcValue::Data(bytes::Bytes::copy_from_slice(tlv_data)),
1666 ),
1667 ("kind", XpcValue::String(kind.to_string())),
1668 ("startNewSession", XpcValue::Bool(start_new_session)),
1669 ];
1670 if let Some(host) = sending_host {
1671 pairs.push(("sendingHost", XpcValue::String(host.to_string())));
1672 }
1673
1674 build_direct_control_envelope(
1675 xpc_dict(&[(
1676 "plain",
1677 xpc_dict(&[(
1678 "_0",
1679 xpc_dict(&[(
1680 "event",
1681 xpc_dict(&[(
1682 "_0",
1683 xpc_dict(&[("pairingData", xpc_dict(&[("_0", xpc_dict(&pairs))]))]),
1684 )]),
1685 )]),
1686 )]),
1687 )]),
1688 sequence_number,
1689 )
1690}
1691
1692fn build_direct_pair_verify_failed_event(sequence_number: u64) -> XpcValue {
1693 build_direct_control_envelope(
1694 xpc_dict(&[(
1695 "plain",
1696 xpc_dict(&[(
1697 "_0",
1698 xpc_dict(&[(
1699 "event",
1700 xpc_dict(&[("_0", xpc_dict(&[("pairVerifyFailed", xpc_dict(&[]))]))]),
1701 )]),
1702 )]),
1703 )]),
1704 sequence_number,
1705 )
1706}
1707
1708fn build_direct_control_envelope(message: XpcValue, sequence_number: u64) -> XpcValue {
1709 xpc_dict(&[
1710 (
1711 "mangledTypeName",
1712 XpcValue::String(DIRECT_CONTROL_CHANNEL_ENVELOPE_TYPE.to_string()),
1713 ),
1714 (
1715 "value",
1716 xpc_dict(&[
1717 ("message", message),
1718 (
1719 "originatedBy",
1720 XpcValue::String(DIRECT_CONTROL_CHANNEL_ORIGIN.to_string()),
1721 ),
1722 ("sequenceNumber", XpcValue::Uint64(sequence_number)),
1723 ]),
1724 ),
1725 ])
1726}
1727
1728async fn create_direct_tcp_listener(
1729 client: &mut XpcClient,
1730 session: &VerifyPairSession,
1731 sequence_number: u64,
1732) -> Result<u16, CoreError> {
1733 let nonce = make_direct_encrypted_nonce(0);
1734 let request = serde_json::json!({
1735 "request": {
1736 "_0": {
1737 "createListener": {
1738 "key": BASE64_STANDARD.encode(session.encryption_key),
1739 "peerConnectionsInfo": [{
1740 "owningPID": std::process::id(),
1741 "owningProcessName": "CoreDeviceService",
1742 }],
1743 "transportProtocolType": "tcp",
1744 }
1745 }
1746 }
1747 });
1748 let client_cipher = chacha20poly1305::ChaCha20Poly1305::new((&session.client_key).into());
1749 let encrypted = client_cipher
1750 .encrypt((&nonce).into(), request.to_string().as_bytes())
1751 .map_err(|e| CoreError::Other(format!("createListener encrypt failed: {e}")))?;
1752
1753 client
1754 .send(build_direct_control_envelope(
1755 xpc_dict(&[(
1756 "streamEncrypted",
1757 xpc_dict(&[("_0", XpcValue::Data(bytes::Bytes::from(encrypted)))]),
1758 )]),
1759 sequence_number,
1760 ))
1761 .await
1762 .map_err(|e| CoreError::Other(format!("createListener request failed: {e}")))?;
1763
1764 let response = client
1765 .recv()
1766 .await
1767 .map_err(|e| CoreError::Other(format!("createListener response failed: {e}")))?;
1768 let encrypted_response = extract_direct_stream_encrypted(
1769 response
1770 .body
1771 .as_ref()
1772 .ok_or_else(|| CoreError::Other("createListener response missing body".into()))?,
1773 )?;
1774 let server_cipher = chacha20poly1305::ChaCha20Poly1305::new((&session.server_key).into());
1775 let plaintext = server_cipher
1776 .decrypt((&nonce).into(), encrypted_response.as_ref())
1777 .map_err(|e| CoreError::Other(format!("createListener decrypt failed: {e}")))?;
1778 let response: serde_json::Value = serde_json::from_slice(&plaintext)
1779 .map_err(|e| CoreError::Other(format!("invalid createListener JSON: {e}")))?;
1780 let response_body = response
1781 .get("response")
1782 .and_then(|value| value.get("_1"))
1783 .ok_or_else(|| CoreError::Other("createListener response missing response._1".into()))?;
1784
1785 if let Some(message) = extract_direct_error_extended_message(response_body) {
1786 return Err(CoreError::Other(format!(
1787 "createListener returned errorExtended: {message}"
1788 )));
1789 }
1790
1791 let port = response_body
1792 .get("createListener")
1793 .and_then(|value| value.get("port"))
1794 .and_then(serde_json::Value::as_u64)
1795 .ok_or_else(|| CoreError::Other("createListener response missing port".into()))?;
1796 u16::try_from(port)
1797 .ok()
1798 .filter(|port| *port != 0)
1799 .ok_or_else(|| CoreError::Other(format!("invalid createListener port {port}")))
1800}
1801
1802async fn create_remote_pairing_tcp_listener(
1803 control: &mut RemotePairingControlChannel,
1804 session: &VerifyPairSession,
1805 sequence_number: u64,
1806) -> Result<u16, CoreError> {
1807 let nonce = make_direct_encrypted_nonce(0);
1808 let request = serde_json::json!({
1809 "request": {
1810 "_0": {
1811 "createListener": {
1812 "key": BASE64_STANDARD.encode(session.encryption_key),
1813 "peerConnectionsInfo": [{
1814 "owningPID": std::process::id(),
1815 "owningProcessName": "CoreDeviceService",
1816 }],
1817 "transportProtocolType": "tcp",
1818 }
1819 }
1820 }
1821 });
1822 let client_cipher = chacha20poly1305::ChaCha20Poly1305::new((&session.client_key).into());
1823 let encrypted = client_cipher
1824 .encrypt((&nonce).into(), request.to_string().as_bytes())
1825 .map_err(|e| {
1826 CoreError::Other(format!("remote pairing createListener encrypt failed: {e}"))
1827 })?;
1828
1829 control
1830 .send(&serde_json::json!({
1831 "message": {
1832 "streamEncrypted": {
1833 "_0": BASE64_STANDARD.encode(encrypted),
1834 }
1835 },
1836 "originatedBy": DIRECT_CONTROL_CHANNEL_ORIGIN,
1837 "sequenceNumber": sequence_number,
1838 }))
1839 .await?;
1840
1841 let response = control.recv().await?;
1842 let encrypted_response = extract_remote_pairing_stream_encrypted(&response)?;
1843 let server_cipher = chacha20poly1305::ChaCha20Poly1305::new((&session.server_key).into());
1844 let plaintext = server_cipher
1845 .decrypt((&nonce).into(), encrypted_response.as_ref())
1846 .map_err(|e| {
1847 CoreError::Other(format!("remote pairing createListener decrypt failed: {e}"))
1848 })?;
1849 let response: serde_json::Value = serde_json::from_slice(&plaintext).map_err(|e| {
1850 CoreError::Other(format!("invalid remote pairing createListener JSON: {e}"))
1851 })?;
1852 let response_body = response
1853 .get("response")
1854 .and_then(|value| value.get("_1"))
1855 .ok_or_else(|| {
1856 CoreError::Other("remote pairing createListener response missing response._1".into())
1857 })?;
1858
1859 if let Some(message) = extract_direct_error_extended_message(response_body) {
1860 return Err(CoreError::Other(format!(
1861 "remote pairing createListener returned errorExtended: {message}"
1862 )));
1863 }
1864
1865 let port = response_body
1866 .get("createListener")
1867 .and_then(|value| value.get("port"))
1868 .and_then(serde_json::Value::as_u64)
1869 .ok_or_else(|| {
1870 CoreError::Other("remote pairing createListener response missing port".into())
1871 })?;
1872 u16::try_from(port)
1873 .ok()
1874 .filter(|port| *port != 0)
1875 .ok_or_else(|| {
1876 CoreError::Other(format!("invalid remote pairing createListener port {port}"))
1877 })
1878}
1879
1880fn xpc_dict(pairs: &[(&str, XpcValue)]) -> XpcValue {
1881 let mut map = IndexMap::new();
1882 for (key, value) in pairs {
1883 map.insert((*key).to_string(), value.clone());
1884 }
1885 XpcValue::Dictionary(map)
1886}
1887
1888fn extract_direct_remote_identifier(body: &XpcValue) -> Result<String, CoreError> {
1889 direct_plain_message(body)?
1890 .get("response")
1891 .and_then(XpcValue::as_dict)
1892 .and_then(|response| response.get("_1"))
1893 .and_then(XpcValue::as_dict)
1894 .and_then(|response| response.get("handshake"))
1895 .and_then(XpcValue::as_dict)
1896 .and_then(|handshake| handshake.get("_0"))
1897 .and_then(XpcValue::as_dict)
1898 .and_then(|handshake| handshake.get("peerDeviceInfo"))
1899 .and_then(XpcValue::as_dict)
1900 .and_then(|peer| peer.get("identifier"))
1901 .and_then(XpcValue::as_str)
1902 .map(ToOwned::to_owned)
1903 .ok_or_else(|| CoreError::Other("handshake missing peerDeviceInfo.identifier".into()))
1904}
1905
1906fn build_remote_pairing_handshake_request(sequence_number: u64) -> serde_json::Value {
1907 serde_json::json!({
1908 "message": {
1909 "plain": {
1910 "_0": {
1911 "request": {
1912 "_0": {
1913 "handshake": {
1914 "_0": {
1915 "hostOptions": {
1916 "attemptPairVerify": true,
1917 },
1918 "wireProtocolVersion": 19,
1919 }
1920 }
1921 }
1922 }
1923 }
1924 }
1925 },
1926 "originatedBy": DIRECT_CONTROL_CHANNEL_ORIGIN,
1927 "sequenceNumber": sequence_number,
1928 })
1929}
1930
1931fn build_remote_pairing_pairing_event(
1932 tlv_data: &[u8],
1933 kind: &str,
1934 start_new_session: bool,
1935 sending_host: Option<&str>,
1936 sequence_number: u64,
1937) -> serde_json::Value {
1938 let mut body = serde_json::Map::new();
1939 body.insert(
1940 "data".into(),
1941 serde_json::Value::String(BASE64_STANDARD.encode(tlv_data)),
1942 );
1943 body.insert("kind".into(), serde_json::Value::String(kind.to_string()));
1944 body.insert(
1945 "startNewSession".into(),
1946 serde_json::Value::Bool(start_new_session),
1947 );
1948 if let Some(host) = sending_host {
1949 body.insert(
1950 "sendingHost".into(),
1951 serde_json::Value::String(host.to_string()),
1952 );
1953 }
1954
1955 serde_json::json!({
1956 "message": {
1957 "plain": {
1958 "_0": {
1959 "event": {
1960 "_0": {
1961 "pairingData": {
1962 "_0": serde_json::Value::Object(body),
1963 }
1964 }
1965 }
1966 }
1967 }
1968 },
1969 "originatedBy": DIRECT_CONTROL_CHANNEL_ORIGIN,
1970 "sequenceNumber": sequence_number,
1971 })
1972}
1973
1974fn build_remote_pairing_pair_verify_failed_event(sequence_number: u64) -> serde_json::Value {
1975 serde_json::json!({
1976 "message": {
1977 "plain": {
1978 "_0": {
1979 "event": {
1980 "_0": {
1981 "pairVerifyFailed": {}
1982 }
1983 }
1984 }
1985 }
1986 },
1987 "originatedBy": DIRECT_CONTROL_CHANNEL_ORIGIN,
1988 "sequenceNumber": sequence_number,
1989 })
1990}
1991
1992fn extract_direct_pairing_tlv(body: &XpcValue) -> Result<Vec<u8>, CoreError> {
1993 let event = direct_plain_message(body)?
1994 .get("event")
1995 .and_then(XpcValue::as_dict)
1996 .and_then(|event| event.get("_0"))
1997 .and_then(XpcValue::as_dict)
1998 .ok_or_else(|| CoreError::Other("pairing response missing event._0".into()))?;
1999
2000 if let Some(message) = event
2001 .get("pairingRejectedWithError")
2002 .and_then(extract_direct_rejection_message)
2003 {
2004 return Err(CoreError::Other(format!("pairing rejected: {message}")));
2005 }
2006
2007 event
2008 .get("pairingData")
2009 .and_then(XpcValue::as_dict)
2010 .and_then(|pairing| pairing.get("_0"))
2011 .and_then(XpcValue::as_dict)
2012 .and_then(|pairing| pairing.get("data"))
2013 .and_then(|value| match value {
2014 XpcValue::Data(bytes) => Some(bytes.to_vec()),
2015 _ => None,
2016 })
2017 .ok_or_else(|| CoreError::Other("pairing response missing pairingData._0.data".into()))
2018}
2019
2020fn extract_remote_pairing_tlv(body: &serde_json::Value) -> Result<Vec<u8>, CoreError> {
2021 let event = body
2022 .get("message")
2023 .and_then(|value| value.get("plain"))
2024 .and_then(|value| value.get("_0"))
2025 .and_then(|value| value.get("event"))
2026 .and_then(|value| value.get("_0"))
2027 .ok_or_else(|| {
2028 CoreError::Other("remote pairing response missing message.plain._0.event._0".into())
2029 })?;
2030
2031 if let Some(message) = event
2032 .get("pairingRejectedWithError")
2033 .and_then(extract_remote_pairing_rejection_message)
2034 {
2035 return Err(CoreError::Other(format!(
2036 "remote pairing rejected: {message}"
2037 )));
2038 }
2039
2040 let data = event
2041 .get("pairingData")
2042 .and_then(|value| value.get("_0"))
2043 .and_then(|value| value.get("data"))
2044 .and_then(serde_json::Value::as_str)
2045 .ok_or_else(|| {
2046 CoreError::Other("remote pairing response missing pairingData._0.data".into())
2047 })?;
2048 BASE64_STANDARD
2049 .decode(data)
2050 .map_err(|e| CoreError::Other(format!("invalid remote pairing TLV base64: {e}")))
2051}
2052
2053fn extract_direct_stream_encrypted(body: &XpcValue) -> Result<Vec<u8>, CoreError> {
2054 direct_control_value(body)?
2055 .get("message")
2056 .and_then(XpcValue::as_dict)
2057 .and_then(|message| message.get("streamEncrypted"))
2058 .and_then(XpcValue::as_dict)
2059 .and_then(|encrypted| encrypted.get("_0"))
2060 .and_then(|value| match value {
2061 XpcValue::Data(bytes) => Some(bytes.to_vec()),
2062 _ => None,
2063 })
2064 .ok_or_else(|| {
2065 CoreError::Other("encrypted response missing message.streamEncrypted._0".into())
2066 })
2067}
2068
2069fn extract_remote_pairing_stream_encrypted(body: &serde_json::Value) -> Result<Vec<u8>, CoreError> {
2070 let encoded = body
2071 .get("message")
2072 .and_then(|value| value.get("streamEncrypted"))
2073 .and_then(|value| value.get("_0"))
2074 .and_then(serde_json::Value::as_str)
2075 .ok_or_else(|| {
2076 CoreError::Other(
2077 "remote pairing encrypted response missing message.streamEncrypted._0".into(),
2078 )
2079 })?;
2080 BASE64_STANDARD.decode(encoded).map_err(|e| {
2081 CoreError::Other(format!(
2082 "invalid remote pairing encrypted payload base64: {e}"
2083 ))
2084 })
2085}
2086
2087fn direct_control_value(body: &XpcValue) -> Result<&IndexMap<String, XpcValue>, CoreError> {
2088 let envelope = body.as_dict().ok_or_else(|| {
2089 CoreError::Other("direct control message body must be a dictionary".into())
2090 })?;
2091 let mangled_type = envelope
2092 .get("mangledTypeName")
2093 .and_then(XpcValue::as_str)
2094 .ok_or_else(|| CoreError::Other("direct control message missing mangledTypeName".into()))?;
2095 if mangled_type != DIRECT_CONTROL_CHANNEL_ENVELOPE_TYPE {
2096 return Err(CoreError::Other(format!(
2097 "unexpected direct control channel type {mangled_type}"
2098 )));
2099 }
2100 envelope
2101 .get("value")
2102 .and_then(XpcValue::as_dict)
2103 .ok_or_else(|| CoreError::Other("direct control message missing value".into()))
2104}
2105
2106fn direct_plain_message(body: &XpcValue) -> Result<&IndexMap<String, XpcValue>, CoreError> {
2107 direct_control_value(body)?
2108 .get("message")
2109 .and_then(XpcValue::as_dict)
2110 .and_then(|message| message.get("plain"))
2111 .and_then(XpcValue::as_dict)
2112 .and_then(|plain| plain.get("_0"))
2113 .and_then(XpcValue::as_dict)
2114 .ok_or_else(|| CoreError::Other("direct control message missing message.plain._0".into()))
2115}
2116
2117fn extract_direct_rejection_message(value: &XpcValue) -> Option<String> {
2118 value
2119 .as_dict()
2120 .and_then(|wrapped| wrapped.get("wrappedError"))
2121 .and_then(XpcValue::as_dict)
2122 .and_then(|wrapped| wrapped.get("userInfo"))
2123 .and_then(XpcValue::as_dict)
2124 .and_then(|user_info| user_info.get("NSLocalizedDescription"))
2125 .and_then(XpcValue::as_str)
2126 .map(ToOwned::to_owned)
2127}
2128
2129fn extract_remote_pairing_rejection_message(value: &serde_json::Value) -> Option<String> {
2130 value
2131 .get("wrappedError")
2132 .and_then(|wrapped| wrapped.get("userInfo"))
2133 .and_then(|user_info| user_info.get("NSLocalizedDescription"))
2134 .and_then(serde_json::Value::as_str)
2135 .map(ToOwned::to_owned)
2136}
2137
2138fn extract_direct_error_extended_message(value: &serde_json::Value) -> Option<String> {
2139 value
2140 .get("errorExtended")
2141 .and_then(|value| value.get("_0"))
2142 .and_then(|value| value.get("userInfo"))
2143 .and_then(|value| value.get("NSLocalizedDescription"))
2144 .and_then(serde_json::Value::as_str)
2145 .map(ToOwned::to_owned)
2146}
2147
2148fn make_direct_encrypted_nonce(sequence_number: u64) -> [u8; 12] {
2149 let mut nonce = [0u8; 12];
2150 nonce[..8].copy_from_slice(&sequence_number.to_le_bytes());
2151 nonce
2152}
2153
2154fn load_wifi_mac_pairings() -> Result<HashMap<String, String>, CoreError> {
2155 let mut wifi_mac_to_udid = HashMap::new();
2156 let pair_record_dir = default_pair_record_dir();
2157
2158 for entry in std::fs::read_dir(pair_record_dir)? {
2159 let entry = entry?;
2160 let path = entry.path();
2161 if !path.is_file() || path.extension().and_then(|ext| ext.to_str()) != Some("plist") {
2162 continue;
2163 }
2164
2165 let Some(udid) = path.file_stem().and_then(|stem| stem.to_str()) else {
2166 continue;
2167 };
2168 if udid.starts_with("remote_") {
2169 continue;
2170 }
2171
2172 let record = PairRecord::load_from_path(&path, udid)?;
2173 let Some(mac) = record.wifi_mac_address else {
2174 continue;
2175 };
2176 wifi_mac_to_udid.insert(mac.to_ascii_lowercase(), udid.to_string());
2177 }
2178
2179 Ok(wifi_mac_to_udid)
2180}
2181
2182fn match_paired_mobdev2_targets(
2183 services: &[BonjourService],
2184 wifi_mac_to_udid: &HashMap<String, String>,
2185) -> Vec<PairedMobdev2Device> {
2186 let mut targets = Vec::new();
2187 let mut seen = std::collections::HashSet::<(String, String)>::new();
2188
2189 for service in services {
2190 let Some(mac) = mobdev2_wifi_mac(&service.instance) else {
2191 continue;
2192 };
2193 let Some(udid) = wifi_mac_to_udid.get(&mac.to_ascii_lowercase()) else {
2194 continue;
2195 };
2196 let Some(host) = preferred_lockdown_address(&service.addresses) else {
2197 continue;
2198 };
2199
2200 let key = (udid.clone(), host.to_string());
2201 if seen.insert(key.clone()) {
2202 targets.push(PairedMobdev2Device {
2203 udid: key.0,
2204 host: key.1,
2205 });
2206 }
2207 }
2208
2209 targets
2210}
2211
2212fn preferred_lockdown_address(addresses: &[String]) -> Option<&str> {
2213 addresses
2214 .iter()
2215 .find(|address| address.parse::<std::net::Ipv4Addr>().is_ok())
2216 .map(String::as_str)
2217 .or_else(|| {
2218 addresses
2219 .iter()
2220 .find(|address| {
2221 !address.contains('%') && !address.to_ascii_lowercase().starts_with("fe80:")
2222 })
2223 .map(String::as_str)
2224 })
2225 .or_else(|| addresses.first().map(String::as_str))
2226}
2227
2228async fn attempt_rsd(server_addr: &str, rsd_port: u16) -> Option<RsdHandshake> {
2230 let addr = Ipv6Addr::from_str(server_addr).ok()?;
2231 match rsd_handshake(addr, rsd_port).await {
2232 Ok(h) => {
2233 tracing::info!(
2234 "RSD: {} services discovered for {}",
2235 h.services.len(),
2236 h.udid
2237 );
2238 Some(h)
2239 }
2240 Err(e) => {
2241 tracing::debug!("RSD handshake failed (may be iOS <17): {e}");
2242 None
2243 }
2244 }
2245}
2246
2247async fn attempt_rsd_via_proxy(
2249 proxy_port: u16,
2250 server_addr: &str,
2251 rsd_port: u16,
2252) -> Option<RsdHandshake> {
2253 tracing::info!(
2254 "RSD via proxy: probing [{server_addr}]:{rsd_port} through proxy port {proxy_port}"
2255 );
2256
2257 let mut framer = match open_rsd_proxy_framer(proxy_port, server_addr, rsd_port).await {
2258 Some(framer) => framer,
2259 None => return None,
2260 };
2261
2262 match tokio::time::timeout(
2263 Duration::from_secs(3),
2264 crate::xpc::rsd::queue_rsd_handshake_bootstrap_on_framer(&mut framer),
2265 )
2266 .await
2267 {
2268 Ok(Ok(())) => match tokio::time::timeout(
2269 Duration::from_secs(4),
2270 crate::xpc::rsd::handshake_on_framer(&mut framer),
2271 )
2272 .await
2273 {
2274 Ok(Ok(handshake)) => {
2275 tracing::info!(
2276 "RSD via proxy: queued bootstrap succeeded with {} services for {}",
2277 handshake.services.len(),
2278 handshake.udid
2279 );
2280 return Some(handshake);
2281 }
2282 Ok(Err(e)) => {
2283 tracing::warn!(
2284 "RSD via proxy: queued bootstrap handshake failed: {e}; trying legacy bootstrap"
2285 );
2286 }
2287 Err(_) => {
2288 tracing::warn!(
2289 "RSD via proxy: queued bootstrap handshake timed out; trying legacy bootstrap"
2290 );
2291 }
2292 },
2293 Ok(Err(e)) => {
2294 tracing::warn!("RSD via proxy: queued bootstrap failed: {e}; trying legacy bootstrap");
2295 }
2296 Err(_) => {
2297 tracing::warn!("RSD via proxy: queued bootstrap timed out; trying legacy bootstrap");
2298 }
2299 }
2300
2301 let mut framer = match open_rsd_proxy_framer(proxy_port, server_addr, rsd_port).await {
2302 Some(framer) => framer,
2303 None => return None,
2304 };
2305
2306 match tokio::time::timeout(
2307 Duration::from_secs(3),
2308 crate::xpc::rsd::initialize_xpc_connection_on_framer(&mut framer),
2309 )
2310 .await
2311 {
2312 Ok(Ok(())) => match tokio::time::timeout(
2313 Duration::from_secs(3),
2314 crate::xpc::rsd::handshake_on_framer(&mut framer),
2315 )
2316 .await
2317 {
2318 Ok(Ok(h)) => {
2319 tracing::info!(
2320 "RSD via proxy: legacy bootstrap succeeded with {} services for {}",
2321 h.services.len(),
2322 h.udid
2323 );
2324 Some(h)
2325 }
2326 Ok(Err(e)) => {
2327 tracing::warn!(
2328 "RSD handshake via proxy after legacy bootstrap: {e}; trying passive fallback"
2329 );
2330 match tokio::time::timeout(
2331 Duration::from_secs(2),
2332 crate::xpc::rsd::handshake_on_framer(&mut framer),
2333 )
2334 .await
2335 {
2336 Ok(Ok(h)) => {
2337 tracing::info!(
2338 "RSD via proxy (passive fallback): {} services for {}",
2339 h.services.len(),
2340 h.udid
2341 );
2342 Some(h)
2343 }
2344 Ok(Err(e)) => {
2345 tracing::warn!("RSD passive fallback failed: {e}");
2346 None
2347 }
2348 Err(_) => {
2349 tracing::warn!("RSD passive fallback timed out");
2350 None
2351 }
2352 }
2353 }
2354 Err(_) => {
2355 tracing::warn!("RSD handshake via proxy timed out after legacy bootstrap");
2356 None
2357 }
2358 },
2359 Ok(Err(e)) => {
2360 tracing::warn!("RSD legacy bootstrap failed: {e}; trying passive fallback");
2361 match tokio::time::timeout(
2362 Duration::from_secs(2),
2363 crate::xpc::rsd::handshake_on_framer(&mut framer),
2364 )
2365 .await
2366 {
2367 Ok(Ok(h)) => {
2368 tracing::info!(
2369 "RSD via proxy (passive fallback): {} services for {}",
2370 h.services.len(),
2371 h.udid
2372 );
2373 Some(h)
2374 }
2375 Ok(Err(e)) => {
2376 tracing::warn!("RSD passive fallback failed: {e}");
2377 None
2378 }
2379 Err(_) => {
2380 tracing::warn!("RSD passive fallback timed out");
2381 None
2382 }
2383 }
2384 }
2385 Err(_) => {
2386 tracing::warn!("RSD legacy bootstrap timed out; trying passive fallback");
2387 match tokio::time::timeout(
2388 Duration::from_secs(2),
2389 crate::xpc::rsd::handshake_on_framer(&mut framer),
2390 )
2391 .await
2392 {
2393 Ok(Ok(h)) => {
2394 tracing::info!(
2395 "RSD via proxy (passive fallback): {} services for {}",
2396 h.services.len(),
2397 h.udid
2398 );
2399 Some(h)
2400 }
2401 Ok(Err(e)) => {
2402 tracing::warn!("RSD passive fallback failed: {e}");
2403 None
2404 }
2405 Err(_) => {
2406 tracing::warn!("RSD passive fallback timed out");
2407 None
2408 }
2409 }
2410 }
2411 }
2412}
2413
2414async fn open_rsd_proxy_framer(
2415 proxy_port: u16,
2416 server_addr: &str,
2417 rsd_port: u16,
2418) -> Option<crate::xpc::h2_raw::H2Framer<tokio::net::TcpStream>> {
2419 use tokio::io::AsyncWriteExt;
2420 use tokio::net::TcpStream;
2421
2422 tracing::info!("RSD via proxy: connecting to 127.0.0.1:{proxy_port}");
2423 let mut proxy = match TcpStream::connect(format!("127.0.0.1:{proxy_port}")).await {
2424 Ok(stream) => {
2425 tracing::info!("RSD via proxy: connected to proxy");
2426 stream
2427 }
2428 Err(e) => {
2429 tracing::warn!("RSD proxy connect failed: {e}");
2430 return None;
2431 }
2432 };
2433
2434 let addr_bytes = match Ipv6Addr::from_str(server_addr) {
2435 Ok(addr) => addr.octets(),
2436 Err(e) => {
2437 tracing::warn!("RSD bad server addr '{server_addr}': {e}");
2438 return None;
2439 }
2440 };
2441
2442 if let Err(e) = proxy.write_all(&addr_bytes).await {
2443 tracing::warn!("RSD write addr: {e}");
2444 return None;
2445 }
2446 if let Err(e) = proxy.write_all(&(rsd_port as u32).to_le_bytes()).await {
2447 tracing::warn!("RSD write port: {e}");
2448 return None;
2449 }
2450 if let Err(e) = proxy.flush().await {
2451 tracing::warn!("RSD flush header: {e}");
2452 return None;
2453 }
2454
2455 tracing::info!(
2456 "RSD via proxy: connecting to [{server_addr}]:{rsd_port} through proxy port {proxy_port}"
2457 );
2458 tracing::info!("RSD via proxy: starting H2 framer connect");
2459 match crate::xpc::h2_raw::H2Framer::connect(proxy).await {
2460 Ok(framer) => {
2461 tracing::info!("RSD via proxy: H2 framer connected");
2462 Some(framer)
2463 }
2464 Err(e) => {
2465 tracing::warn!("RSD H2 framer: {e}");
2466 None
2467 }
2468 }
2469}
2470
2471pub(crate) enum ProxyStream {
2474 Plain(ServiceStream),
2475 Tls(Box<tokio_rustls::client::TlsStream<ServiceStream>>),
2476}
2477
2478impl Unpin for ProxyStream {}
2479
2480impl AsyncRead for ProxyStream {
2481 fn poll_read(
2482 mut self: Pin<&mut Self>,
2483 cx: &mut Context<'_>,
2484 buf: &mut ReadBuf<'_>,
2485 ) -> Poll<std::io::Result<()>> {
2486 match &mut *self {
2487 ProxyStream::Plain(s) => Pin::new(s).poll_read(cx, buf),
2488 ProxyStream::Tls(s) => Pin::new(s).poll_read(cx, buf),
2489 }
2490 }
2491}
2492
2493impl AsyncWrite for ProxyStream {
2494 fn poll_write(
2495 mut self: Pin<&mut Self>,
2496 cx: &mut Context<'_>,
2497 buf: &[u8],
2498 ) -> Poll<std::io::Result<usize>> {
2499 match &mut *self {
2500 ProxyStream::Plain(s) => Pin::new(s).poll_write(cx, buf),
2501 ProxyStream::Tls(s) => Pin::new(s).poll_write(cx, buf),
2502 }
2503 }
2504 fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<std::io::Result<()>> {
2505 match &mut *self {
2506 ProxyStream::Plain(s) => Pin::new(s).poll_flush(cx),
2507 ProxyStream::Tls(s) => Pin::new(s).poll_flush(cx),
2508 }
2509 }
2510 fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<std::io::Result<()>> {
2511 match &mut *self {
2512 ProxyStream::Plain(s) => Pin::new(s).poll_shutdown(cx),
2513 ProxyStream::Tls(s) => Pin::new(s).poll_shutdown(cx),
2514 }
2515 }
2516}
2517
2518fn plist_value_to_string(value: &plist::Value, field: &str) -> Result<String, CoreError> {
2519 value
2520 .as_string()
2521 .map(ToOwned::to_owned)
2522 .ok_or_else(|| CoreError::Other(format!("{field} expected string value, got {:?}", value)))
2523}
2524
2525fn plist_value_to_string_vec(value: &plist::Value, field: &str) -> Result<Vec<String>, CoreError> {
2526 let values = value.as_array().ok_or_else(|| {
2527 CoreError::Other(format!(
2528 "{field} expected string array value, got {:?}",
2529 value
2530 ))
2531 })?;
2532
2533 values
2534 .iter()
2535 .map(|item| {
2536 item.as_string().map(ToOwned::to_owned).ok_or_else(|| {
2537 CoreError::Other(format!("{field} expected string entries, got {:?}", item))
2538 })
2539 })
2540 .collect()
2541}
2542
2543#[cfg(test)]
2544mod tests {
2545 use std::collections::HashMap;
2546 use std::path::PathBuf;
2547
2548 use tokio::io::duplex;
2549
2550 use super::*;
2551
2552 fn temp_test_dir(label: &str) -> PathBuf {
2553 let unique = std::time::SystemTime::now()
2554 .duration_since(std::time::UNIX_EPOCH)
2555 .expect("system time should be after unix epoch")
2556 .as_nanos();
2557 std::env::temp_dir().join(format!("ios_core_device_{label}_{unique}"))
2558 }
2559
2560 fn make_remote_pair_record(identity: &HostIdentity) -> RemotePairingRecord {
2561 RemotePairingRecord {
2562 public_key: identity.public_key_bytes(),
2563 private_key: identity.private_key_bytes(),
2564 remote_unlock_host_key: None,
2565 }
2566 }
2567
2568 #[test]
2569 fn try_load_pair_record_returns_none_for_missing_pair_record() {
2570 let missing_dir = temp_test_dir("missing_pair_record");
2571
2572 let loaded = try_load_pair_record("missing-udid", Some(&missing_dir));
2573
2574 assert!(loaded.is_none());
2575
2576 let _ = std::fs::remove_dir_all(missing_dir);
2577 }
2578
2579 #[test]
2580 fn require_pair_record_rejects_missing_lockdown_pair_record() {
2581 let err = require_pair_record(None, "test-udid", "remote pairing lockdown access requires")
2582 .expect_err("missing pair record should fail");
2583
2584 assert!(err
2585 .to_string()
2586 .contains("remote pairing lockdown access requires"));
2587 assert!(err.to_string().contains("test-udid"));
2588 }
2589
2590 #[test]
2591 fn load_remote_pairing_credentials_accepts_legacy_ios_rs_without_private_key_hex() {
2592 let base_dir = temp_test_dir("legacy_ios_rs");
2593 let ios_rs_dir = base_dir.join("ios-rs");
2594 let pymobiledevice3_dir = base_dir.join(".pymobiledevice3");
2595 let remote_identifier = "test-remote";
2596 let identity = HostIdentity::generate();
2597
2598 make_remote_pair_record(&identity)
2599 .save_for_identifier(&ios_rs_dir, remote_identifier)
2600 .unwrap();
2601 PersistedCredentials {
2602 remote_identifier: Some(remote_identifier.into()),
2603 host_identifier: identity.identifier.clone(),
2604 host_public_key_hex: hex::encode(identity.public_key_bytes()),
2605 host_private_key_hex: None,
2606 remote_unlock_host_key: None,
2607 device_address: "fd00::1".into(),
2608 rsd_port: 58783,
2609 }
2610 .save(&ios_rs_dir)
2611 .unwrap();
2612
2613 let loaded = load_remote_pairing_credentials_from_dirs(
2614 remote_identifier,
2615 &ios_rs_dir,
2616 &pymobiledevice3_dir,
2617 "unused-hostname",
2618 )
2619 .expect("legacy ios-rs credentials should load from remote pair record");
2620
2621 assert_eq!(loaded.host_identity.identifier, identity.identifier);
2622 assert_eq!(
2623 loaded.host_identity.public_key_bytes(),
2624 identity.public_key_bytes()
2625 );
2626
2627 let _ = std::fs::remove_dir_all(base_dir);
2628 }
2629
2630 #[test]
2631 fn load_remote_pairing_credentials_prefers_ios_rs_over_pymobiledevice3() {
2632 let base_dir = temp_test_dir("prefers_ios_rs");
2633 let ios_rs_dir = base_dir.join("ios-rs");
2634 let pymobiledevice3_dir = base_dir.join(".pymobiledevice3");
2635 let remote_identifier = "test-remote";
2636 let ios_rs_identity = HostIdentity::generate();
2637 let fallback_identity = HostIdentity::from_private_key_bytes(
2638 pymobiledevice3_host_identifier("example-host"),
2639 &[0x44; 32],
2640 )
2641 .unwrap();
2642
2643 make_remote_pair_record(&ios_rs_identity)
2644 .save_for_identifier(&ios_rs_dir, remote_identifier)
2645 .unwrap();
2646 PersistedCredentials {
2647 remote_identifier: Some(remote_identifier.into()),
2648 host_identifier: ios_rs_identity.identifier.clone(),
2649 host_public_key_hex: hex::encode(ios_rs_identity.public_key_bytes()),
2650 host_private_key_hex: Some(hex::encode(ios_rs_identity.private_key_bytes())),
2651 remote_unlock_host_key: None,
2652 device_address: "fd00::1".into(),
2653 rsd_port: 58783,
2654 }
2655 .save(&ios_rs_dir)
2656 .unwrap();
2657 make_remote_pair_record(&fallback_identity)
2658 .save_for_identifier(&pymobiledevice3_dir, remote_identifier)
2659 .unwrap();
2660
2661 let loaded = load_remote_pairing_credentials_from_dirs(
2662 remote_identifier,
2663 &ios_rs_dir,
2664 &pymobiledevice3_dir,
2665 "example-host",
2666 )
2667 .expect("ios-rs credentials should take precedence");
2668
2669 assert_eq!(loaded.host_identity.identifier, ios_rs_identity.identifier);
2670 assert_eq!(
2671 loaded.host_identity.public_key_bytes(),
2672 ios_rs_identity.public_key_bytes()
2673 );
2674
2675 let _ = std::fs::remove_dir_all(base_dir);
2676 }
2677
2678 #[test]
2679 fn load_remote_pairing_credentials_falls_back_to_pymobiledevice3_remote_record() {
2680 let base_dir = temp_test_dir("pymobiledevice3_fallback");
2681 let ios_rs_dir = base_dir.join("ios-rs");
2682 let pymobiledevice3_dir = base_dir.join(".pymobiledevice3");
2683 let remote_identifier = "test-remote";
2684 let hostname = "example-host";
2685 let expected_identity = HostIdentity::from_private_key_bytes(
2686 pymobiledevice3_host_identifier(hostname),
2687 &[0x22; 32],
2688 )
2689 .unwrap();
2690
2691 make_remote_pair_record(&expected_identity)
2692 .save_for_identifier(&pymobiledevice3_dir, remote_identifier)
2693 .unwrap();
2694
2695 let loaded = load_remote_pairing_credentials_from_dirs(
2696 remote_identifier,
2697 &ios_rs_dir,
2698 &pymobiledevice3_dir,
2699 hostname,
2700 )
2701 .expect("pymobiledevice3 remote record should be usable as fallback");
2702
2703 assert_eq!(
2704 loaded.host_identity.identifier,
2705 pymobiledevice3_host_identifier(hostname)
2706 );
2707 assert_eq!(
2708 loaded.host_identity.public_key_bytes(),
2709 expected_identity.public_key_bytes()
2710 );
2711
2712 let _ = std::fs::remove_dir_all(base_dir);
2713 }
2714
2715 #[test]
2716 fn direct_handshake_request_carries_attempt_pair_verify() {
2717 let request = build_direct_handshake_request(7);
2718 let envelope = request.as_dict().expect("envelope dict");
2719 assert_eq!(
2720 envelope.get("mangledTypeName").and_then(XpcValue::as_str),
2721 Some(DIRECT_CONTROL_CHANNEL_ENVELOPE_TYPE)
2722 );
2723
2724 let handshake = envelope
2725 .get("value")
2726 .and_then(XpcValue::as_dict)
2727 .and_then(|value| value.get("message"))
2728 .and_then(XpcValue::as_dict)
2729 .and_then(|message| message.get("plain"))
2730 .and_then(XpcValue::as_dict)
2731 .and_then(|plain| plain.get("_0"))
2732 .and_then(XpcValue::as_dict)
2733 .and_then(|plain| plain.get("request"))
2734 .and_then(XpcValue::as_dict)
2735 .and_then(|request| request.get("_0"))
2736 .and_then(XpcValue::as_dict)
2737 .and_then(|request| request.get("handshake"))
2738 .and_then(XpcValue::as_dict)
2739 .and_then(|handshake| handshake.get("_0"))
2740 .and_then(XpcValue::as_dict)
2741 .expect("handshake dict");
2742
2743 assert_eq!(
2744 handshake
2745 .get("hostOptions")
2746 .and_then(XpcValue::as_dict)
2747 .and_then(|options| options.get("attemptPairVerify")),
2748 Some(&XpcValue::Bool(true))
2749 );
2750 assert_eq!(
2751 handshake.get("wireProtocolVersion"),
2752 Some(&XpcValue::Int64(19))
2753 );
2754 }
2755
2756 #[test]
2757 fn remote_pairing_handshake_request_starts_at_plain_message_root() {
2758 let request = build_remote_pairing_handshake_request(0);
2759 assert_eq!(request["originatedBy"], "host");
2760 assert_eq!(request["sequenceNumber"], 0);
2761 assert_eq!(
2762 request["message"]["plain"]["_0"]["request"]["_0"]["handshake"]["_0"]["hostOptions"]
2763 ["attemptPairVerify"],
2764 true
2765 );
2766 assert_eq!(
2767 request["message"]["plain"]["_0"]["request"]["_0"]["handshake"]["_0"]
2768 ["wireProtocolVersion"],
2769 19
2770 );
2771 }
2772
2773 #[test]
2774 fn extract_direct_remote_identifier_reads_peer_device_info() {
2775 let body = build_direct_control_envelope(
2776 xpc_dict(&[(
2777 "plain",
2778 xpc_dict(&[(
2779 "_0",
2780 xpc_dict(&[(
2781 "response",
2782 xpc_dict(&[(
2783 "_1",
2784 xpc_dict(&[(
2785 "handshake",
2786 xpc_dict(&[(
2787 "_0",
2788 xpc_dict(&[(
2789 "peerDeviceInfo",
2790 xpc_dict(&[(
2791 "identifier",
2792 XpcValue::String("test-remote".into()),
2793 )]),
2794 )]),
2795 )]),
2796 )]),
2797 )]),
2798 )]),
2799 )]),
2800 )]),
2801 1,
2802 );
2803
2804 let identifier = extract_direct_remote_identifier(&body).expect("identifier should parse");
2805 assert_eq!(identifier, "test-remote");
2806 }
2807
2808 #[test]
2809 fn extract_direct_pairing_tlv_surfaces_rejection_message() {
2810 let body = build_direct_control_envelope(
2811 xpc_dict(&[(
2812 "plain",
2813 xpc_dict(&[(
2814 "_0",
2815 xpc_dict(&[(
2816 "event",
2817 xpc_dict(&[(
2818 "_0",
2819 xpc_dict(&[(
2820 "pairingRejectedWithError",
2821 xpc_dict(&[(
2822 "wrappedError",
2823 xpc_dict(&[(
2824 "userInfo",
2825 xpc_dict(&[(
2826 "NSLocalizedDescription",
2827 XpcValue::String("Trust denied".into()),
2828 )]),
2829 )]),
2830 )]),
2831 )]),
2832 )]),
2833 )]),
2834 )]),
2835 )]),
2836 2,
2837 );
2838
2839 let err = extract_direct_pairing_tlv(&body).expect_err("rejection should error");
2840 assert!(err.to_string().contains("Trust denied"));
2841 }
2842
2843 #[test]
2844 fn extract_remote_pairing_tlv_decodes_base64_payload() {
2845 let body = serde_json::json!({
2846 "message": {
2847 "plain": {
2848 "_0": {
2849 "event": {
2850 "_0": {
2851 "pairingData": {
2852 "_0": {
2853 "data": BASE64_STANDARD.encode([0x01, 0x02, 0x03]),
2854 "kind": "verifyManualPairing",
2855 "startNewSession": true
2856 }
2857 }
2858 }
2859 }
2860 }
2861 }
2862 }
2863 });
2864
2865 let tlv = extract_remote_pairing_tlv(&body).expect("payload should decode");
2866 assert_eq!(tlv, vec![0x01, 0x02, 0x03]);
2867 }
2868
2869 #[test]
2870 fn extract_remote_pairing_tlv_surfaces_rejection_message() {
2871 let body = serde_json::json!({
2872 "message": {
2873 "plain": {
2874 "_0": {
2875 "event": {
2876 "_0": {
2877 "pairingRejectedWithError": {
2878 "wrappedError": {
2879 "userInfo": {
2880 "NSLocalizedDescription": "Pair denied"
2881 }
2882 }
2883 }
2884 }
2885 }
2886 }
2887 }
2888 }
2889 });
2890
2891 let err = extract_remote_pairing_tlv(&body).expect_err("rejection should error");
2892 assert!(err.to_string().contains("Pair denied"));
2893 }
2894
2895 #[test]
2896 fn make_direct_encrypted_nonce_uses_little_endian_sequence() {
2897 let nonce = make_direct_encrypted_nonce(0x0102_0304_0506_0708);
2898 assert_eq!(
2899 nonce,
2900 [0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0, 0, 0, 0]
2901 );
2902 }
2903
2904 #[test]
2905 fn select_mux_device_prefers_usb_when_multiple_transports_match() {
2906 let selected = select_mux_device(
2907 vec![
2908 crate::mux::MuxDevice {
2909 device_id: 7,
2910 serial_number: "test-udid".into(),
2911 connection_type: "Network".into(),
2912 product_id: 0,
2913 },
2914 crate::mux::MuxDevice {
2915 device_id: 8,
2916 serial_number: "test-udid".into(),
2917 connection_type: "USB".into(),
2918 product_id: 0,
2919 },
2920 ],
2921 "test-udid",
2922 )
2923 .expect("matching device should be selected");
2924
2925 assert_eq!(selected.device_id, 8);
2926 assert_eq!(selected.connection_type, "USB");
2927 }
2928
2929 #[test]
2930 fn select_mux_device_falls_back_to_non_usb_match() {
2931 let selected = select_mux_device(
2932 vec![crate::mux::MuxDevice {
2933 device_id: 9,
2934 serial_number: "test-udid".into(),
2935 connection_type: "Network".into(),
2936 product_id: 0,
2937 }],
2938 "test-udid",
2939 )
2940 .expect("network-only match should still be selected");
2941
2942 assert_eq!(selected.device_id, 9);
2943 assert_eq!(selected.connection_type, "Network");
2944 }
2945
2946 #[test]
2947 fn strip_ssl_selection_matches_legacy_dtx_services() {
2948 assert!(should_strip_service_ssl(
2949 "com.apple.accessibility.axAuditDaemon.remoteserver"
2950 ));
2951 assert!(should_strip_service_ssl(
2952 "com.apple.instruments.remoteserver"
2953 ));
2954 assert!(!should_strip_service_ssl(
2955 "com.apple.instruments.remoteserver.DVTSecureSocketProxy"
2956 ));
2957 assert!(!should_strip_service_ssl("com.apple.mobile.screenshotr"));
2958 assert!(!should_strip_service_ssl("com.apple.webinspector"));
2959 }
2960
2961 #[test]
2962 fn parses_string_array_values_for_international_configuration() {
2963 let value = plist::Value::Array(vec![
2964 plist::Value::String("en-US".into()),
2965 plist::Value::String("zh-Hans".into()),
2966 ]);
2967
2968 let parsed = plist_value_to_string_vec(&value, "SupportedLanguages")
2969 .expect("string array should parse");
2970
2971 assert_eq!(parsed, vec!["en-US".to_string(), "zh-Hans".to_string()]);
2972 }
2973
2974 #[test]
2975 fn rejects_non_string_entries_in_international_configuration_arrays() {
2976 let value = plist::Value::Array(vec![plist::Value::Integer(1i64.into())]);
2977
2978 let err = plist_value_to_string_vec(&value, "SupportedLocales")
2979 .expect_err("non-string entry should fail");
2980
2981 let rendered = err.to_string();
2982 assert!(rendered.contains("SupportedLocales"));
2983 assert!(rendered.contains("string"));
2984 }
2985
2986 #[test]
2987 fn resolve_rsd_service_reports_actual_shim_match() {
2988 let rsd = RsdHandshake {
2989 udid: "test-udid".into(),
2990 services: HashMap::from([(
2991 "com.apple.mobile.notification_proxy.shim.remote".into(),
2992 ServiceDescriptor { port: 1234 },
2993 )]),
2994 };
2995
2996 let resolved = resolve_rsd_service(&rsd, "com.apple.mobile.notification_proxy")
2997 .expect("shim fallback should resolve");
2998
2999 assert_eq!(
3000 resolved,
3001 (
3002 "com.apple.mobile.notification_proxy.shim.remote".into(),
3003 1234
3004 )
3005 );
3006 }
3007
3008 #[test]
3009 fn resolve_tunnel_connection_target_uses_userspace_proxy_when_available() {
3010 let target =
3011 resolve_tunnel_connection_target("fd00::1", Some(60105)).expect("valid proxy target");
3012
3013 assert_eq!(
3014 target,
3015 TunnelConnectionTarget::UserspaceProxy {
3016 proxy_port: 60105,
3017 remote_addr: Ipv6Addr::from_str("fd00::1").expect("valid IPv6"),
3018 }
3019 );
3020 }
3021
3022 #[test]
3023 fn resolve_tunnel_connection_target_falls_back_to_direct_ipv6() {
3024 let target =
3025 resolve_tunnel_connection_target("fd00::2", None).expect("valid direct target");
3026
3027 assert_eq!(
3028 target,
3029 TunnelConnectionTarget::DirectIpv6 {
3030 remote_addr: Ipv6Addr::from_str("fd00::2").expect("valid IPv6"),
3031 }
3032 );
3033 }
3034
3035 #[test]
3036 fn resolve_tunnel_connection_target_rejects_invalid_ipv6() {
3037 let err = resolve_tunnel_connection_target("not-an-ipv6", Some(60105))
3038 .expect_err("invalid IPv6 should fail");
3039
3040 assert!(err.to_string().contains("invalid IPv6 addr"));
3041 }
3042
3043 #[test]
3044 fn preferred_lockdown_address_prefers_ipv4() {
3045 let addresses = vec![
3046 "fe80::1%Ethernet".to_string(),
3047 "192.168.31.247".to_string(),
3048 "fd00::1".to_string(),
3049 ];
3050
3051 assert_eq!(
3052 preferred_lockdown_address(&addresses),
3053 Some("192.168.31.247")
3054 );
3055 }
3056
3057 #[test]
3058 fn match_paired_mobdev2_targets_uses_wifi_mac_and_dedupes() {
3059 let services = vec![
3060 BonjourService {
3061 instance: "34:10:be:1b:a6:4c@fe80::1._apple-mobdev2._tcp.local.".into(),
3062 port: 32498,
3063 addresses: vec!["192.168.31.247".into()],
3064 properties: HashMap::new(),
3065 },
3066 BonjourService {
3067 instance: "34:10:be:1b:a6:4c@fe80::1._apple-mobdev2._tcp.local.".into(),
3068 port: 32498,
3069 addresses: vec!["192.168.31.247".into()],
3070 properties: HashMap::new(),
3071 },
3072 ];
3073 let wifi_mac_to_udid =
3074 HashMap::from([("34:10:be:1b:a6:4c".to_string(), "test-udid".to_string())]);
3075
3076 let targets = match_paired_mobdev2_targets(&services, &wifi_mac_to_udid);
3077
3078 assert_eq!(
3079 targets,
3080 vec![PairedMobdev2Device {
3081 udid: "test-udid".into(),
3082 host: "192.168.31.247".into(),
3083 }]
3084 );
3085 }
3086
3087 #[tokio::test]
3088 async fn rsd_checkin_sends_request_and_consumes_two_responses() {
3089 let (mut client, mut server) = duplex(4096);
3090 let task = tokio::spawn(async move { rsd_checkin(&mut client).await });
3091
3092 let request: plist::Value = recv_lockdown(&mut server).await.expect("request frame");
3093 let dict = request
3094 .into_dictionary()
3095 .expect("RSDCheckin request should be a plist dictionary");
3096 assert_eq!(
3097 dict.get("Request").and_then(plist::Value::as_string),
3098 Some("RSDCheckin")
3099 );
3100 assert_eq!(
3101 dict.get("ProtocolVersion")
3102 .and_then(plist::Value::as_string),
3103 Some("2")
3104 );
3105
3106 send_lockdown(
3107 &mut server,
3108 &plist::Value::Dictionary(plist::Dictionary::from_iter([
3109 (
3110 String::from("Request"),
3111 plist::Value::String("RSDCheckin".into()),
3112 ),
3113 (
3114 String::from("Status"),
3115 plist::Value::String("Acknowledged".into()),
3116 ),
3117 ])),
3118 )
3119 .await
3120 .expect("checkin response");
3121 send_lockdown(
3122 &mut server,
3123 &plist::Value::Dictionary(plist::Dictionary::from_iter([
3124 (
3125 String::from("Request"),
3126 plist::Value::String("StartService".into()),
3127 ),
3128 (String::from("Service"), plist::Value::String("shim".into())),
3129 ])),
3130 )
3131 .await
3132 .expect("start service response");
3133
3134 task.await
3135 .expect("join")
3136 .expect("rsd checkin should succeed");
3137 }
3138
3139 #[tokio::test]
3140 async fn rsd_checkin_rejects_unexpected_first_response() {
3141 let (mut client, mut server) = duplex(4096);
3142 let task = tokio::spawn(async move { rsd_checkin(&mut client).await });
3143
3144 let _: plist::Value = recv_lockdown(&mut server).await.expect("request frame");
3145
3146 send_lockdown(
3147 &mut server,
3148 &plist::Value::Dictionary(plist::Dictionary::from_iter([(
3149 String::from("Request"),
3150 plist::Value::String("StartService".into()),
3151 )])),
3152 )
3153 .await
3154 .expect("unexpected first response");
3155 send_lockdown(
3156 &mut server,
3157 &plist::Value::Dictionary(plist::Dictionary::from_iter([(
3158 String::from("Request"),
3159 plist::Value::String("StartService".into()),
3160 )])),
3161 )
3162 .await
3163 .expect("second response");
3164
3165 let err = task
3166 .await
3167 .expect("join")
3168 .expect_err("rsd checkin should reject mismatched first response");
3169 let rendered = err.to_string();
3170 assert!(rendered.contains("RSD check-in response"));
3171 assert!(rendered.contains("Request=RSDCheckin"));
3172 }
3173
3174 #[tokio::test]
3175 async fn rsd_checkin_rejects_start_service_error() {
3176 let (mut client, mut server) = duplex(4096);
3177 let task = tokio::spawn(async move { rsd_checkin(&mut client).await });
3178
3179 let _: plist::Value = recv_lockdown(&mut server).await.expect("request frame");
3180
3181 send_lockdown(
3182 &mut server,
3183 &plist::Value::Dictionary(plist::Dictionary::from_iter([(
3184 String::from("Request"),
3185 plist::Value::String("RSDCheckin".into()),
3186 )])),
3187 )
3188 .await
3189 .expect("checkin response");
3190 send_lockdown(
3191 &mut server,
3192 &plist::Value::Dictionary(plist::Dictionary::from_iter([
3193 (
3194 String::from("Request"),
3195 plist::Value::String("StartService".into()),
3196 ),
3197 (
3198 String::from("Error"),
3199 plist::Value::String("ServiceProhibited".into()),
3200 ),
3201 ])),
3202 )
3203 .await
3204 .expect("start service error response");
3205
3206 let err = task
3207 .await
3208 .expect("join")
3209 .expect_err("rsd checkin should surface start service errors");
3210 let rendered = err.to_string();
3211 assert!(rendered.contains("RSD start-service response"));
3212 assert!(rendered.contains("ServiceProhibited"));
3213 }
3214}