ios_core/services/house_arrest/
mod.rs1use serde::{Deserialize, Serialize};
5use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
6
7use crate::services::afc::{AfcClient, AfcError};
8
9pub const SERVICE_NAME: &str = "com.apple.mobile.house_arrest";
10
11#[derive(Debug, thiserror::Error)]
12pub enum HouseArrestError {
13 #[error("IO error: {0}")]
14 Io(#[from] std::io::Error),
15 #[error("plist error: {0}")]
16 Plist(String),
17 #[error("protocol error: {0}")]
18 Protocol(String),
19 #[error("house arrest error: {0}")]
20 Service(String),
21}
22
23impl From<AfcError> for HouseArrestError {
24 fn from(err: AfcError) -> Self {
25 match err {
26 AfcError::Io(e) => Self::Io(e),
27 AfcError::Status(code) => Self::Service(code.to_string()),
28 AfcError::Protocol(msg) => Self::Protocol(msg),
29 }
30 }
31}
32
33#[derive(Debug)]
34pub struct HouseArrestClient<S> {
35 stream: S,
36}
37
38impl<S: AsyncRead + AsyncWrite + Unpin> HouseArrestClient<S> {
39 pub fn new(stream: S) -> Self {
40 Self { stream }
41 }
42
43 pub async fn vend_container(self, bundle_id: &str) -> Result<AfcClient<S>, HouseArrestError> {
44 self.send_command("VendContainer", bundle_id).await
45 }
46
47 pub async fn vend_documents(self, bundle_id: &str) -> Result<AfcClient<S>, HouseArrestError> {
48 self.send_command("VendDocuments", bundle_id).await
49 }
50
51 async fn send_command(
52 mut self,
53 command: &'static str,
54 bundle_id: &str,
55 ) -> Result<AfcClient<S>, HouseArrestError> {
56 self.send_plist(&VendContainerRequest {
57 command,
58 identifier: bundle_id,
59 })
60 .await?;
61
62 let response: VendContainerResponse = self.recv_plist().await?;
63 match response.status.as_deref() {
64 Some("Complete") => Ok(AfcClient::new(self.stream)),
65 Some(status) => Err(HouseArrestError::Service(status.to_string())),
66 None => {
67 if let Some(error) = response.error {
68 Err(HouseArrestError::Service(error))
69 } else {
70 Err(HouseArrestError::Protocol(
71 "unknown house arrest response".into(),
72 ))
73 }
74 }
75 }
76 }
77
78 async fn send_plist<T: Serialize>(&mut self, value: &T) -> Result<(), HouseArrestError> {
79 let mut buf = Vec::new();
80 plist::to_writer_xml(&mut buf, value)
81 .map_err(|e| HouseArrestError::Plist(e.to_string()))?;
82 let len = buf.len() as u32;
83 self.stream.write_all(&len.to_be_bytes()).await?;
84 self.stream.write_all(&buf).await?;
85 self.stream.flush().await?;
86 Ok(())
87 }
88
89 async fn recv_plist<T>(&mut self) -> Result<T, HouseArrestError>
90 where
91 T: for<'de> Deserialize<'de>,
92 {
93 let mut len_buf = [0u8; 4];
94 self.stream.read_exact(&mut len_buf).await?;
95 let len = u32::from_be_bytes(len_buf) as usize;
96 const MAX_PLIST_SIZE: usize = 4 * 1024 * 1024;
97 if len > MAX_PLIST_SIZE {
98 return Err(HouseArrestError::Protocol(format!(
99 "plist length {len} exceeds max {MAX_PLIST_SIZE}"
100 )));
101 }
102 let mut buf = vec![0u8; len];
103 self.stream.read_exact(&mut buf).await?;
104 plist::from_bytes(&buf).map_err(|e| HouseArrestError::Plist(e.to_string()))
105 }
106}
107
108#[derive(Serialize)]
109#[serde(rename_all = "PascalCase")]
110struct VendContainerRequest<'a> {
111 command: &'static str,
112 identifier: &'a str,
113}
114
115#[derive(Debug, Deserialize)]
116#[serde(rename_all = "PascalCase")]
117struct VendContainerResponse {
118 #[serde(default)]
119 status: Option<String>,
120 #[serde(default)]
121 error: Option<String>,
122}
123
124#[cfg(test)]
125mod tests {
126 use crate::proto::afc::{AfcHeader, AfcOpcode};
127 use zerocopy::{FromBytes, IntoBytes};
128
129 use super::*;
130
131 async fn read_plist_frame<S>(stream: &mut S) -> Vec<u8>
132 where
133 S: AsyncRead + Unpin,
134 {
135 let mut len_buf = [0u8; 4];
136 stream.read_exact(&mut len_buf).await.unwrap();
137 let len = u32::from_be_bytes(len_buf) as usize;
138 let mut buf = vec![0u8; len];
139 stream.read_exact(&mut buf).await.unwrap();
140 buf
141 }
142
143 async fn read_afc_request<S>(stream: &mut S)
144 where
145 S: AsyncRead + Unpin,
146 {
147 let mut hdr_buf = [0u8; AfcHeader::SIZE];
148 stream.read_exact(&mut hdr_buf).await.unwrap();
149 let hdr = AfcHeader::ref_from_bytes(&hdr_buf).unwrap();
150 let header_payload_len = hdr.this_len.get() as usize - AfcHeader::SIZE;
151 let payload_len = hdr.entire_len.get() as usize - hdr.this_len.get() as usize;
152
153 let mut header_payload = vec![0u8; header_payload_len];
154 let mut payload = vec![0u8; payload_len];
155 if header_payload_len > 0 {
156 stream.read_exact(&mut header_payload).await.unwrap();
157 }
158 if payload_len > 0 {
159 stream.read_exact(&mut payload).await.unwrap();
160 }
161 assert_eq!(hdr.operation.get(), AfcOpcode::ReadDir as u64);
162 }
163
164 #[test]
165 fn test_service_name_matches_house_arrest() {
166 assert_eq!(SERVICE_NAME, "com.apple.mobile.house_arrest");
167 }
168
169 #[tokio::test]
170 async fn test_vend_container_returns_afc_client_over_same_stream() {
171 let (client_side, mut server_side) = tokio::io::duplex(4096);
172
173 tokio::spawn(async move {
174 let request = read_plist_frame(&mut server_side).await;
175 let req_value: plist::Value = plist::from_bytes(&request).unwrap();
176 let dict = req_value.into_dictionary().unwrap();
177 assert_eq!(
178 dict.get("Command").and_then(|v| v.as_string()),
179 Some("VendContainer")
180 );
181 assert_eq!(
182 dict.get("Identifier").and_then(|v| v.as_string()),
183 Some("com.example.TestApp")
184 );
185
186 let response = plist::Dictionary::from_iter([(
187 "Status".to_string(),
188 plist::Value::String("Complete".into()),
189 )]);
190 let mut buf = Vec::new();
191 plist::to_writer_xml(&mut buf, &plist::Value::Dictionary(response)).unwrap();
192 let len = buf.len() as u32;
193 server_side.write_all(&len.to_be_bytes()).await.unwrap();
194 server_side.write_all(&buf).await.unwrap();
195
196 read_afc_request(&mut server_side).await;
197
198 let names = b".\0..\0Sandbox\0";
199 let hdr = AfcHeader::new(1, AfcOpcode::ReadDir, 0, names.len());
200 let mut resp = hdr.as_bytes().to_vec();
201 resp.extend_from_slice(names);
202 server_side.write_all(&resp).await.unwrap();
203 });
204
205 let client = HouseArrestClient::new(client_side);
206 let mut afc = client.vend_container("com.example.TestApp").await.unwrap();
207 let entries = afc.list_dir("/").await.unwrap();
208 assert_eq!(entries, vec!["Sandbox"]);
209 }
210
211 #[tokio::test]
212 async fn test_vend_documents_sends_expected_command() {
213 let (client_side, mut server_side) = tokio::io::duplex(4096);
214
215 tokio::spawn(async move {
216 let request = read_plist_frame(&mut server_side).await;
217 let req_value: plist::Value = plist::from_bytes(&request).unwrap();
218 let dict = req_value.into_dictionary().unwrap();
219 assert_eq!(
220 dict.get("Command").and_then(|v| v.as_string()),
221 Some("VendDocuments")
222 );
223 assert_eq!(
224 dict.get("Identifier").and_then(|v| v.as_string()),
225 Some("com.example.DocumentsApp")
226 );
227
228 let response = plist::Dictionary::from_iter([(
229 "Status".to_string(),
230 plist::Value::String("Complete".into()),
231 )]);
232 let mut buf = Vec::new();
233 plist::to_writer_xml(&mut buf, &plist::Value::Dictionary(response)).unwrap();
234 let len = buf.len() as u32;
235 server_side.write_all(&len.to_be_bytes()).await.unwrap();
236 server_side.write_all(&buf).await.unwrap();
237
238 read_afc_request(&mut server_side).await;
239
240 let names = b".\0..\0Documents\0";
241 let hdr = AfcHeader::new(1, AfcOpcode::ReadDir, 0, names.len());
242 let mut resp = hdr.as_bytes().to_vec();
243 resp.extend_from_slice(names);
244 server_side.write_all(&resp).await.unwrap();
245 });
246
247 let client = HouseArrestClient::new(client_side);
248 let mut afc = client
249 .vend_documents("com.example.DocumentsApp")
250 .await
251 .unwrap();
252 let entries = afc.list_dir("/").await.unwrap();
253 assert_eq!(entries, vec!["Documents"]);
254 }
255}