Skip to main content

ios_core/services/file_relay/
mod.rs

1//! File relay service client.
2//!
3//! Service: `com.apple.mobile.file_relay`
4
5use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
6
7pub const SERVICE_NAME: &str = "com.apple.mobile.file_relay";
8
9service_error!(FileRelayError);
10
11pub struct FileRelayClient<S> {
12    stream: S,
13}
14
15impl<S: AsyncRead + AsyncWrite + Unpin> FileRelayClient<S> {
16    pub fn new(stream: S) -> Self {
17        Self { stream }
18    }
19
20    pub async fn request_sources(&mut self, sources: &[&str]) -> Result<Vec<u8>, FileRelayError> {
21        let request = plist::Dictionary::from_iter([(
22            "Sources".to_string(),
23            plist::Value::Array(
24                sources
25                    .iter()
26                    .map(|source| plist::Value::String((*source).to_string()))
27                    .collect(),
28            ),
29        )]);
30        send_plist(&mut self.stream, &plist::Value::Dictionary(request)).await?;
31        let response = recv_plist(&mut self.stream).await?;
32        match response.get("Status").and_then(plist::Value::as_string) {
33            Some("Acknowledged") => {}
34            Some(other) => {
35                let error = response
36                    .get("Error")
37                    .and_then(plist::Value::as_string)
38                    .unwrap_or(other);
39                return Err(FileRelayError::Protocol(error.to_string()));
40            }
41            None => {
42                return Err(FileRelayError::Protocol(
43                    "file relay response missing Status".into(),
44                ));
45            }
46        }
47
48        let mut data = Vec::new();
49        self.stream.read_to_end(&mut data).await?;
50        Ok(data)
51    }
52}
53
54async fn send_plist<S: AsyncWrite + Unpin>(
55    stream: &mut S,
56    value: &plist::Value,
57) -> Result<(), FileRelayError> {
58    let mut buf = Vec::new();
59    plist::to_writer_xml(&mut buf, value).map_err(|e| FileRelayError::Plist(e.to_string()))?;
60    stream.write_all(&(buf.len() as u32).to_be_bytes()).await?;
61    stream.write_all(&buf).await?;
62    stream.flush().await?;
63    Ok(())
64}
65
66async fn recv_plist<S: AsyncRead + Unpin>(
67    stream: &mut S,
68) -> Result<plist::Dictionary, FileRelayError> {
69    let mut len_buf = [0u8; 4];
70    stream.read_exact(&mut len_buf).await?;
71    let len = u32::from_be_bytes(len_buf) as usize;
72    const MAX_PLIST_SIZE: usize = 1024 * 1024;
73    if len > MAX_PLIST_SIZE {
74        return Err(FileRelayError::Protocol(format!(
75            "plist length {len} exceeds max {MAX_PLIST_SIZE}"
76        )));
77    }
78    let mut buf = vec![0u8; len];
79    stream.read_exact(&mut buf).await?;
80    plist::from_bytes(&buf).map_err(|e| FileRelayError::Plist(e.to_string()))
81}
82
83#[cfg(test)]
84mod tests {
85    use crate::test_util::MockStream;
86
87    use super::*;
88
89    #[tokio::test]
90    async fn request_sources_reads_acknowledged_archive() {
91        let response = plist::Value::Dictionary(plist::Dictionary::from_iter([(
92            "Status".to_string(),
93            plist::Value::String("Acknowledged".into()),
94        )]));
95        let mut stream =
96            MockStream::with_plist_response_and_trailing_bytes(response, b"archive-bytes");
97        let mut client = FileRelayClient::new(&mut stream);
98
99        let archive = client.request_sources(&["Network"]).await.unwrap();
100        assert_eq!(archive, b"archive-bytes");
101
102        let len = u32::from_be_bytes(stream.written[..4].try_into().unwrap()) as usize;
103        let payload = &stream.written[4..4 + len];
104        let dict: plist::Dictionary = plist::from_bytes(payload).unwrap();
105        let sources = dict["Sources"].as_array().unwrap();
106        assert_eq!(sources[0].as_string(), Some("Network"));
107    }
108}