1use std::collections::HashMap;
18use std::net::{Ipv6Addr, SocketAddr};
19
20use crate::lockdown::pairing::{
21 build_device_info_tlv, build_setup_tlv, build_srp_proof_tlv, derive_cipher_keys,
22 verify_device_info_response, HostIdentity, SrpSession,
23};
24use crate::proto::tlv::TlvBuffer;
25use bytes::{Bytes, BytesMut};
26use chacha20poly1305::{aead::Aead, ChaCha20Poly1305, KeyInit};
27use indexmap::IndexMap;
28use tokio::net::TcpStream;
29
30pub const UNTRUSTED_SERVICE_NAME: &str = "com.apple.internal.dt.coredevice.untrusted.tunnelservice";
31const CONTROL_CHANNEL_ENVELOPE_TYPE: &str = "RemotePairing.ControlChannelMessageEnvelope";
32const CONTROL_CHANNEL_ORIGIN: &str = "host";
33const MAX_XPC_BODY_SIZE: usize = 1024 * 1024;
34
35const TYPE_PUBLIC_KEY: u8 = 0x03;
37const TYPE_PROOF: u8 = 0x04;
38const TYPE_ENCRYPTED_DATA: u8 = 0x05;
39const TYPE_SALT: u8 = 0x02;
40
41#[derive(Debug, thiserror::Error)]
43pub enum PairingTransportError {
44 #[error("IO error: {0}")]
45 Io(#[from] std::io::Error),
46 #[error("XPC error: {0}")]
47 Xpc(String),
48 #[error("RSD error: {0}")]
49 Rsd(String),
50 #[error("SRP crypto error: {0}")]
51 Crypto(String),
52 #[error("pairing failed: {0}")]
53 Failed(String),
54 #[error("pairing rejected: {0}")]
55 Rejected(String),
56 #[error("missing required field: {0}")]
57 MissingField(String),
58 #[error("unexpected field type: {0}")]
59 UnexpectedType(String),
60 #[error("no untrusted tunnel service found in RSD")]
61 ServiceNotFound,
62}
63
64#[derive(Debug, Clone)]
66pub struct PairedCredentials {
67 pub remote_identifier: String,
68 pub host_identifier: String,
69 pub host_public_key: Vec<u8>,
70 pub host_private_key: Vec<u8>,
71 pub remote_unlock_host_key: Option<String>,
72 pub session_keys: Option<([u8; 32], [u8; 32])>,
74}
75
76pub async fn pair_new_device(
84 device_addr: Ipv6Addr,
85) -> Result<PairedCredentials, PairingTransportError> {
86 let rsd = crate::xpc::rsd::handshake(device_addr, crate::xpc::rsd::RSD_PORT)
88 .await
89 .map_err(|e| PairingTransportError::Rsd(e.to_string()))?;
90
91 let port = rsd
92 .get_port(UNTRUSTED_SERVICE_NAME)
93 .ok_or(PairingTransportError::ServiceNotFound)?;
94
95 let sock_addr = SocketAddr::new(device_addr.into(), port);
97 let stream = TcpStream::connect(sock_addr).await?;
98
99 let mut framer = crate::xpc::h2_raw::H2Framer::connect(stream)
100 .await
101 .map_err(|e| PairingTransportError::Xpc(format!("H2: {e}")))?;
102
103 bootstrap_remote_xpc(&mut framer).await?;
105 let mut sequence_number = 1;
106 let handshake_body = build_handshake_request(next_sequence_number(&mut sequence_number));
107 send_xpc(&mut framer, &handshake_body, 1).await?;
108 let handshake = recv_handshake_response(&mut framer).await?;
109 let remote_identifier = extract_remote_identifier(&handshake)?;
110
111 let identity = HostIdentity::generate();
113
114 let setup_tlv = build_setup_tlv();
116 let pairing_event = build_pairing_event(
117 &setup_tlv,
118 "setupManualPairing",
119 true,
120 None,
121 next_sequence_number(&mut sequence_number),
122 );
123 send_xpc(&mut framer, &pairing_event, 2).await?;
124 recv_control_plain_message(&mut framer).await?; let device_data = recv_xpc_pairing_data(&mut framer).await?;
128 let device_tlv = parse_tlv(&device_data);
129
130 let device_pub = device_tlv
131 .get(&TYPE_PUBLIC_KEY)
132 .ok_or_else(|| PairingTransportError::Failed("no public key from device".into()))?
133 .to_vec();
134 let salt = device_tlv
135 .get(&TYPE_SALT)
136 .ok_or_else(|| PairingTransportError::Failed("no salt from device".into()))?
137 .to_vec();
138
139 let srp = SrpSession::new(&salt, &device_pub)
141 .map_err(|e| PairingTransportError::Crypto(e.to_string()))?;
142
143 let proof_tlv = build_srp_proof_tlv(&srp);
145 let proof_event = build_pairing_event(
146 &proof_tlv,
147 "setupManualPairing",
148 false,
149 None,
150 next_sequence_number(&mut sequence_number),
151 );
152 send_xpc(&mut framer, &proof_event, 3).await?;
153
154 let server_data = recv_xpc_pairing_data(&mut framer).await?;
156 let server_tlv = parse_tlv(&server_data);
157
158 let server_proof = server_tlv
159 .get(&TYPE_PROOF)
160 .ok_or_else(|| PairingTransportError::Failed("no server proof".into()))?
161 .to_vec();
162
163 if !srp.verify_server_proof(&server_proof) {
164 return Err(PairingTransportError::Failed(
165 "server proof verification failed".into(),
166 ));
167 }
168
169 let (info_tlv, setup_key) = build_device_info_tlv(&srp.session_key, &identity)
171 .map_err(|e| PairingTransportError::Crypto(e.to_string()))?;
172
173 let info_event = build_pairing_event(
174 &info_tlv,
175 "setupManualPairing",
176 false,
177 Some("ios-rs-host"),
178 next_sequence_number(&mut sequence_number),
179 );
180 send_xpc(&mut framer, &info_event, 4).await?;
181
182 let enc_data = recv_xpc_pairing_data(&mut framer).await?;
184 let enc_tlv = parse_tlv(&enc_data);
185 let enc_payload = enc_tlv
186 .get(&TYPE_ENCRYPTED_DATA)
187 .ok_or_else(|| PairingTransportError::Failed("no encrypted data in response".into()))?;
188
189 verify_device_info_response(&setup_key, enc_payload)
190 .map_err(|e| PairingTransportError::Crypto(e.to_string()))?;
191
192 let (client_key, server_key) = derive_cipher_keys(&srp.session_key)
194 .map_err(|e| PairingTransportError::Crypto(e.to_string()))?;
195 let remote_unlock_host_key =
196 create_remote_unlock_key(&mut framer, &client_key, &server_key, &mut sequence_number)
197 .await?;
198
199 Ok(PairedCredentials {
200 remote_identifier,
201 host_identifier: identity.identifier.clone(),
202 host_public_key: identity.public_key_bytes(),
203 host_private_key: identity.private_key_bytes(),
204 remote_unlock_host_key,
205 session_keys: Some((client_key, server_key)),
206 })
207}
208
209fn xpc_dict(pairs: &[(&str, crate::xpc::message::XpcValue)]) -> crate::xpc::message::XpcValue {
212 let mut map = IndexMap::new();
213 for (k, v) in pairs {
214 map.insert(k.to_string(), v.clone());
215 }
216 crate::xpc::message::XpcValue::Dictionary(map)
217}
218
219fn xpc_bool(b: bool) -> crate::xpc::message::XpcValue {
220 crate::xpc::message::XpcValue::Bool(b)
221}
222
223fn xpc_int(n: i64) -> crate::xpc::message::XpcValue {
224 crate::xpc::message::XpcValue::Int64(n)
225}
226
227fn xpc_uint(n: u64) -> crate::xpc::message::XpcValue {
228 crate::xpc::message::XpcValue::Uint64(n)
229}
230
231fn xpc_data(d: &[u8]) -> crate::xpc::message::XpcValue {
232 crate::xpc::message::XpcValue::Data(Bytes::copy_from_slice(d))
233}
234
235fn xpc_string(s: &str) -> crate::xpc::message::XpcValue {
236 crate::xpc::message::XpcValue::String(s.to_string())
237}
238
239fn next_sequence_number(sequence_number: &mut u64) -> u64 {
240 let current = *sequence_number;
241 *sequence_number += 1;
242 current
243}
244
245fn build_handshake_request(sequence_number: u64) -> crate::xpc::message::XpcValue {
246 let request = xpc_dict(&[(
247 "handshake",
248 xpc_dict(&[(
249 "_0",
250 xpc_dict(&[
251 (
252 "hostOptions",
253 xpc_dict(&[("attemptPairVerify", xpc_bool(true))]),
254 ),
255 ("wireProtocolVersion", xpc_int(19)),
256 ]),
257 )]),
258 )]);
259 build_plain_request(request, sequence_number)
260}
261
262fn build_plain_request(
263 request: crate::xpc::message::XpcValue,
264 sequence_number: u64,
265) -> crate::xpc::message::XpcValue {
266 build_control_channel_envelope(
267 xpc_dict(&[(
268 "plain",
269 xpc_dict(&[("_0", xpc_dict(&[("request", xpc_dict(&[("_0", request)]))]))]),
270 )]),
271 sequence_number,
272 )
273}
274
275fn build_control_channel_envelope(
276 message: crate::xpc::message::XpcValue,
277 sequence_number: u64,
278) -> crate::xpc::message::XpcValue {
279 xpc_dict(&[
280 ("mangledTypeName", xpc_string(CONTROL_CHANNEL_ENVELOPE_TYPE)),
281 (
282 "value",
283 xpc_dict(&[
284 ("message", message),
285 ("originatedBy", xpc_string(CONTROL_CHANNEL_ORIGIN)),
286 ("sequenceNumber", xpc_uint(sequence_number)),
287 ]),
288 ),
289 ])
290}
291
292fn build_encrypted_request(
293 encrypted_payload: &[u8],
294 sequence_number: u64,
295) -> crate::xpc::message::XpcValue {
296 build_control_channel_envelope(
297 xpc_dict(&[(
298 "streamEncrypted",
299 xpc_dict(&[("_0", xpc_data(encrypted_payload))]),
300 )]),
301 sequence_number,
302 )
303}
304
305fn build_pairing_event(
306 tlv_data: &[u8],
307 kind: &str,
308 start_new_session: bool,
309 sending_host: Option<&str>,
310 sequence_number: u64,
311) -> crate::xpc::message::XpcValue {
312 let mut pairs = vec![
313 ("data", xpc_data(tlv_data)),
314 ("kind", xpc_string(kind)),
315 ("startNewSession", xpc_bool(start_new_session)),
316 ];
317 if let Some(h) = sending_host {
318 pairs.push(("sendingHost", xpc_string(h)));
319 }
320 build_control_channel_envelope(
321 xpc_dict(&[(
322 "plain",
323 xpc_dict(&[(
324 "_0",
325 xpc_dict(&[(
326 "event",
327 xpc_dict(&[(
328 "_0",
329 xpc_dict(&[("pairingData", xpc_dict(&[("_0", xpc_dict(&pairs))]))]),
330 )]),
331 )]),
332 )]),
333 )]),
334 sequence_number,
335 )
336}
337
338async fn bootstrap_remote_xpc<S: tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin>(
339 framer: &mut crate::xpc::h2_raw::H2Framer<S>,
340) -> Result<(), PairingTransportError> {
341 crate::xpc::rsd::initialize_xpc_connection_on_framer(framer)
342 .await
343 .map_err(|e| PairingTransportError::Xpc(format!("RemoteXPC bootstrap: {e}")))
344}
345
346async fn send_xpc<S: tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin>(
347 framer: &mut crate::xpc::h2_raw::H2Framer<S>,
348 body: &crate::xpc::message::XpcValue,
349 msg_id: u64,
350) -> Result<(), PairingTransportError> {
351 use crate::xpc::message::{encode_message, flags, XpcMessage};
352 let msg = XpcMessage {
353 flags: flags::ALWAYS_SET | flags::DATA,
354 msg_id,
355 body: Some(body.clone()),
356 };
357 let bytes = encode_message(&msg).map_err(|e| PairingTransportError::Xpc(e.to_string()))?;
358 framer
359 .write_client_server(&bytes)
360 .await
361 .map_err(|e| PairingTransportError::Xpc(e.to_string()))
362}
363
364fn take_required_field(
365 dict: &mut IndexMap<String, crate::xpc::message::XpcValue>,
366 key: &str,
367 path: &str,
368) -> Result<crate::xpc::message::XpcValue, PairingTransportError> {
369 dict.swap_remove(key)
370 .ok_or_else(|| PairingTransportError::MissingField(path.to_string()))
371}
372
373fn take_required_dict(
374 dict: &mut IndexMap<String, crate::xpc::message::XpcValue>,
375 key: &str,
376 path: &str,
377) -> Result<IndexMap<String, crate::xpc::message::XpcValue>, PairingTransportError> {
378 match take_required_field(dict, key, path)? {
379 crate::xpc::message::XpcValue::Dictionary(value) => Ok(value),
380 _ => Err(PairingTransportError::UnexpectedType(format!(
381 "{path} must be a dictionary"
382 ))),
383 }
384}
385
386fn take_required_data(
387 dict: &mut IndexMap<String, crate::xpc::message::XpcValue>,
388 key: &str,
389 path: &str,
390) -> Result<Vec<u8>, PairingTransportError> {
391 match take_required_field(dict, key, path)? {
392 crate::xpc::message::XpcValue::Data(value) => Ok(value.to_vec()),
393 _ => Err(PairingTransportError::UnexpectedType(format!(
394 "{path} must be a data blob"
395 ))),
396 }
397}
398
399fn take_required_string(
400 dict: &mut IndexMap<String, crate::xpc::message::XpcValue>,
401 key: &str,
402 path: &str,
403) -> Result<String, PairingTransportError> {
404 match take_required_field(dict, key, path)? {
405 crate::xpc::message::XpcValue::String(value) => Ok(value),
406 _ => Err(PairingTransportError::UnexpectedType(format!(
407 "{path} must be a string"
408 ))),
409 }
410}
411
412fn decode_control_plain_message(
413 body: crate::xpc::message::XpcValue,
414) -> Result<IndexMap<String, crate::xpc::message::XpcValue>, PairingTransportError> {
415 let mut envelope = match body {
416 crate::xpc::message::XpcValue::Dictionary(value) => value,
417 _ => {
418 return Err(PairingTransportError::UnexpectedType(
419 "control channel body must be a dictionary".into(),
420 ));
421 }
422 };
423
424 let mangled_type = take_required_string(&mut envelope, "mangledTypeName", "mangledTypeName")?;
425 if mangled_type != CONTROL_CHANNEL_ENVELOPE_TYPE {
426 return Err(PairingTransportError::Failed(format!(
427 "unexpected control channel type: {mangled_type}"
428 )));
429 }
430
431 let mut value = take_required_dict(&mut envelope, "value", "value")?;
432 let mut message = take_required_dict(&mut value, "message", "value.message")?;
433 let mut plain = take_required_dict(&mut message, "plain", "value.message.plain")?;
434 take_required_dict(&mut plain, "_0", "value.message.plain._0")
435}
436
437fn decode_pairing_data_event(
438 mut plain: IndexMap<String, crate::xpc::message::XpcValue>,
439) -> Result<Vec<u8>, PairingTransportError> {
440 let mut event = take_required_dict(&mut plain, "event", "value.message.plain._0.event")?;
441 let mut event_body = take_required_dict(&mut event, "_0", "value.message.plain._0.event._0")?;
442
443 if let Some(rejection) = event_body.get("pairingRejectedWithError") {
444 return Err(PairingTransportError::Rejected(
445 extract_pairing_rejection_message(rejection),
446 ));
447 }
448
449 let mut pairing_data = take_required_dict(
450 &mut event_body,
451 "pairingData",
452 "value.message.plain._0.event._0.pairingData",
453 )?;
454 let mut pairing_data_body = take_required_dict(
455 &mut pairing_data,
456 "_0",
457 "value.message.plain._0.event._0.pairingData._0",
458 )?;
459 take_required_data(
460 &mut pairing_data_body,
461 "data",
462 "value.message.plain._0.event._0.pairingData._0.data",
463 )
464}
465
466fn extract_pairing_rejection_message(value: &crate::xpc::message::XpcValue) -> String {
467 value
468 .as_dict()
469 .and_then(|wrapped| wrapped.get("wrappedError"))
470 .and_then(crate::xpc::message::XpcValue::as_dict)
471 .and_then(|user_info| user_info.get("userInfo"))
472 .and_then(crate::xpc::message::XpcValue::as_dict)
473 .and_then(|user_info| user_info.get("NSLocalizedDescription"))
474 .and_then(crate::xpc::message::XpcValue::as_str)
475 .unwrap_or("pairing rejected by device")
476 .to_string()
477}
478
479async fn recv_xpc<S: tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin>(
480 framer: &mut crate::xpc::h2_raw::H2Framer<S>,
481) -> Result<crate::xpc::message::XpcValue, PairingTransportError> {
482 use crate::xpc::message::decode_message;
483 let header = framer
484 .read_client_server(24)
485 .await
486 .map_err(|e| PairingTransportError::Xpc(e.to_string()))?;
487 let body_len = xpc_body_len(&header)?;
488 let body = if body_len > 0 {
489 framer
490 .read_client_server(body_len)
491 .await
492 .map_err(|e| PairingTransportError::Xpc(e.to_string()))?
493 } else {
494 Bytes::new()
495 };
496 let mut full = BytesMut::new();
497 full.extend_from_slice(&header);
498 full.extend_from_slice(&body);
499 let msg =
500 decode_message(full.freeze()).map_err(|e| PairingTransportError::Xpc(e.to_string()))?;
501 msg.body
502 .ok_or_else(|| PairingTransportError::MissingField("xpc message body".into()))
503}
504
505fn xpc_body_len(header: &[u8]) -> Result<usize, PairingTransportError> {
506 let len = u64::from_le_bytes(
507 header[8..16]
508 .try_into()
509 .map_err(|_| PairingTransportError::Xpc("bad header length field".into()))?,
510 );
511 let len = usize::try_from(len)
512 .map_err(|_| PairingTransportError::Xpc("xpc body length exceeds usize".into()))?;
513 if len > MAX_XPC_BODY_SIZE {
514 return Err(PairingTransportError::Xpc(format!(
515 "body too large: {len} bytes exceeds {MAX_XPC_BODY_SIZE}"
516 )));
517 }
518 Ok(len)
519}
520
521async fn recv_control_plain_message<S: tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin>(
522 framer: &mut crate::xpc::h2_raw::H2Framer<S>,
523) -> Result<IndexMap<String, crate::xpc::message::XpcValue>, PairingTransportError> {
524 decode_control_plain_message(recv_xpc(framer).await?)
525}
526
527async fn recv_handshake_response<S: tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin>(
528 framer: &mut crate::xpc::h2_raw::H2Framer<S>,
529) -> Result<IndexMap<String, crate::xpc::message::XpcValue>, PairingTransportError> {
530 let mut plain = recv_control_plain_message(framer).await?;
531 let mut response =
532 take_required_dict(&mut plain, "response", "value.message.plain._0.response")?;
533 let mut response_body =
534 take_required_dict(&mut response, "_1", "value.message.plain._0.response._1")?;
535 let mut handshake = take_required_dict(
536 &mut response_body,
537 "handshake",
538 "value.message.plain._0.response._1.handshake",
539 )?;
540 take_required_dict(
541 &mut handshake,
542 "_0",
543 "value.message.plain._0.response._1.handshake._0",
544 )
545}
546
547async fn recv_xpc_pairing_data<S: tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin>(
548 framer: &mut crate::xpc::h2_raw::H2Framer<S>,
549) -> Result<Vec<u8>, PairingTransportError> {
550 decode_pairing_data_event(recv_control_plain_message(framer).await?)
551}
552
553fn extract_remote_identifier(
554 handshake: &IndexMap<String, crate::xpc::message::XpcValue>,
555) -> Result<String, PairingTransportError> {
556 handshake
557 .get("peerDeviceInfo")
558 .and_then(crate::xpc::message::XpcValue::as_dict)
559 .and_then(|peer| peer.get("identifier"))
560 .and_then(crate::xpc::message::XpcValue::as_str)
561 .map(ToOwned::to_owned)
562 .ok_or_else(|| {
563 PairingTransportError::MissingField(
564 "value.message.plain._0.response._1.handshake._0.peerDeviceInfo.identifier".into(),
565 )
566 })
567}
568
569fn parse_tlv(data: &[u8]) -> HashMap<u8, Vec<u8>> {
570 let map = TlvBuffer::decode(data);
571 map.into_iter().map(|(k, v)| (k, v.to_vec())).collect()
572}
573
574fn make_encrypted_nonce(sequence_number: u64) -> [u8; 12] {
575 let mut nonce = [0u8; 12];
576 nonce[..8].copy_from_slice(&sequence_number.to_le_bytes());
577 nonce
578}
579
580fn decode_encrypted_response(
581 body: crate::xpc::message::XpcValue,
582) -> Result<Vec<u8>, PairingTransportError> {
583 let mut envelope = match body {
584 crate::xpc::message::XpcValue::Dictionary(value) => value,
585 _ => {
586 return Err(PairingTransportError::UnexpectedType(
587 "encrypted control channel body must be a dictionary".into(),
588 ));
589 }
590 };
591
592 let mangled_type = take_required_string(&mut envelope, "mangledTypeName", "mangledTypeName")?;
593 if mangled_type != CONTROL_CHANNEL_ENVELOPE_TYPE {
594 return Err(PairingTransportError::Failed(format!(
595 "unexpected control channel type: {mangled_type}"
596 )));
597 }
598
599 let mut value = take_required_dict(&mut envelope, "value", "value")?;
600 let mut message = take_required_dict(&mut value, "message", "value.message")?;
601 take_required_data(&mut message, "_0", "value.message.streamEncrypted._0").or_else(|_| {
602 let mut stream_encrypted = take_required_dict(
603 &mut message,
604 "streamEncrypted",
605 "value.message.streamEncrypted",
606 )?;
607 take_required_data(
608 &mut stream_encrypted,
609 "_0",
610 "value.message.streamEncrypted._0",
611 )
612 })
613}
614
615fn extract_remote_unlock_host_key(
616 response_body: &serde_json::Value,
617) -> Result<Option<String>, PairingTransportError> {
618 let Some(create_remote_unlock_key) = response_body.get("createRemoteUnlockKey") else {
619 return Err(PairingTransportError::MissingField(
620 "encrypted response.response._1.createRemoteUnlockKey".into(),
621 ));
622 };
623
624 Ok(create_remote_unlock_key
625 .get("hostKey")
626 .and_then(serde_json::Value::as_str)
627 .map(ToOwned::to_owned)
628 .filter(|value| !value.is_empty()))
629}
630
631async fn create_remote_unlock_key<S>(
632 framer: &mut crate::xpc::h2_raw::H2Framer<S>,
633 client_key: &[u8; 32],
634 server_key: &[u8; 32],
635 sequence_number: &mut u64,
636) -> Result<Option<String>, PairingTransportError>
637where
638 S: tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin,
639{
640 let client_cipher = ChaCha20Poly1305::new(client_key.into());
641 let server_cipher = ChaCha20Poly1305::new(server_key.into());
642 let nonce = make_encrypted_nonce(0);
643 let request = serde_json::json!({
644 "request": {
645 "_0": {
646 "createRemoteUnlockKey": {}
647 }
648 }
649 });
650 let encrypted_request = client_cipher
651 .encrypt(&nonce.into(), request.to_string().as_bytes())
652 .map_err(|e| PairingTransportError::Crypto(e.to_string()))?;
653 let body = build_encrypted_request(&encrypted_request, next_sequence_number(sequence_number));
654 send_xpc(framer, &body, 5).await?;
655
656 let encrypted_response = decode_encrypted_response(recv_xpc(framer).await?)?;
657 let plaintext = server_cipher
658 .decrypt(&nonce.into(), encrypted_response.as_ref())
659 .map_err(|e| PairingTransportError::Crypto(e.to_string()))?;
660 let response: serde_json::Value = serde_json::from_slice(&plaintext)
661 .map_err(|e| PairingTransportError::Xpc(format!("invalid encrypted JSON: {e}")))?;
662 let response_body = response
663 .get("response")
664 .and_then(|value| value.get("_1"))
665 .ok_or_else(|| {
666 PairingTransportError::MissingField("encrypted response.response._1".into())
667 })?;
668 if let Some(error) = response_body.get("errorExtended") {
669 return Err(PairingTransportError::Failed(format!(
670 "createRemoteUnlockKey failed: {error:?}"
671 )));
672 }
673 extract_remote_unlock_host_key(response_body)
674}
675
676#[cfg(test)]
677mod tests {
678 use tokio::io::{duplex, AsyncReadExt, AsyncWriteExt};
679 use tokio::time::{timeout, Duration};
680
681 use super::*;
682
683 const FRAME_DATA: u8 = 0x00;
684 const FRAME_SETTINGS: u8 = 0x04;
685 const FLAG_SETTINGS_ACK: u8 = 0x01;
686
687 #[test]
688 fn handshake_envelope_contains_required_control_fields() {
689 let envelope = build_handshake_request(7);
690 let outer = match envelope {
691 crate::xpc::message::XpcValue::Dictionary(value) => value,
692 other => panic!("expected envelope dictionary, got {other:?}"),
693 };
694
695 assert_eq!(
696 outer
697 .get("mangledTypeName")
698 .and_then(crate::xpc::message::XpcValue::as_str),
699 Some(CONTROL_CHANNEL_ENVELOPE_TYPE)
700 );
701
702 let value = outer
703 .get("value")
704 .and_then(crate::xpc::message::XpcValue::as_dict)
705 .expect("value dict");
706 assert_eq!(
707 value
708 .get("originatedBy")
709 .and_then(crate::xpc::message::XpcValue::as_str),
710 Some(CONTROL_CHANNEL_ORIGIN)
711 );
712 assert_eq!(
713 value
714 .get("sequenceNumber")
715 .and_then(crate::xpc::message::XpcValue::as_uint64),
716 Some(7)
717 );
718
719 let handshake = value
720 .get("message")
721 .and_then(crate::xpc::message::XpcValue::as_dict)
722 .and_then(|message| message.get("plain"))
723 .and_then(crate::xpc::message::XpcValue::as_dict)
724 .and_then(|plain| plain.get("_0"))
725 .and_then(crate::xpc::message::XpcValue::as_dict)
726 .and_then(|plain| plain.get("request"))
727 .and_then(crate::xpc::message::XpcValue::as_dict)
728 .and_then(|request| request.get("_0"))
729 .and_then(crate::xpc::message::XpcValue::as_dict)
730 .and_then(|request| request.get("handshake"))
731 .and_then(crate::xpc::message::XpcValue::as_dict)
732 .and_then(|handshake| handshake.get("_0"))
733 .and_then(crate::xpc::message::XpcValue::as_dict)
734 .expect("handshake payload");
735
736 assert_eq!(
737 handshake
738 .get("hostOptions")
739 .and_then(crate::xpc::message::XpcValue::as_dict)
740 .and_then(|options| options.get("attemptPairVerify")),
741 Some(&crate::xpc::message::XpcValue::Bool(true))
742 );
743 assert_eq!(
744 handshake.get("wireProtocolVersion"),
745 Some(&crate::xpc::message::XpcValue::Int64(19))
746 );
747 }
748
749 #[tokio::test]
750 async fn recv_xpc_reads_control_messages_from_client_server_stream() {
751 let (client, mut server) = duplex(4096);
752
753 let server_task = tokio::spawn(async move {
754 let mut preface = [0u8; 24];
755 server.read_exact(&mut preface).await.unwrap();
756 assert_eq!(&preface, crate::xpc::h2_raw::H2_PREFACE);
757
758 let mut settings = [0u8; 21];
759 server.read_exact(&mut settings).await.unwrap();
760 assert_eq!(settings[3], FRAME_SETTINGS);
761
762 let mut window_update = [0u8; 13];
763 server.read_exact(&mut window_update).await.unwrap();
764
765 server.write_all(&settings_frame()).await.unwrap();
766 server.flush().await.unwrap();
767
768 let mut ack = [0u8; 9];
769 server.read_exact(&mut ack).await.unwrap();
770 assert_eq!(ack, settings_ack_frame().as_slice());
771
772 let payload = crate::xpc::message::encode_message(&crate::xpc::message::XpcMessage {
773 flags: crate::xpc::message::flags::ALWAYS_SET | crate::xpc::message::flags::DATA,
774 msg_id: 1,
775 body: Some(build_control_channel_envelope(
776 xpc_dict(&[("plain", xpc_dict(&[("_0", xpc_dict(&[]))]))]),
777 1,
778 )),
779 })
780 .unwrap();
781
782 server
783 .write_all(&data_frame(
784 crate::xpc::h2_raw::STREAM_CLIENT_SERVER,
785 &payload,
786 ))
787 .await
788 .unwrap();
789 server.flush().await.unwrap();
790 });
791
792 let mut framer = crate::xpc::h2_raw::H2Framer::connect(client).await.unwrap();
793 let plain = timeout(
794 Duration::from_secs(1),
795 recv_control_plain_message(&mut framer),
796 )
797 .await
798 .expect("recv timed out")
799 .unwrap();
800 assert!(plain.is_empty());
801
802 server_task.await.unwrap();
803 }
804
805 #[test]
806 fn decode_pairing_data_event_extracts_inner_data() {
807 let plain = dict_value(&[(
808 "event",
809 dict_value(&[(
810 "_0",
811 dict_value(&[(
812 "pairingData",
813 dict_value(&[(
814 "_0",
815 dict_value(&[
816 (
817 "data",
818 crate::xpc::message::XpcValue::Data(Bytes::from_static(
819 b"\x01\x02",
820 )),
821 ),
822 ("kind", xpc_string("setupManualPairing")),
823 ("startNewSession", xpc_bool(false)),
824 ]),
825 )]),
826 )]),
827 )]),
828 )]);
829
830 let data = decode_pairing_data_event(unwrap_dict(plain)).unwrap();
831 assert_eq!(data, vec![1, 2]);
832 }
833
834 #[test]
835 fn decode_pairing_data_event_surfaces_rejection_reason() {
836 let plain = dict_value(&[(
837 "event",
838 dict_value(&[(
839 "_0",
840 dict_value(&[(
841 "pairingRejectedWithError",
842 dict_value(&[(
843 "wrappedError",
844 dict_value(&[(
845 "userInfo",
846 dict_value(&[(
847 "NSLocalizedDescription",
848 xpc_string("Trust dialog denied"),
849 )]),
850 )]),
851 )]),
852 )]),
853 )]),
854 )]);
855
856 let err = decode_pairing_data_event(unwrap_dict(plain)).unwrap_err();
857 assert!(
858 matches!(err, PairingTransportError::Rejected(message) if message == "Trust dialog denied")
859 );
860 }
861
862 #[test]
863 fn extract_remote_identifier_reads_peer_device_info() {
864 let handshake = unwrap_dict(dict_value(&[(
865 "peerDeviceInfo",
866 dict_value(&[("identifier", xpc_string("00008150-000D6D6A1122401C"))]),
867 )]));
868
869 let remote_identifier = extract_remote_identifier(&handshake).unwrap();
870 assert_eq!(remote_identifier, "00008150-000D6D6A1122401C");
871 }
872
873 #[test]
874 fn xpc_body_len_rejects_oversized_body_before_allocation() {
875 let mut header = [0u8; 24];
876 header[8..16].copy_from_slice(&((MAX_XPC_BODY_SIZE as u64) + 1).to_le_bytes());
877
878 let err = xpc_body_len(&header).unwrap_err();
879 assert!(
880 matches!(err, PairingTransportError::Xpc(message) if message.contains("body too large"))
881 );
882 }
883
884 #[test]
885 fn extract_remote_unlock_host_key_reads_host_key() {
886 let response_body = serde_json::json!({
887 "createRemoteUnlockKey": {
888 "hostKey": "PcV5xhyuJBL7Qq9HOGeGVwtU4sJLe1jtl/vRy1tRKcI="
889 }
890 });
891
892 let host_key = extract_remote_unlock_host_key(&response_body).unwrap();
893 assert_eq!(
894 host_key.as_deref(),
895 Some("PcV5xhyuJBL7Qq9HOGeGVwtU4sJLe1jtl/vRy1tRKcI=")
896 );
897 }
898
899 #[test]
900 fn extract_remote_unlock_host_key_allows_missing_host_key() {
901 let response_body = serde_json::json!({
902 "createRemoteUnlockKey": {}
903 });
904
905 let host_key = extract_remote_unlock_host_key(&response_body).unwrap();
906 assert!(host_key.is_none());
907 }
908
909 fn unwrap_dict(
910 value: crate::xpc::message::XpcValue,
911 ) -> IndexMap<String, crate::xpc::message::XpcValue> {
912 match value {
913 crate::xpc::message::XpcValue::Dictionary(dict) => dict,
914 other => panic!("expected dictionary, got {other:?}"),
915 }
916 }
917
918 fn dict_value(
919 pairs: &[(&str, crate::xpc::message::XpcValue)],
920 ) -> crate::xpc::message::XpcValue {
921 xpc_dict(pairs)
922 }
923
924 fn settings_frame() -> Vec<u8> {
925 let mut payload = Vec::new();
926 payload.extend_from_slice(&0x03u16.to_be_bytes());
927 payload.extend_from_slice(&100u32.to_be_bytes());
928 payload.extend_from_slice(&0x04u16.to_be_bytes());
929 payload.extend_from_slice(&1_048_576u32.to_be_bytes());
930 frame(FRAME_SETTINGS, 0, 0, &payload)
931 }
932
933 fn settings_ack_frame() -> Vec<u8> {
934 frame(FRAME_SETTINGS, FLAG_SETTINGS_ACK, 0, &[])
935 }
936
937 fn data_frame(stream_id: u32, payload: &[u8]) -> Vec<u8> {
938 frame(FRAME_DATA, 0, stream_id, payload)
939 }
940
941 fn frame(frame_type: u8, flags: u8, stream_id: u32, payload: &[u8]) -> Vec<u8> {
942 let len = payload.len();
943 let mut out = Vec::with_capacity(9 + len);
944 out.push(((len >> 16) & 0xFF) as u8);
945 out.push(((len >> 8) & 0xFF) as u8);
946 out.push((len & 0xFF) as u8);
947 out.push(frame_type);
948 out.push(flags);
949 out.extend_from_slice(&(stream_id & 0x7fff_ffff).to_be_bytes());
950 out.extend_from_slice(payload);
951 out
952 }
953}