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