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 chrono::DateTime;
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::attribute::OwnedAttribute;
13use xml::reader::{ParserConfig, XmlEvent};
14use xml::writer::{EmitterConfig, XmlEvent as XmlWriteEvent};
15use xml::EventWriter;
16
17use crate::packets::{bool_to_string, str_to_bool, truncate_string, ISO_8601_TIME_FORMAT};
18use crate::MessageType;
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 xml_event = reader.next()?;
674        let version = MessagesListing::validate_messages_listing_element(xml_event)?;
675
676        prev.push(ParsedXmlEvent::MessagesListingElement);
677        let mut messages_listing = MessagesListing::new(version);
678
679        // Process remaining elements.
680        let mut finished_document = false;
681        let mut finished_messages_listing = false;
682        while !finished_document {
683            // Could be either end of folder listing element,
684            let e = reader.next()?;
685            let invalid_elem_err = Err(Error::InvalidData(format!("{:?}", e)));
686            match e {
687                XmlEvent::StartElement { ref name, .. } => {
688                    match name.local_name.as_str() {
689                        MESSAGE_ELEM => prev.push(ParsedXmlEvent::MessageElement(
690                            (e, messages_listing.version).try_into()?,
691                        )),
692                        _ => return invalid_elem_err,
693                    };
694                }
695                XmlEvent::EndElement { ref name } => {
696                    let Some(parsed_elem) = prev.pop() else {
697                        return invalid_elem_err;
698                    };
699                    match name.local_name.as_str() {
700                        MESSAGES_LISTING_ELEM => {
701                            let ParsedXmlEvent::MessagesListingElement = parsed_elem else {
702                                return Err(Error::MissingData(format!(
703                                    "closing {MESSAGES_LISTING_ELEM}"
704                                )));
705                            };
706                            finished_messages_listing = true;
707                        }
708                        MESSAGE_ELEM => {
709                            let ParsedXmlEvent::MessageElement(m) = parsed_elem else {
710                                return Err(Error::MissingData(format!("closing {MESSAGE_ELEM}")));
711                            };
712                            let _ = m.validate()?;
713                            messages_listing.messages.push(m);
714                        }
715                        _ => return invalid_elem_err,
716                    };
717                }
718                XmlEvent::EndDocument => {
719                    if !finished_messages_listing {
720                        return Err(Error::MissingData(format!("closing {MESSAGES_LISTING_ELEM}")));
721                    }
722                    finished_document = true;
723                }
724                _ => return invalid_elem_err,
725            }
726        }
727        Ok(messages_listing)
728    }
729}
730
731impl Builder for MessagesListing {
732    type Error = Error;
733
734    // Returns the document type of the raw bytes of data.
735    fn mime_type(&self) -> String {
736        "application/xml".to_string()
737    }
738
739    /// Builds self into raw bytes of the specific Document Type.
740    fn build<W: std::io::Write>(&self, buf: W) -> Result<(), Self::Error> {
741        let mut w = EmitterConfig::new()
742            .write_document_declaration(true)
743            .perform_indent(true)
744            .create_writer(buf);
745
746        // Begin `MAP-msg-listing` element.
747        let version = self.version.to_string();
748        let messages_listing = XmlWriteEvent::start_element(MESSAGES_LISTING_ELEM)
749            .attr(VERSION_ATTR, version.as_str());
750        w.write(messages_listing)?;
751
752        // Validate each message before writing it and ensure that all the messages have the same version.
753        if self.messages.len() > 0 {
754            let mut prev_version = self.messages[0].version();
755            self.messages.iter().try_for_each(|m| {
756                m.validate()?;
757                if m.version() != prev_version {
758                    return Err(Error::invalid_data(m.version()));
759                }
760                prev_version = m.version();
761                Ok(())
762            })?;
763        }
764
765        // Write each msg element.
766        self.messages.iter().try_for_each(|m| m.write(&mut w))?;
767        // End `MAP-msg-listing` element.
768        Ok(w.write(XmlWriteEvent::end_element())?)
769    }
770}
771
772#[cfg(test)]
773mod tests {
774    use super::*;
775
776    use chrono::{NaiveDate, NaiveTime};
777    use std::fs;
778    use std::io::Cursor;
779
780    #[fuchsia::test]
781    fn safe_truncate_string() {
782        const TEST_STR: &str = "Löwe 老虎 Léopard";
783
784        // Case 1. string is less than or equal to max length.
785        let res = truncate_string(&TEST_STR.to_string(), 200);
786        assert_eq!(TEST_STR.to_string(), res);
787
788        // Case 2. string is greater than the max length, but the max length is on the char boundary.
789        let res = truncate_string(&TEST_STR.to_string(), 6);
790        assert_eq!("Löwe ", res);
791        assert_eq!(6, res.len());
792
793        // Case 3. string is greater than max length, max length is not on the char boundary.
794        let res = truncate_string(&TEST_STR.to_string(), 8);
795        assert_eq!("Löwe ", res); // truncated to cloest char boundary from desired length.
796        assert_eq!(6, res.len());
797    }
798
799    #[fuchsia::test]
800    fn parse_empty_messages_listing_success() {
801        const V1_0_TEST_FILE: &str = "/pkg/data/sample_messages_listing_v1_0_1.xml";
802        let bytes = fs::read(V1_0_TEST_FILE).expect("should be ok");
803        let messages_listing = MessagesListing::parse(Cursor::new(bytes)).expect("should be ok");
804        assert_eq!(messages_listing, MessagesListing::new(MessagesListingVersion::V1_0));
805
806        const V1_1_TEST_FILE: &str = "/pkg/data/sample_messages_listing_v1_1_1.xml";
807        let bytes = fs::read(V1_1_TEST_FILE).expect("should be ok");
808        let messages_listing = MessagesListing::parse(Cursor::new(bytes)).expect("should be ok");
809        assert_eq!(messages_listing, MessagesListing::new(MessagesListingVersion::V1_1));
810    }
811
812    #[fuchsia::test]
813    fn parse_messages_listing_success() {
814        const V1_0_TEST_FILE: &str = "/pkg/data/sample_messages_listing_v1_0_2.xml";
815        let bytes = fs::read(V1_0_TEST_FILE).expect("should be ok");
816        let messages_listing = MessagesListing::parse(Cursor::new(bytes)).expect("should be ok");
817        assert_eq!(messages_listing.version, MessagesListingVersion::V1_0);
818        assert_eq!(messages_listing.messages.len(), 4);
819        assert_eq!(
820            messages_listing.messages[0],
821            Message::v1_0(HashSet::from([
822                AttributeV1_0::Handle(0x20000100001u64),
823                AttributeV1_0::Subject("Hello".to_string()),
824                AttributeV1_0::Datetime(NaiveDateTime::new(
825                    NaiveDate::from_ymd_opt(2007, 12, 13).unwrap(),
826                    NaiveTime::from_hms_opt(13, 05, 10).unwrap(),
827                )),
828                AttributeV1_0::SenderName("Jamie".to_string()),
829                AttributeV1_0::SenderAddressing("+1-987-6543210".to_string()),
830                AttributeV1_0::RecipientAddressing(vec!["+1-0123-456789".to_string()]),
831                AttributeV1_0::Type(MessageType::SmsGsm),
832                AttributeV1_0::Size(256u64),
833                AttributeV1_0::AttachmentSize(0u64),
834                AttributeV1_0::Priority(false),
835                AttributeV1_0::Read(true),
836                AttributeV1_0::Sent(false),
837                AttributeV1_0::Protected(false),
838            ]))
839        );
840        assert_eq!(
841            messages_listing.messages[1],
842            Message::v1_0(HashSet::from([
843                AttributeV1_0::Handle(0x20000100002u64),
844                AttributeV1_0::Subject("Guten Tag".to_string()),
845                AttributeV1_0::Datetime(NaiveDateTime::new(
846                    NaiveDate::from_ymd_opt(2007, 12, 14).unwrap(),
847                    NaiveTime::from_hms_opt(09, 22, 00).unwrap(),
848                )),
849                AttributeV1_0::SenderName("Dmitri".to_string()),
850                AttributeV1_0::SenderAddressing("8765432109".to_string()),
851                AttributeV1_0::RecipientAddressing(vec!["+49-9012-345678".to_string()]),
852                AttributeV1_0::Type(MessageType::SmsGsm),
853                AttributeV1_0::Size(512u64),
854                AttributeV1_0::AttachmentSize(3000u64),
855                AttributeV1_0::Priority(false),
856                AttributeV1_0::Read(false),
857                AttributeV1_0::Sent(true),
858                AttributeV1_0::Protected(false),
859            ]))
860        );
861        assert_eq!(
862            messages_listing.messages[2],
863            Message::v1_0(HashSet::from([
864                AttributeV1_0::Handle(0x20000100003u64),
865                AttributeV1_0::Subject("Ohayougozaimasu".to_string()),
866                AttributeV1_0::Datetime(NaiveDateTime::new(
867                    NaiveDate::from_ymd_opt(2007, 12, 15).unwrap(),
868                    NaiveTime::from_hms_opt(13, 43, 26).unwrap(),
869                )),
870                AttributeV1_0::SenderName("Andy".to_string()),
871                AttributeV1_0::SenderAddressing("+49-7654-321098".to_string()),
872                AttributeV1_0::RecipientAddressing(vec!["+49-89-01234567".to_string()]),
873                AttributeV1_0::Type(MessageType::SmsGsm),
874                AttributeV1_0::Size(256u64),
875                AttributeV1_0::AttachmentSize(0u64),
876                AttributeV1_0::Priority(false),
877                AttributeV1_0::Read(true),
878                AttributeV1_0::Sent(false),
879                AttributeV1_0::Protected(false),
880            ]))
881        );
882        assert_eq!(
883            messages_listing.messages[3],
884            Message::v1_0(HashSet::from([
885                AttributeV1_0::Handle(0x20000100000u64),
886                AttributeV1_0::Subject("Bonjour".to_string()),
887                AttributeV1_0::Datetime(NaiveDateTime::new(
888                    NaiveDate::from_ymd_opt(2007, 12, 15).unwrap(),
889                    NaiveTime::from_hms_opt(17, 12, 04).unwrap(),
890                )),
891                AttributeV1_0::SenderName("Marc".to_string()),
892                AttributeV1_0::SenderAddressing("marc@carworkinggroup.bluetooth".to_string()),
893                AttributeV1_0::RecipientAddressing(vec![
894                    "burch@carworkinggroup.bluetoot7".to_string()
895                ]),
896                AttributeV1_0::Type(MessageType::Email),
897                AttributeV1_0::Size(1032u64),
898                AttributeV1_0::AttachmentSize(0u64),
899                AttributeV1_0::Priority(true),
900                AttributeV1_0::Read(true),
901                AttributeV1_0::Sent(false),
902                AttributeV1_0::Protected(true),
903            ]))
904        );
905
906        const V1_1_TEST_FILE: &str = "/pkg/data/sample_messages_listing_v1_1_2.xml";
907        let bytes = fs::read(V1_1_TEST_FILE).expect("should be ok");
908        let messages_listing = MessagesListing::parse(Cursor::new(bytes)).expect("should be ok");
909        assert_eq!(messages_listing.version, MessagesListingVersion::V1_1);
910        assert_eq!(messages_listing.messages.len(), 2);
911        assert_eq!(
912            messages_listing.messages[0],
913            Message::v1_1(
914                HashSet::from([
915                    AttributeV1_0::Handle(0x20000100001u64),
916                    AttributeV1_0::Subject("Welcome Clara Nicole".to_string()),
917                    AttributeV1_0::Datetime(NaiveDateTime::new(
918                        NaiveDate::from_ymd_opt(2014, 07, 06).unwrap(),
919                        NaiveTime::from_hms_opt(09, 50, 00).unwrap(),
920                    )),
921                    AttributeV1_0::SenderName("Max".to_string()),
922                    AttributeV1_0::SenderAddressing("4924689753@s.whateverapp.net".to_string()),
923                    AttributeV1_0::RecipientAddressing(vec!["".to_string()]),
924                    AttributeV1_0::Type(MessageType::Im),
925                    AttributeV1_0::Size(256u64),
926                    AttributeV1_0::AttachmentSize(0u64),
927                    AttributeV1_0::Priority(false),
928                    AttributeV1_0::Read(false),
929                    AttributeV1_0::Sent(false),
930                    AttributeV1_0::Protected(false),
931                ]),
932                HashSet::from([
933                    AttributeV1_1::ConversationId(0xE1E2E3E4F1F2F3F4A1A2A3A4B1B2B3B4),
934                    AttributeV1_1::Direction(Direction::Incoming),
935                ]),
936            )
937        );
938        assert_eq!(
939            messages_listing.messages[1],
940            Message::v1_1(
941                HashSet::from([
942                    AttributeV1_0::Handle(0x20000100002u64),
943                    AttributeV1_0::Subject("What’s the progress Max?".to_string()),
944                    AttributeV1_0::Datetime(NaiveDateTime::new(
945                        NaiveDate::from_ymd_opt(2014, 07, 05).unwrap(),
946                        NaiveTime::from_hms_opt(09, 22, 00).unwrap(),
947                    )),
948                    AttributeV1_0::SenderName("Jonas".to_string()),
949                    AttributeV1_0::SenderAddressing("4913579864@s.whateverapp.net".to_string()),
950                    AttributeV1_0::RecipientAddressing(vec!["".to_string()]),
951                    AttributeV1_0::Type(MessageType::Im),
952                    AttributeV1_0::Size(512u64),
953                    AttributeV1_0::AttachmentSize(8671724u64),
954                    AttributeV1_0::Priority(false),
955                    AttributeV1_0::Read(true),
956                    AttributeV1_0::Sent(true),
957                    AttributeV1_0::Protected(false),
958                ]),
959                HashSet::from([
960                    AttributeV1_1::ConversationId(0xE1E2E3E4F1F2F3F4A1A2A3A4B1B2B3B4),
961                    AttributeV1_1::Direction(Direction::Incoming),
962                    AttributeV1_1::AttachmentMimeTypes(vec!["video/mpeg".to_string()]),
963                ]),
964            )
965        );
966    }
967
968    #[fuchsia::test]
969    fn parse_messages_listing_fail() {
970        let bad_sample_xml_files = vec![
971            "/pkg/data/bad_sample.xml",
972            "/pkg/data/bad_sample_messages_listing_v1_0_1.xml",
973            "/pkg/data/bad_sample_messages_listing_v1_0_2.xml",
974            "/pkg/data/bad_sample_messages_listing_v1_1_1.xml",
975            "/pkg/data/bad_sample_messages_listing_v1_1_2.xml",
976        ];
977
978        bad_sample_xml_files.iter().for_each(|f| {
979            let bytes = fs::read(f).expect("should be ok");
980            let _ = MessagesListing::parse(Cursor::new(bytes)).expect_err("should have failed");
981        });
982    }
983
984    #[fuchsia::test]
985    fn build_empty_messages_listing_success() {
986        // v1.0.
987        let empty_messages_listing = MessagesListing::new(MessagesListingVersion::V1_0);
988        let mut buf = Vec::new();
989        assert_eq!(empty_messages_listing.mime_type(), "application/xml");
990        empty_messages_listing.build(&mut buf).expect("should be ok");
991        assert_eq!(
992            empty_messages_listing,
993            MessagesListing::parse(Cursor::new(buf)).expect("should be ok")
994        );
995
996        // v1.1.
997        let empty_messages_listing = MessagesListing::new(MessagesListingVersion::V1_1);
998        let mut buf = Vec::new();
999        assert_eq!(empty_messages_listing.mime_type(), "application/xml");
1000        empty_messages_listing.build(&mut buf).expect("should be ok");
1001        assert_eq!(
1002            empty_messages_listing,
1003            MessagesListing::parse(Cursor::new(buf)).expect("should be ok")
1004        );
1005    }
1006
1007    #[fuchsia::test]
1008    fn build_messages_listing_success() {
1009        // v1.0.
1010        let messages_listing = MessagesListing {
1011            version: MessagesListingVersion::V1_0,
1012            messages: vec![Message::v1_0(HashSet::from([
1013                AttributeV1_0::Handle(0x20000100001u64),
1014                AttributeV1_0::Subject("Hello".to_string()),
1015                AttributeV1_0::Datetime(NaiveDateTime::new(
1016                    NaiveDate::from_ymd_opt(2007, 12, 13).unwrap(),
1017                    NaiveTime::from_hms_opt(13, 05, 10).unwrap(),
1018                )),
1019                AttributeV1_0::SenderName("Jamie".to_string()),
1020                AttributeV1_0::SenderAddressing("+1-987-6543210".to_string()),
1021                AttributeV1_0::RecipientAddressing(vec!["+1-0123-456789".to_string()]),
1022                AttributeV1_0::Type(MessageType::SmsGsm),
1023                AttributeV1_0::Size(256u64),
1024                AttributeV1_0::AttachmentSize(0u64),
1025                AttributeV1_0::Priority(false),
1026                AttributeV1_0::Read(true),
1027                AttributeV1_0::Sent(false),
1028                AttributeV1_0::Protected(false),
1029            ]))],
1030        };
1031        let mut buf = Vec::new();
1032        assert_eq!(messages_listing.mime_type(), "application/xml");
1033        messages_listing.build(&mut buf).expect("should have succeeded");
1034        assert_eq!(
1035            messages_listing,
1036            MessagesListing::parse(Cursor::new(buf)).expect("should be valid xml")
1037        );
1038
1039        // v1.1.
1040        let messages_listing = MessagesListing {
1041            version: MessagesListingVersion::V1_1,
1042            messages: vec![Message::v1_1(
1043                HashSet::from([
1044                    AttributeV1_0::Handle(0x20000100001u64),
1045                    AttributeV1_0::Subject("Welcome Clara Nicole".to_string()),
1046                    AttributeV1_0::Datetime(NaiveDateTime::new(
1047                        NaiveDate::from_ymd_opt(2014, 07, 06).unwrap(),
1048                        NaiveTime::from_hms_opt(09, 50, 00).unwrap(),
1049                    )),
1050                    AttributeV1_0::SenderName("Max".to_string()),
1051                    AttributeV1_0::SenderAddressing("4924689753@s.whateverapp.net".to_string()),
1052                    AttributeV1_0::RecipientAddressing(vec!["".to_string()]),
1053                    AttributeV1_0::Type(MessageType::Im),
1054                    AttributeV1_0::Size(256u64),
1055                    AttributeV1_0::AttachmentSize(0u64),
1056                    AttributeV1_0::Priority(false),
1057                    AttributeV1_0::Read(false),
1058                    AttributeV1_0::Sent(false),
1059                    AttributeV1_0::Protected(false),
1060                ]),
1061                HashSet::from([
1062                    AttributeV1_1::ConversationId(0xE1E2E3E4F1F2F3F4A1A2A3A4B1B2B3B4),
1063                    AttributeV1_1::Direction(Direction::Incoming),
1064                ]),
1065            )],
1066        };
1067        let mut buf = Vec::new();
1068        assert_eq!(messages_listing.mime_type(), "application/xml");
1069        messages_listing.build(&mut buf).expect("should be ok");
1070        assert_eq!(
1071            messages_listing,
1072            MessagesListing::parse(Cursor::new(buf)).expect("should be ok")
1073        );
1074    }
1075
1076    #[fuchsia::test]
1077    fn build_messages_listing_fail() {
1078        // Inconsistent message versions.
1079        let messages_listing = MessagesListing {
1080            version: MessagesListingVersion::V1_0,
1081            messages: vec![
1082                Message::v1_0(HashSet::from([
1083                    AttributeV1_0::Handle(0x20000100001u64),
1084                    AttributeV1_0::Subject("Hello".to_string()),
1085                    AttributeV1_0::Datetime(NaiveDateTime::new(
1086                        NaiveDate::from_ymd_opt(2007, 12, 13).unwrap(),
1087                        NaiveTime::from_hms_opt(13, 05, 10).unwrap(),
1088                    )),
1089                    AttributeV1_0::SenderName("Jamie".to_string()),
1090                    AttributeV1_0::SenderAddressing("+1-987-6543210".to_string()),
1091                    AttributeV1_0::RecipientAddressing(vec!["+1-0123-456789".to_string()]),
1092                    AttributeV1_0::Type(MessageType::SmsGsm),
1093                    AttributeV1_0::Size(256u64),
1094                    AttributeV1_0::AttachmentSize(0u64),
1095                    AttributeV1_0::Priority(false),
1096                    AttributeV1_0::Read(true),
1097                    AttributeV1_0::Sent(false),
1098                    AttributeV1_0::Protected(false),
1099                ])),
1100                Message::v1_1(
1101                    HashSet::from([
1102                        AttributeV1_0::Handle(0x20000100001u64),
1103                        AttributeV1_0::Subject("Welcome Clara Nicole".to_string()),
1104                        AttributeV1_0::Datetime(NaiveDateTime::new(
1105                            NaiveDate::from_ymd_opt(2014, 07, 06).unwrap(),
1106                            NaiveTime::from_hms_opt(09, 50, 00).unwrap(),
1107                        )),
1108                        AttributeV1_0::SenderName("Max".to_string()),
1109                        AttributeV1_0::SenderAddressing("4924689753@s.whateverapp.net".to_string()),
1110                        AttributeV1_0::RecipientAddressing(vec!["".to_string()]),
1111                        AttributeV1_0::Type(MessageType::Im),
1112                        AttributeV1_0::Size(256u64),
1113                        AttributeV1_0::AttachmentSize(0u64),
1114                        AttributeV1_0::Priority(false),
1115                        AttributeV1_0::Read(false),
1116                        AttributeV1_0::Sent(false),
1117                        AttributeV1_0::Protected(false),
1118                    ]),
1119                    HashSet::from([
1120                        AttributeV1_1::ConversationId(0xE1E2E3E4F1F2F3F4A1A2A3A4B1B2B3B4),
1121                        AttributeV1_1::Direction(Direction::Incoming),
1122                    ]),
1123                ),
1124            ],
1125        };
1126        let mut buf = Vec::new();
1127        assert_eq!(messages_listing.mime_type(), "application/xml");
1128        let _ = messages_listing.build(&mut buf).expect_err("should have failed");
1129    }
1130}