1use bytes::Bytes;
4use formats::bmessage::BMessage;
5use formats::xml::FolderListing;
6use futures::{SinkExt, StreamExt};
7use obex_core::{
8 client::{ObexClient, ObexError},
9 headers::Header,
10};
11use obex_core::{wrap, ObexTransport};
12use tokio::io::{AsyncRead, AsyncWrite};
13
14use crate::{
15 folders::Folder,
16 messages::{ListMessagesFilter, MessageEntry},
17 params::{
18 get_message_params, push_message_params, set_message_status_params,
19 set_notification_registration_params, INDICATOR_DELETED_STATUS, INDICATOR_READ_STATUS,
20 },
21 MapError, MessageStatus,
22};
23
24const MAP_UUID: [u8; 16] = [
25 0xbb, 0x58, 0x2b, 0x40, 0x42, 0x0c, 0x11, 0xdb, 0xb0, 0xde, 0x08, 0x00, 0x20, 0x0c, 0x9a, 0x66,
26];
27
28const MAX_BODY_BYTES: usize = 4 * 1024 * 1024;
29
30pub struct MapClient<T> {
34 obex: ObexClient,
35 transport: ObexTransport<T>,
36 depth: u8,
38}
39
40impl<T: AsyncRead + AsyncWrite + Unpin> MapClient<T> {
41 pub async fn connect(stream: T) -> Result<Self, MapError> {
48 let mut transport = wrap(stream);
49 let mut obex = ObexClient::new();
50 let req = ObexClient::connect_request(&MAP_UUID)?;
51 transport.send(req).await?;
52 let rsp = Self::recv(&mut transport).await?;
53 obex.handle_connect_response(&rsp)?;
54 Ok(Self { obex, transport, depth: 0 })
55 }
56
57 pub(crate) async fn setpath(&mut self, segment: &str) -> Result<(), MapError> {
65 let req = self.obex.setpath_request(segment)?;
66 self.transport.send(req).await?;
67 let rsp_bytes = Self::recv(&mut self.transport).await?;
68 let rsp = ObexClient::parse_response(&rsp_bytes)?;
69 if !rsp.opcode.is_ok() {
70 return Err(MapError::ServerError(rsp.opcode.to_byte()));
71 }
72 self.depth = self.depth.saturating_add(1);
73 Ok(())
74 }
75
76 pub(crate) async fn setpath_up(&mut self) -> Result<(), MapError> {
83 debug_assert!(self.depth > 0, "setpath_up called at root");
84 let req = self.obex.setpath_backup_request()?;
85 self.transport.send(req).await?;
86 let rsp_bytes = Self::recv(&mut self.transport).await?;
87 let rsp = ObexClient::parse_response(&rsp_bytes)?;
88 if !rsp.opcode.is_ok() {
89 return Err(MapError::ServerError(rsp.opcode.to_byte()));
90 }
91 self.depth = self.depth.saturating_sub(1);
92 Ok(())
93 }
94
95 pub(crate) async fn reset_to_root(&mut self) -> Result<(), MapError> {
102 for _ in 0..self.depth {
103 self.setpath_up().await?;
104 }
105 Ok(())
106 }
107
108 pub async fn set_folder(&mut self, folder: Folder) -> Result<(), MapError> {
117 self.reset_to_root().await?;
118 for segment in ["telecom", "msg", folder.as_str()] {
119 self.setpath(segment).await?;
120 }
121 Ok(())
122 }
123
124 pub async fn list_messages(
133 &mut self,
134 filter: &ListMessagesFilter,
135 ) -> Result<Vec<MessageEntry>, MapError> {
136 let req = self.obex.get_request(
137 b"x-bt/MAP-msg-listing\x00",
138 None,
139 Some(filter.to_app_params()?),
140 )?;
141 self.transport.send(req).await?;
142 let body = self.collect_body().await?;
143 Ok(crate::xml::parse_message_listing(&body)?)
144 }
145
146 pub async fn get_message(&mut self, handle: &str) -> Result<BMessage, MapError> {
156 if handle.is_empty() {
157 return Err(MapError::InvalidInput("handle must not be empty"));
158 }
159 if handle.contains(['\r', '\n']) {
160 return Err(MapError::InvalidInput("handle must not contain CR or LF"));
161 }
162 let req =
163 self.obex.get_request(b"x-bt/message\x00", Some(handle), Some(get_message_params()))?;
164 self.transport.send(req).await?;
165 let body = self.collect_body().await?;
166 let text = std::str::from_utf8(&body).map_err(|_| MapError::InvalidEncoding)?;
167 Ok(BMessage::parse(text)?)
168 }
169
170 pub async fn push_message(&mut self, phone: &str, text: &str) -> Result<String, MapError> {
179 if phone.contains(['\r', '\n']) {
180 return Err(MapError::InvalidInput("phone must not contain CR or LF"));
181 }
182 let body = BMessage::outbound_sms(phone, text).encode();
183 let body_bytes = body.as_bytes();
184 let len = u32::try_from(body_bytes.len()).map_err(|_| ObexError::BodyTooLarge)?;
185 let req = self.obex.put_final_request(
186 b"x-bt/message\x00",
187 vec![
188 Header::Name(String::new()),
189 Header::Length(len),
190 Header::AppParams(push_message_params()),
191 Header::EndOfBody(Bytes::copy_from_slice(body_bytes)),
192 ],
193 )?;
194 self.transport.send(req).await?;
195 let rsp_bytes = Self::recv(&mut self.transport).await?;
196 let rsp = ObexClient::parse_response(&rsp_bytes)?;
197 if !rsp.opcode.is_ok() {
198 return Err(MapError::ServerError(rsp.opcode.to_byte()));
199 }
200 rsp.header_name().ok_or(MapError::MissingHandle)
201 }
202
203 pub async fn set_message_status_read(
212 &mut self,
213 handle: &str,
214 status: MessageStatus,
215 ) -> Result<(), MapError> {
216 let value = match status {
217 MessageStatus::Read => 0x01_u8,
218 MessageStatus::Unread => 0x00_u8,
219 };
220 self.do_set_message_status(handle, INDICATOR_READ_STATUS, value).await
221 }
222
223 pub async fn set_message_status_deleted(
232 &mut self,
233 handle: &str,
234 deleted: bool,
235 ) -> Result<(), MapError> {
236 self.do_set_message_status(handle, INDICATOR_DELETED_STATUS, u8::from(deleted)).await
237 }
238
239 pub async fn get_folder_listing(&mut self) -> Result<FolderListing, MapError> {
248 let req = self.obex.get_request(b"x-obex/folder-listing\x00", None, None)?;
249 self.transport.send(req).await?;
250 let body = self.collect_body().await?;
251 Ok(FolderListing::parse(&body)?)
252 }
253
254 pub async fn set_notification_registration(&mut self, enable: bool) -> Result<(), MapError> {
262 let req = self.obex.put_final_request(
263 b"x-bt/MAP-NotificationRegistration\x00",
264 vec![
265 Header::AppParams(set_notification_registration_params(enable)),
266 Header::EndOfBody(Bytes::new()),
267 ],
268 )?;
269 self.transport.send(req).await?;
270 let rsp_bytes = Self::recv(&mut self.transport).await?;
271 let rsp = ObexClient::parse_response(&rsp_bytes)?;
272 if !rsp.opcode.is_ok() {
273 return Err(MapError::ServerError(rsp.opcode.to_byte()));
274 }
275 Ok(())
276 }
277
278 async fn do_set_message_status(
279 &mut self,
280 handle: &str,
281 indicator: u8,
282 value: u8,
283 ) -> Result<(), MapError> {
284 if handle.is_empty() {
285 return Err(MapError::InvalidInput("handle must not be empty"));
286 }
287 if handle.contains(['\r', '\n']) {
288 return Err(MapError::InvalidInput("handle must not contain CR or LF"));
289 }
290 let req = self.obex.put_final_request(
291 b"x-bt/messageStatus\x00",
292 vec![
293 Header::Name(handle.to_owned()),
294 Header::AppParams(Bytes::from(set_message_status_params(indicator, value))),
295 ],
296 )?;
297 self.transport.send(req).await?;
298 let rsp_bytes = Self::recv(&mut self.transport).await?;
299 let rsp = ObexClient::parse_response(&rsp_bytes)?;
300 if !rsp.opcode.is_ok() {
301 return Err(MapError::ServerError(rsp.opcode.to_byte()));
302 }
303 Ok(())
304 }
305
306 async fn collect_body(&mut self) -> Result<Vec<u8>, MapError> {
307 let mut body = Vec::with_capacity(512);
308 loop {
309 let rsp_bytes = Self::recv(&mut self.transport).await?;
310 let rsp = ObexClient::parse_response(&rsp_bytes)?;
311 if rsp.opcode.is_continue() {
312 if let Some(chunk) = rsp.body_payload() {
313 let new_len =
314 body.len().checked_add(chunk.len()).ok_or(MapError::ResponseTooLarge)?;
315 if new_len > MAX_BODY_BYTES {
316 return Err(MapError::ResponseTooLarge);
317 }
318 body.extend_from_slice(chunk);
319 }
320 let cont = self.obex.get_continue_request()?;
321 self.transport.send(cont).await?;
322 } else if rsp.opcode.is_ok() {
323 if let Some(chunk) = rsp.body_payload() {
324 let new_len =
325 body.len().checked_add(chunk.len()).ok_or(MapError::ResponseTooLarge)?;
326 if new_len > MAX_BODY_BYTES {
327 return Err(MapError::ResponseTooLarge);
328 }
329 body.extend_from_slice(chunk);
330 }
331 break;
332 } else {
333 return Err(MapError::ServerError(rsp.opcode.to_byte()));
334 }
335 }
336 Ok(body)
337 }
338
339 pub async fn hold(&mut self) -> Result<(), MapError> {
347 loop {
348 match self.transport.next().await {
349 None => return Ok(()),
350 Some(Ok(_)) => {}
351 Some(Err(e)) => return Err(MapError::Transport(e)),
352 }
353 }
354 }
355
356 async fn recv(transport: &mut ObexTransport<T>) -> Result<Bytes, MapError> {
357 transport.next().await.ok_or(MapError::UnexpectedEof)?.map_err(MapError::Transport)
358 }
359}