jingle.rs

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