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