1use crate::iq::IqSetPayload;
8use crate::jingle_grouping::Group;
9use crate::jingle_ibb::Transport as IbbTransport;
10use crate::jingle_ice_udp::Transport as IceUdpTransport;
11use crate::jingle_rtp::Description as RtpDescription;
12use crate::jingle_s5b::Transport as Socks5Transport;
13use crate::ns;
14use jid::Jid;
15use minidom::Element;
16use std::collections::BTreeMap;
17use std::fmt;
18use std::str::FromStr;
19use xso::error::{Error, FromElementError};
20
21generate_attribute!(
22 Action, "action", {
24 ContentAccept => "content-accept",
26
27 ContentAdd => "content-add",
29
30 ContentModify => "content-modify",
32
33 ContentReject => "content-reject",
35
36 ContentRemove => "content-remove",
38
39 DescriptionInfo => "description-info",
41
42 SecurityInfo => "security-info",
44
45 SessionAccept => "session-accept",
47
48 SessionInfo => "session-info",
50
51 SessionInitiate => "session-initiate",
53
54 SessionTerminate => "session-terminate",
56
57 TransportAccept => "transport-accept",
59
60 TransportInfo => "transport-info",
62
63 TransportReject => "transport-reject",
65
66 TransportReplace => "transport-replace",
68 }
69);
70
71generate_attribute!(
72 Creator, "creator", {
74 Initiator => "initiator",
76
77 Responder => "responder",
79 }
80);
81
82generate_attribute!(
83 Senders, "senders", {
85 Both => "both",
87
88 Initiator => "initiator",
90
91 None => "none",
93
94 Responder => "responder",
96 }, Default = Both
97);
98
99generate_attribute!(
100 Disposition, "disposition", {
107 Inline => "inline",
109
110 Attachment => "attachment",
112
113 FormData => "form-data",
115
116 Signal => "signal",
118
119 Alert => "alert",
121
122 Icon => "icon",
124
125 Render => "render",
127
128 RecipientListHistory => "recipient-list-history",
131
132 Session => "session",
135
136 Aib => "aib",
138
139 EarlySession => "early-session",
142
143 RecipientList => "recipient-list",
146
147 Notification => "notification",
151
152 ByReference => "by-reference",
155
156 InfoPackage => "info-package",
158
159 RecordingSession => "recording-session",
163 }, Default = Session
164);
165
166generate_id!(
167 ContentId
170);
171
172#[derive(Debug, Clone, PartialEq)]
174pub enum Description {
175 Rtp(RtpDescription),
177
178 Unknown(Element),
180}
181
182impl TryFrom<Element> for Description {
183 type Error = Error;
184
185 fn try_from(elem: Element) -> Result<Description, Error> {
186 Ok(if elem.is("description", ns::JINGLE_RTP) {
187 Description::Rtp(RtpDescription::try_from(elem)?)
188 } else {
189 Description::Unknown(elem)
190 })
191 }
192}
193
194impl From<RtpDescription> for Description {
195 fn from(desc: RtpDescription) -> Description {
196 Description::Rtp(desc)
197 }
198}
199
200impl From<Description> for Element {
201 fn from(desc: Description) -> Element {
202 match desc {
203 Description::Rtp(desc) => desc.into(),
204 Description::Unknown(elem) => elem,
205 }
206 }
207}
208
209#[derive(Debug, Clone, PartialEq)]
211pub enum Transport {
212 IceUdp(IceUdpTransport),
214
215 Ibb(IbbTransport),
217
218 Socks5(Socks5Transport),
220
221 Unknown(Element),
223}
224
225impl TryFrom<Element> for Transport {
226 type Error = Error;
227
228 fn try_from(elem: Element) -> Result<Transport, Error> {
229 Ok(if elem.is("transport", ns::JINGLE_ICE_UDP) {
230 Transport::IceUdp(IceUdpTransport::try_from(elem)?)
231 } else if elem.is("transport", ns::JINGLE_IBB) {
232 Transport::Ibb(IbbTransport::try_from(elem)?)
233 } else if elem.is("transport", ns::JINGLE_S5B) {
234 Transport::Socks5(Socks5Transport::try_from(elem)?)
235 } else {
236 Transport::Unknown(elem)
237 })
238 }
239}
240
241impl From<IceUdpTransport> for Transport {
242 fn from(transport: IceUdpTransport) -> Transport {
243 Transport::IceUdp(transport)
244 }
245}
246
247impl From<IbbTransport> for Transport {
248 fn from(transport: IbbTransport) -> Transport {
249 Transport::Ibb(transport)
250 }
251}
252
253impl From<Socks5Transport> for Transport {
254 fn from(transport: Socks5Transport) -> Transport {
255 Transport::Socks5(transport)
256 }
257}
258
259impl From<Transport> for Element {
260 fn from(transport: Transport) -> Element {
261 match transport {
262 Transport::IceUdp(transport) => transport.into(),
263 Transport::Ibb(transport) => transport.into(),
264 Transport::Socks5(transport) => transport.into(),
265 Transport::Unknown(elem) => elem,
266 }
267 }
268}
269
270generate_element!(
271 Content, "content", JINGLE,
274 attributes: [
275 creator: Required<Creator> = "creator",
277
278 disposition: Default<Disposition> = "disposition",
280
281 name: Required<ContentId> = "name",
283
284 senders: Default<Senders> = "senders",
286 ],
287 children: [
288 description: Option<Description> = ("description", *) => Description,
290
291 transport: Option<Transport> = ("transport", *) => Transport,
293
294 security: Option<Element> = ("security", JINGLE) => Element
296 ]
297);
298
299impl Content {
300 pub fn new(creator: Creator, name: ContentId) -> Content {
302 Content {
303 creator,
304 name,
305 disposition: Disposition::Session,
306 senders: Senders::Both,
307 description: None,
308 transport: None,
309 security: None,
310 }
311 }
312
313 pub fn with_disposition(mut self, disposition: Disposition) -> Content {
315 self.disposition = disposition;
316 self
317 }
318
319 pub fn with_senders(mut self, senders: Senders) -> Content {
321 self.senders = senders;
322 self
323 }
324
325 pub fn with_description<D: Into<Description>>(mut self, description: D) -> Content {
327 self.description = Some(description.into());
328 self
329 }
330
331 pub fn with_transport<T: Into<Transport>>(mut self, transport: T) -> Content {
333 self.transport = Some(transport.into());
334 self
335 }
336
337 pub fn with_security(mut self, security: Element) -> Content {
339 self.security = Some(security);
340 self
341 }
342}
343
344#[derive(Debug, Clone, PartialEq)]
346pub enum Reason {
347 AlternativeSession, Busy,
355
356 Cancel,
358
359 ConnectivityError,
361
362 Decline,
364
365 Expired,
368
369 FailedApplication,
372
373 FailedTransport,
376
377 GeneralError,
379
380 Gone,
382
383 IncompatibleParameters,
386
387 MediaError,
389
390 SecurityError,
392
393 Success,
396
397 Timeout,
400
401 UnsupportedApplications,
403
404 UnsupportedTransports,
406}
407
408impl FromStr for Reason {
409 type Err = Error;
410
411 fn from_str(s: &str) -> Result<Reason, Error> {
412 Ok(match s {
413 "alternative-session" => Reason::AlternativeSession,
414 "busy" => Reason::Busy,
415 "cancel" => Reason::Cancel,
416 "connectivity-error" => Reason::ConnectivityError,
417 "decline" => Reason::Decline,
418 "expired" => Reason::Expired,
419 "failed-application" => Reason::FailedApplication,
420 "failed-transport" => Reason::FailedTransport,
421 "general-error" => Reason::GeneralError,
422 "gone" => Reason::Gone,
423 "incompatible-parameters" => Reason::IncompatibleParameters,
424 "media-error" => Reason::MediaError,
425 "security-error" => Reason::SecurityError,
426 "success" => Reason::Success,
427 "timeout" => Reason::Timeout,
428 "unsupported-applications" => Reason::UnsupportedApplications,
429 "unsupported-transports" => Reason::UnsupportedTransports,
430
431 _ => return Err(Error::Other("Unknown reason.")),
432 })
433 }
434}
435
436impl From<Reason> for Element {
437 fn from(reason: Reason) -> Element {
438 Element::builder(
439 match reason {
440 Reason::AlternativeSession => "alternative-session",
441 Reason::Busy => "busy",
442 Reason::Cancel => "cancel",
443 Reason::ConnectivityError => "connectivity-error",
444 Reason::Decline => "decline",
445 Reason::Expired => "expired",
446 Reason::FailedApplication => "failed-application",
447 Reason::FailedTransport => "failed-transport",
448 Reason::GeneralError => "general-error",
449 Reason::Gone => "gone",
450 Reason::IncompatibleParameters => "incompatible-parameters",
451 Reason::MediaError => "media-error",
452 Reason::SecurityError => "security-error",
453 Reason::Success => "success",
454 Reason::Timeout => "timeout",
455 Reason::UnsupportedApplications => "unsupported-applications",
456 Reason::UnsupportedTransports => "unsupported-transports",
457 },
458 ns::JINGLE,
459 )
460 .build()
461 }
462}
463
464type Lang = String;
465
466#[derive(Debug, Clone, PartialEq)]
468pub struct ReasonElement {
469 pub reason: Reason,
471
472 pub texts: BTreeMap<Lang, String>,
474}
475
476impl fmt::Display for ReasonElement {
477 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
478 write!(fmt, "{}", Element::from(self.reason.clone()).name())?;
479 if let Some(text) = self.texts.get("en") {
480 write!(fmt, ": {}", text)?;
481 } else if let Some(text) = self.texts.get("") {
482 write!(fmt, ": {}", text)?;
483 }
484 Ok(())
485 }
486}
487
488impl TryFrom<Element> for ReasonElement {
489 type Error = FromElementError;
490
491 fn try_from(elem: Element) -> Result<ReasonElement, FromElementError> {
492 check_self!(elem, "reason", JINGLE);
493 check_no_attributes!(elem, "reason");
494 let mut reason = None;
495 let mut texts = BTreeMap::new();
496 for child in elem.children() {
497 if child.is("text", ns::JINGLE) {
498 check_no_children!(child, "text");
499 check_no_unknown_attributes!(child, "text", ["xml:lang"]);
500 let lang = get_attr!(elem, "xml:lang", Default);
501 if texts.insert(lang, child.text()).is_some() {
502 return Err(
503 Error::Other("Text element present twice for the same xml:lang.").into(),
504 );
505 }
506 } else if child.has_ns(ns::JINGLE) {
507 if reason.is_some() {
508 return Err(Error::Other("Reason must not have more than one reason.").into());
509 }
510 check_no_children!(child, "reason");
511 check_no_attributes!(child, "reason");
512 reason = Some(child.name().parse()?);
513 } else {
514 return Err(Error::Other("Reason contains a foreign element.").into());
515 }
516 }
517 let reason = reason.ok_or(Error::Other("Reason doesn’t contain a valid reason."))?;
518 Ok(ReasonElement { reason, texts })
519 }
520}
521
522impl From<ReasonElement> for Element {
523 fn from(reason: ReasonElement) -> Element {
524 Element::builder("reason", ns::JINGLE)
525 .append(Element::from(reason.reason))
526 .append_all(reason.texts.into_iter().map(|(lang, text)| {
527 Element::builder("text", ns::JINGLE)
528 .attr("xml:lang", lang)
529 .append(text)
530 }))
531 .build()
532 }
533}
534
535generate_id!(
536 SessionId
538);
539
540#[derive(Debug, Clone, PartialEq)]
542pub struct Jingle {
543 pub action: Action,
545
546 pub initiator: Option<Jid>,
548
549 pub responder: Option<Jid>,
551
552 pub sid: SessionId,
554
555 pub contents: Vec<Content>,
557
558 pub reason: Option<ReasonElement>,
560
561 pub group: Option<Group>,
563
564 pub other: Vec<Element>,
566}
567
568impl IqSetPayload for Jingle {}
569
570impl Jingle {
571 pub fn new(action: Action, sid: SessionId) -> Jingle {
573 Jingle {
574 action,
575 sid,
576 initiator: None,
577 responder: None,
578 contents: Vec::new(),
579 reason: None,
580 group: None,
581 other: Vec::new(),
582 }
583 }
584
585 pub fn with_initiator(mut self, initiator: Jid) -> Jingle {
587 self.initiator = Some(initiator);
588 self
589 }
590
591 pub fn with_responder(mut self, responder: Jid) -> Jingle {
593 self.responder = Some(responder);
594 self
595 }
596
597 pub fn add_content(mut self, content: Content) -> Jingle {
599 self.contents.push(content);
600 self
601 }
602
603 pub fn set_reason(mut self, reason: ReasonElement) -> Jingle {
605 self.reason = Some(reason);
606 self
607 }
608
609 pub fn set_group(mut self, group: Group) -> Jingle {
611 self.group = Some(group);
612 self
613 }
614}
615
616impl TryFrom<Element> for Jingle {
617 type Error = FromElementError;
618
619 fn try_from(root: Element) -> Result<Jingle, FromElementError> {
620 check_self!(root, "jingle", JINGLE, "Jingle");
621 check_no_unknown_attributes!(root, "Jingle", ["action", "initiator", "responder", "sid"]);
622
623 let mut jingle = Jingle {
624 action: get_attr!(root, "action", Required),
625 initiator: get_attr!(root, "initiator", Option),
626 responder: get_attr!(root, "responder", Option),
627 sid: get_attr!(root, "sid", Required),
628 contents: vec![],
629 reason: None,
630 group: None,
631 other: vec![],
632 };
633
634 for child in root.children().cloned() {
635 if child.is("content", ns::JINGLE) {
636 let content = Content::try_from(child)?;
637 jingle.contents.push(content);
638 } else if child.is("reason", ns::JINGLE) {
639 if jingle.reason.is_some() {
640 return Err(Error::Other("Jingle must not have more than one reason.").into());
641 }
642 let reason = ReasonElement::try_from(child)?;
643 jingle.reason = Some(reason);
644 } else if child.is("group", ns::JINGLE_GROUPING) {
645 if jingle.group.is_some() {
646 return Err(Error::Other("Jingle must not have more than one grouping.").into());
647 }
648 let group = Group::try_from(child)?;
649 jingle.group = Some(group);
650 } else {
651 jingle.other.push(child);
652 }
653 }
654
655 Ok(jingle)
656 }
657}
658
659impl From<Jingle> for Element {
660 fn from(jingle: Jingle) -> Element {
661 Element::builder("jingle", ns::JINGLE)
662 .attr("action", jingle.action)
663 .attr("initiator", jingle.initiator)
664 .attr("responder", jingle.responder)
665 .attr("sid", jingle.sid)
666 .append_all(jingle.contents)
667 .append_all(jingle.reason.map(Element::from))
668 .append_all(jingle.group.map(Element::from))
669 .build()
670 }
671}
672
673#[cfg(test)]
674mod tests {
675 use super::*;
676
677 #[cfg(target_pointer_width = "32")]
678 #[test]
679 fn test_size() {
680 assert_size!(Action, 1);
681 assert_size!(Creator, 1);
682 assert_size!(Senders, 1);
683 assert_size!(Disposition, 1);
684 assert_size!(ContentId, 12);
685 assert_size!(Content, 216);
686 assert_size!(Reason, 1);
687 assert_size!(ReasonElement, 16);
688 assert_size!(SessionId, 12);
689 assert_size!(Jingle, 104);
690 }
691
692 #[cfg(target_pointer_width = "64")]
693 #[test]
694 fn test_size() {
695 assert_size!(Action, 1);
696 assert_size!(Creator, 1);
697 assert_size!(Senders, 1);
698 assert_size!(Disposition, 1);
699 assert_size!(ContentId, 24);
700 assert_size!(Content, 432);
701 assert_size!(Reason, 1);
702 assert_size!(ReasonElement, 32);
703 assert_size!(SessionId, 24);
704 assert_size!(Jingle, 208);
705 }
706
707 #[test]
708 fn test_simple() {
709 let elem: Element =
710 "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'/>"
711 .parse()
712 .unwrap();
713 let jingle = Jingle::try_from(elem).unwrap();
714 assert_eq!(jingle.action, Action::SessionInitiate);
715 assert_eq!(jingle.sid, SessionId(String::from("coucou")));
716 }
717
718 #[test]
719 fn test_invalid_jingle() {
720 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1'/>".parse().unwrap();
721 let error = Jingle::try_from(elem).unwrap_err();
722 let message = match error {
723 FromElementError::Invalid(Error::Other(string)) => string,
724 _ => panic!(),
725 };
726 assert_eq!(message, "Required attribute 'action' missing.");
727
728 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-info'/>"
729 .parse()
730 .unwrap();
731 let error = Jingle::try_from(elem).unwrap_err();
732 let message = match error {
733 FromElementError::Invalid(Error::Other(string)) => string,
734 _ => panic!(),
735 };
736 assert_eq!(message, "Required attribute 'sid' missing.");
737
738 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='coucou' sid='coucou'/>"
739 .parse()
740 .unwrap();
741 let error = Jingle::try_from(elem).unwrap_err();
742 let message = match error {
743 FromElementError::Invalid(Error::TextParseError(string)) => string,
744 _ => panic!(),
745 };
746 assert_eq!(message.to_string(), "Unknown value for 'action' attribute.");
747 }
748
749 #[test]
750 fn test_content() {
751 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><content creator='initiator' name='coucou'><description/><transport xmlns='urn:xmpp:jingle:transports:stub:0'/></content></jingle>".parse().unwrap();
752 let jingle = Jingle::try_from(elem).unwrap();
753 assert_eq!(jingle.contents[0].creator, Creator::Initiator);
754 assert_eq!(jingle.contents[0].name, ContentId(String::from("coucou")));
755 assert_eq!(jingle.contents[0].senders, Senders::Both);
756 assert_eq!(jingle.contents[0].disposition, Disposition::Session);
757
758 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><content creator='initiator' name='coucou' senders='both'><description/><transport xmlns='urn:xmpp:jingle:transports:stub:0'/></content></jingle>".parse().unwrap();
759 let jingle = Jingle::try_from(elem).unwrap();
760 assert_eq!(jingle.contents[0].senders, Senders::Both);
761
762 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><content creator='initiator' name='coucou' disposition='early-session'><description/><transport xmlns='urn:xmpp:jingle:transports:stub:0'/></content></jingle>".parse().unwrap();
763 let jingle = Jingle::try_from(elem).unwrap();
764 assert_eq!(jingle.contents[0].disposition, Disposition::EarlySession);
765 }
766
767 #[test]
768 fn test_invalid_content() {
769 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><content/></jingle>".parse().unwrap();
770 let error = Jingle::try_from(elem).unwrap_err();
771 let message = match error {
772 FromElementError::Invalid(Error::Other(string)) => string,
773 _ => panic!(),
774 };
775 assert_eq!(message, "Required attribute 'creator' missing.");
776
777 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><content creator='initiator'/></jingle>".parse().unwrap();
778 let error = Jingle::try_from(elem).unwrap_err();
779 let message = match error {
780 FromElementError::Invalid(Error::Other(string)) => string,
781 _ => panic!(),
782 };
783 assert_eq!(message, "Required attribute 'name' missing.");
784
785 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><content creator='coucou' name='coucou'/></jingle>".parse().unwrap();
786 let error = Jingle::try_from(elem).unwrap_err();
787 let message = match error {
788 FromElementError::Invalid(Error::TextParseError(string)) => string,
789 other => panic!("unexpected result: {:?}", other),
790 };
791 assert_eq!(
792 message.to_string(),
793 "Unknown value for 'creator' attribute."
794 );
795
796 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><content creator='initiator' name='coucou' senders='coucou'/></jingle>".parse().unwrap();
797 let error = Jingle::try_from(elem).unwrap_err();
798 let message = match error {
799 FromElementError::Invalid(Error::TextParseError(string)) => string,
800 _ => panic!(),
801 };
802 assert_eq!(
803 message.to_string(),
804 "Unknown value for 'senders' attribute."
805 );
806
807 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><content creator='initiator' name='coucou' senders=''/></jingle>".parse().unwrap();
808 let error = Jingle::try_from(elem).unwrap_err();
809 let message = match error {
810 FromElementError::Invalid(Error::TextParseError(string)) => string,
811 _ => panic!(),
812 };
813 assert_eq!(
814 message.to_string(),
815 "Unknown value for 'senders' attribute."
816 );
817 }
818
819 #[test]
820 fn test_reason() {
821 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><reason><success/></reason></jingle>".parse().unwrap();
822 let jingle = Jingle::try_from(elem).unwrap();
823 let reason = jingle.reason.unwrap();
824 assert_eq!(reason.reason, Reason::Success);
825 assert_eq!(reason.texts, BTreeMap::new());
826
827 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><reason><success/><text>coucou</text></reason></jingle>".parse().unwrap();
828 let jingle = Jingle::try_from(elem).unwrap();
829 let reason = jingle.reason.unwrap();
830 assert_eq!(reason.reason, Reason::Success);
831 assert_eq!(reason.texts.get(""), Some(&String::from("coucou")));
832 }
833
834 #[test]
835 fn test_invalid_reason() {
836 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><reason/></jingle>".parse().unwrap();
837 let error = Jingle::try_from(elem).unwrap_err();
838 let message = match error {
839 FromElementError::Invalid(Error::Other(string)) => string,
840 _ => panic!(),
841 };
842 assert_eq!(message, "Reason doesn’t contain a valid reason.");
843
844 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><reason><a/></reason></jingle>".parse().unwrap();
845 let error = Jingle::try_from(elem).unwrap_err();
846 let message = match error {
847 FromElementError::Invalid(Error::Other(string)) => string,
848 _ => panic!(),
849 };
850 assert_eq!(message, "Unknown reason.");
851
852 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><reason><a xmlns='http://www.w3.org/1999/xhtml'/></reason></jingle>".parse().unwrap();
853 let error = Jingle::try_from(elem).unwrap_err();
854 let message = match error {
855 FromElementError::Invalid(Error::Other(string)) => string,
856 _ => panic!(),
857 };
858 assert_eq!(message, "Reason contains a foreign element.");
859
860 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><reason><decline/></reason><reason/></jingle>".parse().unwrap();
861 let error = Jingle::try_from(elem).unwrap_err();
862 let message = match error {
863 FromElementError::Invalid(Error::Other(string)) => string,
864 _ => panic!(),
865 };
866 assert_eq!(message, "Jingle must not have more than one reason.");
867
868 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><reason><decline/><text/><text/></reason></jingle>".parse().unwrap();
869 let error = Jingle::try_from(elem).unwrap_err();
870 let message = match error {
871 FromElementError::Invalid(Error::Other(string)) => string,
872 _ => panic!(),
873 };
874 assert_eq!(message, "Text element present twice for the same xml:lang.");
875 }
876
877 #[test]
878 fn test_serialize_jingle() {
879 let reference: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='a73sjjvkla37jfea'><content xmlns='urn:xmpp:jingle:1' creator='initiator' name='this-is-a-stub'><description xmlns='urn:xmpp:jingle:apps:stub:0'/><transport xmlns='urn:xmpp:jingle:transports:stub:0'/></content></jingle>"
880 .parse()
881 .unwrap();
882
883 let jingle = Jingle {
884 action: Action::SessionInitiate,
885 initiator: None,
886 responder: None,
887 sid: SessionId(String::from("a73sjjvkla37jfea")),
888 contents: vec![Content {
889 creator: Creator::Initiator,
890 disposition: Disposition::default(),
891 name: ContentId(String::from("this-is-a-stub")),
892 senders: Senders::default(),
893 description: Some(Description::Unknown(
894 Element::builder("description", "urn:xmpp:jingle:apps:stub:0").build(),
895 )),
896 transport: Some(Transport::Unknown(
897 Element::builder("transport", "urn:xmpp:jingle:transports:stub:0").build(),
898 )),
899 security: None,
900 }],
901 reason: None,
902 group: None,
903 other: vec![],
904 };
905 let serialized: Element = jingle.into();
906 assert_eq!(serialized, reference);
907 }
908}