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