Skip to main content

ios_core/proto/
xpc.rs

1use bytes::{Buf, BufMut, Bytes, BytesMut};
2use indexmap::IndexMap;
3
4/// XPC binary message magic numbers
5pub const XPC_MAGIC: u32 = 0x29B00B92;
6pub const XPC_BODY_MAGIC: u32 = 0x42133742;
7pub const XPC_ALWAYS_SET: u32 = 0x00000001;
8pub const XPC_DATA_LEN_OFFSET: u32 = 0x00000002; // always-set mask for data presence
9
10/// XPC value types
11#[derive(Debug, Clone, PartialEq)]
12pub enum XpcValue {
13    Null,
14    Bool(bool),
15    Int64(i64),
16    Uint64(u64),
17    Double(f64),
18    Date(i64),
19    Data(Bytes),
20    String(String),
21    Uuid([u8; 16]),
22    Array(Vec<XpcValue>),
23    Dictionary(IndexMap<String, XpcValue>),
24}
25
26/// A parsed XPC message frame.
27#[derive(Debug, Clone)]
28pub struct XpcMessage {
29    pub flags: u32,
30    pub msg_id: u64,
31    pub body: Option<XpcValue>,
32}
33
34#[derive(Debug, thiserror::Error)]
35pub enum XpcError {
36    #[error("buffer too short")]
37    BufferTooShort,
38    #[error("bad magic: expected 0x{expected:08X}, got 0x{got:08X}")]
39    BadMagic { expected: u32, got: u32 },
40    #[error("unknown XPC type: 0x{0:08X}")]
41    UnknownType(u32),
42    #[error("invalid UTF-8 in string")]
43    InvalidUtf8,
44}
45
46// XPC type tags
47const TYPE_NULL: u32 = 0x00001000;
48const TYPE_BOOL: u32 = 0x00002000;
49const TYPE_INT64: u32 = 0x00003000;
50const TYPE_UINT64: u32 = 0x00004000;
51const TYPE_DOUBLE: u32 = 0x00005000;
52const TYPE_DATE: u32 = 0x00007000;
53const TYPE_DATA: u32 = 0x00008000;
54const TYPE_STRING: u32 = 0x00009000;
55const TYPE_UUID: u32 = 0x0000A000;
56const TYPE_ARRAY: u32 = 0x0000E000;
57const TYPE_DICTIONARY: u32 = 0x0000F000;
58
59/// Encode an XPC message to bytes.
60pub fn encode_xpc(msg: &XpcMessage) -> Bytes {
61    let mut buf = BytesMut::new();
62    buf.put_u32_le(XPC_MAGIC);
63    buf.put_u32_le(msg.flags);
64    buf.put_u64_le(msg.msg_id);
65
66    if let Some(body) = &msg.body {
67        let body_bytes = encode_body(body);
68        buf.put_u32_le(body_bytes.len() as u32);
69        buf.put_slice(&body_bytes);
70    } else {
71        buf.put_u32_le(0);
72    }
73
74    buf.freeze()
75}
76
77fn encode_body(value: &XpcValue) -> BytesMut {
78    let mut buf = BytesMut::new();
79    buf.put_u32_le(XPC_BODY_MAGIC);
80    encode_value(value, &mut buf);
81    buf
82}
83
84fn encode_value(value: &XpcValue, buf: &mut BytesMut) {
85    match value {
86        XpcValue::Null => {
87            buf.put_u32_le(TYPE_NULL);
88            buf.put_u32_le(0);
89        }
90        XpcValue::Bool(b) => {
91            buf.put_u32_le(TYPE_BOOL);
92            buf.put_u32_le(4);
93            buf.put_u32_le(if *b { 1 } else { 0 });
94            // pad to 4-byte boundary (already aligned)
95        }
96        XpcValue::Int64(n) => {
97            buf.put_u32_le(TYPE_INT64);
98            buf.put_u32_le(8);
99            buf.put_i64_le(*n);
100        }
101        XpcValue::Uint64(n) => {
102            buf.put_u32_le(TYPE_UINT64);
103            buf.put_u32_le(8);
104            buf.put_u64_le(*n);
105        }
106        XpcValue::Double(f) => {
107            buf.put_u32_le(TYPE_DOUBLE);
108            buf.put_u32_le(8);
109            buf.put_f64_le(*f);
110        }
111        XpcValue::Date(n) => {
112            buf.put_u32_le(TYPE_DATE);
113            buf.put_u32_le(8);
114            buf.put_i64_le(*n);
115        }
116        XpcValue::Data(d) => {
117            buf.put_u32_le(TYPE_DATA);
118            let padded = (d.len() + 3) & !3;
119            buf.put_u32_le(d.len() as u32);
120            buf.put_slice(d);
121            for _ in d.len()..padded {
122                buf.put_u8(0);
123            }
124        }
125        XpcValue::String(s) => {
126            buf.put_u32_le(TYPE_STRING);
127            let bytes = s.as_bytes();
128            let total = bytes.len() + 1; // null terminator
129            let padded = (total + 3) & !3;
130            buf.put_u32_le(total as u32);
131            buf.put_slice(bytes);
132            for _ in bytes.len()..padded {
133                buf.put_u8(0);
134            }
135        }
136        XpcValue::Uuid(u) => {
137            buf.put_u32_le(TYPE_UUID);
138            buf.put_u32_le(16);
139            buf.put_slice(u);
140        }
141        XpcValue::Array(arr) => {
142            buf.put_u32_le(TYPE_ARRAY);
143            let len_offset = buf.len();
144            buf.put_u32_le(0); // placeholder
145            let start = buf.len();
146            buf.put_u32_le(arr.len() as u32);
147            for v in arr {
148                encode_value(v, buf);
149            }
150            let len = (buf.len() - start) as u32;
151            buf[len_offset..len_offset + 4].copy_from_slice(&len.to_le_bytes());
152        }
153        XpcValue::Dictionary(map) => {
154            buf.put_u32_le(TYPE_DICTIONARY);
155            let len_offset = buf.len();
156            buf.put_u32_le(0); // placeholder
157            let start = buf.len();
158            buf.put_u32_le(map.len() as u32);
159            for (k, v) in map {
160                encode_value(&XpcValue::String(k.clone()), buf);
161                encode_value(v, buf);
162            }
163            let len = (buf.len() - start) as u32;
164            buf[len_offset..len_offset + 4].copy_from_slice(&len.to_le_bytes());
165        }
166    }
167}
168
169/// Decode an XPC message from a byte buffer.
170pub fn decode_xpc(buf: &mut Bytes) -> Result<XpcMessage, XpcError> {
171    if buf.remaining() < 16 {
172        return Err(XpcError::BufferTooShort);
173    }
174    let magic = buf.get_u32_le();
175    if magic != XPC_MAGIC {
176        return Err(XpcError::BadMagic {
177            expected: XPC_MAGIC,
178            got: magic,
179        });
180    }
181    let flags = buf.get_u32_le();
182    let msg_id = buf.get_u64_le();
183
184    if buf.remaining() < 4 {
185        return Err(XpcError::BufferTooShort);
186    }
187    let body_len = buf.get_u32_le() as usize;
188
189    let body = if body_len > 0 {
190        if buf.remaining() < body_len {
191            return Err(XpcError::BufferTooShort);
192        }
193        let body_magic = buf.get_u32_le();
194        if body_magic != XPC_BODY_MAGIC {
195            return Err(XpcError::BadMagic {
196                expected: XPC_BODY_MAGIC,
197                got: body_magic,
198            });
199        }
200        Some(decode_value(buf)?)
201    } else {
202        None
203    };
204
205    Ok(XpcMessage {
206        flags,
207        msg_id,
208        body,
209    })
210}
211
212fn decode_value(buf: &mut Bytes) -> Result<XpcValue, XpcError> {
213    if buf.remaining() < 8 {
214        return Err(XpcError::BufferTooShort);
215    }
216    let type_tag = buf.get_u32_le();
217    let data_len = buf.get_u32_le() as usize;
218
219    match type_tag {
220        TYPE_NULL => Ok(XpcValue::Null),
221        TYPE_BOOL => {
222            if buf.remaining() < 4 {
223                return Err(XpcError::BufferTooShort);
224            }
225            let v = buf.get_u32_le();
226            Ok(XpcValue::Bool(v != 0))
227        }
228        TYPE_INT64 => {
229            if buf.remaining() < 8 {
230                return Err(XpcError::BufferTooShort);
231            }
232            Ok(XpcValue::Int64(buf.get_i64_le()))
233        }
234        TYPE_UINT64 => {
235            if buf.remaining() < 8 {
236                return Err(XpcError::BufferTooShort);
237            }
238            Ok(XpcValue::Uint64(buf.get_u64_le()))
239        }
240        TYPE_DOUBLE => {
241            if buf.remaining() < 8 {
242                return Err(XpcError::BufferTooShort);
243            }
244            Ok(XpcValue::Double(buf.get_f64_le()))
245        }
246        TYPE_DATE => {
247            if buf.remaining() < 8 {
248                return Err(XpcError::BufferTooShort);
249            }
250            Ok(XpcValue::Date(buf.get_i64_le()))
251        }
252        TYPE_DATA => {
253            let padded = (data_len + 3) & !3;
254            if buf.remaining() < padded {
255                return Err(XpcError::BufferTooShort);
256            }
257            let d = buf.copy_to_bytes(data_len);
258            // skip padding bytes
259            let pad = padded - data_len;
260            if pad > 0 {
261                buf.advance(pad);
262            }
263            Ok(XpcValue::Data(d))
264        }
265        TYPE_STRING => {
266            let padded = (data_len + 3) & !3;
267            if buf.remaining() < padded {
268                return Err(XpcError::BufferTooShort);
269            }
270            let raw = buf.copy_to_bytes(data_len);
271            // skip padding bytes
272            let pad = padded - data_len;
273            if pad > 0 {
274                buf.advance(pad);
275            }
276            // Find null terminator
277            let end = raw.iter().position(|&b| b == 0).unwrap_or(raw.len());
278            let s = std::str::from_utf8(&raw[..end]).map_err(|_| XpcError::InvalidUtf8)?;
279            Ok(XpcValue::String(s.to_string()))
280        }
281        TYPE_UUID => {
282            if buf.remaining() < 16 {
283                return Err(XpcError::BufferTooShort);
284            }
285            let mut u = [0u8; 16];
286            buf.copy_to_slice(&mut u);
287            Ok(XpcValue::Uuid(u))
288        }
289        TYPE_ARRAY => {
290            if buf.remaining() < 4 {
291                return Err(XpcError::BufferTooShort);
292            }
293            let count = buf.get_u32_le() as usize;
294            let mut arr = Vec::with_capacity(count);
295            for _ in 0..count {
296                arr.push(decode_value(buf)?);
297            }
298            Ok(XpcValue::Array(arr))
299        }
300        TYPE_DICTIONARY => {
301            if buf.remaining() < 4 {
302                return Err(XpcError::BufferTooShort);
303            }
304            let count = buf.get_u32_le() as usize;
305            let mut map = IndexMap::new();
306            for _ in 0..count {
307                let key = match decode_value(buf)? {
308                    XpcValue::String(s) => s,
309                    _ => return Err(XpcError::UnknownType(0)),
310                };
311                let val = decode_value(buf)?;
312                map.insert(key, val);
313            }
314            Ok(XpcValue::Dictionary(map))
315        }
316        other => Err(XpcError::UnknownType(other)),
317    }
318}