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