jingle.rs

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