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 try_from::TryFrom;
8use std::str::FromStr;
9
10use minidom::Element;
11use jid::Jid;
12
13use error::Error;
14use ns;
15use iq::IqSetPayload;
16
17generate_attribute!(Action, "action", {
18 ContentAccept => "content-accept",
19 ContentAdd => "content-add",
20 ContentModify => "content-modify",
21 ContentReject => "content-reject",
22 ContentRemove => "content-remove",
23 DescriptionInfo => "description-info",
24 SecurityInfo => "security-info",
25 SessionAccept => "session-accept",
26 SessionInfo => "session-info",
27 SessionInitiate => "session-initiate",
28 SessionTerminate => "session-terminate",
29 TransportAccept => "transport-accept",
30 TransportInfo => "transport-info",
31 TransportReject => "transport-reject",
32 TransportReplace => "transport-replace",
33});
34
35generate_attribute!(Creator, "creator", {
36 Initiator => "initiator",
37 Responder => "responder",
38});
39
40generate_attribute!(Senders, "senders", {
41 Both => "both",
42 Initiator => "initiator",
43 None => "none",
44 Responder => "responder",
45}, Default = Both);
46
47// From https://www.iana.org/assignments/cont-disp/cont-disp.xhtml
48generate_attribute!(Disposition, "disposition", {
49 Inline => "inline",
50 Attachment => "attachment",
51 FormData => "form-data",
52 Signal => "signal",
53 Alert => "alert",
54 Icon => "icon",
55 Render => "render",
56 RecipientListHistory => "recipient-list-history",
57 Session => "session",
58 Aib => "aib",
59 EarlySession => "early-session",
60 RecipientList => "recipient-list",
61 Notification => "notification",
62 ByReference => "by-reference",
63 InfoPackage => "info-package",
64 RecordingSession => "recording-session",
65}, Default = Session);
66
67generate_id!(ContentId);
68
69#[derive(Debug, Clone)]
70pub struct Content {
71 pub creator: Creator,
72 pub disposition: Disposition,
73 pub name: ContentId,
74 pub senders: Senders,
75 pub description: Option<Element>,
76 pub transport: Option<Element>,
77 pub security: Option<Element>,
78}
79
80impl Content {
81 pub fn new(creator: Creator, name: ContentId) -> Content {
82 Content {
83 creator,
84 name,
85 disposition: Disposition::Session,
86 senders: Senders::Both,
87 description: None,
88 transport: None,
89 security: None,
90 }
91 }
92
93 pub fn with_disposition(mut self, disposition: Disposition) -> Content {
94 self.disposition = disposition;
95 self
96 }
97
98 pub fn with_senders(mut self, senders: Senders) -> Content {
99 self.senders = senders;
100 self
101 }
102
103 pub fn with_description(mut self, description: Element) -> Content {
104 self.description = Some(description);
105 self
106 }
107
108 pub fn with_transport(mut self, transport: Element) -> Content {
109 self.transport = Some(transport);
110 self
111 }
112
113 pub fn with_security(mut self, security: Element) -> Content {
114 self.security = Some(security);
115 self
116 }
117}
118
119impl TryFrom<Element> for Content {
120 type Err = Error;
121
122 fn try_from(elem: Element) -> Result<Content, Error> {
123 check_self!(elem, "content", JINGLE);
124 check_no_unknown_attributes!(elem, "content", ["creator", "disposition", "name", "senders"]);
125
126 let mut content = Content {
127 creator: get_attr!(elem, "creator", required),
128 disposition: get_attr!(elem, "disposition", default),
129 name: get_attr!(elem, "name", required),
130 senders: get_attr!(elem, "senders", default),
131 description: None,
132 transport: None,
133 security: None,
134 };
135 for child in elem.children() {
136 if child.name() == "description" {
137 if content.description.is_some() {
138 return Err(Error::ParseError("Content must not have more than one description."));
139 }
140 content.description = Some(child.clone());
141 } else if child.name() == "transport" {
142 if content.transport.is_some() {
143 return Err(Error::ParseError("Content must not have more than one transport."));
144 }
145 content.transport = Some(child.clone());
146 } else if child.name() == "security" {
147 if content.security.is_some() {
148 return Err(Error::ParseError("Content must not have more than one security."));
149 }
150 content.security = Some(child.clone());
151 } else {
152 return Err(Error::ParseError("Unknown child in content element."));
153 }
154 }
155 Ok(content)
156 }
157}
158
159impl From<Content> for Element {
160 fn from(content: Content) -> Element {
161 Element::builder("content")
162 .ns(ns::JINGLE)
163 .attr("creator", content.creator)
164 .attr("disposition", content.disposition)
165 .attr("name", content.name)
166 .attr("senders", content.senders)
167 .append(content.description)
168 .append(content.transport)
169 .append(content.security)
170 .build()
171 }
172}
173
174#[derive(Debug, Clone, PartialEq)]
175pub enum Reason {
176 AlternativeSession, //(String),
177 Busy,
178 Cancel,
179 ConnectivityError,
180 Decline,
181 Expired,
182 FailedApplication,
183 FailedTransport,
184 GeneralError,
185 Gone,
186 IncompatibleParameters,
187 MediaError,
188 SecurityError,
189 Success,
190 Timeout,
191 UnsupportedApplications,
192 UnsupportedTransports,
193}
194
195impl FromStr for Reason {
196 type Err = Error;
197
198 fn from_str(s: &str) -> Result<Reason, Error> {
199 Ok(match s {
200 "alternative-session" => Reason::AlternativeSession,
201 "busy" => Reason::Busy,
202 "cancel" => Reason::Cancel,
203 "connectivity-error" => Reason::ConnectivityError,
204 "decline" => Reason::Decline,
205 "expired" => Reason::Expired,
206 "failed-application" => Reason::FailedApplication,
207 "failed-transport" => Reason::FailedTransport,
208 "general-error" => Reason::GeneralError,
209 "gone" => Reason::Gone,
210 "incompatible-parameters" => Reason::IncompatibleParameters,
211 "media-error" => Reason::MediaError,
212 "security-error" => Reason::SecurityError,
213 "success" => Reason::Success,
214 "timeout" => Reason::Timeout,
215 "unsupported-applications" => Reason::UnsupportedApplications,
216 "unsupported-transports" => Reason::UnsupportedTransports,
217
218 _ => return Err(Error::ParseError("Unknown reason.")),
219 })
220 }
221}
222
223impl From<Reason> for Element {
224 fn from(reason: Reason) -> Element {
225 Element::builder(match reason {
226 Reason::AlternativeSession => "alternative-session",
227 Reason::Busy => "busy",
228 Reason::Cancel => "cancel",
229 Reason::ConnectivityError => "connectivity-error",
230 Reason::Decline => "decline",
231 Reason::Expired => "expired",
232 Reason::FailedApplication => "failed-application",
233 Reason::FailedTransport => "failed-transport",
234 Reason::GeneralError => "general-error",
235 Reason::Gone => "gone",
236 Reason::IncompatibleParameters => "incompatible-parameters",
237 Reason::MediaError => "media-error",
238 Reason::SecurityError => "security-error",
239 Reason::Success => "success",
240 Reason::Timeout => "timeout",
241 Reason::UnsupportedApplications => "unsupported-applications",
242 Reason::UnsupportedTransports => "unsupported-transports",
243 }).build()
244 }
245}
246
247#[derive(Debug, Clone)]
248pub struct ReasonElement {
249 pub reason: Reason,
250 pub text: Option<String>,
251}
252
253impl TryFrom<Element> for ReasonElement {
254 type Err = Error;
255
256 fn try_from(elem: Element) -> Result<ReasonElement, Error> {
257 check_self!(elem, "reason", JINGLE);
258 let mut reason = None;
259 let mut text = None;
260 for child in elem.children() {
261 if !child.has_ns(ns::JINGLE) {
262 return Err(Error::ParseError("Reason contains a foreign element."));
263 }
264 match child.name() {
265 "text" => {
266 if text.is_some() {
267 return Err(Error::ParseError("Reason must not have more than one text."));
268 }
269 text = Some(child.text());
270 },
271 name => {
272 if reason.is_some() {
273 return Err(Error::ParseError("Reason must not have more than one reason."));
274 }
275 reason = Some(name.parse()?);
276 },
277 }
278 }
279 let reason = reason.ok_or(Error::ParseError("Reason doesn’t contain a valid reason."))?;
280 Ok(ReasonElement {
281 reason: reason,
282 text: text,
283 })
284 }
285}
286
287impl From<ReasonElement> for Element {
288 fn from(reason: ReasonElement) -> Element {
289 Element::builder("reason")
290 .append(Element::from(reason.reason))
291 .append(reason.text)
292 .build()
293 }
294}
295
296generate_id!(SessionId);
297
298#[derive(Debug, Clone)]
299pub struct Jingle {
300 pub action: Action,
301 pub initiator: Option<Jid>,
302 pub responder: Option<Jid>,
303 pub sid: SessionId,
304 pub contents: Vec<Content>,
305 pub reason: Option<ReasonElement>,
306 pub other: Vec<Element>,
307}
308
309impl IqSetPayload for Jingle {}
310
311impl Jingle {
312 pub fn new(action: Action, sid: SessionId) -> Jingle {
313 Jingle {
314 action: action,
315 sid: sid,
316 initiator: None,
317 responder: None,
318 contents: Vec::new(),
319 reason: None,
320 other: Vec::new(),
321 }
322 }
323
324 pub fn with_initiator(mut self, initiator: Jid) -> Jingle {
325 self.initiator = Some(initiator);
326 self
327 }
328
329 pub fn with_responder(mut self, responder: Jid) -> Jingle {
330 self.responder = Some(responder);
331 self
332 }
333
334 pub fn add_content(mut self, content: Content) -> Jingle {
335 self.contents.push(content);
336 self
337 }
338
339 pub fn set_reason(mut self, content: Content) -> Jingle {
340 self.contents.push(content);
341 self
342 }
343}
344
345impl TryFrom<Element> for Jingle {
346 type Err = Error;
347
348 fn try_from(root: Element) -> Result<Jingle, Error> {
349 check_self!(root, "jingle", JINGLE, "Jingle");
350 check_no_unknown_attributes!(root, "Jingle", ["action", "initiator", "responder", "sid"]);
351
352 let mut jingle = Jingle {
353 action: get_attr!(root, "action", required),
354 initiator: get_attr!(root, "initiator", optional),
355 responder: get_attr!(root, "responder", optional),
356 sid: get_attr!(root, "sid", required),
357 contents: vec!(),
358 reason: None,
359 other: vec!(),
360 };
361
362 for child in root.children().cloned() {
363 if child.is("content", ns::JINGLE) {
364 let content = Content::try_from(child)?;
365 jingle.contents.push(content);
366 } else if child.is("reason", ns::JINGLE) {
367 if jingle.reason.is_some() {
368 return Err(Error::ParseError("Jingle must not have more than one reason."));
369 }
370 let reason = ReasonElement::try_from(child)?;
371 jingle.reason = Some(reason);
372 } else {
373 jingle.other.push(child);
374 }
375 }
376
377 Ok(jingle)
378 }
379}
380
381impl From<Jingle> for Element {
382 fn from(jingle: Jingle) -> Element {
383 Element::builder("jingle")
384 .ns(ns::JINGLE)
385 .attr("action", jingle.action)
386 .attr("initiator", jingle.initiator)
387 .attr("responder", jingle.responder)
388 .attr("sid", jingle.sid)
389 .append(jingle.contents)
390 .append(jingle.reason)
391 .build()
392 }
393}
394
395#[cfg(test)]
396mod tests {
397 use super::*;
398
399 #[test]
400 fn test_simple() {
401 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'/>".parse().unwrap();
402 let jingle = Jingle::try_from(elem).unwrap();
403 assert_eq!(jingle.action, Action::SessionInitiate);
404 assert_eq!(jingle.sid, SessionId(String::from("coucou")));
405 }
406
407 #[test]
408 fn test_invalid_jingle() {
409 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1'/>".parse().unwrap();
410 let error = Jingle::try_from(elem).unwrap_err();
411 let message = match error {
412 Error::ParseError(string) => string,
413 _ => panic!(),
414 };
415 assert_eq!(message, "Required attribute 'action' missing.");
416
417 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-info'/>".parse().unwrap();
418 let error = Jingle::try_from(elem).unwrap_err();
419 let message = match error {
420 Error::ParseError(string) => string,
421 _ => panic!(),
422 };
423 assert_eq!(message, "Required attribute 'sid' missing.");
424
425 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='coucou' sid='coucou'/>".parse().unwrap();
426 let error = Jingle::try_from(elem).unwrap_err();
427 let message = match error {
428 Error::ParseError(string) => string,
429 _ => panic!(),
430 };
431 assert_eq!(message, "Unknown value for 'action' attribute.");
432 }
433
434 #[test]
435 fn test_content() {
436 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();
437 let jingle = Jingle::try_from(elem).unwrap();
438 assert_eq!(jingle.contents[0].creator, Creator::Initiator);
439 assert_eq!(jingle.contents[0].name, ContentId(String::from("coucou")));
440 assert_eq!(jingle.contents[0].senders, Senders::Both);
441 assert_eq!(jingle.contents[0].disposition, Disposition::Session);
442
443 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();
444 let jingle = Jingle::try_from(elem).unwrap();
445 assert_eq!(jingle.contents[0].senders, Senders::Both);
446
447 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();
448 let jingle = Jingle::try_from(elem).unwrap();
449 assert_eq!(jingle.contents[0].disposition, Disposition::EarlySession);
450 }
451
452 #[test]
453 fn test_invalid_content() {
454 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><content/></jingle>".parse().unwrap();
455 let error = Jingle::try_from(elem).unwrap_err();
456 let message = match error {
457 Error::ParseError(string) => string,
458 _ => panic!(),
459 };
460 assert_eq!(message, "Required attribute 'creator' missing.");
461
462 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><content creator='initiator'/></jingle>".parse().unwrap();
463 let error = Jingle::try_from(elem).unwrap_err();
464 let message = match error {
465 Error::ParseError(string) => string,
466 _ => panic!(),
467 };
468 assert_eq!(message, "Required attribute 'name' missing.");
469
470 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><content creator='coucou' name='coucou'/></jingle>".parse().unwrap();
471 let error = Jingle::try_from(elem).unwrap_err();
472 let message = match error {
473 Error::ParseError(string) => string,
474 _ => panic!(),
475 };
476 assert_eq!(message, "Unknown value for 'creator' attribute.");
477
478 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><content creator='initiator' name='coucou' senders='coucou'/></jingle>".parse().unwrap();
479 let error = Jingle::try_from(elem).unwrap_err();
480 let message = match error {
481 Error::ParseError(string) => string,
482 _ => panic!(),
483 };
484 assert_eq!(message, "Unknown value for 'senders' attribute.");
485
486 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><content creator='initiator' name='coucou' senders=''/></jingle>".parse().unwrap();
487 let error = Jingle::try_from(elem).unwrap_err();
488 let message = match error {
489 Error::ParseError(string) => string,
490 _ => panic!(),
491 };
492 assert_eq!(message, "Unknown value for 'senders' attribute.");
493 }
494
495 #[test]
496 fn test_reason() {
497 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><reason><success/></reason></jingle>".parse().unwrap();
498 let jingle = Jingle::try_from(elem).unwrap();
499 let reason = jingle.reason.unwrap();
500 assert_eq!(reason.reason, Reason::Success);
501 assert_eq!(reason.text, None);
502
503 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><reason><success/><text>coucou</text></reason></jingle>".parse().unwrap();
504 let jingle = Jingle::try_from(elem).unwrap();
505 let reason = jingle.reason.unwrap();
506 assert_eq!(reason.reason, Reason::Success);
507 assert_eq!(reason.text, Some(String::from("coucou")));
508 }
509
510 #[test]
511 fn test_invalid_reason() {
512 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><reason/></jingle>".parse().unwrap();
513 let error = Jingle::try_from(elem).unwrap_err();
514 let message = match error {
515 Error::ParseError(string) => string,
516 _ => panic!(),
517 };
518 assert_eq!(message, "Reason doesn’t contain a valid reason.");
519
520 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><reason><a/></reason></jingle>".parse().unwrap();
521 let error = Jingle::try_from(elem).unwrap_err();
522 let message = match error {
523 Error::ParseError(string) => string,
524 _ => panic!(),
525 };
526 assert_eq!(message, "Unknown reason.");
527
528 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();
529 let error = Jingle::try_from(elem).unwrap_err();
530 let message = match error {
531 Error::ParseError(string) => string,
532 _ => panic!(),
533 };
534 assert_eq!(message, "Reason contains a foreign element.");
535
536 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><reason><decline/></reason><reason/></jingle>".parse().unwrap();
537 let error = Jingle::try_from(elem).unwrap_err();
538 let message = match error {
539 Error::ParseError(string) => string,
540 _ => panic!(),
541 };
542 assert_eq!(message, "Jingle must not have more than one reason.");
543
544 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><reason><decline/><text/><text/></reason></jingle>".parse().unwrap();
545 let error = Jingle::try_from(elem).unwrap_err();
546 let message = match error {
547 Error::ParseError(string) => string,
548 _ => panic!(),
549 };
550 assert_eq!(message, "Reason must not have more than one text.");
551 }
552}