1// Copyright (c) 2017 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
2//
3// This Source Code Form is subject to the terms of the Mozilla Public
4// License, v. 2.0. If a copy of the MPL was not distributed with this
5// file, You can obtain one at http://mozilla.org/MPL/2.0/.
6
7use 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 crate::util::error::Error;
15use crate::Element;
16use jid::Jid;
17use std::collections::BTreeMap;
18use std::convert::TryFrom;
19use std::fmt;
20use std::str::FromStr;
21
22generate_attribute!(
23 /// The action attribute.
24 Action, "action", {
25 /// Accept a content-add action received from another party.
26 ContentAccept => "content-accept",
27
28 /// Add one or more new content definitions to the session.
29 ContentAdd => "content-add",
30
31 /// Change the directionality of media sending.
32 ContentModify => "content-modify",
33
34 /// Reject a content-add action received from another party.
35 ContentReject => "content-reject",
36
37 /// Remove one or more content definitions from the session.
38 ContentRemove => "content-remove",
39
40 /// Exchange information about parameters for an application type.
41 DescriptionInfo => "description-info",
42
43 /// Exchange information about security preconditions.
44 SecurityInfo => "security-info",
45
46 /// Definitively accept a session negotiation.
47 SessionAccept => "session-accept",
48
49 /// Send session-level information, such as a ping or a ringing message.
50 SessionInfo => "session-info",
51
52 /// Request negotiation of a new Jingle session.
53 SessionInitiate => "session-initiate",
54
55 /// End an existing session.
56 SessionTerminate => "session-terminate",
57
58 /// Accept a transport-replace action received from another party.
59 TransportAccept => "transport-accept",
60
61 /// Exchange transport candidates.
62 TransportInfo => "transport-info",
63
64 /// Reject a transport-replace action received from another party.
65 TransportReject => "transport-reject",
66
67 /// Redefine a transport method or replace it with a different method.
68 TransportReplace => "transport-replace",
69 }
70);
71
72generate_attribute!(
73 /// Which party originally generated the content type.
74 Creator, "creator", {
75 /// This content was created by the initiator of this session.
76 Initiator => "initiator",
77
78 /// This content was created by the responder of this session.
79 Responder => "responder",
80 }
81);
82
83generate_attribute!(
84 /// Which parties in the session will be generating content.
85 Senders, "senders", {
86 /// Both parties can send for this content.
87 Both => "both",
88
89 /// Only the initiator can send for this content.
90 Initiator => "initiator",
91
92 /// No one can send for this content.
93 None => "none",
94
95 /// Only the responder can send for this content.
96 Responder => "responder",
97 }, Default = Both
98);
99
100generate_attribute!(
101 /// How the content definition is to be interpreted by the recipient. The
102 /// meaning of this attribute matches the "Content-Disposition" header as
103 /// defined in RFC 2183 and applied to SIP by RFC 3261.
104 ///
105 /// Possible values are defined here:
106 /// <https://www.iana.org/assignments/cont-disp/cont-disp.xhtml>
107 Disposition, "disposition", {
108 /// Displayed automatically.
109 Inline => "inline",
110
111 /// User controlled display.
112 Attachment => "attachment",
113
114 /// Process as form response.
115 FormData => "form-data",
116
117 /// Tunneled content to be processed silently.
118 Signal => "signal",
119
120 /// The body is a custom ring tone to alert the user.
121 Alert => "alert",
122
123 /// The body is displayed as an icon to the user.
124 Icon => "icon",
125
126 /// The body should be displayed to the user.
127 Render => "render",
128
129 /// The body contains a list of URIs that indicates the recipients of
130 /// the request.
131 RecipientListHistory => "recipient-list-history",
132
133 /// The body describes a communications session, for example, an
134 /// [RFC2327](https://www.rfc-editor.org/rfc/rfc2327) SDP body.
135 Session => "session",
136
137 /// Authenticated Identity Body.
138 Aib => "aib",
139
140 /// The body describes an early communications session, for example,
141 /// and [RFC2327](https://www.rfc-editor.org/rfc/rfc2327) SDP body.
142 EarlySession => "early-session",
143
144 /// The body includes a list of URIs to which URI-list services are to
145 /// be applied.
146 RecipientList => "recipient-list",
147
148 /// The payload of the message carrying this Content-Disposition header
149 /// field value is an Instant Message Disposition Notification as
150 /// requested in the corresponding Instant Message.
151 Notification => "notification",
152
153 /// The body needs to be handled according to a reference to the body
154 /// that is located in the same SIP message as the body.
155 ByReference => "by-reference",
156
157 /// The body contains information associated with an Info Package.
158 InfoPackage => "info-package",
159
160 /// The body describes either metadata about the RS or the reason for
161 /// the metadata snapshot request as determined by the MIME value
162 /// indicated in the Content-Type.
163 RecordingSession => "recording-session",
164 }, Default = Session
165);
166
167generate_id!(
168 /// An unique identifier in a session, referencing a
169 /// [struct.Content.html](Content element).
170 ContentId
171);
172
173/// Enum wrapping all of the various supported descriptions of a Content.
174#[derive(Debug, Clone, PartialEq)]
175pub enum Description {
176 /// Jingle RTP Sessions (XEP-0167) description.
177 Rtp(RtpDescription),
178
179 /// To be used for any description that isn’t known at compile-time.
180 Unknown(Element),
181}
182
183impl TryFrom<Element> for Description {
184 type Error = Error;
185
186 fn try_from(elem: Element) -> Result<Description, Error> {
187 Ok(if elem.is("description", ns::JINGLE_RTP) {
188 Description::Rtp(RtpDescription::try_from(elem)?)
189 } else {
190 Description::Unknown(elem)
191 })
192 }
193}
194
195impl From<RtpDescription> for Description {
196 fn from(desc: RtpDescription) -> Description {
197 Description::Rtp(desc)
198 }
199}
200
201impl From<Description> for Element {
202 fn from(desc: Description) -> Element {
203 match desc {
204 Description::Rtp(desc) => desc.into(),
205 Description::Unknown(elem) => elem,
206 }
207 }
208}
209
210/// Enum wrapping all of the various supported transports of a Content.
211#[derive(Debug, Clone, PartialEq)]
212pub enum Transport {
213 /// Jingle ICE-UDP Bytestreams (XEP-0176) transport.
214 IceUdp(IceUdpTransport),
215
216 /// Jingle In-Band Bytestreams (XEP-0261) transport.
217 Ibb(IbbTransport),
218
219 /// Jingle SOCKS5 Bytestreams (XEP-0260) transport.
220 Socks5(Socks5Transport),
221
222 /// To be used for any transport that isn’t known at compile-time.
223 Unknown(Element),
224}
225
226impl TryFrom<Element> for Transport {
227 type Error = Error;
228
229 fn try_from(elem: Element) -> Result<Transport, Error> {
230 Ok(if elem.is("transport", ns::JINGLE_ICE_UDP) {
231 Transport::IceUdp(IceUdpTransport::try_from(elem)?)
232 } else if elem.is("transport", ns::JINGLE_IBB) {
233 Transport::Ibb(IbbTransport::try_from(elem)?)
234 } else if elem.is("transport", ns::JINGLE_S5B) {
235 Transport::Socks5(Socks5Transport::try_from(elem)?)
236 } else {
237 Transport::Unknown(elem)
238 })
239 }
240}
241
242impl From<IceUdpTransport> for Transport {
243 fn from(transport: IceUdpTransport) -> Transport {
244 Transport::IceUdp(transport)
245 }
246}
247
248impl From<IbbTransport> for Transport {
249 fn from(transport: IbbTransport) -> Transport {
250 Transport::Ibb(transport)
251 }
252}
253
254impl From<Socks5Transport> for Transport {
255 fn from(transport: Socks5Transport) -> Transport {
256 Transport::Socks5(transport)
257 }
258}
259
260impl From<Transport> for Element {
261 fn from(transport: Transport) -> Element {
262 match transport {
263 Transport::IceUdp(transport) => transport.into(),
264 Transport::Ibb(transport) => transport.into(),
265 Transport::Socks5(transport) => transport.into(),
266 Transport::Unknown(elem) => elem,
267 }
268 }
269}
270
271generate_element!(
272 /// Describes a session’s content, there can be multiple content in one
273 /// session.
274 Content, "content", JINGLE,
275 attributes: [
276 /// Who created this content.
277 creator: Required<Creator> = "creator",
278
279 /// How the content definition is to be interpreted by the recipient.
280 disposition: Default<Disposition> = "disposition",
281
282 /// A per-session unique identifier for this content.
283 name: Required<ContentId> = "name",
284
285 /// Who can send data for this content.
286 senders: Default<Senders> = "senders",
287 ],
288 children: [
289 /// What to send.
290 description: Option<Description> = ("description", *) => Description,
291
292 /// How to send it.
293 transport: Option<Transport> = ("transport", *) => Transport,
294
295 /// With which security.
296 security: Option<Element> = ("security", JINGLE) => Element
297 ]
298);
299
300impl Content {
301 /// Create a new content.
302 pub fn new(creator: Creator, name: ContentId) -> Content {
303 Content {
304 creator,
305 name,
306 disposition: Disposition::Session,
307 senders: Senders::Both,
308 description: None,
309 transport: None,
310 security: None,
311 }
312 }
313
314 /// Set how the content is to be interpreted by the recipient.
315 pub fn with_disposition(mut self, disposition: Disposition) -> Content {
316 self.disposition = disposition;
317 self
318 }
319
320 /// Specify who can send data for this content.
321 pub fn with_senders(mut self, senders: Senders) -> Content {
322 self.senders = senders;
323 self
324 }
325
326 /// Set the description of this content.
327 pub fn with_description<D: Into<Description>>(mut self, description: D) -> Content {
328 self.description = Some(description.into());
329 self
330 }
331
332 /// Set the transport of this content.
333 pub fn with_transport<T: Into<Transport>>(mut self, transport: T) -> Content {
334 self.transport = Some(transport.into());
335 self
336 }
337
338 /// Set the security of this content.
339 pub fn with_security(mut self, security: Element) -> Content {
340 self.security = Some(security);
341 self
342 }
343}
344
345/// Lists the possible reasons to be included in a Jingle iq.
346#[derive(Debug, Clone, PartialEq)]
347pub enum Reason {
348 /// The party prefers to use an existing session with the peer rather than
349 /// initiate a new session; the Jingle session ID of the alternative
350 /// session SHOULD be provided as the XML character data of the \<sid/\>
351 /// child.
352 AlternativeSession, //(String),
353
354 /// The party is busy and cannot accept a session.
355 Busy,
356
357 /// The initiator wishes to formally cancel the session initiation request.
358 Cancel,
359
360 /// The action is related to connectivity problems.
361 ConnectivityError,
362
363 /// The party wishes to formally decline the session.
364 Decline,
365
366 /// The session length has exceeded a pre-defined time limit (e.g., a
367 /// meeting hosted at a conference service).
368 Expired,
369
370 /// The party has been unable to initialize processing related to the
371 /// application type.
372 FailedApplication,
373
374 /// The party has been unable to establish connectivity for the transport
375 /// method.
376 FailedTransport,
377
378 /// The action is related to a non-specific application error.
379 GeneralError,
380
381 /// The entity is going offline or is no longer available.
382 Gone,
383
384 /// The party supports the offered application type but does not support
385 /// the offered or negotiated parameters.
386 IncompatibleParameters,
387
388 /// The action is related to media processing problems.
389 MediaError,
390
391 /// The action is related to a violation of local security policies.
392 SecurityError,
393
394 /// The action is generated during the normal course of state management
395 /// and does not reflect any error.
396 Success,
397
398 /// A request has not been answered so the sender is timing out the
399 /// request.
400 Timeout,
401
402 /// The party supports none of the offered application types.
403 UnsupportedApplications,
404
405 /// The party supports none of the offered transport methods.
406 UnsupportedTransports,
407}
408
409impl FromStr for Reason {
410 type Err = Error;
411
412 fn from_str(s: &str) -> Result<Reason, Error> {
413 Ok(match s {
414 "alternative-session" => Reason::AlternativeSession,
415 "busy" => Reason::Busy,
416 "cancel" => Reason::Cancel,
417 "connectivity-error" => Reason::ConnectivityError,
418 "decline" => Reason::Decline,
419 "expired" => Reason::Expired,
420 "failed-application" => Reason::FailedApplication,
421 "failed-transport" => Reason::FailedTransport,
422 "general-error" => Reason::GeneralError,
423 "gone" => Reason::Gone,
424 "incompatible-parameters" => Reason::IncompatibleParameters,
425 "media-error" => Reason::MediaError,
426 "security-error" => Reason::SecurityError,
427 "success" => Reason::Success,
428 "timeout" => Reason::Timeout,
429 "unsupported-applications" => Reason::UnsupportedApplications,
430 "unsupported-transports" => Reason::UnsupportedTransports,
431
432 _ => return Err(Error::ParseError("Unknown reason.")),
433 })
434 }
435}
436
437impl From<Reason> for Element {
438 fn from(reason: Reason) -> Element {
439 Element::builder(
440 match reason {
441 Reason::AlternativeSession => "alternative-session",
442 Reason::Busy => "busy",
443 Reason::Cancel => "cancel",
444 Reason::ConnectivityError => "connectivity-error",
445 Reason::Decline => "decline",
446 Reason::Expired => "expired",
447 Reason::FailedApplication => "failed-application",
448 Reason::FailedTransport => "failed-transport",
449 Reason::GeneralError => "general-error",
450 Reason::Gone => "gone",
451 Reason::IncompatibleParameters => "incompatible-parameters",
452 Reason::MediaError => "media-error",
453 Reason::SecurityError => "security-error",
454 Reason::Success => "success",
455 Reason::Timeout => "timeout",
456 Reason::UnsupportedApplications => "unsupported-applications",
457 Reason::UnsupportedTransports => "unsupported-transports",
458 },
459 ns::JINGLE,
460 )
461 .build()
462 }
463}
464
465type Lang = String;
466
467/// Informs the recipient of something.
468#[derive(Debug, Clone, PartialEq)]
469pub struct ReasonElement {
470 /// The list of possible reasons to be included in a Jingle iq.
471 pub reason: Reason,
472
473 /// A human-readable description of this reason.
474 pub texts: BTreeMap<Lang, String>,
475}
476
477impl fmt::Display for ReasonElement {
478 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
479 write!(fmt, "{}", Element::from(self.reason.clone()).name())?;
480 if let Some(text) = self.texts.get("en") {
481 write!(fmt, ": {}", text)?;
482 } else if let Some(text) = self.texts.get("") {
483 write!(fmt, ": {}", text)?;
484 }
485 Ok(())
486 }
487}
488
489impl TryFrom<Element> for ReasonElement {
490 type Error = Error;
491
492 fn try_from(elem: Element) -> Result<ReasonElement, Error> {
493 check_self!(elem, "reason", JINGLE);
494 check_no_attributes!(elem, "reason");
495 let mut reason = None;
496 let mut texts = BTreeMap::new();
497 for child in elem.children() {
498 if child.is("text", ns::JINGLE) {
499 check_no_children!(child, "text");
500 check_no_unknown_attributes!(child, "text", ["xml:lang"]);
501 let lang = get_attr!(elem, "xml:lang", Default);
502 if texts.insert(lang, child.text()).is_some() {
503 return Err(Error::ParseError(
504 "Text element present twice for the same xml:lang.",
505 ));
506 }
507 } else if child.has_ns(ns::JINGLE) {
508 if reason.is_some() {
509 return Err(Error::ParseError(
510 "Reason must not have more than one reason.",
511 ));
512 }
513 check_no_children!(child, "reason");
514 check_no_attributes!(child, "reason");
515 reason = Some(child.name().parse()?);
516 } else {
517 return Err(Error::ParseError("Reason contains a foreign element."));
518 }
519 }
520 let reason = reason.ok_or(Error::ParseError("Reason doesn’t contain a valid reason."))?;
521 Ok(ReasonElement { reason, texts })
522 }
523}
524
525impl From<ReasonElement> for Element {
526 fn from(reason: ReasonElement) -> Element {
527 Element::builder("reason", ns::JINGLE)
528 .append(Element::from(reason.reason))
529 .append_all(reason.texts.into_iter().map(|(lang, text)| {
530 Element::builder("text", ns::JINGLE)
531 .attr("xml:lang", lang)
532 .append(text)
533 }))
534 .build()
535 }
536}
537
538generate_id!(
539 /// Unique identifier for a session between two JIDs.
540 SessionId
541);
542
543/// The main Jingle container, to be included in an iq stanza.
544#[derive(Debug, Clone, PartialEq)]
545pub struct Jingle {
546 /// The action to execute on both ends.
547 pub action: Action,
548
549 /// Who the initiator is.
550 pub initiator: Option<Jid>,
551
552 /// Who the responder is.
553 pub responder: Option<Jid>,
554
555 /// Unique session identifier between two entities.
556 pub sid: SessionId,
557
558 /// A list of contents to be negotiated in this session.
559 pub contents: Vec<Content>,
560
561 /// An optional reason.
562 pub reason: Option<ReasonElement>,
563
564 /// An optional grouping.
565 pub group: Option<Group>,
566
567 /// Payloads to be included.
568 pub other: Vec<Element>,
569}
570
571impl IqSetPayload for Jingle {}
572
573impl Jingle {
574 /// Create a new Jingle element.
575 pub fn new(action: Action, sid: SessionId) -> Jingle {
576 Jingle {
577 action,
578 sid,
579 initiator: None,
580 responder: None,
581 contents: Vec::new(),
582 reason: None,
583 group: None,
584 other: Vec::new(),
585 }
586 }
587
588 /// Set the initiator’s JID.
589 pub fn with_initiator(mut self, initiator: Jid) -> Jingle {
590 self.initiator = Some(initiator);
591 self
592 }
593
594 /// Set the responder’s JID.
595 pub fn with_responder(mut self, responder: Jid) -> Jingle {
596 self.responder = Some(responder);
597 self
598 }
599
600 /// Add a content to this Jingle container.
601 pub fn add_content(mut self, content: Content) -> Jingle {
602 self.contents.push(content);
603 self
604 }
605
606 /// Set the reason in this Jingle container.
607 pub fn set_reason(mut self, reason: ReasonElement) -> Jingle {
608 self.reason = Some(reason);
609 self
610 }
611
612 /// Set the grouping in this Jingle container.
613 pub fn set_group(mut self, group: Group) -> Jingle {
614 self.group = Some(group);
615 self
616 }
617}
618
619impl TryFrom<Element> for Jingle {
620 type Error = Error;
621
622 fn try_from(root: Element) -> Result<Jingle, Error> {
623 check_self!(root, "jingle", JINGLE, "Jingle");
624 check_no_unknown_attributes!(root, "Jingle", ["action", "initiator", "responder", "sid"]);
625
626 let mut jingle = Jingle {
627 action: get_attr!(root, "action", Required),
628 initiator: get_attr!(root, "initiator", Option),
629 responder: get_attr!(root, "responder", Option),
630 sid: get_attr!(root, "sid", Required),
631 contents: vec![],
632 reason: None,
633 group: None,
634 other: vec![],
635 };
636
637 for child in root.children().cloned() {
638 if child.is("content", ns::JINGLE) {
639 let content = Content::try_from(child)?;
640 jingle.contents.push(content);
641 } else if child.is("reason", ns::JINGLE) {
642 if jingle.reason.is_some() {
643 return Err(Error::ParseError(
644 "Jingle must not have more than one reason.",
645 ));
646 }
647 let reason = ReasonElement::try_from(child)?;
648 jingle.reason = Some(reason);
649 } else if child.is("group", ns::JINGLE_GROUPING) {
650 if jingle.group.is_some() {
651 return Err(Error::ParseError(
652 "Jingle must not have more than one grouping.",
653 ));
654 }
655 let group = Group::try_from(child)?;
656 jingle.group = Some(group);
657 } else {
658 jingle.other.push(child);
659 }
660 }
661
662 Ok(jingle)
663 }
664}
665
666impl From<Jingle> for Element {
667 fn from(jingle: Jingle) -> Element {
668 Element::builder("jingle", ns::JINGLE)
669 .attr("action", jingle.action)
670 .attr("initiator", jingle.initiator)
671 .attr("responder", jingle.responder)
672 .attr("sid", jingle.sid)
673 .append_all(jingle.contents)
674 .append_all(jingle.reason.map(Element::from))
675 .append_all(jingle.group.map(Element::from))
676 .build()
677 }
678}
679
680#[cfg(test)]
681mod tests {
682 use super::*;
683
684 #[cfg(target_pointer_width = "32")]
685 #[test]
686 fn test_size() {
687 assert_size!(Action, 1);
688 assert_size!(Creator, 1);
689 assert_size!(Senders, 1);
690 assert_size!(Disposition, 1);
691 assert_size!(ContentId, 12);
692 assert_size!(Content, 228);
693 assert_size!(Reason, 1);
694 assert_size!(ReasonElement, 16);
695 assert_size!(SessionId, 12);
696 assert_size!(Jingle, 152);
697 }
698
699 #[cfg(target_pointer_width = "64")]
700 #[test]
701 fn test_size() {
702 assert_size!(Action, 1);
703 assert_size!(Creator, 1);
704 assert_size!(Senders, 1);
705 assert_size!(Disposition, 1);
706 assert_size!(ContentId, 24);
707 // TODO: Remove conditional
708 #[cfg(feature = "nightly")]
709 assert_size!(Content, 448);
710 #[cfg(feature = "stable")]
711 assert_size!(Content, 440);
712 assert_size!(Reason, 1);
713 assert_size!(ReasonElement, 32);
714 assert_size!(SessionId, 24);
715 assert_size!(Jingle, 304);
716 }
717
718 #[test]
719 fn test_simple() {
720 let elem: Element =
721 "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'/>"
722 .parse()
723 .unwrap();
724 let jingle = Jingle::try_from(elem).unwrap();
725 assert_eq!(jingle.action, Action::SessionInitiate);
726 assert_eq!(jingle.sid, SessionId(String::from("coucou")));
727 }
728
729 #[test]
730 fn test_invalid_jingle() {
731 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1'/>".parse().unwrap();
732 let error = Jingle::try_from(elem).unwrap_err();
733 let message = match error {
734 Error::ParseError(string) => string,
735 _ => panic!(),
736 };
737 assert_eq!(message, "Required attribute 'action' missing.");
738
739 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-info'/>"
740 .parse()
741 .unwrap();
742 let error = Jingle::try_from(elem).unwrap_err();
743 let message = match error {
744 Error::ParseError(string) => string,
745 _ => panic!(),
746 };
747 assert_eq!(message, "Required attribute 'sid' missing.");
748
749 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='coucou' sid='coucou'/>"
750 .parse()
751 .unwrap();
752 let error = Jingle::try_from(elem).unwrap_err();
753 let message = match error {
754 Error::ParseError(string) => string,
755 _ => panic!(),
756 };
757 assert_eq!(message, "Unknown value for 'action' attribute.");
758 }
759
760 #[test]
761 fn test_content() {
762 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();
763 let jingle = Jingle::try_from(elem).unwrap();
764 assert_eq!(jingle.contents[0].creator, Creator::Initiator);
765 assert_eq!(jingle.contents[0].name, ContentId(String::from("coucou")));
766 assert_eq!(jingle.contents[0].senders, Senders::Both);
767 assert_eq!(jingle.contents[0].disposition, Disposition::Session);
768
769 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();
770 let jingle = Jingle::try_from(elem).unwrap();
771 assert_eq!(jingle.contents[0].senders, Senders::Both);
772
773 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();
774 let jingle = Jingle::try_from(elem).unwrap();
775 assert_eq!(jingle.contents[0].disposition, Disposition::EarlySession);
776 }
777
778 #[test]
779 fn test_invalid_content() {
780 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><content/></jingle>".parse().unwrap();
781 let error = Jingle::try_from(elem).unwrap_err();
782 let message = match error {
783 Error::ParseError(string) => string,
784 _ => panic!(),
785 };
786 assert_eq!(message, "Required attribute 'creator' missing.");
787
788 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><content creator='initiator'/></jingle>".parse().unwrap();
789 let error = Jingle::try_from(elem).unwrap_err();
790 let message = match error {
791 Error::ParseError(string) => string,
792 _ => panic!(),
793 };
794 assert_eq!(message, "Required attribute 'name' missing.");
795
796 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><content creator='coucou' name='coucou'/></jingle>".parse().unwrap();
797 let error = Jingle::try_from(elem).unwrap_err();
798 let message = match error {
799 Error::ParseError(string) => string,
800 _ => panic!(),
801 };
802 assert_eq!(message, "Unknown value for 'creator' attribute.");
803
804 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><content creator='initiator' name='coucou' senders='coucou'/></jingle>".parse().unwrap();
805 let error = Jingle::try_from(elem).unwrap_err();
806 let message = match error {
807 Error::ParseError(string) => string,
808 _ => panic!(),
809 };
810 assert_eq!(message, "Unknown value for 'senders' attribute.");
811
812 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><content creator='initiator' name='coucou' senders=''/></jingle>".parse().unwrap();
813 let error = Jingle::try_from(elem).unwrap_err();
814 let message = match error {
815 Error::ParseError(string) => string,
816 _ => panic!(),
817 };
818 assert_eq!(message, "Unknown value for 'senders' attribute.");
819 }
820
821 #[test]
822 fn test_reason() {
823 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><reason><success/></reason></jingle>".parse().unwrap();
824 let jingle = Jingle::try_from(elem).unwrap();
825 let reason = jingle.reason.unwrap();
826 assert_eq!(reason.reason, Reason::Success);
827 assert_eq!(reason.texts, BTreeMap::new());
828
829 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><reason><success/><text>coucou</text></reason></jingle>".parse().unwrap();
830 let jingle = Jingle::try_from(elem).unwrap();
831 let reason = jingle.reason.unwrap();
832 assert_eq!(reason.reason, Reason::Success);
833 assert_eq!(reason.texts.get(""), Some(&String::from("coucou")));
834 }
835
836 #[test]
837 fn test_invalid_reason() {
838 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><reason/></jingle>".parse().unwrap();
839 let error = Jingle::try_from(elem).unwrap_err();
840 let message = match error {
841 Error::ParseError(string) => string,
842 _ => panic!(),
843 };
844 assert_eq!(message, "Reason doesn’t contain a valid reason.");
845
846 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><reason><a/></reason></jingle>".parse().unwrap();
847 let error = Jingle::try_from(elem).unwrap_err();
848 let message = match error {
849 Error::ParseError(string) => string,
850 _ => panic!(),
851 };
852 assert_eq!(message, "Unknown reason.");
853
854 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();
855 let error = Jingle::try_from(elem).unwrap_err();
856 let message = match error {
857 Error::ParseError(string) => string,
858 _ => panic!(),
859 };
860 assert_eq!(message, "Reason contains a foreign element.");
861
862 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><reason><decline/></reason><reason/></jingle>".parse().unwrap();
863 let error = Jingle::try_from(elem).unwrap_err();
864 let message = match error {
865 Error::ParseError(string) => string,
866 _ => panic!(),
867 };
868 assert_eq!(message, "Jingle must not have more than one reason.");
869
870 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><reason><decline/><text/><text/></reason></jingle>".parse().unwrap();
871 let error = Jingle::try_from(elem).unwrap_err();
872 let message = match error {
873 Error::ParseError(string) => string,
874 _ => panic!(),
875 };
876 assert_eq!(message, "Text element present twice for the same xml:lang.");
877 }
878
879 #[test]
880 fn test_serialize_jingle() {
881 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>"
882 .parse()
883 .unwrap();
884
885 let jingle = Jingle {
886 action: Action::SessionInitiate,
887 initiator: None,
888 responder: None,
889 sid: SessionId(String::from("a73sjjvkla37jfea")),
890 contents: vec![Content {
891 creator: Creator::Initiator,
892 disposition: Disposition::default(),
893 name: ContentId(String::from("this-is-a-stub")),
894 senders: Senders::default(),
895 description: Some(Description::Unknown(
896 Element::builder("description", "urn:xmpp:jingle:apps:stub:0").build(),
897 )),
898 transport: Some(Transport::Unknown(
899 Element::builder("transport", "urn:xmpp:jingle:transports:stub:0").build(),
900 )),
901 security: None,
902 }],
903 reason: None,
904 group: None,
905 other: vec![],
906 };
907 let serialized: Element = jingle.into();
908 assert_eq!(serialized, reference);
909 }
910}