jingle_s5b.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::convert::TryFrom;
  8use std::str::FromStr;
  9
 10use minidom::{Element, IntoAttributeValue};
 11
 12use error::Error;
 13
 14use ns;
 15
 16#[derive(Debug, Clone, PartialEq)]
 17pub enum Type {
 18    Assisted,
 19    Direct,
 20    Proxy,
 21    Tunnel,
 22}
 23
 24impl Default for Type {
 25    fn default() -> Type {
 26        Type::Direct
 27    }
 28}
 29
 30impl FromStr for Type {
 31    type Err = Error;
 32
 33    fn from_str(s: &str) -> Result<Type, Error> {
 34        Ok(match s {
 35            "assisted" => Type::Assisted,
 36            "direct" => Type::Direct,
 37            "proxy" => Type::Proxy,
 38            "tunnel" => Type::Tunnel,
 39
 40            _ => return Err(Error::ParseError("Invalid 'type' attribute in candidate element.")),
 41        })
 42    }
 43}
 44
 45impl IntoAttributeValue for Type {
 46    fn into_attribute_value(self) -> Option<String> {
 47        Some(match self {
 48            Type::Assisted => String::from("assisted"),
 49            Type::Direct => return None,
 50            Type::Proxy => String::from("proxy"),
 51            Type::Tunnel => String::from("tunnel"),
 52        })
 53    }
 54}
 55
 56#[derive(Debug, Clone)]
 57pub struct Candidate {
 58    pub cid: String,
 59    pub host: String,
 60    pub jid: String,
 61    pub port: Option<u16>,
 62    pub priority: u32,
 63    pub type_: Type,
 64}
 65
 66impl Into<Element> for Candidate {
 67    fn into(self) -> Element {
 68        Element::builder("candidate")
 69                .ns(ns::JINGLE_S5B)
 70                .attr("cid", self.cid)
 71                .attr("host", self.host)
 72                .attr("jid", self.jid)
 73                .attr("port", match self.port { Some(port) => Some(format!("{}", port)), None => None })
 74                .attr("priority", format!("{}", self.priority))
 75                .attr("type", self.type_)
 76                .build()
 77    }
 78}
 79
 80#[derive(Debug, Clone, PartialEq)]
 81pub enum Mode {
 82    Tcp,
 83    Udp,
 84}
 85
 86impl Default for Mode {
 87    fn default() -> Mode {
 88        Mode::Tcp
 89    }
 90}
 91
 92impl FromStr for Mode {
 93    type Err = Error;
 94
 95    fn from_str(s: &str) -> Result<Mode, Error> {
 96        Ok(match s {
 97            "tcp" => Mode::Tcp,
 98            "udp" => Mode::Udp,
 99
100            _ => return Err(Error::ParseError("Invalid 'mode' attribute.")),
101        })
102    }
103}
104
105impl IntoAttributeValue for Mode {
106    fn into_attribute_value(self) -> Option<String> {
107        match self {
108            Mode::Tcp => None,
109            Mode::Udp => Some(String::from("udp")),
110        }
111    }
112}
113
114#[derive(Debug, Clone)]
115pub enum TransportPayload {
116    Activated(String),
117    Candidates(Vec<Candidate>),
118    CandidateError,
119    CandidateUsed(String),
120    ProxyError,
121    None,
122}
123
124#[derive(Debug, Clone)]
125pub struct Transport {
126    pub sid: String,
127    pub dstaddr: Option<String>,
128    pub mode: Mode,
129    pub payload: TransportPayload,
130}
131
132impl TryFrom<Element> for Transport {
133    type Error = Error;
134
135    fn try_from(elem: Element) -> Result<Transport, Error> {
136        if elem.is("transport", ns::JINGLE_S5B) {
137            let sid = get_attr!(elem, "sid", required);
138            let dstaddr = get_attr!(elem, "dstaddr", optional);
139            let mode = get_attr!(elem, "mode", default);
140
141            let mut payload = None;
142            for child in elem.children() {
143                payload = Some(if child.is("candidate", ns::JINGLE_S5B) {
144                    let mut candidates = match payload {
145                        Some(TransportPayload::Candidates(candidates)) => candidates,
146                        Some(_) => return Err(Error::ParseError("Non-candidate child already present in JingleS5B transport element.")),
147                        None => vec!(),
148                    };
149                    candidates.push(Candidate {
150                        cid: get_attr!(child, "cid", required),
151                        host: get_attr!(child, "host", required),
152                        jid: get_attr!(child, "jid", required),
153                        port: get_attr!(child, "port", optional),
154                        priority: get_attr!(child, "priority", required),
155                        type_: get_attr!(child, "type", default),
156                    });
157                    TransportPayload::Candidates(candidates)
158                } else if child.is("activated", ns::JINGLE_S5B) {
159                    if payload.is_some() {
160                        return Err(Error::ParseError("Non-activated child already present in JingleS5B transport element."));
161                    }
162                    let cid = get_attr!(child, "cid", required);
163                    TransportPayload::Activated(cid)
164                } else if child.is("candidate-error", ns::JINGLE_S5B) {
165                    if payload.is_some() {
166                        return Err(Error::ParseError("Non-candidate-error child already present in JingleS5B transport element."));
167                    }
168                    TransportPayload::CandidateError
169                } else if child.is("candidate-used", ns::JINGLE_S5B) {
170                    if payload.is_some() {
171                        return Err(Error::ParseError("Non-candidate-used child already present in JingleS5B transport element."));
172                    }
173                    let cid = get_attr!(child, "cid", required);
174                    TransportPayload::CandidateUsed(cid)
175                } else if child.is("proxy-error", ns::JINGLE_S5B) {
176                    if payload.is_some() {
177                        return Err(Error::ParseError("Non-proxy-error child already present in JingleS5B transport element."));
178                    }
179                    TransportPayload::ProxyError
180                } else {
181                    return Err(Error::ParseError("Unknown child in JingleS5B transport element."));
182                });
183            }
184            let payload = payload.unwrap_or(TransportPayload::None);
185            Ok(Transport {
186                sid: sid,
187                dstaddr: dstaddr,
188                mode: mode,
189                payload: payload,
190            })
191        } else {
192            Err(Error::ParseError("This is not an JingleS5B transport element."))
193        }
194    }
195}
196
197impl Into<Element> for Transport {
198    fn into(self) -> Element {
199        Element::builder("transport")
200                .ns(ns::JINGLE_S5B)
201                .attr("sid", self.sid)
202                .attr("dstaddr", self.dstaddr)
203                .attr("mode", self.mode)
204                .append(match self.payload {
205                     TransportPayload::Candidates(mut candidates) => {
206                         candidates.drain(..)
207                                   .map(|candidate| candidate.into())
208                                   .collect::<Vec<Element>>()
209                     },
210                     TransportPayload::Activated(cid) => {
211                         vec!(Element::builder("activated")
212                                      .ns(ns::JINGLE_S5B)
213                                      .attr("cid", cid)
214                                      .build())
215                     },
216                     TransportPayload::CandidateError => {
217                         vec!(Element::builder("candidate-error")
218                                      .ns(ns::JINGLE_S5B)
219                                      .build())
220                     },
221                     TransportPayload::CandidateUsed(ref cid) => {
222                         vec!(Element::builder("candidate-used")
223                                      .ns(ns::JINGLE_S5B)
224                                      .attr("cid", cid.to_owned())
225                                      .build())
226                     },
227                     TransportPayload::ProxyError => {
228                         vec!(Element::builder("proxy-error")
229                                      .ns(ns::JINGLE_S5B)
230                                      .build())
231                     },
232                     TransportPayload::None => vec!(),
233                 })
234                .build()
235    }
236}
237
238#[cfg(test)]
239mod tests {
240    use super::*;
241
242    #[test]
243    fn test_simple() {
244        let elem: Element = "<transport xmlns='urn:xmpp:jingle:transports:s5b:1' sid='coucou'/>".parse().unwrap();
245        let transport = Transport::try_from(elem).unwrap();
246        assert_eq!(transport.sid, "coucou");
247        assert_eq!(transport.dstaddr, None);
248        assert_eq!(transport.mode, Mode::Tcp);
249        match transport.payload {
250            TransportPayload::None => (),
251            _ => panic!("Wrong element inside transport!"),
252        }
253    }
254
255    #[test]
256    fn test_serialise_activated() {
257        let elem: Element = "<transport xmlns='urn:xmpp:jingle:transports:s5b:1' sid='coucou'><activated cid='coucou'/></transport>".parse().unwrap();
258        let transport = Transport {
259            sid: String::from("coucou"),
260            dstaddr: None,
261            mode: Mode::Tcp,
262            payload: TransportPayload::Activated(String::from("coucou")),
263        };
264        let elem2: Element = transport.into();
265        assert_eq!(elem, elem2);
266    }
267
268    #[test]
269    fn test_serialise_candidate() {
270        let elem: Element = "<transport xmlns='urn:xmpp:jingle:transports:s5b:1' sid='coucou'><candidate cid='coucou' host='coucou' jid='coucou@coucou' priority='0'/></transport>".parse().unwrap();
271        let transport = Transport {
272            sid: String::from("coucou"),
273            dstaddr: None,
274            mode: Mode::Tcp,
275            payload: TransportPayload::Candidates(vec!(Candidate {
276                cid: String::from("coucou"),
277                host: String::from("coucou"),
278                jid: String::from("coucou@coucou"),
279                port: None,
280                priority: 0u32,
281                type_: Type::Direct,
282            })),
283        };
284        let elem2: Element = transport.into();
285        assert_eq!(elem, elem2);
286    }
287}