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