jingle.rs

  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}