Skip to main content

bt_map/packets/
messages_listing.rs

1// Copyright 2023 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use chrono::DateTime;
6use chrono::naive::NaiveDateTime;
7use objects::{Builder, ObexObjectError as Error, Parser};
8use std::collections::HashSet;
9use std::fmt;
10use std::hash::{Hash, Hasher};
11use std::str::FromStr;
12use xml::EventWriter;
13use xml::attribute::OwnedAttribute;
14use xml::reader::{ParserConfig, XmlEvent};
15use xml::writer::{EmitterConfig, XmlEvent as XmlWriteEvent};
16
17use crate::MessageType;
18use crate::packets::{ISO_8601_TIME_FORMAT, bool_to_string, str_to_bool, truncate_string};
19
20// From MAP v1.4.2 section 3.1.6 Message-Listing Object:
21//
22// <!DTD for the MAP Messages-Listing Object-->
23// <!DOCTYPE MAP-msg-listing [
24// <!ELEMENT MAP-msg-listing ( msg )* >
25// <!ATTLIST MAP-msg-listing version CDATA #FIXED "1.1">
26// <!ELEMENT msg EMPTY>
27// <!ATTLIST msg
28//     handle CDATA #REQUIRED
29//     subject CDATA #REQUIRED
30//     datetime CDATA #REQUIRED
31//     sender_name CDATA #IMPLIED
32//     sender_addressing CDATA #IMPLIED
33//     replyto_addressing CDATA #IMPLIED
34//     recipient_name CDATA #IMPLIED
35//     recipient_addressing CDATA #REQUIRED
36//     type CDATA #REQUIRED
37//     size CDATA #REQUIRED
38//     text (yes|no) "no"
39//     reception_status CDATA #REQUIRED
40//     attachment_size CDATA #REQUIRED
41//     priority (yes|no) "no"
42//     read (yes|no) "no"
43//     sent (yes|no) "no"
44//     protected (yes|no) "no"
45//     delivery_status CDATA #IMPLIED
46//     conversation_id CDATA #REQUIRED
47//     conversation_name CDATA #IMPLIED
48//     direction CDATA #REQUIRED
49//     attachment_mime_types CDATA #IMPLIED
50// >
51// ]>
52
53const MESSAGE_ELEM: &str = "msg";
54const MESSAGES_LISTING_ELEM: &str = "MAP-msg-listing";
55
56const VERSION_ATTR: &str = "version";
57
58const HANDLE_ATTR: &str = "handle";
59const SUBJECT_ATTR: &str = "subject";
60const DATETIME_ATTR: &str = "datetime";
61const SENDER_NAME_ATTR: &str = "sender_name";
62const SENDER_ADDRESSING_ATTR: &str = "sender_addressing";
63const REPLYTO_ADDRESSING_ATTR: &str = "relyto_addressing";
64const RECIPIENT_NAME_ATTR: &str = "recipient_name";
65const RECIPIENT_ADDRESSING_ATTR: &str = "recipient_addressing";
66const TYPE_ATTR: &str = "type";
67
68// Must have one of these attributes.
69const SIZE_ATTR: &str = "size";
70const TEXT_ATTR: &str = "text";
71const RECEPTION_STATUS_ATTR: &str = "reception_status";
72const ATTACHMENT_SIZE_ATTR: &str = "attachment_size";
73const PRIORITY_ATTR: &str = "priority";
74const READ_ATTR: &str = "read";
75const SENT_ATTR: &str = "sent";
76const PROTECTED_ATTR: &str = "protected";
77
78// V1.1 specific attributes.
79const DELIVERY_STATUS_ATTR: &str = "delivery_status";
80const CONVERSATION_ID_ATTR: &str = "conversation_id";
81const CONVERSATION_NAME_ATTR: &str = "conversation_name";
82const DIRECTION_ATTR: &str = "direction";
83const ATTACHMENT_MIME_TYPES_ATTR: &str = "attachment_mime_types";
84
85#[derive(Debug, PartialEq)]
86pub enum ReceptionStatus {
87    // Complete message has been received by the MSE
88    Complete,
89    // Only a part of the message has been received by the MSE (e.g. fractioned email of
90    // push-service)
91    Fractioned,
92    // Only a notification of the message has been received by the MSE
93    Notification,
94}
95
96impl fmt::Display for ReceptionStatus {
97    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
98        match self {
99            Self::Complete => write!(f, "complete"),
100            Self::Fractioned => write!(f, "fractioned"),
101            Self::Notification => write!(f, "notification"),
102        }
103    }
104}
105
106impl FromStr for ReceptionStatus {
107    type Err = Error;
108    fn from_str(src: &str) -> Result<Self, Self::Err> {
109        match src {
110            "complete" => Ok(Self::Complete),
111            "fractioned" => Ok(Self::Fractioned),
112            "notification" => Ok(Self::Notification),
113            v => Err(Error::invalid_data(v)),
114        }
115    }
116}
117
118#[derive(Debug, PartialEq)]
119pub enum DeliveryStatus {
120    Unknown,
121    Delivered,
122    Sent,
123}
124
125impl fmt::Display for DeliveryStatus {
126    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
127        match self {
128            Self::Unknown => write!(f, "unknown"),
129            Self::Delivered => write!(f, "delivered"),
130            Self::Sent => write!(f, "sent"),
131        }
132    }
133}
134
135impl FromStr for DeliveryStatus {
136    type Err = Error;
137    fn from_str(src: &str) -> Result<Self, Self::Err> {
138        match src {
139            "unknown" => Ok(Self::Unknown),
140            "delivered" => Ok(Self::Delivered),
141            "sent" => Ok(Self::Sent),
142            v => Err(Error::invalid_data(v)),
143        }
144    }
145}
146
147#[derive(Clone, Debug, PartialEq)]
148pub enum Direction {
149    Incoming,
150    Outgoing,
151    OutgoingDraft,
152    OutgoingPending,
153}
154
155impl fmt::Display for Direction {
156    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
157        match self {
158            Self::Incoming => write!(f, "incoming"),
159            Self::Outgoing => write!(f, "outgoing"),
160            Self::OutgoingDraft => write!(f, "outgoingdraft"),
161            Self::OutgoingPending => write!(f, "outgoingpending"),
162        }
163    }
164}
165
166impl FromStr for Direction {
167    type Err = Error;
168    fn from_str(src: &str) -> Result<Self, Self::Err> {
169        match src {
170            "incoming" => Ok(Self::Incoming),
171            "outgoing" => Ok(Self::Outgoing),
172            "outgoingdraft" => Ok(Self::OutgoingDraft),
173            "outgoingpending" => Ok(Self::OutgoingPending),
174            v => Err(Error::invalid_data(v)),
175        }
176    }
177}
178
179#[derive(Clone, Copy, Debug, PartialEq)]
180pub enum MessagesListingVersion {
181    V1_0,
182    V1_1,
183}
184
185impl fmt::Display for MessagesListingVersion {
186    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
187        match self {
188            Self::V1_0 => write!(f, "1.0"),
189            Self::V1_1 => write!(f, "1.1"),
190        }
191    }
192}
193
194impl FromStr for MessagesListingVersion {
195    type Err = Error;
196    fn from_str(src: &str) -> Result<Self, Self::Err> {
197        match src {
198            "1.0" => Ok(Self::V1_0),
199            "1.1" => Ok(Self::V1_1),
200            _v => Err(Error::UnsupportedVersion),
201        }
202    }
203}
204
205/// List of attributes available for messages-listing v1.0 objects.
206/// See MAP v1.4.2 section 3.1.6.1 for details.
207#[derive(Debug)]
208pub enum AttributeV1_0 {
209    Handle(u64),
210    Subject(String),
211    Datetime(NaiveDateTime),
212    SenderName(String),
213    SenderAddressing(String),
214    ReplyToAddressing(String),
215    RecipientName(String),
216    RecipientAddressing(Vec<String>),
217    Type(MessageType),
218    // At least one of the below attributes shall be supported by the MSE.
219    Size(u64),
220    Text(bool),
221    ReceiptionStatus(ReceptionStatus),
222    AttachmentSize(u64),
223    Priority(bool),
224    Read(bool),
225    Sent(bool),
226    Protected(bool),
227}
228
229impl Hash for AttributeV1_0 {
230    fn hash<H: Hasher>(&self, state: &mut H) {
231        std::mem::discriminant(self).hash(state);
232    }
233}
234
235/// Note that partial equality for AttributeV1_0 indicates that two attributes
236/// are of the same type, but not necessarily the same value.
237impl PartialEq for AttributeV1_0 {
238    fn eq(&self, other: &AttributeV1_0) -> bool {
239        std::mem::discriminant(self) == std::mem::discriminant(other)
240    }
241}
242
243impl Eq for AttributeV1_0 {}
244
245impl TryFrom<&OwnedAttribute> for AttributeV1_0 {
246    type Error = Error;
247
248    fn try_from(src: &OwnedAttribute) -> Result<Self, Error> {
249        let attr_name = src.name.local_name.as_str();
250        let attribute = match attr_name {
251            HANDLE_ATTR => {
252                // MAP v1.4.2 section 3.1.6.1 - "handle" is the message handle in hexadecimal representation with up to 16
253                // digits; leading zero digits may be used so the MCE shall accept both handles with and without leading zeros.
254                if src.value.len() > 16 {
255                    return Err(Error::invalid_data(&src.value));
256                }
257                Self::Handle(u64::from_be_bytes(
258                    hex::decode(format!("{:0>16}", src.value.as_str()))
259                        .map_err(|e| Error::invalid_data(e))?
260                        .as_slice()
261                        .try_into()
262                        .unwrap(),
263                ))
264            }
265            SUBJECT_ATTR => {
266                // MAP v1.4.2 section 3.1.6.1 - "subject" parameter shall not exceed 256 bytes.
267                if src.value.len() > 256 {
268                    return Err(Error::invalid_data(&src.value));
269                }
270                Self::Subject(src.value.clone())
271            }
272            DATETIME_ATTR => Self::Datetime(
273                NaiveDateTime::parse_from_str(src.value.as_str(), ISO_8601_TIME_FORMAT)
274                    .map_err(|e| Error::invalid_data(e))?,
275            ),
276            // Below attributes have max byte data length 256 limit. See MAP v1.5.2 section 3.1.6.1 for details.
277            SENDER_NAME_ATTR => Self::SenderName(truncate_string(&src.value, 256)),
278            SENDER_ADDRESSING_ATTR => Self::SenderAddressing(truncate_string(&src.value, 256)),
279            REPLYTO_ADDRESSING_ATTR => Self::ReplyToAddressing(truncate_string(&src.value, 256)),
280            RECIPIENT_NAME_ATTR => Self::RecipientName(truncate_string(&src.value, 256)),
281            RECIPIENT_ADDRESSING_ATTR => {
282                // Recipients shall be separated by semicolon.
283                let mut recipients = Vec::new();
284                src.value.split(";").for_each(|r| recipients.push(r.to_string()));
285                Self::RecipientAddressing(recipients)
286            }
287            TYPE_ATTR => Self::Type(str::parse(src.value.as_str())?),
288            SIZE_ATTR | ATTACHMENT_SIZE_ATTR => {
289                let value =
290                    src.value.parse::<u64>().map_err(|_| Error::invalid_data(&src.value))?;
291                if attr_name == SIZE_ATTR {
292                    Self::Size(value)
293                } else
294                /* attr_name == ATTACHMENT_SIZE_ATTR */
295                {
296                    Self::AttachmentSize(value)
297                }
298            }
299            TEXT_ATTR => Self::Text(str_to_bool(&src.value)?),
300            PRIORITY_ATTR => Self::Priority(str_to_bool(&src.value)?),
301            READ_ATTR => Self::Read(str_to_bool(&src.value)?),
302            SENT_ATTR => Self::Sent(str_to_bool(&src.value)?),
303            PROTECTED_ATTR => Self::Protected(str_to_bool(&src.value)?),
304            RECEPTION_STATUS_ATTR => Self::ReceiptionStatus(str::parse(src.value.as_str())?),
305            val => return Err(Error::invalid_data(val)),
306        };
307        Ok(attribute)
308    }
309}
310
311impl AttributeV1_0 {
312    // Validate the attribute against the message listing version.
313    fn validate(&self) -> Result<(), Error> {
314        if let Self::RecipientAddressing(v) = self {
315            if v.len() == 0 {
316                return Err(Error::MissingData(RECIPIENT_ADDRESSING_ATTR.to_string()));
317            }
318        }
319        Ok(())
320    }
321
322    fn xml_attribute_name(&self) -> &'static str {
323        match self {
324            Self::Handle(_) => HANDLE_ATTR,
325            Self::Subject(_) => SUBJECT_ATTR,
326            Self::Datetime(_) => DATETIME_ATTR,
327            Self::SenderName(_) => SENDER_NAME_ATTR,
328            Self::SenderAddressing(_) => SENDER_ADDRESSING_ATTR,
329            Self::ReplyToAddressing(_) => REPLYTO_ADDRESSING_ATTR,
330            Self::RecipientName(_) => RECIPIENT_NAME_ATTR,
331            Self::RecipientAddressing(_) => RECIPIENT_ADDRESSING_ATTR,
332            Self::Type(_) => TYPE_ATTR,
333            Self::Size(_) => SIZE_ATTR,
334            Self::Text(_) => TEXT_ATTR,
335            Self::ReceiptionStatus(_) => RECEPTION_STATUS_ATTR,
336            Self::AttachmentSize(_) => ATTACHMENT_SIZE_ATTR,
337            Self::Priority(_) => PRIORITY_ATTR,
338            Self::Read(_) => READ_ATTR,
339            Self::Sent(_) => SENT_ATTR,
340            Self::Protected(_) => PROTECTED_ATTR,
341        }
342    }
343
344    fn xml_attribute_value(&self) -> String {
345        match self {
346            Self::Handle(v) => hex::encode(v.to_be_bytes()),
347            Self::Subject(v) => truncate_string(v, 256),
348            Self::Datetime(v) => v.format(ISO_8601_TIME_FORMAT).to_string(),
349            Self::SenderName(v) => v.clone(),
350            Self::SenderAddressing(v) => v.clone(),
351            Self::ReplyToAddressing(v) => v.clone(),
352            Self::RecipientName(v) => v.clone(),
353            Self::RecipientAddressing(v) => v.join(";"),
354            Self::Type(v) => v.to_string(),
355            Self::Size(v) => v.to_string(),
356            Self::Text(v)
357            | Self::Priority(v)
358            | Self::Read(v)
359            | Self::Sent(v)
360            | Self::Protected(v) => bool_to_string(*v),
361            Self::ReceiptionStatus(v) => v.to_string(),
362            Self::AttachmentSize(v) => v.to_string(),
363        }
364    }
365
366    fn validate_all(attrs: &HashSet<AttributeV1_0>) -> Result<(), Error> {
367        // See MAP v1.4.2 section 3.1.6.1 for details.
368        // We are checking if the attribute types exist at all in the hashset, so
369        // the values are irrelevant.
370        let required_attrs = vec![
371            AttributeV1_0::Handle(0),
372            AttributeV1_0::Subject(String::new()),
373            AttributeV1_0::Datetime(DateTime::from_timestamp(0, 0).unwrap().naive_utc()),
374            AttributeV1_0::RecipientAddressing(Vec::new()),
375            AttributeV1_0::Type(MessageType::Email),
376        ];
377        for a in &required_attrs {
378            if !attrs.contains(&a) {
379                return Err(Error::MissingData(a.xml_attribute_name().to_string()));
380            }
381        }
382
383        // At least one of these types shall be supported by the MSE.
384        let required_oneof: HashSet<AttributeV1_0> = HashSet::from([
385            AttributeV1_0::Size(0),
386            AttributeV1_0::Text(false),
387            AttributeV1_0::ReceiptionStatus(ReceptionStatus::Complete),
388            AttributeV1_0::AttachmentSize(0),
389            AttributeV1_0::Priority(false),
390            AttributeV1_0::Read(false),
391            AttributeV1_0::Sent(false),
392            AttributeV1_0::Protected(false),
393        ]);
394        if attrs.intersection(&required_oneof).next().is_none() {
395            return Err(Error::MissingData(format!(
396                "should have one of {:?}",
397                vec![
398                    SIZE_ATTR,
399                    TEXT_ATTR,
400                    RECEPTION_STATUS_ATTR,
401                    ATTACHMENT_SIZE_ATTR,
402                    PRIORITY_ATTR,
403                    READ_ATTR,
404                    SENT_ATTR,
405                    PROTECTED_ATTR,
406                ]
407            )));
408        }
409
410        for a in attrs {
411            a.validate()?;
412        }
413        Ok(())
414    }
415}
416
417/// Additional information unique to messages-listing v1.1.
418/// See MAP v1.4.2 section 3.1.6.2.
419#[derive(Debug)]
420pub enum AttributeV1_1 {
421    DeliveryStatus(DeliveryStatus),
422    ConversationId(u128),
423    ConversationName(String),
424    Direction(Direction),
425    AttachmentMimeTypes(Vec<String>),
426}
427
428impl Hash for AttributeV1_1 {
429    fn hash<H: Hasher>(&self, state: &mut H) {
430        std::mem::discriminant(self).hash(state);
431    }
432}
433
434/// Note that partial equality for AttributeV1_1 indicates that two attributes
435/// are of the same type, but not necessarily the same value.
436impl PartialEq for AttributeV1_1 {
437    fn eq(&self, other: &AttributeV1_1) -> bool {
438        std::mem::discriminant(self) == std::mem::discriminant(other)
439    }
440}
441
442impl Eq for AttributeV1_1 {}
443
444impl TryFrom<&OwnedAttribute> for AttributeV1_1 {
445    type Error = Error;
446
447    fn try_from(src: &OwnedAttribute) -> Result<Self, Error> {
448        let attr_name = src.name.local_name.as_str();
449        // See MAP v1.4.2 section 3.1.6.1 Messages-Listing Object.
450        match attr_name {
451            DELIVERY_STATUS_ATTR => Ok(Self::DeliveryStatus(str::parse(src.value.as_str())?)),
452            CONVERSATION_ID_ATTR => {
453                let id = hex::decode(src.value.as_str()).map_err(|e| Error::invalid_data(e))?;
454                if id.len() != 16 {
455                    return Err(Error::invalid_data(&src.value));
456                }
457                let bytes: &[u8; 16] = id[..].try_into().unwrap();
458                Ok(Self::ConversationId(u128::from_be_bytes(*bytes)))
459            }
460            CONVERSATION_NAME_ATTR => Ok(Self::ConversationName(src.value.to_string())),
461            DIRECTION_ATTR => Ok(Self::Direction(str::parse(src.value.as_str())?)),
462            ATTACHMENT_MIME_TYPES_ATTR => {
463                // Mime type shall be separated by comma.
464                let mut mime_types = Vec::new();
465                src.value.split(",").for_each(|t| mime_types.push(t.to_string()));
466                Ok(Self::AttachmentMimeTypes(mime_types))
467            }
468            val => Err(Error::invalid_data(val)),
469        }
470    }
471}
472
473impl AttributeV1_1 {
474    fn xml_attribute_name(&self) -> &'static str {
475        match self {
476            Self::DeliveryStatus(_) => DELIVERY_STATUS_ATTR,
477            Self::ConversationId(_) => CONVERSATION_ID_ATTR,
478            Self::ConversationName(_) => CONVERSATION_NAME_ATTR,
479            Self::Direction(_) => DIRECTION_ATTR,
480            Self::AttachmentMimeTypes(_) => ATTACHMENT_MIME_TYPES_ATTR,
481        }
482    }
483
484    fn xml_attribute_value(&self) -> String {
485        match self {
486            Self::DeliveryStatus(v) => v.to_string(),
487            Self::ConversationId(v) => hex::encode_upper(v.to_be_bytes()),
488            Self::ConversationName(v) => v.clone(),
489            Self::Direction(v) => v.to_string(),
490            Self::AttachmentMimeTypes(vals) => vals.join(","),
491        }
492    }
493
494    fn validate_all(attrs: &HashSet<AttributeV1_1>) -> Result<(), Error> {
495        // See MAP v1.4.2 section 3.1.6.2 for details.
496        let required_attrs: HashSet<AttributeV1_1> = HashSet::from([
497            AttributeV1_1::ConversationId(0),
498            AttributeV1_1::Direction(Direction::Incoming),
499        ]);
500        for a in required_attrs {
501            if !attrs.contains(&a) {
502                return Err(Error::MissingData(a.xml_attribute_name().to_string()));
503            }
504        }
505        Ok(())
506    }
507}
508
509#[derive(Debug, PartialEq)]
510pub enum Message {
511    V1_0 { attrs: HashSet<AttributeV1_0> },
512    V1_1 { attrs_v1_0: HashSet<AttributeV1_0>, attrs_v1_1: HashSet<AttributeV1_1> },
513}
514
515impl Message {
516    /// Creates a new v1.0 message.
517    fn v1_0(attrs: HashSet<AttributeV1_0>) -> Self {
518        Self::V1_0 { attrs }
519    }
520
521    /// Creates a new v1.1 message.
522    fn v1_1(attrs_v1_0: HashSet<AttributeV1_0>, attrs_v1_1: HashSet<AttributeV1_1>) -> Self {
523        Self::V1_1 { attrs_v1_0, attrs_v1_1 }
524    }
525
526    fn version(&self) -> MessagesListingVersion {
527        match self {
528            Message::V1_0 { .. } => MessagesListingVersion::V1_0,
529            Message::V1_1 { .. } => MessagesListingVersion::V1_1,
530        }
531    }
532
533    fn write<W: std::io::Write>(&self, writer: &mut EventWriter<W>) -> Result<(), Error> {
534        let mut builder = XmlWriteEvent::start_element(MESSAGE_ELEM);
535        let mut attributes: Vec<(&str, String)> = Vec::new();
536        match self {
537            Message::V1_0 { attrs } => {
538                attrs.iter().for_each(|a| {
539                    attributes.push((a.xml_attribute_name(), a.xml_attribute_value()))
540                });
541            }
542            Message::V1_1 { attrs_v1_0, attrs_v1_1 } => {
543                attrs_v1_0.iter().for_each(|a| {
544                    attributes.push((a.xml_attribute_name(), a.xml_attribute_value()))
545                });
546                attrs_v1_1.iter().for_each(|a| {
547                    attributes.push((a.xml_attribute_name(), a.xml_attribute_value()))
548                });
549            }
550        };
551        for a in &attributes {
552            builder = builder.attr(a.0, a.1.as_str());
553        }
554        writer.write(builder)?;
555        Ok(writer.write(XmlWriteEvent::end_element())?)
556    }
557
558    fn validate(&self) -> Result<(), Error> {
559        match self {
560            Message::V1_0 { attrs } => {
561                AttributeV1_0::validate_all(attrs)?;
562                match attrs.get(&AttributeV1_0::Type(MessageType::Im)).unwrap() {
563                    AttributeV1_0::Type(t) => {
564                        if *t == MessageType::Im {
565                            return Err(Error::invalid_data(t));
566                        }
567                    }
568                    _ => unreachable!(),
569                };
570            }
571            Message::V1_1 { attrs_v1_0, attrs_v1_1 } => {
572                AttributeV1_0::validate_all(attrs_v1_0)?;
573                AttributeV1_1::validate_all(attrs_v1_1)?;
574            }
575        };
576        Ok(())
577    }
578}
579
580impl TryFrom<(XmlEvent, MessagesListingVersion)> for Message {
581    type Error = Error;
582    fn try_from(src: (XmlEvent, MessagesListingVersion)) -> Result<Self, Error> {
583        let XmlEvent::StartElement { ref name, ref attributes, .. } = src.0 else {
584            return Err(Error::InvalidData(format!("{:?}", src)));
585        };
586        if name.local_name.as_str() != MESSAGE_ELEM {
587            return Err(Error::invalid_data(&name.local_name));
588        }
589        let mut attrs_v1_0 = HashSet::new();
590        let mut attrs_v1_1 = HashSet::new();
591        attributes.iter().try_for_each(|a| {
592            let new_insert = match AttributeV1_0::try_from(a) {
593                Ok(attr) => attrs_v1_0.insert(attr),
594                Err(e) => match src.1 {
595                    MessagesListingVersion::V1_0 => return Err(e),
596                    MessagesListingVersion::V1_1 => attrs_v1_1.insert(AttributeV1_1::try_from(a)?),
597                },
598            };
599            if !new_insert {
600                return Err(Error::DuplicateData(a.name.local_name.to_string()));
601            }
602            Ok(())
603        })?;
604        Ok(match src.1 {
605            MessagesListingVersion::V1_0 => Message::v1_0(attrs_v1_0),
606            MessagesListingVersion::V1_1 => Message::v1_1(attrs_v1_0, attrs_v1_1),
607        })
608    }
609}
610
611enum ParsedXmlEvent {
612    DocumentStart,
613    MessagesListingElement,
614    MessageElement(Message),
615}
616
617#[derive(Debug, PartialEq)]
618pub struct MessagesListing {
619    version: MessagesListingVersion,
620    messages: Vec<Message>,
621}
622
623impl MessagesListing {
624    fn new(version: MessagesListingVersion) -> MessagesListing {
625        MessagesListing { version, messages: Vec::new() }
626    }
627
628    // Given the XML StartElement, checks whether or not it is a valid folder
629    // listing element.
630    fn validate_messages_listing_element(
631        element: XmlEvent,
632    ) -> Result<MessagesListingVersion, Error> {
633        let XmlEvent::StartElement { ref name, ref attributes, .. } = element else {
634            return Err(Error::InvalidData(format!("{:?}", element)));
635        };
636
637        if name.local_name != MESSAGES_LISTING_ELEM {
638            return Err(Error::invalid_data(&name.local_name));
639        }
640
641        let version_attr = &attributes
642            .iter()
643            .find(|a| a.name.local_name == VERSION_ATTR)
644            .ok_or_else(|| Error::MissingData(VERSION_ATTR.to_string()))?
645            .value;
646        str::parse(version_attr.as_str())
647    }
648}
649
650impl Parser for MessagesListing {
651    type Error = Error;
652
653    /// Parses MessagesListing from raw bytes of XML data.
654    fn parse<R: std::io::prelude::Read>(buf: R) -> Result<Self, Self::Error> {
655        let mut reader = ParserConfig::new()
656            .ignore_comments(true)
657            .whitespace_to_characters(true)
658            .cdata_to_characters(true)
659            .trim_whitespace(true)
660            .create_reader(buf);
661        let mut prev = Vec::new();
662
663        // Process start of document.
664        match reader.next() {
665            Ok(XmlEvent::StartDocument { .. }) => {
666                prev.push(ParsedXmlEvent::DocumentStart);
667            }
668            Ok(element) => return Err(Error::InvalidData(format!("{:?}", element))),
669            Err(e) => return Err(Error::ReadXml(e)),
670        };
671
672        // Process start of folder listing element.
673        let mut xml_event = reader.next()?;
674        while matches!(xml_event, XmlEvent::Doctype { .. } | XmlEvent::Comment(_)) {
675            xml_event = reader.next()?;
676        }
677        let version = MessagesListing::validate_messages_listing_element(xml_event)?;
678
679        prev.push(ParsedXmlEvent::MessagesListingElement);
680        let mut messages_listing = MessagesListing::new(version);
681
682        // Process remaining elements.
683        let mut finished_document = false;
684        let mut finished_messages_listing = false;
685        while !finished_document {
686            // Could be either end of folder listing element,
687            let e = reader.next()?;
688            let invalid_elem_err = Err(Error::InvalidData(format!("{:?}", e)));
689            match e {
690                XmlEvent::StartElement { ref name, .. } => {
691                    match name.local_name.as_str() {
692                        MESSAGE_ELEM => prev.push(ParsedXmlEvent::MessageElement(
693                            (e, messages_listing.version).try_into()?,
694                        )),
695                        _ => return invalid_elem_err,
696                    };
697                }
698                XmlEvent::EndElement { ref name } => {
699                    let Some(parsed_elem) = prev.pop() else {
700                        return invalid_elem_err;
701                    };
702                    match name.local_name.as_str() {
703                        MESSAGES_LISTING_ELEM => {
704                            let ParsedXmlEvent::MessagesListingElement = parsed_elem else {
705                                return Err(Error::MissingData(format!(
706                                    "closing {MESSAGES_LISTING_ELEM}"
707                                )));
708                            };
709                            finished_messages_listing = true;
710                        }
711                        MESSAGE_ELEM => {
712                            let ParsedXmlEvent::MessageElement(m) = parsed_elem else {
713                                return Err(Error::MissingData(format!("closing {MESSAGE_ELEM}")));
714                            };
715                            let _ = m.validate()?;
716                            messages_listing.messages.push(m);
717                        }
718                        _ => return invalid_elem_err,
719                    };
720                }
721                XmlEvent::EndDocument => {
722                    if !finished_messages_listing {
723                        return Err(Error::MissingData(format!("closing {MESSAGES_LISTING_ELEM}")));
724                    }
725                    finished_document = true;
726                }
727                XmlEvent::Doctype { .. } | XmlEvent::Comment(_) => {}
728                _ => return invalid_elem_err,
729            }
730        }
731        Ok(messages_listing)
732    }
733}
734
735impl Builder for MessagesListing {
736    type Error = Error;
737
738    // Returns the document type of the raw bytes of data.
739    fn mime_type(&self) -> String {
740        "application/xml".to_string()
741    }
742
743    /// Builds self into raw bytes of the specific Document Type.
744    fn build<W: std::io::Write>(&self, buf: W) -> Result<(), Self::Error> {
745        let mut w = EmitterConfig::new()
746            .write_document_declaration(true)
747            .perform_indent(true)
748            .create_writer(buf);
749
750        // Begin `MAP-msg-listing` element.
751        let version = self.version.to_string();
752        let messages_listing = XmlWriteEvent::start_element(MESSAGES_LISTING_ELEM)
753            .attr(VERSION_ATTR, version.as_str());
754        w.write(messages_listing)?;
755
756        // Validate each message before writing it and ensure that all the messages have the same version.
757        if self.messages.len() > 0 {
758            let mut prev_version = self.messages[0].version();
759            self.messages.iter().try_for_each(|m| {
760                m.validate()?;
761                if m.version() != prev_version {
762                    return Err(Error::invalid_data(m.version()));
763                }
764                prev_version = m.version();
765                Ok(())
766            })?;
767        }
768
769        // Write each msg element.
770        self.messages.iter().try_for_each(|m| m.write(&mut w))?;
771        // End `MAP-msg-listing` element.
772        Ok(w.write(XmlWriteEvent::end_element())?)
773    }
774}
775
776#[cfg(test)]
777mod tests {
778    use super::*;
779
780    use chrono::{NaiveDate, NaiveTime};
781    use std::fs;
782    use std::io::Cursor;
783
784    #[fuchsia::test]
785    fn safe_truncate_string() {
786        const TEST_STR: &str = "Löwe 老虎 Léopard";
787
788        // Case 1. string is less than or equal to max length.
789        let res = truncate_string(&TEST_STR.to_string(), 200);
790        assert_eq!(TEST_STR.to_string(), res);
791
792        // Case 2. string is greater than the max length, but the max length is on the char boundary.
793        let res = truncate_string(&TEST_STR.to_string(), 6);
794        assert_eq!("Löwe ", res);
795        assert_eq!(6, res.len());
796
797        // Case 3. string is greater than max length, max length is not on the char boundary.
798        let res = truncate_string(&TEST_STR.to_string(), 8);
799        assert_eq!("Löwe ", res); // truncated to cloest char boundary from desired length.
800        assert_eq!(6, res.len());
801    }
802
803    #[fuchsia::test]
804    fn parse_empty_messages_listing_success() {
805        const V1_0_TEST_FILE: &str = "/pkg/data/sample_messages_listing_v1_0_1.xml";
806        let bytes = fs::read(V1_0_TEST_FILE).expect("should be ok");
807        let messages_listing = MessagesListing::parse(Cursor::new(bytes)).expect("should be ok");
808        assert_eq!(messages_listing, MessagesListing::new(MessagesListingVersion::V1_0));
809
810        const V1_1_TEST_FILE: &str = "/pkg/data/sample_messages_listing_v1_1_1.xml";
811        let bytes = fs::read(V1_1_TEST_FILE).expect("should be ok");
812        let messages_listing = MessagesListing::parse(Cursor::new(bytes)).expect("should be ok");
813        assert_eq!(messages_listing, MessagesListing::new(MessagesListingVersion::V1_1));
814    }
815
816    #[fuchsia::test]
817    fn parse_messages_listing_success() {
818        const V1_0_TEST_FILE: &str = "/pkg/data/sample_messages_listing_v1_0_2.xml";
819        let bytes = fs::read(V1_0_TEST_FILE).expect("should be ok");
820        let messages_listing = MessagesListing::parse(Cursor::new(bytes)).expect("should be ok");
821        assert_eq!(messages_listing.version, MessagesListingVersion::V1_0);
822        assert_eq!(messages_listing.messages.len(), 4);
823        assert_eq!(
824            messages_listing.messages[0],
825            Message::v1_0(HashSet::from([
826                AttributeV1_0::Handle(0x20000100001u64),
827                AttributeV1_0::Subject("Hello".to_string()),
828                AttributeV1_0::Datetime(NaiveDateTime::new(
829                    NaiveDate::from_ymd_opt(2007, 12, 13).unwrap(),
830                    NaiveTime::from_hms_opt(13, 05, 10).unwrap(),
831                )),
832                AttributeV1_0::SenderName("Jamie".to_string()),
833                AttributeV1_0::SenderAddressing("+1-987-6543210".to_string()),
834                AttributeV1_0::RecipientAddressing(vec!["+1-0123-456789".to_string()]),
835                AttributeV1_0::Type(MessageType::SmsGsm),
836                AttributeV1_0::Size(256u64),
837                AttributeV1_0::AttachmentSize(0u64),
838                AttributeV1_0::Priority(false),
839                AttributeV1_0::Read(true),
840                AttributeV1_0::Sent(false),
841                AttributeV1_0::Protected(false),
842            ]))
843        );
844        assert_eq!(
845            messages_listing.messages[1],
846            Message::v1_0(HashSet::from([
847                AttributeV1_0::Handle(0x20000100002u64),
848                AttributeV1_0::Subject("Guten Tag".to_string()),
849                AttributeV1_0::Datetime(NaiveDateTime::new(
850                    NaiveDate::from_ymd_opt(2007, 12, 14).unwrap(),
851                    NaiveTime::from_hms_opt(09, 22, 00).unwrap(),
852                )),
853                AttributeV1_0::SenderName("Dmitri".to_string()),
854                AttributeV1_0::SenderAddressing("8765432109".to_string()),
855                AttributeV1_0::RecipientAddressing(vec!["+49-9012-345678".to_string()]),
856                AttributeV1_0::Type(MessageType::SmsGsm),
857                AttributeV1_0::Size(512u64),
858                AttributeV1_0::AttachmentSize(3000u64),
859                AttributeV1_0::Priority(false),
860                AttributeV1_0::Read(false),
861                AttributeV1_0::Sent(true),
862                AttributeV1_0::Protected(false),
863            ]))
864        );
865        assert_eq!(
866            messages_listing.messages[2],
867            Message::v1_0(HashSet::from([
868                AttributeV1_0::Handle(0x20000100003u64),
869                AttributeV1_0::Subject("Ohayougozaimasu".to_string()),
870                AttributeV1_0::Datetime(NaiveDateTime::new(
871                    NaiveDate::from_ymd_opt(2007, 12, 15).unwrap(),
872                    NaiveTime::from_hms_opt(13, 43, 26).unwrap(),
873                )),
874                AttributeV1_0::SenderName("Andy".to_string()),
875                AttributeV1_0::SenderAddressing("+49-7654-321098".to_string()),
876                AttributeV1_0::RecipientAddressing(vec!["+49-89-01234567".to_string()]),
877                AttributeV1_0::Type(MessageType::SmsGsm),
878                AttributeV1_0::Size(256u64),
879                AttributeV1_0::AttachmentSize(0u64),
880                AttributeV1_0::Priority(false),
881                AttributeV1_0::Read(true),
882                AttributeV1_0::Sent(false),
883                AttributeV1_0::Protected(false),
884            ]))
885        );
886        assert_eq!(
887            messages_listing.messages[3],
888            Message::v1_0(HashSet::from([
889                AttributeV1_0::Handle(0x20000100000u64),
890                AttributeV1_0::Subject("Bonjour".to_string()),
891                AttributeV1_0::Datetime(NaiveDateTime::new(
892                    NaiveDate::from_ymd_opt(2007, 12, 15).unwrap(),
893                    NaiveTime::from_hms_opt(17, 12, 04).unwrap(),
894                )),
895                AttributeV1_0::SenderName("Marc".to_string()),
896                AttributeV1_0::SenderAddressing("marc@carworkinggroup.bluetooth".to_string()),
897                AttributeV1_0::RecipientAddressing(vec![
898                    "burch@carworkinggroup.bluetoot7".to_string()
899                ]),
900                AttributeV1_0::Type(MessageType::Email),
901                AttributeV1_0::Size(1032u64),
902                AttributeV1_0::AttachmentSize(0u64),
903                AttributeV1_0::Priority(true),
904                AttributeV1_0::Read(true),
905                AttributeV1_0::Sent(false),
906                AttributeV1_0::Protected(true),
907            ]))
908        );
909
910        const V1_1_TEST_FILE: &str = "/pkg/data/sample_messages_listing_v1_1_2.xml";
911        let bytes = fs::read(V1_1_TEST_FILE).expect("should be ok");
912        let messages_listing = MessagesListing::parse(Cursor::new(bytes)).expect("should be ok");
913        assert_eq!(messages_listing.version, MessagesListingVersion::V1_1);
914        assert_eq!(messages_listing.messages.len(), 2);
915        assert_eq!(
916            messages_listing.messages[0],
917            Message::v1_1(
918                HashSet::from([
919                    AttributeV1_0::Handle(0x20000100001u64),
920                    AttributeV1_0::Subject("Welcome Clara Nicole".to_string()),
921                    AttributeV1_0::Datetime(NaiveDateTime::new(
922                        NaiveDate::from_ymd_opt(2014, 07, 06).unwrap(),
923                        NaiveTime::from_hms_opt(09, 50, 00).unwrap(),
924                    )),
925                    AttributeV1_0::SenderName("Max".to_string()),
926                    AttributeV1_0::SenderAddressing("4924689753@s.whateverapp.net".to_string()),
927                    AttributeV1_0::RecipientAddressing(vec!["".to_string()]),
928                    AttributeV1_0::Type(MessageType::Im),
929                    AttributeV1_0::Size(256u64),
930                    AttributeV1_0::AttachmentSize(0u64),
931                    AttributeV1_0::Priority(false),
932                    AttributeV1_0::Read(false),
933                    AttributeV1_0::Sent(false),
934                    AttributeV1_0::Protected(false),
935                ]),
936                HashSet::from([
937                    AttributeV1_1::ConversationId(0xE1E2E3E4F1F2F3F4A1A2A3A4B1B2B3B4),
938                    AttributeV1_1::Direction(Direction::Incoming),
939                ]),
940            )
941        );
942        assert_eq!(
943            messages_listing.messages[1],
944            Message::v1_1(
945                HashSet::from([
946                    AttributeV1_0::Handle(0x20000100002u64),
947                    AttributeV1_0::Subject("What’s the progress Max?".to_string()),
948                    AttributeV1_0::Datetime(NaiveDateTime::new(
949                        NaiveDate::from_ymd_opt(2014, 07, 05).unwrap(),
950                        NaiveTime::from_hms_opt(09, 22, 00).unwrap(),
951                    )),
952                    AttributeV1_0::SenderName("Jonas".to_string()),
953                    AttributeV1_0::SenderAddressing("4913579864@s.whateverapp.net".to_string()),
954                    AttributeV1_0::RecipientAddressing(vec!["".to_string()]),
955                    AttributeV1_0::Type(MessageType::Im),
956                    AttributeV1_0::Size(512u64),
957                    AttributeV1_0::AttachmentSize(8671724u64),
958                    AttributeV1_0::Priority(false),
959                    AttributeV1_0::Read(true),
960                    AttributeV1_0::Sent(true),
961                    AttributeV1_0::Protected(false),
962                ]),
963                HashSet::from([
964                    AttributeV1_1::ConversationId(0xE1E2E3E4F1F2F3F4A1A2A3A4B1B2B3B4),
965                    AttributeV1_1::Direction(Direction::Incoming),
966                    AttributeV1_1::AttachmentMimeTypes(vec!["video/mpeg".to_string()]),
967                ]),
968            )
969        );
970    }
971
972    #[fuchsia::test]
973    fn parse_messages_listing_fail() {
974        let bad_sample_xml_files = vec![
975            "/pkg/data/bad_sample.xml",
976            "/pkg/data/bad_sample_messages_listing_v1_0_1.xml",
977            "/pkg/data/bad_sample_messages_listing_v1_0_2.xml",
978            "/pkg/data/bad_sample_messages_listing_v1_1_1.xml",
979            "/pkg/data/bad_sample_messages_listing_v1_1_2.xml",
980        ];
981
982        bad_sample_xml_files.iter().for_each(|f| {
983            let bytes = fs::read(f).expect("should be ok");
984            let _ = MessagesListing::parse(Cursor::new(bytes)).expect_err("should have failed");
985        });
986    }
987
988    #[fuchsia::test]
989    fn build_empty_messages_listing_success() {
990        // v1.0.
991        let empty_messages_listing = MessagesListing::new(MessagesListingVersion::V1_0);
992        let mut buf = Vec::new();
993        assert_eq!(empty_messages_listing.mime_type(), "application/xml");
994        empty_messages_listing.build(&mut buf).expect("should be ok");
995        assert_eq!(
996            empty_messages_listing,
997            MessagesListing::parse(Cursor::new(buf)).expect("should be ok")
998        );
999
1000        // v1.1.
1001        let empty_messages_listing = MessagesListing::new(MessagesListingVersion::V1_1);
1002        let mut buf = Vec::new();
1003        assert_eq!(empty_messages_listing.mime_type(), "application/xml");
1004        empty_messages_listing.build(&mut buf).expect("should be ok");
1005        assert_eq!(
1006            empty_messages_listing,
1007            MessagesListing::parse(Cursor::new(buf)).expect("should be ok")
1008        );
1009    }
1010
1011    #[fuchsia::test]
1012    fn build_messages_listing_success() {
1013        // v1.0.
1014        let messages_listing = MessagesListing {
1015            version: MessagesListingVersion::V1_0,
1016            messages: vec![Message::v1_0(HashSet::from([
1017                AttributeV1_0::Handle(0x20000100001u64),
1018                AttributeV1_0::Subject("Hello".to_string()),
1019                AttributeV1_0::Datetime(NaiveDateTime::new(
1020                    NaiveDate::from_ymd_opt(2007, 12, 13).unwrap(),
1021                    NaiveTime::from_hms_opt(13, 05, 10).unwrap(),
1022                )),
1023                AttributeV1_0::SenderName("Jamie".to_string()),
1024                AttributeV1_0::SenderAddressing("+1-987-6543210".to_string()),
1025                AttributeV1_0::RecipientAddressing(vec!["+1-0123-456789".to_string()]),
1026                AttributeV1_0::Type(MessageType::SmsGsm),
1027                AttributeV1_0::Size(256u64),
1028                AttributeV1_0::AttachmentSize(0u64),
1029                AttributeV1_0::Priority(false),
1030                AttributeV1_0::Read(true),
1031                AttributeV1_0::Sent(false),
1032                AttributeV1_0::Protected(false),
1033            ]))],
1034        };
1035        let mut buf = Vec::new();
1036        assert_eq!(messages_listing.mime_type(), "application/xml");
1037        messages_listing.build(&mut buf).expect("should have succeeded");
1038        assert_eq!(
1039            messages_listing,
1040            MessagesListing::parse(Cursor::new(buf)).expect("should be valid xml")
1041        );
1042
1043        // v1.1.
1044        let messages_listing = MessagesListing {
1045            version: MessagesListingVersion::V1_1,
1046            messages: vec![Message::v1_1(
1047                HashSet::from([
1048                    AttributeV1_0::Handle(0x20000100001u64),
1049                    AttributeV1_0::Subject("Welcome Clara Nicole".to_string()),
1050                    AttributeV1_0::Datetime(NaiveDateTime::new(
1051                        NaiveDate::from_ymd_opt(2014, 07, 06).unwrap(),
1052                        NaiveTime::from_hms_opt(09, 50, 00).unwrap(),
1053                    )),
1054                    AttributeV1_0::SenderName("Max".to_string()),
1055                    AttributeV1_0::SenderAddressing("4924689753@s.whateverapp.net".to_string()),
1056                    AttributeV1_0::RecipientAddressing(vec!["".to_string()]),
1057                    AttributeV1_0::Type(MessageType::Im),
1058                    AttributeV1_0::Size(256u64),
1059                    AttributeV1_0::AttachmentSize(0u64),
1060                    AttributeV1_0::Priority(false),
1061                    AttributeV1_0::Read(false),
1062                    AttributeV1_0::Sent(false),
1063                    AttributeV1_0::Protected(false),
1064                ]),
1065                HashSet::from([
1066                    AttributeV1_1::ConversationId(0xE1E2E3E4F1F2F3F4A1A2A3A4B1B2B3B4),
1067                    AttributeV1_1::Direction(Direction::Incoming),
1068                ]),
1069            )],
1070        };
1071        let mut buf = Vec::new();
1072        assert_eq!(messages_listing.mime_type(), "application/xml");
1073        messages_listing.build(&mut buf).expect("should be ok");
1074        assert_eq!(
1075            messages_listing,
1076            MessagesListing::parse(Cursor::new(buf)).expect("should be ok")
1077        );
1078    }
1079
1080    #[fuchsia::test]
1081    fn build_messages_listing_fail() {
1082        // Inconsistent message versions.
1083        let messages_listing = MessagesListing {
1084            version: MessagesListingVersion::V1_0,
1085            messages: vec![
1086                Message::v1_0(HashSet::from([
1087                    AttributeV1_0::Handle(0x20000100001u64),
1088                    AttributeV1_0::Subject("Hello".to_string()),
1089                    AttributeV1_0::Datetime(NaiveDateTime::new(
1090                        NaiveDate::from_ymd_opt(2007, 12, 13).unwrap(),
1091                        NaiveTime::from_hms_opt(13, 05, 10).unwrap(),
1092                    )),
1093                    AttributeV1_0::SenderName("Jamie".to_string()),
1094                    AttributeV1_0::SenderAddressing("+1-987-6543210".to_string()),
1095                    AttributeV1_0::RecipientAddressing(vec!["+1-0123-456789".to_string()]),
1096                    AttributeV1_0::Type(MessageType::SmsGsm),
1097                    AttributeV1_0::Size(256u64),
1098                    AttributeV1_0::AttachmentSize(0u64),
1099                    AttributeV1_0::Priority(false),
1100                    AttributeV1_0::Read(true),
1101                    AttributeV1_0::Sent(false),
1102                    AttributeV1_0::Protected(false),
1103                ])),
1104                Message::v1_1(
1105                    HashSet::from([
1106                        AttributeV1_0::Handle(0x20000100001u64),
1107                        AttributeV1_0::Subject("Welcome Clara Nicole".to_string()),
1108                        AttributeV1_0::Datetime(NaiveDateTime::new(
1109                            NaiveDate::from_ymd_opt(2014, 07, 06).unwrap(),
1110                            NaiveTime::from_hms_opt(09, 50, 00).unwrap(),
1111                        )),
1112                        AttributeV1_0::SenderName("Max".to_string()),
1113                        AttributeV1_0::SenderAddressing("4924689753@s.whateverapp.net".to_string()),
1114                        AttributeV1_0::RecipientAddressing(vec!["".to_string()]),
1115                        AttributeV1_0::Type(MessageType::Im),
1116                        AttributeV1_0::Size(256u64),
1117                        AttributeV1_0::AttachmentSize(0u64),
1118                        AttributeV1_0::Priority(false),
1119                        AttributeV1_0::Read(false),
1120                        AttributeV1_0::Sent(false),
1121                        AttributeV1_0::Protected(false),
1122                    ]),
1123                    HashSet::from([
1124                        AttributeV1_1::ConversationId(0xE1E2E3E4F1F2F3F4A1A2A3A4B1B2B3B4),
1125                        AttributeV1_1::Direction(Direction::Incoming),
1126                    ]),
1127                ),
1128            ],
1129        };
1130        let mut buf = Vec::new();
1131        assert_eq!(messages_listing.mime_type(), "application/xml");
1132        let _ = messages_listing.build(&mut buf).expect_err("should have failed");
1133    }
1134}