1use 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
20const 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
68const 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
78const 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,
89 Fractioned,
92 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#[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 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
235impl 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 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 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 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 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 {
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 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 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 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#[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
434impl 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 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 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 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 fn v1_0(attrs: HashSet<AttributeV1_0>) -> Self {
518 Self::V1_0 { attrs }
519 }
520
521 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 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 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 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 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 let mut finished_document = false;
681 let mut finished_messages_listing = false;
682 while !finished_document {
683 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 fn mime_type(&self) -> String {
736 "application/xml".to_string()
737 }
738
739 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 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 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 self.messages.iter().try_for_each(|m| m.write(&mut w))?;
767 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 let res = truncate_string(&TEST_STR.to_string(), 200);
786 assert_eq!(TEST_STR.to_string(), res);
787
788 let res = truncate_string(&TEST_STR.to_string(), 6);
790 assert_eq!("Löwe ", res);
791 assert_eq!(6, res.len());
792
793 let res = truncate_string(&TEST_STR.to_string(), 8);
795 assert_eq!("Löwe ", res); 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 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 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 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 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 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}