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        println!("{:#?}", jingle);
379
380        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();
381        let jingle = jingle::parse_jingle(&elem).unwrap();
382        assert_eq!(jingle.contents[0].senders, jingle::Senders::Both);
383
384        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();
385        let jingle = jingle::parse_jingle(&elem).unwrap();
386        assert_eq!(jingle.contents[0].disposition, "early-session");
387    }
388
389    #[test]
390    fn test_invalid_content() {
391        let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><content/></jingle>".parse().unwrap();
392        let error = jingle::parse_jingle(&elem).unwrap_err();
393        let message = match error {
394            Error::ParseError(string) => string,
395            _ => panic!(),
396        };
397        assert_eq!(message, "Content must have a 'creator' attribute.");
398
399        let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><content creator='initiator'/></jingle>".parse().unwrap();
400        let error = jingle::parse_jingle(&elem).unwrap_err();
401        let message = match error {
402            Error::ParseError(string) => string,
403            _ => panic!(),
404        };
405        assert_eq!(message, "Content must have a 'name' attribute.");
406
407        let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><content creator='coucou' name='coucou'/></jingle>".parse().unwrap();
408        let error = jingle::parse_jingle(&elem).unwrap_err();
409        let message = match error {
410            Error::ParseError(string) => string,
411            _ => panic!(),
412        };
413        assert_eq!(message, "Unknown creator.");
414
415        let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><content creator='initiator' name='coucou' senders='coucou'/></jingle>".parse().unwrap();
416        let error = jingle::parse_jingle(&elem).unwrap_err();
417        let message = match error {
418            Error::ParseError(string) => string,
419            _ => panic!(),
420        };
421        assert_eq!(message, "Unknown senders.");
422
423        let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><content creator='initiator' name='coucou' senders=''/></jingle>".parse().unwrap();
424        let error = jingle::parse_jingle(&elem).unwrap_err();
425        let message = match error {
426            Error::ParseError(string) => string,
427            _ => panic!(),
428        };
429        assert_eq!(message, "Unknown senders.");
430
431        let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><content creator='initiator' name='coucou'/></jingle>".parse().unwrap();
432        let error = jingle::parse_jingle(&elem).unwrap_err();
433        let message = match error {
434            Error::ParseError(string) => string,
435            _ => panic!(),
436        };
437        assert_eq!(message, "Content must have one description.");
438
439        let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><content creator='initiator' name='coucou'><description/></content></jingle>".parse().unwrap();
440        let error = jingle::parse_jingle(&elem).unwrap_err();
441        let message = match error {
442            Error::ParseError(string) => string,
443            _ => panic!(),
444        };
445        assert_eq!(message, "Content must have one transport.");
446    }
447
448    #[test]
449    fn test_reason() {
450        let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><reason><success/></reason></jingle>".parse().unwrap();
451        let jingle = jingle::parse_jingle(&elem).unwrap();
452        let reason = jingle.reason.unwrap();
453        assert_eq!(reason.reason, jingle::Reason::Success);
454        assert_eq!(reason.text, None);
455
456        let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><reason><success/><text>coucou</text></reason></jingle>".parse().unwrap();
457        let jingle = jingle::parse_jingle(&elem).unwrap();
458        let reason = jingle.reason.unwrap();
459        assert_eq!(reason.reason, jingle::Reason::Success);
460        assert_eq!(reason.text, Some(String::from("coucou")));
461    }
462
463    #[test]
464    fn test_invalid_reason() {
465        let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><reason/></jingle>".parse().unwrap();
466        let error = jingle::parse_jingle(&elem).unwrap_err();
467        let message = match error {
468            Error::ParseError(string) => string,
469            _ => panic!(),
470        };
471        assert_eq!(message, "Reason doesn’t contain a valid reason.");
472
473        let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><reason><a/></reason></jingle>".parse().unwrap();
474        let error = jingle::parse_jingle(&elem).unwrap_err();
475        let message = match error {
476            Error::ParseError(string) => string,
477            _ => panic!(),
478        };
479        assert_eq!(message, "Unknown reason.");
480
481        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();
482        let error = jingle::parse_jingle(&elem).unwrap_err();
483        let message = match error {
484            Error::ParseError(string) => string,
485            _ => panic!(),
486        };
487        assert_eq!(message, "Reason contains a foreign element.");
488
489        let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><reason><decline/></reason><reason/></jingle>".parse().unwrap();
490        let error = jingle::parse_jingle(&elem).unwrap_err();
491        let message = match error {
492            Error::ParseError(string) => string,
493            _ => panic!(),
494        };
495        assert_eq!(message, "Jingle must not have more than one reason.");
496
497        let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><reason><decline/><text/><text/></reason></jingle>".parse().unwrap();
498        let error = jingle::parse_jingle(&elem).unwrap_err();
499        let message = match error {
500            Error::ParseError(string) => string,
501            _ => panic!(),
502        };
503        assert_eq!(message, "Reason must not have more than one text.");
504    }
505}