jingle.rs

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