ios_core/services/
device_link.rs1use serde::Serialize;
2use tokio::io::{AsyncRead, AsyncWrite};
3
4service_error!(DeviceLinkError);
5
6const MAX_PLIST_SIZE: usize = 4 * 1024 * 1024;
7
8impl From<super::plist_frame::PlistFrameError> for DeviceLinkError {
9 fn from(error: super::plist_frame::PlistFrameError) -> Self {
10 match error {
11 super::plist_frame::PlistFrameError::Io(error) => Self::Io(error),
12 super::plist_frame::PlistFrameError::Plist(error) => Self::Plist(error),
13 super::plist_frame::PlistFrameError::Protocol(message) => Self::Protocol(message),
14 }
15 }
16}
17
18pub struct DeviceLinkClient<S> {
19 stream: S,
20}
21
22impl<S> DeviceLinkClient<S> {
23 pub fn new(stream: S) -> Self {
24 Self { stream }
25 }
26
27 pub fn into_inner(self) -> S {
28 self.stream
29 }
30
31 pub fn stream_mut(&mut self) -> &mut S {
32 &mut self.stream
33 }
34}
35
36impl<S> DeviceLinkClient<S>
37where
38 S: AsyncRead + AsyncWrite + Unpin,
39{
40 pub async fn version_exchange(&mut self) -> Result<u64, DeviceLinkError> {
41 let response = self.recv_message().await?;
42 let message = response.as_array().ok_or_else(|| {
43 DeviceLinkError::Protocol(format!(
44 "device link version exchange expected array, got {response:?}"
45 ))
46 })?;
47
48 let message_type = message
49 .first()
50 .and_then(plist::Value::as_string)
51 .ok_or_else(|| {
52 DeviceLinkError::Protocol(format!(
53 "device link version exchange missing message type: {response:?}"
54 ))
55 })?;
56 if message_type != "DLMessageVersionExchange" {
57 return Err(DeviceLinkError::Protocol(format!(
58 "expected DLMessageVersionExchange, got {message_type}"
59 )));
60 }
61
62 let version = message
63 .get(1)
64 .and_then(|value| match value {
65 plist::Value::Integer(value) => value.as_unsigned(),
66 _ => None,
67 })
68 .ok_or_else(|| {
69 DeviceLinkError::Protocol(format!(
70 "device link version exchange missing major version: {response:?}"
71 ))
72 })?;
73
74 self.send_message(&vec![
75 plist::Value::String("DLMessageVersionExchange".into()),
76 plist::Value::String("DLVersionsOk".into()),
77 plist::Value::Integer(version.into()),
78 ])
79 .await?;
80
81 let ready = self.recv_message().await?;
82 let ready_message = ready.as_array().ok_or_else(|| {
83 DeviceLinkError::Protocol(format!("device ready expected array, got {ready:?}"))
84 })?;
85 let ready_type = ready_message
86 .first()
87 .and_then(plist::Value::as_string)
88 .ok_or_else(|| {
89 DeviceLinkError::Protocol(format!("device ready missing message type: {ready:?}"))
90 })?;
91 if ready_type != "DLMessageDeviceReady" {
92 return Err(DeviceLinkError::Protocol(format!(
93 "expected DLMessageDeviceReady, got {ready_type}"
94 )));
95 }
96
97 Ok(version)
98 }
99
100 pub async fn send_process_message<T>(&mut self, message: &T) -> Result<(), DeviceLinkError>
101 where
102 T: Serialize,
103 {
104 self.send_message(&("DLMessageProcessMessage", message))
105 .await
106 }
107
108 pub async fn recv_process_message(&mut self) -> Result<plist::Dictionary, DeviceLinkError> {
109 let response = self.recv_message().await?;
110 let message = response.as_array().ok_or_else(|| {
111 DeviceLinkError::Protocol(format!("process message expected array, got {response:?}"))
112 })?;
113
114 let message_type = message
115 .first()
116 .and_then(plist::Value::as_string)
117 .ok_or_else(|| {
118 DeviceLinkError::Protocol(format!(
119 "process message missing message type: {response:?}"
120 ))
121 })?;
122 if message_type != "DLMessageProcessMessage" {
123 return Err(DeviceLinkError::Protocol(format!(
124 "expected DLMessageProcessMessage, got {message_type}"
125 )));
126 }
127
128 message
129 .get(1)
130 .and_then(plist::Value::as_dictionary)
131 .cloned()
132 .ok_or_else(|| {
133 DeviceLinkError::Protocol(format!(
134 "process message missing dictionary payload: {response:?}"
135 ))
136 })
137 }
138
139 pub async fn send_message<T>(&mut self, message: &T) -> Result<(), DeviceLinkError>
140 where
141 T: Serialize,
142 {
143 super::plist_frame::write_xml_plist_frame(&mut self.stream, message, MAX_PLIST_SIZE)
144 .await
145 .map_err(DeviceLinkError::from)
146 }
147
148 pub async fn recv_message(&mut self) -> Result<plist::Value, DeviceLinkError> {
149 super::plist_frame::read_plist_frame(&mut self.stream, MAX_PLIST_SIZE)
150 .await
151 .map_err(DeviceLinkError::from)
152 }
153
154 pub async fn disconnect(&mut self) -> Result<(), DeviceLinkError> {
155 self.send_message(&vec![
156 plist::Value::String("DLMessageDisconnect".into()),
157 plist::Value::String("___EmptyParameterString___".into()),
158 ])
159 .await
160 }
161}
162
163#[cfg(test)]
164mod tests {
165 use tokio::io::{duplex, AsyncReadExt, AsyncWriteExt};
166
167 use super::*;
168
169 fn encode_frame(value: &plist::Value) -> Vec<u8> {
170 let mut payload = Vec::new();
171 plist::to_writer_xml(&mut payload, value).expect("plist serialization");
172 let mut frame = Vec::with_capacity(payload.len() + 4);
173 frame.extend_from_slice(&(payload.len() as u32).to_be_bytes());
174 frame.extend_from_slice(&payload);
175 frame
176 }
177
178 async fn read_frame(stream: &mut tokio::io::DuplexStream) -> plist::Value {
179 let mut len_buf = [0u8; 4];
180 stream.read_exact(&mut len_buf).await.expect("frame length");
181 let len = u32::from_be_bytes(len_buf) as usize;
182 let mut payload = vec![0u8; len];
183 stream
184 .read_exact(&mut payload)
185 .await
186 .expect("frame payload");
187 plist::from_bytes(&payload).expect("plist decode")
188 }
189
190 #[tokio::test]
191 async fn version_exchange_sends_versions_ok_and_returns_major_version() {
192 let (client_stream, mut server_stream) = duplex(4096);
193 let task = tokio::spawn(async move {
194 let mut client = DeviceLinkClient::new(client_stream);
195 client.version_exchange().await.unwrap()
196 });
197
198 server_stream
199 .write_all(&encode_frame(&plist::Value::Array(vec![
200 plist::Value::String("DLMessageVersionExchange".into()),
201 plist::Value::Integer(300u64.into()),
202 ])))
203 .await
204 .unwrap();
205
206 let versions_ok = read_frame(&mut server_stream).await;
207 assert_eq!(
208 versions_ok.as_array(),
209 Some(&vec![
210 plist::Value::String("DLMessageVersionExchange".into()),
211 plist::Value::String("DLVersionsOk".into()),
212 plist::Value::Integer(300u64.into()),
213 ])
214 );
215
216 server_stream
217 .write_all(&encode_frame(&plist::Value::Array(vec![
218 plist::Value::String("DLMessageDeviceReady".into()),
219 ])))
220 .await
221 .unwrap();
222
223 assert_eq!(task.await.unwrap(), 300);
224 }
225
226 #[tokio::test]
227 async fn recv_process_message_requires_dictionary_payload() {
228 let (client_stream, mut server_stream) = duplex(4096);
229 let task = tokio::spawn(async move {
230 let mut client = DeviceLinkClient::new(client_stream);
231 client.recv_process_message().await
232 });
233
234 server_stream
235 .write_all(&encode_frame(&plist::Value::Array(vec![
236 plist::Value::String("DLMessageProcessMessage".into()),
237 plist::Value::String("not-a-dict".into()),
238 ])))
239 .await
240 .unwrap();
241
242 let err = task
243 .await
244 .unwrap()
245 .expect_err("non-dictionary payload must fail");
246 assert!(err
247 .to_string()
248 .contains("process message missing dictionary payload"));
249 }
250
251 #[tokio::test]
252 async fn disconnect_sends_expected_message() {
253 let (client_stream, mut server_stream) = duplex(4096);
254 let task = tokio::spawn(async move {
255 let mut client = DeviceLinkClient::new(client_stream);
256 client.disconnect().await.unwrap();
257 });
258
259 let disconnect = read_frame(&mut server_stream).await;
260 assert_eq!(
261 disconnect.as_array(),
262 Some(&vec![
263 plist::Value::String("DLMessageDisconnect".into()),
264 plist::Value::String("___EmptyParameterString___".into()),
265 ])
266 );
267
268 task.await.unwrap();
269 }
270}