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