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