Skip to main content

ios_core/mux/
protocol.rs

1use serde::{Deserialize, Serialize};
2use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
3
4use crate::mux::MuxError;
5
6const MAX_MUX_MESSAGE_SIZE: usize = 16 * 1024 * 1024;
7
8pub fn encode_message(payload: &[u8], tag: u32) -> Vec<u8> {
9    let total = 16 + payload.len();
10    let mut buf = Vec::with_capacity(total);
11    buf.extend_from_slice(&(total as u32).to_le_bytes());
12    buf.extend_from_slice(&1u32.to_le_bytes()); // version
13    buf.extend_from_slice(&8u32.to_le_bytes()); // type = plist
14    buf.extend_from_slice(&tag.to_le_bytes());
15    buf.extend_from_slice(payload);
16    buf
17}
18
19pub async fn send_plist<W, T>(writer: &mut W, value: &T, tag: u32) -> Result<(), MuxError>
20where
21    W: AsyncWrite + Unpin,
22    T: Serialize,
23{
24    let mut plist_bytes = Vec::new();
25    plist::to_writer_xml(&mut plist_bytes, value).map_err(|e| MuxError::Protocol(e.to_string()))?;
26    let msg = encode_message(&plist_bytes, tag);
27    writer.write_all(&msg).await?;
28    writer.flush().await?;
29    Ok(())
30}
31
32pub async fn recv_plist<R, T>(reader: &mut R) -> Result<T, MuxError>
33where
34    R: AsyncRead + Unpin,
35    T: for<'de> Deserialize<'de>,
36{
37    let mut header = [0u8; 16];
38    reader.read_exact(&mut header).await?;
39    let length = u32::from_le_bytes(header[0..4].try_into().unwrap()) as usize;
40    if length < 16 {
41        return Err(MuxError::Protocol(format!(
42            "invalid message length: {length}"
43        )));
44    }
45    if length > MAX_MUX_MESSAGE_SIZE {
46        return Err(MuxError::Protocol(format!(
47            "message too large: {length} bytes exceeds {MAX_MUX_MESSAGE_SIZE}"
48        )));
49    }
50    let mut payload = vec![0u8; length - 16];
51    reader.read_exact(&mut payload).await?;
52    let value = plist::from_bytes(&payload).map_err(|e| MuxError::Protocol(e.to_string()))?;
53    Ok(value)
54}
55
56// ── Device discovery messages ────────────────────────
57
58#[derive(Serialize)]
59#[serde(rename_all = "PascalCase")]
60pub(crate) struct ListDevicesRequest {
61    pub message_type: &'static str,
62    pub prog_name: &'static str,
63    pub client_version_string: &'static str,
64}
65
66#[derive(Debug, Deserialize)]
67#[serde(rename_all = "PascalCase")]
68pub(crate) struct DeviceList {
69    pub device_list: Vec<DeviceEntryRaw>,
70}
71
72#[derive(Debug, Deserialize, Clone)]
73#[serde(rename_all = "PascalCase")]
74pub(crate) struct DeviceEntryRaw {
75    #[serde(rename = "DeviceID")]
76    pub device_id: u32,
77    pub properties: DevicePropertiesRaw,
78}
79
80#[derive(Debug, Deserialize, Clone)]
81#[serde(rename_all = "PascalCase")]
82pub(crate) struct DevicePropertiesRaw {
83    pub serial_number: String,
84    pub connection_type: String,
85    pub product_id: Option<u16>,
86}
87
88// ── ReadPairRecord ───────────────────────────────────
89
90#[derive(Serialize)]
91#[serde(rename_all = "PascalCase")]
92pub(crate) struct ReadPairRecordRequest {
93    pub message_type: &'static str,
94    pub prog_name: &'static str,
95    pub client_version_string: &'static str,
96    pub bundle_id: &'static str,
97    #[serde(rename = "kLibUSBMuxVersion")]
98    pub lib_usbmux_version: u32,
99    #[serde(rename = "PairRecordID")]
100    pub pair_record_id: String,
101}
102
103// ── ReadBUID ─────────────────────────────────────────
104
105#[derive(Serialize)]
106#[serde(rename_all = "PascalCase")]
107pub(crate) struct ReadBuidRequest {
108    pub message_type: &'static str,
109    pub prog_name: &'static str,
110    pub client_version_string: &'static str,
111    pub bundle_id: &'static str,
112    #[serde(rename = "kLibUSBMuxVersion")]
113    pub lib_usbmux_version: u32,
114}
115
116#[derive(Debug, Deserialize)]
117#[serde(rename_all = "PascalCase")]
118pub(crate) struct ReadBuidResponse {
119    #[serde(rename = "BUID")]
120    pub buid: String,
121}
122
123// ── Connect messages ─────────────────────────────────
124
125#[derive(Serialize)]
126#[serde(rename_all = "PascalCase")]
127pub(crate) struct ConnectRequest {
128    pub message_type: &'static str,
129    pub prog_name: &'static str,
130    pub client_version_string: &'static str,
131    pub bundle_id: &'static str,
132    #[serde(rename = "kLibUSBMuxVersion")]
133    pub lib_usbmux_version: u32,
134    #[serde(rename = "DeviceID")]
135    pub device_id: u32,
136    pub port_number: u16,
137}
138
139#[derive(Debug, Deserialize)]
140#[serde(rename_all = "PascalCase")]
141pub(crate) struct ConnectResponse {
142    #[allow(dead_code)]
143    pub message_type: String,
144    pub number: u32,
145}
146
147// ── Listen message ───────────────────────────────────
148
149#[derive(Serialize)]
150#[serde(rename_all = "PascalCase")]
151pub(crate) struct ListenRequest {
152    pub message_type: &'static str,
153    pub prog_name: &'static str,
154    pub client_version_string: &'static str,
155}
156
157#[derive(Debug, Deserialize)]
158#[serde(rename_all = "PascalCase")]
159pub(crate) struct DeviceEvent {
160    pub message_type: String,
161    #[serde(rename = "DeviceID")]
162    pub device_id: u32,
163    pub properties: Option<DevicePropertiesRaw>,
164}
165
166#[cfg(test)]
167mod tests {
168    use super::*;
169
170    #[tokio::test]
171    async fn recv_plist_rejects_oversized_message() {
172        let mut header = [0u8; 16];
173        header[..4].copy_from_slice(&((MAX_MUX_MESSAGE_SIZE as u32) + 1).to_le_bytes());
174        let mut cursor = std::io::Cursor::new(header);
175
176        let err = recv_plist::<_, plist::Value>(&mut cursor)
177            .await
178            .unwrap_err();
179        assert!(
180            matches!(err, MuxError::Protocol(message) if message.contains("message too large"))
181        );
182    }
183}