Skip to main content

bt_map/packets/
event_report.rs

1// Copyright 2024 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 fidl_fuchsia_bluetooth_map::{Audience, Notification, NotificationType};
7use objects::{Builder, ObexObjectError as Error, Parser};
8use std::collections::HashSet;
9use std::fmt;
10use std::str::FromStr;
11use xml::EventWriter;
12use xml::attribute::OwnedAttribute;
13use xml::reader::{ParserConfig, XmlEvent};
14use xml::writer::{EmitterConfig, XmlEvent as XmlWriteEvent};
15
16use crate::MessageType;
17use crate::packets::{ISO_8601_TIME_FORMAT, bool_to_string, str_to_bool, truncate_string};
18
19// From MAP v1.4.2 section 3.1.7 Event-Report Object:
20//
21// <!DTD for the OBEX MAP-Event-Report object--> <!DOCTYPE MAP-eventreport [
22// <!ELEMENT MAP-event-report ( event ) >
23// <!ATTLIST MAP-event-report version CDATA #FIXED "1.1">
24// <!ELEMENT event EMPTY>
25// <!ATTLIST event
26// type CDATA #REQUIRED
27// handle CDATA #IMPLIED
28// folder CDATA #IMPLIED
29// old_folder CDATA #IMPLIED
30// msg_type CDATA #IMPLIED
31// datetime CDATA #IMPLIED
32// subject CDATA #IMPLIED
33// sender_name CDATA #IMPLIED
34// priority CDATA #IMPLIED
35// >
36// ]>
37
38const EVENT_ELEM: &str = "event";
39const EVENT_REPORT_ELEM: &str = "MAP-event-report";
40
41const VERSION_ATTR: &str = "version";
42
43const TYPE_ATTR: &str = "type";
44const HANDLE_ATTR: &str = "handle";
45const FOLDER_ATTR: &str = "folder";
46const OLD_FOLDER_ATTR: &str = "old_folder";
47const MSG_TYPE_ATTR: &str = "msg_type";
48
49// V1.1 specific attributes.
50const DATETIME_ATTR: &str = "datetime";
51const SUBJECT_ATTR: &str = "subject";
52const SENDER_NAME_ATTR: &str = "sender_name";
53const PRIORITY_ATTR: &str = "priority";
54
55/// Type of MAP event report. See MAP v1.4.2 Table 3.2 for details.
56/// Not all event report types form the spec are represented since
57/// we don't expect to observe the events.
58#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
59pub enum Type {
60    NewMessage,
61    DeliverySuccess,
62    SendingSuccess,
63    DeliveryFailure,
64    SendingFailure,
65    MessageDeleted,
66    MessageShift,
67    ReadStatusChanged,
68}
69
70impl fmt::Display for Type {
71    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
72        let str_value = match self {
73            Type::NewMessage => "NewMessage",
74            Type::DeliverySuccess => "DeliverySuccess",
75            Type::SendingSuccess => "SendingSuccess",
76            Type::DeliveryFailure => "DeliveryFailure",
77            Type::SendingFailure => "SendingFailure",
78            Type::MessageDeleted => "MessageDeleted",
79            Type::MessageShift => "MessageShift",
80            Type::ReadStatusChanged => "ReadStatusChanged",
81        };
82
83        write!(f, "{}", str_value)
84    }
85}
86
87impl FromStr for Type {
88    type Err = Error;
89    fn from_str(src: &str) -> Result<Self, Self::Err> {
90        let val = match src {
91            "NewMessage" => Self::NewMessage,
92            "DeliverySuccess" => Self::DeliverySuccess,
93            "SendingSuccess" => Self::SendingSuccess,
94            "SendingFailure" => Self::DeliveryFailure,
95            "MessageDeleted" => Self::MessageDeleted,
96            "MessageShift" => Self::MessageShift,
97            "ReadStatusChanged" => Self::ReadStatusChanged,
98            v => return Err(Error::invalid_data(v)),
99        };
100        Ok(val)
101    }
102}
103
104impl From<Type> for NotificationType {
105    fn from(value: Type) -> Self {
106        match value {
107            Type::NewMessage => NotificationType::NewMessage,
108            Type::DeliverySuccess => NotificationType::DeliverySuccess,
109            Type::SendingSuccess => NotificationType::SendingSuccess,
110            Type::DeliveryFailure => NotificationType::DeliveryFailure,
111            Type::SendingFailure => NotificationType::SendingFailure,
112            Type::MessageDeleted => NotificationType::MessageDeleted,
113            Type::MessageShift => NotificationType::MessageShift,
114            Type::ReadStatusChanged => NotificationType::ReadStatusChanged,
115        }
116    }
117}
118
119#[derive(Clone, Copy, Debug, PartialEq)]
120pub enum EventReportVersion {
121    V1_0,
122    V1_1,
123}
124
125impl fmt::Display for EventReportVersion {
126    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
127        match self {
128            Self::V1_0 => write!(f, "1.0"),
129            Self::V1_1 => write!(f, "1.1"),
130        }
131    }
132}
133
134impl FromStr for EventReportVersion {
135    type Err = Error;
136    fn from_str(src: &str) -> Result<Self, Self::Err> {
137        match src {
138            "1.0" => Ok(Self::V1_0),
139            "1.1" => Ok(Self::V1_1),
140            _v => Err(Error::UnsupportedVersion),
141        }
142    }
143}
144
145/// List of attributes available for event-report objects.
146/// See MAP v1.4.2 section 3.1.7 for details.
147#[derive(Clone, Debug, Eq, Hash, PartialEq)]
148pub enum EventAttribute {
149    // Attributes that exist in EventReport v1.0 and above.
150    Type(Type),
151    Handle(u64),
152    Folder(String),
153    OldFolder(String),
154    MessageType(MessageType),
155    // Attributes that exist in EventReport v1.1 and above.
156    Datetime(NaiveDateTime),
157    Subject(String),
158    SenderName(String),
159    Priority(bool),
160}
161
162impl EventAttribute {
163    fn xml_attribute_name(&self) -> &'static str {
164        match self {
165            EventAttribute::Type(_) => TYPE_ATTR,
166            EventAttribute::Handle(_) => HANDLE_ATTR,
167            EventAttribute::Folder(_) => FOLDER_ATTR,
168            EventAttribute::OldFolder(_) => OLD_FOLDER_ATTR,
169            EventAttribute::MessageType(_) => MSG_TYPE_ATTR,
170            EventAttribute::Datetime(_) => DATETIME_ATTR,
171            EventAttribute::Subject(_) => SUBJECT_ATTR,
172            EventAttribute::SenderName(_) => SENDER_NAME_ATTR,
173            EventAttribute::Priority(_) => PRIORITY_ATTR,
174        }
175    }
176
177    fn xml_attribute_value(&self) -> String {
178        match self {
179            EventAttribute::Type(v) => v.to_string(),
180            EventAttribute::Handle(v) => hex::encode(v.to_be_bytes()),
181            EventAttribute::Folder(v) => v.into(),
182            EventAttribute::OldFolder(v) => v.into(),
183            EventAttribute::MessageType(v) => v.to_string(),
184            EventAttribute::Datetime(v) => v.format(ISO_8601_TIME_FORMAT).to_string(),
185            EventAttribute::Subject(v) => v.to_string(),
186            EventAttribute::SenderName(v) => v.to_string(),
187            EventAttribute::Priority(v) => bool_to_string(*v),
188        }
189    }
190
191    /// Adds this attribute to notification.
192    fn add_to_notification(&self, notification: &mut Notification) -> Result<(), Error> {
193        match self {
194            EventAttribute::Type(type_) => notification.type_ = Some((*type_).into()),
195            EventAttribute::Handle(h) => notification.message_handle = Some(*h),
196            EventAttribute::Folder(name) => notification.folder = Some(name.clone()),
197            // Attributes that aren't represented in FIDL struct are ignored.
198            EventAttribute::MessageType(type_) => {
199                notification.message_type = Some((*type_).into());
200            }
201            EventAttribute::Datetime(datetime) => {
202                let timestamp_nanos = datetime
203                    .and_utc()
204                    .timestamp_nanos_opt()
205                    .ok_or_else(|| Error::invalid_data(datetime))?;
206                notification.timestamp = Some(timestamp_nanos);
207            }
208            EventAttribute::Subject(value) => notification.subject = Some(value.to_string()),
209            EventAttribute::SenderName(value) => {
210                notification.sender =
211                    Some(Audience { name: Some(value.to_string()), ..Default::default() })
212            }
213            EventAttribute::Priority(value) => notification.priority = Some(*value),
214            _ => {}
215        };
216        Ok(())
217    }
218
219    /// Returns the event report version the attribute was introduced in.
220    fn version(&self) -> EventReportVersion {
221        match self {
222            &EventAttribute::Datetime(_)
223            | &EventAttribute::Subject(_)
224            | &EventAttribute::SenderName(_)
225            | &EventAttribute::Priority(_) => EventReportVersion::V1_1,
226            _other => EventReportVersion::V1_0,
227        }
228    }
229
230    /// Returns whether or not the event attribute is an attribute that's
231    /// only allowed to exist for NewMessage event type.
232    // See MAP v1.4.2 section 3.1.7 for details.
233    fn is_new_message_attribute(&self) -> bool {
234        if self.version() == EventReportVersion::V1_1 {
235            return true;
236        }
237        false
238    }
239
240    /// Validate that all the attributes are valid for event report v1.0.
241    fn validate_v1_0(attrs: &Vec<EventAttribute>) -> Result<(), Error> {
242        // Make sure there are no v1.1 attributes.
243        if let Some(a) = attrs.iter().find(|a| a.version() == EventReportVersion::V1_1) {
244            return Err(Error::invalid_data(a.xml_attribute_name()));
245        }
246        Ok(())
247    }
248}
249
250impl TryFrom<&OwnedAttribute> for EventAttribute {
251    type Error = Error;
252
253    fn try_from(src: &OwnedAttribute) -> Result<Self, Error> {
254        let attr_name = src.name.local_name.as_str();
255        let attribute = match attr_name {
256            TYPE_ATTR => Self::Type(str::parse(src.value.as_str())?),
257            HANDLE_ATTR => {
258                if src.value.len() > 16 {
259                    return Err(Error::invalid_data(&src.value));
260                }
261                Self::Handle(u64::from_be_bytes(
262                    hex::decode(format!("{:0>16}", src.value.as_str()))
263                        .map_err(|e| Error::invalid_data(e))?
264                        .as_slice()
265                        .try_into()
266                        .unwrap(),
267                ))
268            }
269            // Shall be restricted to 512 bytes
270            FOLDER_ATTR => Self::Folder(truncate_string(&src.value, 512)),
271            OLD_FOLDER_ATTR => Self::OldFolder(truncate_string(&src.value, 512)),
272            MSG_TYPE_ATTR => Self::MessageType(str::parse(src.value.as_str())?),
273            // TODO(b/348051261): Support UTC Offset Timestamp Format feature.
274            DATETIME_ATTR => Self::Datetime(
275                NaiveDateTime::parse_from_str(src.value.as_str(), ISO_8601_TIME_FORMAT)
276                    .map_err(|e| Error::invalid_data(e))?,
277            ),
278            SUBJECT_ATTR => Self::Subject(truncate_string(&src.value, 256)),
279            SENDER_NAME_ATTR => Self::SenderName(truncate_string(&src.value, 256)),
280            PRIORITY_ATTR => Self::Priority(str_to_bool(&src.value)?),
281            val => return Err(Error::invalid_data(val)),
282        };
283        Ok(attribute)
284    }
285}
286
287/// Represents a MAP-Event-Report object as defined in MAP v1.4.2 section 3.1.7.
288/// The object contains one event element which contains attributes that
289/// describes the event.
290#[derive(Debug, PartialEq)]
291pub struct EventReport {
292    version: EventReportVersion,
293    attrs: Vec<EventAttribute>,
294}
295
296impl EventReport {
297    /// Creates a new v1.0 message.
298    pub fn v1_0(attrs: Vec<EventAttribute>) -> Result<Self, Error> {
299        EventReport::new(EventReportVersion::V1_0, attrs)
300    }
301
302    /// Creates a new v1.1 message.
303    pub fn v1_1(attrs: Vec<EventAttribute>) -> Result<Self, Error> {
304        EventReport::new(EventReportVersion::V1_1, attrs)
305    }
306
307    fn new(version: EventReportVersion, attrs: Vec<EventAttribute>) -> Result<Self, Error> {
308        let event_report = EventReport { version, attrs };
309        event_report.validate()?;
310        Ok(event_report)
311    }
312
313    pub fn version(&self) -> EventReportVersion {
314        self.version
315    }
316
317    fn validate(&self) -> Result<(), Error> {
318        // See MAP v1.4.2 section 3.1.7 for details.
319        // Type is a required attribute.
320        let type_ = self
321            .attrs
322            .iter()
323            .find_map(|a| if let EventAttribute::Type(t) = a { Some(t) } else { None })
324            .ok_or_else(|| Error::missing_data(TYPE_ATTR))?;
325
326        // Old folder attribute is only allowed if event type is message shift.
327        if type_ != &Type::MessageShift
328            && self.attrs.iter().any(|a| a.xml_attribute_name() == OLD_FOLDER_ATTR)
329        {
330            return Err(Error::invalid_data(
331                "old_folder attribute is only allowed for MessageShift event",
332            ));
333        }
334        match self.version {
335            EventReportVersion::V1_0 => EventAttribute::validate_v1_0(&self.attrs)?,
336            EventReportVersion::V1_1 => {
337                // If new message event, we have no further checks.
338                if type_ == &Type::NewMessage {
339                    return Ok(());
340                }
341                // As per MAP v1.4.2 section 3.1.7 certain attributes are
342                // not allowed if not for new message event.
343                if self.attrs.iter().any(|a| a.is_new_message_attribute()) {
344                    return Err(Error::invalid_data(
345                        "contains attribute only allowed in NewMessage event",
346                    ));
347                }
348            }
349        }
350        Ok(())
351    }
352
353    // Given the XML StartElement, checks whether or not it is a valid
354    // `MAP-event-report` element.
355    fn check_map_event_report_element(element: XmlEvent) -> Result<EventReportVersion, Error> {
356        let XmlEvent::StartElement { ref name, ref attributes, .. } = element else {
357            return Err(Error::invalid_data(format!("{element:?}")));
358        };
359
360        if name.local_name != EVENT_REPORT_ELEM {
361            return Err(Error::invalid_data(&name.local_name));
362        }
363
364        let version_attr = &attributes
365            .iter()
366            .find(|a| a.name.local_name == VERSION_ATTR)
367            .ok_or_else(|| Error::MissingData(VERSION_ATTR.to_string()))?
368            .value;
369        str::parse(version_attr.as_str())
370    }
371
372    fn write<W: std::io::Write>(&self, writer: &mut EventWriter<W>) -> Result<(), Error> {
373        let mut builder = XmlWriteEvent::start_element(EVENT_ELEM);
374        let attr_pairs: Vec<_> =
375            self.attrs.iter().map(|a| (a.xml_attribute_name(), a.xml_attribute_value())).collect();
376        for a in &attr_pairs {
377            builder = builder.attr(a.0, a.1.as_str());
378        }
379        writer.write(builder)?;
380        Ok(writer.write(XmlWriteEvent::end_element())?)
381    }
382}
383
384impl TryFrom<(XmlEvent, EventReportVersion)> for EventReport {
385    type Error = Error;
386    fn try_from(src: (XmlEvent, EventReportVersion)) -> Result<Self, Error> {
387        let XmlEvent::StartElement { ref name, ref attributes, .. } = src.0 else {
388            return Err(Error::InvalidData(format!("{:?}", src)));
389        };
390        if name.local_name.as_str() != EVENT_ELEM {
391            return Err(Error::invalid_data(&name.local_name));
392        }
393        let mut seen_attrs: HashSet<_> = HashSet::new();
394        let mut attrs = vec![];
395
396        attributes.iter().try_for_each(|a| {
397            let attr = EventAttribute::try_from(a)?;
398            if !seen_attrs.insert(std::mem::discriminant(&attr)) {
399                return Err(Error::DuplicateData(a.name.local_name.to_string()));
400            }
401            attrs.push(attr);
402            Ok(())
403        })?;
404
405        let event_report = EventReport { version: src.1, attrs };
406        let _ = event_report.validate()?;
407        Ok(event_report)
408    }
409}
410
411impl TryFrom<&EventReport> for Notification {
412    type Error = Error;
413    fn try_from(value: &EventReport) -> Result<Self, Error> {
414        let mut notification = Notification::default();
415
416        for a in &value.attrs {
417            let _ = a.add_to_notification(&mut notification)?;
418        }
419        Ok(notification)
420    }
421}
422
423impl Parser for EventReport {
424    type Error = Error;
425
426    /// Parses EventReport from raw bytes of XML data.
427    fn parse<R: std::io::prelude::Read>(buf: R) -> Result<Self, Self::Error> {
428        let mut reader = ParserConfig::new()
429            .ignore_comments(true)
430            .whitespace_to_characters(true)
431            .cdata_to_characters(true)
432            .trim_whitespace(true)
433            .create_reader(buf);
434
435        // Process start of document.
436        match reader.next() {
437            Ok(XmlEvent::StartDocument { .. }) => {}
438            Ok(element) => return Err(Error::InvalidData(format!("{:?}", element))),
439            Err(e) => return Err(Error::ReadXml(e)),
440        };
441
442        // Process start of `MAP-event-report` element.
443        let mut xml_event = reader.next()?;
444        while matches!(xml_event, XmlEvent::Doctype { .. } | XmlEvent::Comment(_)) {
445            xml_event = reader.next()?;
446        }
447        let version = EventReport::check_map_event_report_element(xml_event)?;
448
449        // Process start of `event` element.
450        let mut xml_event: XmlEvent = reader.next()?;
451        while matches!(xml_event, XmlEvent::Doctype { .. } | XmlEvent::Comment(_)) {
452            xml_event = reader.next()?;
453        }
454        let invalid_elem_err = Err(Error::InvalidData(format!("{:?}", xml_event)));
455        let event_report: EventReport = match xml_event {
456            XmlEvent::StartElement { ref name, .. } => match name.local_name.as_str() {
457                EVENT_ELEM => (xml_event, version).try_into()?,
458                _ => return invalid_elem_err,
459            },
460            _ => return invalid_elem_err,
461        };
462
463        // Process end of `event` element.
464        let xml_event = reader.next()?;
465        match xml_event {
466            XmlEvent::EndElement { ref name } => {
467                if name.local_name.as_str() != EVENT_ELEM {
468                    return Err(Error::InvalidData(format!("{:?}", xml_event)));
469                }
470            }
471            _ => return Err(Error::InvalidData(format!("{:?}", xml_event))),
472        };
473
474        // Process end of `MAP-event-report` element.
475        let xml_event = reader.next()?;
476        match xml_event {
477            XmlEvent::EndElement { ref name } => {
478                if name.local_name.as_str() != EVENT_REPORT_ELEM {
479                    return Err(Error::InvalidData(format!("{:?}", xml_event)));
480                }
481            }
482            _ => return Err(Error::InvalidData(format!("{:?}", xml_event))),
483        };
484
485        // Process end of document.
486        let xml_event = reader.next()?;
487        match xml_event {
488            XmlEvent::EndDocument { .. } => {}
489            _ => return Err(Error::InvalidData(format!("{:?}", xml_event))),
490        };
491
492        Ok(event_report)
493    }
494}
495
496impl Builder for EventReport {
497    type Error = Error;
498
499    // Returns the document type of the raw bytes of data.
500    fn mime_type(&self) -> String {
501        "application/xml".to_string()
502    }
503
504    fn build<W: std::io::Write>(&self, buf: W) -> core::result::Result<(), Self::Error> {
505        // Only build the XML if the event report is a valid event report.
506        let _ = self.validate()?;
507
508        let mut w = EmitterConfig::new()
509            .write_document_declaration(true)
510            .perform_indent(true)
511            .create_writer(buf);
512
513        // Begin `MAP-event-report` element.
514        let version = self.version().to_string();
515        let event_report =
516            XmlWriteEvent::start_element(EVENT_REPORT_ELEM).attr(VERSION_ATTR, version.as_str());
517        w.write(event_report)?;
518
519        // Write `event` element.
520        self.write(&mut w)?;
521
522        // End `MAP-event-report` element.
523        Ok(w.write(XmlWriteEvent::end_element())?)
524    }
525}
526
527#[cfg(test)]
528mod tests {
529    use super::*;
530
531    use chrono::{NaiveDate, NaiveTime};
532    use fidl_fuchsia_bluetooth_map as fidl_bt_map;
533
534    use std::fs;
535    use std::io::Cursor;
536
537    #[fuchsia::test]
538    fn event_report() {
539        let _event_report = EventReport::v1_0(vec![
540            EventAttribute::Type(Type::NewMessage),
541            EventAttribute::Handle(0x12345678),
542            EventAttribute::Folder("TELECOM/MSG/INBOX".to_string()),
543            EventAttribute::MessageType(MessageType::SmsCdma),
544        ])
545        .expect("should be created");
546
547        let _event_report = EventReport::v1_1(vec![
548            EventAttribute::Type(Type::NewMessage),
549            EventAttribute::Handle(0x12345678),
550            EventAttribute::Folder("TELECOM/MSG/INBOX".to_string()),
551            EventAttribute::MessageType(MessageType::SmsCdma),
552            EventAttribute::Subject("Hello".to_string()),
553        ])
554        .expect("should be created");
555    }
556
557    #[fuchsia::test]
558    fn event_report_fail() {
559        // Old folder attr not allowed.
560        let _ = EventReport::v1_0(vec![
561            EventAttribute::Type(Type::DeliveryFailure),
562            EventAttribute::Handle(0x12345678),
563            EventAttribute::Folder("TELECOM/MSG/INBOX".to_string()),
564            EventAttribute::OldFolder("TELECOM/MSG/SOME".to_string()),
565            EventAttribute::MessageType(MessageType::SmsCdma),
566        ])
567        .expect_err("should fail");
568
569        // Subject attr not allowed for non NewMessage type.
570        let _ = EventReport::v1_1(vec![
571            EventAttribute::Type(Type::DeliveryFailure),
572            EventAttribute::Handle(0x12345678),
573            EventAttribute::Folder("TELECOM/MSG/INBOX".to_string()),
574            EventAttribute::MessageType(MessageType::SmsCdma),
575            EventAttribute::Subject("Hello".to_string()),
576        ])
577        .expect_err("should fail");
578    }
579
580    #[fuchsia::test]
581    fn parse_event_report_v1_0_success() {
582        const V1_0_TEST_FILE: &str = "/pkg/data/sample_event_report_v1_0.xml";
583        let bytes = fs::read(V1_0_TEST_FILE).expect("should be ok");
584        let report = EventReport::parse(Cursor::new(bytes)).expect("should be ok");
585        assert_eq!(report.version(), EventReportVersion::V1_0);
586        assert_eq!(
587            report.attrs,
588            vec![
589                EventAttribute::Type(Type::NewMessage),
590                EventAttribute::Handle(0x12345678),
591                EventAttribute::Folder("TELECOM/MSG/INBOX".to_string()),
592                EventAttribute::MessageType(MessageType::SmsCdma),
593            ]
594        );
595    }
596
597    #[fuchsia::test]
598    fn parse_event_report_v1_1_success() {
599        const V1_1_TEST_FILE: &str = "/pkg/data/sample_event_report_v1_1.xml";
600        let bytes = fs::read(V1_1_TEST_FILE).expect("should be ok");
601        let report = EventReport::parse(Cursor::new(bytes)).expect("should be ok");
602        assert_eq!(report.version(), EventReportVersion::V1_1);
603        assert_eq!(
604            report.attrs,
605            vec![
606                EventAttribute::Type(Type::NewMessage),
607                EventAttribute::Handle(0x12345678),
608                EventAttribute::Folder("TELECOM/MSG/INBOX".to_string()),
609                EventAttribute::MessageType(MessageType::SmsCdma),
610                EventAttribute::Subject("Hello".to_string()),
611                EventAttribute::Datetime(NaiveDateTime::new(
612                    NaiveDate::from_ymd_opt(2011, 02, 21).unwrap(),
613                    NaiveTime::from_hms_opt(13, 05, 10).unwrap(),
614                )),
615                EventAttribute::SenderName("Jamie".to_string()),
616                EventAttribute::Priority(true),
617            ]
618        );
619    }
620
621    #[fuchsia::test]
622    fn parse_event_report_fail() {
623        let bytes = fs::read("/pkg/data/bad_sample.xml").unwrap();
624        let Error::InvalidData(_) =
625            EventReport::parse(Cursor::new(bytes)).expect_err("should have failed")
626        else {
627            panic!("expected failure due to invalid data");
628        };
629
630        let bytes = fs::read("/pkg/data/sample_event_report_v1_2.xml").unwrap();
631        let Error::UnsupportedVersion =
632            EventReport::parse(Cursor::new(bytes)).expect_err("should have failed")
633        else {
634            panic!("expected failure due to unsupported version");
635        };
636
637        let bytes = fs::read("/pkg/data/bad_sample_event_report_v1_0_1.xml").unwrap();
638        let Error::ReadXml(_) =
639            EventReport::parse(Cursor::new(bytes)).expect_err("should have failed")
640        else {
641            panic!("expected failure due to read xml error");
642        };
643
644        let bytes = fs::read("/pkg/data/bad_sample_event_report_v1_0_2.xml").unwrap();
645        let Error::MissingData(_) =
646            EventReport::parse(Cursor::new(bytes)).expect_err("should have failed")
647        else {
648            panic!("expected failure due to missing data");
649        };
650
651        let bytes = fs::read("/pkg/data/bad_sample_event_report_v1_1.xml").unwrap();
652        let Error::InvalidData(_) =
653            EventReport::parse(Cursor::new(bytes)).expect_err("should have failed")
654        else {
655            panic!("expected failure due to invalid data");
656        };
657    }
658
659    #[fuchsia::test]
660    fn build_event_report_v1_0_success() {
661        let event_report = EventReport {
662            version: EventReportVersion::V1_0,
663            attrs: vec![
664                EventAttribute::Type(Type::NewMessage),
665                EventAttribute::Handle(12345678),
666                EventAttribute::Folder("TELECOM/MSG/INBOX".to_string()),
667                EventAttribute::MessageType(MessageType::SmsCdma),
668            ],
669        };
670
671        let mut buf = Vec::new();
672        let _ = event_report.build(&mut buf).expect("should have succeeded");
673        assert_eq!(
674            event_report,
675            EventReport::parse(Cursor::new(buf)).expect("should be valid xml")
676        );
677    }
678
679    #[fuchsia::test]
680    fn build_event_report_v1_1_success() {
681        let event_report = EventReport {
682            version: EventReportVersion::V1_1,
683            attrs: vec![
684                EventAttribute::Type(Type::NewMessage),
685                EventAttribute::Handle(12345678),
686                EventAttribute::Folder("TELECOM/MSG/INBOX".to_string()),
687                EventAttribute::MessageType(MessageType::SmsCdma),
688                EventAttribute::Subject("Hello".to_string()),
689                EventAttribute::Datetime(NaiveDateTime::new(
690                    NaiveDate::from_ymd_opt(2011, 02, 21).unwrap(),
691                    NaiveTime::from_hms_opt(13, 05, 10).unwrap(),
692                )),
693                EventAttribute::SenderName("Jamie".to_string()),
694                EventAttribute::Priority(true),
695            ],
696        };
697
698        let mut buf = Vec::new();
699        event_report.build(&mut buf).expect("should have succeeded");
700        assert_eq!(
701            event_report,
702            EventReport::parse(Cursor::new(buf)).expect("should be valid xml")
703        );
704    }
705
706    #[fuchsia::test]
707    fn build_event_report_fail() {
708        // Invalid v1.0 since missing Type attr.
709        let event_report = EventReport {
710            version: EventReportVersion::V1_0,
711            attrs: vec![
712                EventAttribute::Handle(12345678),
713                EventAttribute::Folder("TELECOM/MSG/INBOX".to_string()),
714                EventAttribute::MessageType(MessageType::SmsCdma),
715            ],
716        };
717        let mut buf = Vec::new();
718        let _ = event_report.build(&mut buf).expect_err("should have failed");
719
720        // Invalid v1.1 since incompatible Type and Subject attrs.
721        let event_report = EventReport {
722            version: EventReportVersion::V1_1,
723            attrs: vec![
724                EventAttribute::Type(Type::DeliveryFailure),
725                EventAttribute::Handle(12345678),
726                EventAttribute::Folder("TELECOM/MSG/INBOX".to_string()),
727                EventAttribute::MessageType(MessageType::SmsCdma),
728                EventAttribute::Subject("Hello".to_string()),
729            ],
730        };
731        let mut buf = Vec::new();
732        let _ = event_report.build(&mut buf).expect_err("should have failed");
733    }
734
735    #[fuchsia::test]
736    fn notification() {
737        let event_report = EventReport::v1_1(vec![
738            EventAttribute::Type(Type::NewMessage),
739            EventAttribute::Handle(12345678),
740            EventAttribute::Folder("TELECOM/MSG/INBOX".to_string()),
741            EventAttribute::MessageType(MessageType::SmsCdma),
742            EventAttribute::Datetime(NaiveDateTime::new(
743                NaiveDate::from_ymd_opt(2000, 01, 09).unwrap(),
744                NaiveTime::from_hms_opt(01, 09, 19).unwrap(),
745            )),
746        ])
747        .unwrap();
748
749        // Should be converted successfully.
750        let notification: Notification = (&event_report).try_into().expect("should succeed");
751        assert_eq!(
752            notification,
753            Notification {
754                type_: Some(NotificationType::NewMessage),
755                message_handle: Some(12345678),
756                folder: Some("TELECOM/MSG/INBOX".to_string()),
757                message_type: Some(fidl_bt_map::MessageType::SMS_CDMA),
758                timestamp: Some(947380159000000000),
759                ..Default::default()
760            }
761        );
762    }
763
764    #[fuchsia::test]
765    fn notification_fail() {
766        let event_report = EventReport::v1_1(vec![
767            EventAttribute::Type(Type::NewMessage),
768            EventAttribute::Handle(12345678),
769            EventAttribute::Folder("TELECOM/MSG/INBOX".to_string()),
770            EventAttribute::MessageType(MessageType::SmsCdma),
771            EventAttribute::Datetime(NaiveDateTime::new(
772                // Out of range date time.
773                NaiveDate::from_ymd_opt(1500, 02, 21).unwrap(),
774                NaiveTime::from_hms_opt(13, 05, 10).unwrap(),
775            )),
776        ])
777        .unwrap();
778
779        // Conversion should fail.
780        let res: Result<Notification, _> = (&event_report).try_into();
781        let _ = res.expect_err("should have failed");
782    }
783}