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}