ios_core/services/
mobileactivation.rs1use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
6
7pub const SERVICE_NAME: &str = "com.apple.mobileactivationd";
8
9service_error!(MobileActivationError);
10
11#[derive(Debug)]
12pub struct MobileActivationClient<S> {
13 stream: S,
14}
15
16impl<S: AsyncRead + AsyncWrite + Unpin> MobileActivationClient<S> {
17 pub fn new(stream: S) -> Self {
18 Self { stream }
19 }
20
21 pub async fn request_session_info(
22 &mut self,
23 ) -> Result<plist::Dictionary, MobileActivationError> {
24 let request = plist::Dictionary::from_iter([(
25 "Command".to_string(),
26 plist::Value::String("CreateTunnel1SessionInfoRequest".into()),
27 )]);
28 send_plist(&mut self.stream, &plist::Value::Dictionary(request)).await?;
29 recv_plist(&mut self.stream).await
30 }
31
32 pub async fn request_activation_info(
33 &mut self,
34 handshake_response: &[u8],
35 ) -> Result<plist::Dictionary, MobileActivationError> {
36 let request = plist::Dictionary::from_iter([
37 (
38 "Command".to_string(),
39 plist::Value::String("CreateActivationInfoRequest".into()),
40 ),
41 (
42 "Value".to_string(),
43 plist::Value::Data(handshake_response.to_vec()),
44 ),
45 (
46 "Options".to_string(),
47 plist::Value::Dictionary(plist::Dictionary::from_iter([(
48 "BasebandWaitCount".to_string(),
49 plist::Value::Integer(90i64.into()),
50 )])),
51 ),
52 ]);
53 send_plist(&mut self.stream, &plist::Value::Dictionary(request)).await?;
54 recv_plist(&mut self.stream).await
55 }
56}
57
58async fn send_plist<S: AsyncWrite + Unpin>(
59 stream: &mut S,
60 value: &plist::Value,
61) -> Result<(), MobileActivationError> {
62 let mut buf = Vec::new();
63 plist::to_writer_xml(&mut buf, value)?;
64 stream.write_all(&(buf.len() as u32).to_be_bytes()).await?;
65 stream.write_all(&buf).await?;
66 stream.flush().await?;
67 Ok(())
68}
69
70async fn recv_plist<S: AsyncRead + Unpin>(
71 stream: &mut S,
72) -> Result<plist::Dictionary, MobileActivationError> {
73 let mut len_buf = [0u8; 4];
74 stream.read_exact(&mut len_buf).await?;
75 let len = u32::from_be_bytes(len_buf) as usize;
76 const MAX_PLIST_SIZE: usize = 8 * 1024 * 1024;
77 if len > MAX_PLIST_SIZE {
78 return Err(MobileActivationError::Protocol(format!(
79 "plist length {len} exceeds max {MAX_PLIST_SIZE}"
80 )));
81 }
82 let mut buf = vec![0u8; len];
83 stream.read_exact(&mut buf).await?;
84 Ok(plist::from_bytes(&buf)?)
85}
86
87#[cfg(test)]
88mod tests {
89 use crate::test_util::MockStream;
90
91 use super::*;
92
93 #[tokio::test]
94 async fn request_session_info_sends_tunnel1_command_and_returns_response_dict() {
95 let mut stream =
96 MockStream::with_response(plist::Value::Dictionary(plist::Dictionary::from_iter([
97 (
98 "Command".to_string(),
99 plist::Value::String("CreateTunnel1SessionInfoRequest".into()),
100 ),
101 (
102 "Value".to_string(),
103 plist::Value::Dictionary(plist::Dictionary::from_iter([(
104 "HandshakeRequestMessage".to_string(),
105 plist::Value::Data(vec![1, 2, 3]),
106 )])),
107 ),
108 ])));
109 let mut client = MobileActivationClient::new(&mut stream);
110
111 let response = client.request_session_info().await.unwrap();
112 assert_eq!(
113 response.get("Command").and_then(plist::Value::as_string),
114 Some("CreateTunnel1SessionInfoRequest")
115 );
116 assert!(response.contains_key("Value"));
117
118 let len = u32::from_be_bytes(stream.written[..4].try_into().unwrap()) as usize;
119 let payload = &stream.written[4..4 + len];
120 let dict: plist::Dictionary = plist::from_bytes(payload).unwrap();
121 assert_eq!(
122 dict.get("Command").and_then(plist::Value::as_string),
123 Some("CreateTunnel1SessionInfoRequest")
124 );
125 }
126
127 #[tokio::test]
128 async fn request_activation_info_sends_handshake_value_and_options() {
129 let mut stream =
130 MockStream::with_response(plist::Value::Dictionary(plist::Dictionary::from_iter([
131 (
132 "Command".to_string(),
133 plist::Value::String("CreateActivationInfoRequest".into()),
134 ),
135 (
136 "Value".to_string(),
137 plist::Value::Dictionary(plist::Dictionary::from_iter([(
138 "ActivationInfoXML".to_string(),
139 plist::Value::String("<plist/>".into()),
140 )])),
141 ),
142 ])));
143 let mut client = MobileActivationClient::new(&mut stream);
144
145 let response = client.request_activation_info(&[9, 8, 7]).await.unwrap();
146 assert_eq!(
147 response.get("Command").and_then(plist::Value::as_string),
148 Some("CreateActivationInfoRequest")
149 );
150
151 let len = u32::from_be_bytes(stream.written[..4].try_into().unwrap()) as usize;
152 let payload = &stream.written[4..4 + len];
153 let dict: plist::Dictionary = plist::from_bytes(payload).unwrap();
154 assert_eq!(
155 dict.get("Command").and_then(plist::Value::as_string),
156 Some("CreateActivationInfoRequest")
157 );
158 assert_eq!(
159 dict.get("Value").and_then(plist::Value::as_data),
160 Some(&b"\x09\x08\x07"[..])
161 );
162 let options = dict
163 .get("Options")
164 .and_then(plist::Value::as_dictionary)
165 .expect("Options dictionary");
166 assert_eq!(
167 options
168 .get("BasebandWaitCount")
169 .and_then(plist::Value::as_unsigned_integer),
170 Some(90)
171 );
172 }
173}