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