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 .map_err(|e| MobileActivationError::Plist(e.to_string()))?;
65 stream.write_all(&(buf.len() as u32).to_be_bytes()).await?;
66 stream.write_all(&buf).await?;
67 stream.flush().await?;
68 Ok(())
69}
70
71async fn recv_plist<S: AsyncRead + Unpin>(
72 stream: &mut S,
73) -> Result<plist::Dictionary, MobileActivationError> {
74 let mut len_buf = [0u8; 4];
75 stream.read_exact(&mut len_buf).await?;
76 let len = u32::from_be_bytes(len_buf) as usize;
77 const MAX_PLIST_SIZE: usize = 8 * 1024 * 1024;
78 if len > MAX_PLIST_SIZE {
79 return Err(MobileActivationError::Protocol(format!(
80 "plist length {len} exceeds max {MAX_PLIST_SIZE}"
81 )));
82 }
83 let mut buf = vec![0u8; len];
84 stream.read_exact(&mut buf).await?;
85 plist::from_bytes(&buf).map_err(|e| MobileActivationError::Plist(e.to_string()))
86}
87
88#[cfg(test)]
89mod tests {
90 use crate::test_util::MockStream;
91
92 use super::*;
93
94 #[tokio::test]
95 async fn request_session_info_sends_tunnel1_command_and_returns_response_dict() {
96 let mut stream =
97 MockStream::with_response(plist::Value::Dictionary(plist::Dictionary::from_iter([
98 (
99 "Command".to_string(),
100 plist::Value::String("CreateTunnel1SessionInfoRequest".into()),
101 ),
102 (
103 "Value".to_string(),
104 plist::Value::Dictionary(plist::Dictionary::from_iter([(
105 "HandshakeRequestMessage".to_string(),
106 plist::Value::Data(vec![1, 2, 3]),
107 )])),
108 ),
109 ])));
110 let mut client = MobileActivationClient::new(&mut stream);
111
112 let response = client.request_session_info().await.unwrap();
113 assert_eq!(
114 response.get("Command").and_then(plist::Value::as_string),
115 Some("CreateTunnel1SessionInfoRequest")
116 );
117 assert!(response.contains_key("Value"));
118
119 let len = u32::from_be_bytes(stream.written[..4].try_into().unwrap()) as usize;
120 let payload = &stream.written[4..4 + len];
121 let dict: plist::Dictionary = plist::from_bytes(payload).unwrap();
122 assert_eq!(
123 dict.get("Command").and_then(plist::Value::as_string),
124 Some("CreateTunnel1SessionInfoRequest")
125 );
126 }
127
128 #[tokio::test]
129 async fn request_activation_info_sends_handshake_value_and_options() {
130 let mut stream =
131 MockStream::with_response(plist::Value::Dictionary(plist::Dictionary::from_iter([
132 (
133 "Command".to_string(),
134 plist::Value::String("CreateActivationInfoRequest".into()),
135 ),
136 (
137 "Value".to_string(),
138 plist::Value::Dictionary(plist::Dictionary::from_iter([(
139 "ActivationInfoXML".to_string(),
140 plist::Value::String("<plist/>".into()),
141 )])),
142 ),
143 ])));
144 let mut client = MobileActivationClient::new(&mut stream);
145
146 let response = client.request_activation_info(&[9, 8, 7]).await.unwrap();
147 assert_eq!(
148 response.get("Command").and_then(plist::Value::as_string),
149 Some("CreateActivationInfoRequest")
150 );
151
152 let len = u32::from_be_bytes(stream.written[..4].try_into().unwrap()) as usize;
153 let payload = &stream.written[4..4 + len];
154 let dict: plist::Dictionary = plist::from_bytes(payload).unwrap();
155 assert_eq!(
156 dict.get("Command").and_then(plist::Value::as_string),
157 Some("CreateActivationInfoRequest")
158 );
159 assert_eq!(
160 dict.get("Value").and_then(plist::Value::as_data),
161 Some(&b"\x09\x08\x07"[..])
162 );
163 let options = dict
164 .get("Options")
165 .and_then(plist::Value::as_dictionary)
166 .expect("Options dictionary");
167 assert_eq!(
168 options
169 .get("BasebandWaitCount")
170 .and_then(plist::Value::as_unsigned_integer),
171 Some(90)
172 );
173 }
174}