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;
  9use std::net::IpAddr;
 10
 11use minidom::{Element, IntoAttributeValue};
 12use jid::Jid;
 13
 14use error::Error;
 15
 16use ns;
 17
 18generate_attribute!(Type, "type", {
 19    Assisted => "assisted",
 20    Direct => "direct",
 21    Proxy => "proxy",
 22    Tunnel => "tunnel",
 23}, Default = Direct);
 24
 25generate_attribute!(Mode, "mode", {
 26    Tcp => "tcp",
 27    Udp => "udp",
 28}, Default = Tcp);
 29
 30generate_id!(CandidateId);
 31
 32generate_id!(StreamId);
 33
 34generate_element_with_only_attributes!(Candidate, "candidate", ns::JINGLE_S5B, [
 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(String),
 69    Candidates(Vec<Candidate>),
 70    CandidateError,
 71    CandidateUsed(String),
 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        if elem.is("transport", ns::JINGLE_S5B) {
115            let sid = get_attr!(elem, "sid", required);
116            let dstaddr = get_attr!(elem, "dstaddr", optional);
117            let mode = get_attr!(elem, "mode", default);
118
119            let mut payload = None;
120            for child in elem.children() {
121                payload = Some(if child.is("candidate", ns::JINGLE_S5B) {
122                    let mut candidates = match payload {
123                        Some(TransportPayload::Candidates(candidates)) => candidates,
124                        Some(_) => return Err(Error::ParseError("Non-candidate child already present in JingleS5B transport element.")),
125                        None => vec!(),
126                    };
127                    candidates.push(Candidate::try_from(child.clone())?);
128                    TransportPayload::Candidates(candidates)
129                } else if child.is("activated", ns::JINGLE_S5B) {
130                    if payload.is_some() {
131                        return Err(Error::ParseError("Non-activated child already present in JingleS5B transport element."));
132                    }
133                    let cid = get_attr!(child, "cid", required);
134                    TransportPayload::Activated(cid)
135                } else if child.is("candidate-error", ns::JINGLE_S5B) {
136                    if payload.is_some() {
137                        return Err(Error::ParseError("Non-candidate-error child already present in JingleS5B transport element."));
138                    }
139                    TransportPayload::CandidateError
140                } else if child.is("candidate-used", ns::JINGLE_S5B) {
141                    if payload.is_some() {
142                        return Err(Error::ParseError("Non-candidate-used child already present in JingleS5B transport element."));
143                    }
144                    let cid = get_attr!(child, "cid", required);
145                    TransportPayload::CandidateUsed(cid)
146                } else if child.is("proxy-error", ns::JINGLE_S5B) {
147                    if payload.is_some() {
148                        return Err(Error::ParseError("Non-proxy-error child already present in JingleS5B transport element."));
149                    }
150                    TransportPayload::ProxyError
151                } else {
152                    return Err(Error::ParseError("Unknown child in JingleS5B transport element."));
153                });
154            }
155            let payload = payload.unwrap_or(TransportPayload::None);
156            Ok(Transport {
157                sid: sid,
158                dstaddr: dstaddr,
159                mode: mode,
160                payload: payload,
161            })
162        } else {
163            Err(Error::ParseError("This is not an JingleS5B transport element."))
164        }
165    }
166}
167
168impl From<Transport> for Element {
169    fn from(transport: Transport) -> Element {
170        Element::builder("transport")
171                .ns(ns::JINGLE_S5B)
172                .attr("sid", transport.sid)
173                .attr("dstaddr", transport.dstaddr)
174                .attr("mode", transport.mode)
175                .append(match transport.payload {
176                     TransportPayload::Candidates(candidates) => {
177                         candidates.into_iter()
178                                   .map(Element::from)
179                                   .collect::<Vec<_>>()
180                     },
181                     TransportPayload::Activated(cid) => {
182                         vec!(Element::builder("activated")
183                                      .ns(ns::JINGLE_S5B)
184                                      .attr("cid", cid)
185                                      .build())
186                     },
187                     TransportPayload::CandidateError => {
188                         vec!(Element::builder("candidate-error")
189                                      .ns(ns::JINGLE_S5B)
190                                      .build())
191                     },
192                     TransportPayload::CandidateUsed(cid) => {
193                         vec!(Element::builder("candidate-used")
194                                      .ns(ns::JINGLE_S5B)
195                                      .attr("cid", cid)
196                                      .build())
197                     },
198                     TransportPayload::ProxyError => {
199                         vec!(Element::builder("proxy-error")
200                                      .ns(ns::JINGLE_S5B)
201                                      .build())
202                     },
203                     TransportPayload::None => vec!(),
204                 })
205                .build()
206    }
207}
208
209#[cfg(test)]
210mod tests {
211    use super::*;
212    use compare_elements::NamespaceAwareCompare;
213
214    #[test]
215    fn test_simple() {
216        let elem: Element = "<transport xmlns='urn:xmpp:jingle:transports:s5b:1' sid='coucou'/>".parse().unwrap();
217        let transport = Transport::try_from(elem).unwrap();
218        assert_eq!(transport.sid, StreamId(String::from("coucou")));
219        assert_eq!(transport.dstaddr, None);
220        assert_eq!(transport.mode, Mode::Tcp);
221        match transport.payload {
222            TransportPayload::None => (),
223            _ => panic!("Wrong element inside transport!"),
224        }
225    }
226
227    #[test]
228    fn test_serialise_activated() {
229        let elem: Element = "<transport xmlns='urn:xmpp:jingle:transports:s5b:1' sid='coucou'><activated cid='coucou'/></transport>".parse().unwrap();
230        let transport = Transport {
231            sid: StreamId(String::from("coucou")),
232            dstaddr: None,
233            mode: Mode::Tcp,
234            payload: TransportPayload::Activated(String::from("coucou")),
235        };
236        let elem2: Element = transport.into();
237        assert!(elem.compare_to(&elem2));
238    }
239
240    #[test]
241    fn test_serialise_candidate() {
242        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();
243        let transport = Transport {
244            sid: StreamId(String::from("coucou")),
245            dstaddr: None,
246            mode: Mode::Tcp,
247            payload: TransportPayload::Candidates(vec!(Candidate {
248                cid: CandidateId(String::from("coucou")),
249                host: IpAddr::from_str("127.0.0.1").unwrap(),
250                jid: Jid::from_str("coucou@coucou").unwrap(),
251                port: None,
252                priority: 0u32,
253                type_: Type::Direct,
254            })),
255        };
256        let elem2: Element = transport.into();
257        assert!(elem.compare_to(&elem2));
258    }
259}