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