imsg_map/client/control.rs
1//! Folder listing, notification registration, and session teardown.
2
3use bytes::Bytes;
4use formats::xml::FolderListing;
5use futures::{SinkExt, StreamExt};
6use obex_core::{client::ObexClient, headers::Header};
7use tokio::io::{AsyncRead, AsyncWrite};
8
9use super::MapClient;
10use crate::{params::set_notification_registration_params, MapError};
11
12impl<T: AsyncRead + AsyncWrite + Unpin> MapClient<T> {
13 /// Returns the MAP folder listing for the current object store level via `GetFolderListing`
14 /// GET with Type `x-obex/folder-listing`. Folders are in device-reported document order.
15 ///
16 /// # Errors
17 ///
18 /// Returns [`MapError::ServerError`] if the remote returns a non-OK response.
19 /// Returns [`MapError::FolderListing`] if the response body is malformed XML.
20 /// Returns [`MapError::Obex`] or [`MapError::Transport`] on lower-layer failure.
21 pub async fn get_folder_listing(&mut self) -> Result<FolderListing, MapError> {
22 let req = self.obex.get_request(b"x-obex/folder-listing\x00", None, None)?;
23 self.transport.send(req).await?;
24 let body = self.collect_body().await?;
25 Ok(FolderListing::parse(&body)?)
26 }
27
28 /// When `enable` is `true`, the phone will connect to the MNS channel and push event reports.
29 /// When `false`, it stops. The caller must keep the MAP session alive while notifications are active.
30 ///
31 /// # Errors
32 ///
33 /// Returns [`MapError::ServerError`] if the remote returns a non-OK response.
34 /// Returns [`MapError::Obex`] or [`MapError::Transport`] on lower-layer failure.
35 pub async fn set_notification_registration(&mut self, enable: bool) -> Result<(), MapError> {
36 let req = self.obex.put_final_request(
37 b"x-bt/MAP-NotificationRegistration\x00",
38 vec![
39 Header::AppParams(set_notification_registration_params(enable)),
40 Header::EndOfBody(Bytes::new()),
41 ],
42 )?;
43 self.transport.send(req).await?;
44 let rsp_bytes = Self::recv(&mut self.transport).await?;
45 let rsp = ObexClient::parse_response(&rsp_bytes)?;
46 if !rsp.opcode.is_ok() {
47 return Err(MapError::ServerError(rsp.opcode.to_byte()));
48 }
49 Ok(())
50 }
51
52 /// Reads from the transport until the remote closes the stream, discarding all received
53 /// packets. Returns `Ok(())` on clean close. Does not send OBEX DISCONNECT and does not
54 /// parse received packet opcodes.
55 ///
56 /// # Errors
57 ///
58 /// Returns [`MapError::Transport`] on a framing error from the underlying codec.
59 pub async fn hold(&mut self) -> Result<(), MapError> {
60 loop {
61 match self.transport.next().await {
62 None => return Ok(()),
63 Some(Ok(_)) => {}
64 Some(Err(e)) => return Err(MapError::Transport(e)),
65 }
66 }
67 }
68
69 /// Sends OBEX DISCONNECT and awaits the server acknowledgement.
70 ///
71 /// Consumes `self` — the session is unusable after this call regardless of the outcome.
72 /// Does not close the underlying stream; the stream is dropped when `self` is consumed.
73 ///
74 /// # Errors
75 ///
76 /// Returns [`MapError`] if the request cannot be encoded, the transport fails, or the
77 /// server returns a non-OK response.
78 pub async fn disconnect(mut self) -> Result<(), MapError> {
79 let req = self.obex.disconnect_request()?;
80 self.transport.send(req).await?;
81 let rsp_bytes = Self::recv(&mut self.transport).await?;
82 let rsp = ObexClient::parse_response(&rsp_bytes)?;
83 if !rsp.opcode.is_ok() {
84 return Err(MapError::ServerError(rsp.opcode.to_byte()));
85 }
86 Ok(())
87 }
88}