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