jingle_message.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;
  8
  9use minidom::Element;
 10
 11use error::Error;
 12
 13use jingle::SessionId;
 14
 15use ns;
 16
 17/// Defines a protocol for broadcasting Jingle requests to all of the clients
 18/// of a user.
 19#[derive(Debug, Clone)]
 20pub enum JingleMI {
 21    /// Indicates we want to start a Jingle session.
 22    Propose {
 23        /// The generated session identifier, must be unique between two users.
 24        sid: SessionId,
 25
 26        /// The application description of the proposed session.
 27        // TODO: Use a more specialised type here.
 28        description: Element,
 29    },
 30
 31    /// Cancels a previously proposed session.
 32    Retract(SessionId),
 33
 34    /// Accepts a session proposed by the other party.
 35    Accept(SessionId),
 36
 37    /// Proceed with a previously proposed session.
 38    Proceed(SessionId),
 39
 40    /// Rejects a session proposed by the other party.
 41    Reject(SessionId),
 42}
 43
 44fn get_sid(elem: Element) -> Result<SessionId, Error> {
 45    check_no_unknown_attributes!(elem, "Jingle message", ["id"]);
 46    Ok(SessionId(get_attr!(elem, "id", required)))
 47}
 48
 49fn check_empty_and_get_sid(elem: Element) -> Result<SessionId, Error> {
 50    check_no_children!(elem, "Jingle message");
 51    get_sid(elem)
 52}
 53
 54impl TryFrom<Element> for JingleMI {
 55    type Err = Error;
 56
 57    fn try_from(elem: Element) -> Result<JingleMI, Error> {
 58        if !elem.has_ns(ns::JINGLE_MESSAGE) {
 59            return Err(Error::ParseError("This is not a Jingle message element."));
 60        }
 61        Ok(match elem.name() {
 62            "propose" => {
 63                let mut description = None;
 64                for child in elem.children() {
 65                    if child.name() != "description" {
 66                        return Err(Error::ParseError("Unknown child in propose element."));
 67                    }
 68                    if description.is_some() {
 69                        return Err(Error::ParseError("Too many children in propose element."));
 70                    }
 71                    description = Some(child.clone());
 72                }
 73                JingleMI::Propose {
 74                    sid: get_sid(elem)?,
 75                    description: description.ok_or(Error::ParseError("Propose element doesn’t contain a description."))?,
 76                }
 77            },
 78            "retract" => JingleMI::Retract(check_empty_and_get_sid(elem)?),
 79            "accept" => JingleMI::Accept(check_empty_and_get_sid(elem)?),
 80            "proceed" => JingleMI::Proceed(check_empty_and_get_sid(elem)?),
 81            "reject" => JingleMI::Reject(check_empty_and_get_sid(elem)?),
 82            _ => return Err(Error::ParseError("This is not a Jingle message element.")),
 83        })
 84    }
 85}
 86
 87impl From<JingleMI> for Element {
 88    fn from(jingle_mi: JingleMI) -> Element {
 89        match jingle_mi {
 90            JingleMI::Propose { sid, description } => {
 91                Element::builder("propose")
 92                        .ns(ns::JINGLE_MESSAGE)
 93                        .attr("id", sid)
 94                        .append(description)
 95            },
 96            JingleMI::Retract(sid) => {
 97                Element::builder("retract")
 98                        .ns(ns::JINGLE_MESSAGE)
 99                        .attr("id", sid)
100            }
101            JingleMI::Accept(sid) => {
102                Element::builder("accept")
103                        .ns(ns::JINGLE_MESSAGE)
104                        .attr("id", sid)
105            }
106            JingleMI::Proceed(sid) => {
107                Element::builder("proceed")
108                        .ns(ns::JINGLE_MESSAGE)
109                        .attr("id", sid)
110            }
111            JingleMI::Reject(sid) => {
112                Element::builder("reject")
113                        .ns(ns::JINGLE_MESSAGE)
114                        .attr("id", sid)
115            }
116        }.build()
117    }
118}
119
120#[cfg(test)]
121mod tests {
122    use super::*;
123
124    #[test]
125    fn test_size() {
126        assert_size!(JingleMI, 136);
127    }
128
129    #[test]
130    fn test_simple() {
131        let elem: Element = "<accept xmlns='urn:xmpp:jingle-message:0' id='coucou'/>".parse().unwrap();
132        JingleMI::try_from(elem).unwrap();
133    }
134
135    #[test]
136    fn test_invalid_child() {
137        let elem: Element = "<propose xmlns='urn:xmpp:jingle-message:0' id='coucou'><coucou/></propose>".parse().unwrap();
138        let error = JingleMI::try_from(elem).unwrap_err();
139        let message = match error {
140            Error::ParseError(string) => string,
141            _ => panic!(),
142        };
143        assert_eq!(message, "Unknown child in propose element.");
144    }
145}