Skip to main content

imsg_map/
mns_event.rs

1//! MAP MNS event types, event struct, parser error, and XML event-report parser.
2
3use obex_core::client::ObexError;
4use obex_core::TransportError;
5use quick_xml::{events::Event, Reader, XmlVersion};
6use thiserror::Error;
7
8/// Reported in `MAP-event-report` `<event type=...>` attribute.
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub enum EventType {
11    /// New message in store; `handle` and `folder` are always present.
12    NewMessage,
13    /// An outbound message was delivered to its recipient.
14    DeliverySuccess,
15    /// An outbound message was accepted by the network for delivery.
16    SendingSuccess,
17    /// Delivery of an outbound message permanently failed.
18    DeliveryFailure,
19    /// The network rejected an outbound message.
20    SendingFailure,
21    /// Message removed from store; `handle` present, `folder` is the folder it was in.
22    MessageDeleted,
23    /// A message was moved to a different folder; `old_folder` carries the previous location.
24    MessageShift,
25    /// Message store is full; no new messages can be received until space is freed.
26    MemoryFull,
27    /// Message store has space available again after a `MemoryFull` event.
28    MemoryAvailable,
29    /// Read/unread flag toggled on device; `handle` present.
30    ReadStatusChanged,
31}
32
33/// Fields absent in the `<event>` element are `None`.
34#[derive(Debug, Clone, PartialEq, Eq)]
35pub struct MnsEvent {
36    event_type: EventType,
37    handle: Option<String>,
38    folder: Option<String>,
39    old_folder: Option<String>,
40    msg_type: Option<String>,
41    datetime: Option<String>,
42}
43
44impl MnsEvent {
45    /// Governs which optional fields are present.
46    #[must_use]
47    pub const fn event_type(&self) -> EventType {
48        self.event_type
49    }
50
51    /// Opaque message handle assigned by the device; absent for `MemoryFull`/`MemoryAvailable`.
52    #[must_use]
53    pub fn handle(&self) -> Option<&str> {
54        self.handle.as_deref()
55    }
56
57    /// Current folder path, e.g. `TELECOM/MSG/INBOX`; absent for memory events.
58    #[must_use]
59    pub fn folder(&self) -> Option<&str> {
60        self.folder.as_deref()
61    }
62
63    /// Previous folder path; present only for `MessageShift`.
64    #[must_use]
65    pub fn old_folder(&self) -> Option<&str> {
66        self.old_folder.as_deref()
67    }
68
69    /// Message type string, e.g. `SMS_GSM`; absent for memory events.
70    #[must_use]
71    pub fn msg_type(&self) -> Option<&str> {
72        self.msg_type.as_deref()
73    }
74
75    /// ISO 8601 basic datetime string, e.g. `20260604T120000`; present for `NewMessage`.
76    #[must_use]
77    pub fn datetime(&self) -> Option<&str> {
78        self.datetime.as_deref()
79    }
80}
81
82/// MNS server and event report XML parsing errors.
83#[derive(Debug, Error)]
84pub enum MnsError {
85    /// quick-xml reader error encountered while parsing the event report body.
86    #[error("XML error: {0}")]
87    Parse(#[from] quick_xml::Error),
88    /// Attribute value in the `<event>` element could not be decoded.
89    #[error("attribute error: {0}")]
90    Attr(#[from] quick_xml::events::attributes::AttrError),
91    /// `type` attribute value did not match any MAP 1.4 event type; inner string is the raw value.
92    #[error("unknown event type: {0}")]
93    UnknownEventType(String),
94    /// `<event>` element present but `type` attribute is absent.
95    #[error("<event> element has no type attribute")]
96    MissingEventType,
97    /// Document contained no `<event>` element; body may be empty or malformed.
98    #[error("MAP-event-report contained no <event> element")]
99    MissingEvent,
100    /// OBEX protocol-level error during MNS server operation.
101    #[error("OBEX: {0}")]
102    Obex(#[from] ObexError),
103    /// Transport I/O error during MNS server operation.
104    #[error("transport: {0}")]
105    Transport(#[from] TransportError),
106    /// Transport stream closed before the device sent a clean OBEX DISCONNECT.
107    #[error("unexpected end of stream")]
108    UnexpectedEof,
109    /// Device sent an opcode other than PUT, `PUT_FINAL`, or DISCONNECT after CONNECT.
110    #[error("unexpected OBEX opcode {0:#04x} from MNS client")]
111    UnexpectedOpcode(u8),
112    /// CONNECT request Target header is absent or does not match the MNS service UUID.
113    #[error("CONNECT Target UUID does not match MNS service UUID")]
114    InvalidTarget,
115}
116
117/// Parses an OBEX PUT body into an [`MnsEvent`].
118///
119/// The document must contain at least one `<event>` element; only the first is returned. The
120/// `type` attribute is required and must match a known [`EventType`] value. All other attributes
121/// are optional and default to `None` when absent.
122///
123/// # Errors
124///
125/// Returns [`MnsError::Parse`] on malformed XML, [`MnsError::Attr`] on undecodable attribute
126/// values, [`MnsError::UnknownEventType`] if the `type` attribute is not a defined MAP 1.4 event
127/// type, or [`MnsError::MissingEvent`] if no `<event>` element is found.
128pub fn parse_event_report(xml: &[u8]) -> Result<MnsEvent, MnsError> {
129    let mut reader = Reader::from_reader(xml);
130    reader.config_mut().trim_text(true);
131    let mut buf = Vec::with_capacity(256);
132    loop {
133        match reader.read_event_into(&mut buf)? {
134            Event::Empty(e) | Event::Start(e) if e.name().as_ref() == b"event" => {
135                let mut raw_type: Option<String> = None;
136                let mut handle = None;
137                let mut folder = None;
138                let mut old_folder = None;
139                let mut msg_type = None;
140                let mut datetime = None;
141                for attr in e.attributes() {
142                    let a = attr?;
143                    let val = a.normalized_value(XmlVersion::Implicit1_0)?.into_owned();
144                    match a.key.as_ref() {
145                        b"type" => raw_type = Some(val),
146                        b"handle" => handle = Some(val),
147                        b"folder" => folder = Some(val),
148                        b"old_folder" => old_folder = Some(val),
149                        b"msg_type" => msg_type = Some(val),
150                        b"datetime" => datetime = Some(val),
151                        _ => {}
152                    }
153                }
154                let event_type = match raw_type {
155                    Some(ref s) => event_type_from_str(s)?,
156                    None => return Err(MnsError::MissingEventType),
157                };
158                return Ok(MnsEvent { event_type, handle, folder, old_folder, msg_type, datetime });
159            }
160            Event::Eof => return Err(MnsError::MissingEvent),
161            _ => {}
162        }
163        buf.clear();
164    }
165}
166
167fn event_type_from_str(s: &str) -> Result<EventType, MnsError> {
168    match s {
169        "NewMessage" => Ok(EventType::NewMessage),
170        "DeliverySuccess" => Ok(EventType::DeliverySuccess),
171        "SendingSuccess" => Ok(EventType::SendingSuccess),
172        "DeliveryFailure" => Ok(EventType::DeliveryFailure),
173        "SendingFailure" => Ok(EventType::SendingFailure),
174        "MessageDeleted" => Ok(EventType::MessageDeleted),
175        "MessageShift" => Ok(EventType::MessageShift),
176        "MemoryFull" => Ok(EventType::MemoryFull),
177        "MemoryAvailable" => Ok(EventType::MemoryAvailable),
178        "ReadStatusChanged" => Ok(EventType::ReadStatusChanged),
179        other => Err(MnsError::UnknownEventType(other.to_owned())),
180    }
181}