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