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 crate::util::error::Error;
8use crate::ns;
9use jid::Jid;
10use minidom::Element;
11use std::net::IpAddr;
12use std::convert::TryFrom;
13
14generate_attribute!(
15 /// The type of the connection being proposed by this candidate.
16 Type, "type", {
17 /// Direct connection using NAT assisting technologies like NAT-PMP or
18 /// UPnP-IGD.
19 Assisted => "assisted",
20
21 /// Direct connection using the given interface.
22 Direct => "direct",
23
24 /// SOCKS5 relay.
25 Proxy => "proxy",
26
27 /// Tunnel protocol such as Teredo.
28 Tunnel => "tunnel",
29 }, Default = Direct
30);
31
32generate_attribute!(
33 /// Which mode to use for the connection.
34 Mode, "mode", {
35 /// Use TCP, which is the default.
36 Tcp => "tcp",
37
38 /// Use UDP.
39 Udp => "udp",
40 }, Default = Tcp
41);
42
43generate_id!(
44 /// An identifier for a candidate.
45 CandidateId
46);
47
48generate_id!(
49 /// An identifier for a stream.
50 StreamId
51);
52
53generate_element!(
54 /// A candidate for a connection.
55 Candidate, "candidate", JINGLE_S5B,
56 attributes: [
57 /// The identifier for this candidate.
58 cid: Required<CandidateId> = "cid",
59
60 /// The host to connect to.
61 host: Required<IpAddr> = "host",
62
63 /// The JID to request at the given end.
64 jid: Required<Jid> = "jid",
65
66 /// The port to connect to.
67 port: Option<u16> = "port",
68
69 /// The priority of this candidate, computed using this formula:
70 /// priority = (2^16)*(type preference) + (local preference)
71 priority: Required<u32> = "priority",
72
73 /// The type of the connection being proposed by this candidate.
74 type_: Default<Type> = "type",
75 ]
76);
77
78impl Candidate {
79 /// Creates a new candidate with the given parameters.
80 pub fn new(cid: CandidateId, host: IpAddr, jid: Jid, priority: u32) -> Candidate {
81 Candidate {
82 cid,
83 host,
84 jid,
85 priority,
86 port: Default::default(),
87 type_: Default::default(),
88 }
89 }
90
91 /// Sets the port of this candidate.
92 pub fn with_port(mut self, port: u16) -> Candidate {
93 self.port = Some(port);
94 self
95 }
96
97 /// Sets the type of this candidate.
98 pub fn with_type(mut self, type_: Type) -> Candidate {
99 self.type_ = type_;
100 self
101 }
102}
103
104/// The payload of a transport.
105#[derive(Debug, Clone)]
106pub enum TransportPayload {
107 /// The responder informs the initiator that the bytestream pointed by this
108 /// candidate has been activated.
109 Activated(CandidateId),
110
111 /// A list of suggested candidates.
112 Candidates(Vec<Candidate>),
113
114 /// Both parties failed to use a candidate, they should fallback to another
115 /// transport.
116 CandidateError,
117
118 /// The candidate pointed here should be used by both parties.
119 CandidateUsed(CandidateId),
120
121 /// This entity can’t connect to the SOCKS5 proxy.
122 ProxyError,
123
124 /// XXX: Invalid, should not be found in the wild.
125 None,
126}
127
128/// Describes a Jingle transport using a direct or proxied connection.
129#[derive(Debug, Clone)]
130pub struct Transport {
131 /// The stream identifier for this transport.
132 pub sid: StreamId,
133
134 /// The destination address.
135 pub dstaddr: Option<String>,
136
137 /// The mode to be used for the transfer.
138 pub mode: Mode,
139
140 /// The payload of this transport.
141 pub payload: TransportPayload,
142}
143
144impl Transport {
145 /// Creates a new transport element.
146 pub fn new(sid: StreamId) -> Transport {
147 Transport {
148 sid,
149 dstaddr: None,
150 mode: Default::default(),
151 payload: TransportPayload::None,
152 }
153 }
154
155 /// Sets the destination address of this transport.
156 pub fn with_dstaddr(mut self, dstaddr: String) -> Transport {
157 self.dstaddr = Some(dstaddr);
158 self
159 }
160
161 /// Sets the mode of this transport.
162 pub fn with_mode(mut self, mode: Mode) -> Transport {
163 self.mode = mode;
164 self
165 }
166
167 /// Sets the payload of this transport.
168 pub fn with_payload(mut self, payload: TransportPayload) -> Transport {
169 self.payload = payload;
170 self
171 }
172}
173
174impl TryFrom<Element> for Transport {
175 type Error = Error;
176
177 fn try_from(elem: Element) -> Result<Transport, Error> {
178 check_self!(elem, "transport", JINGLE_S5B);
179 check_no_unknown_attributes!(elem, "transport", ["sid", "dstaddr", "mode"]);
180 let sid = get_attr!(elem, "sid", Required);
181 let dstaddr = get_attr!(elem, "dstaddr", Option);
182 let mode = get_attr!(elem, "mode", Default);
183
184 let mut payload = None;
185 for child in elem.children() {
186 payload = Some(if child.is("candidate", ns::JINGLE_S5B) {
187 let mut candidates =
188 match payload {
189 Some(TransportPayload::Candidates(candidates)) => candidates,
190 Some(_) => return Err(Error::ParseError(
191 "Non-candidate child already present in JingleS5B transport element.",
192 )),
193 None => vec![],
194 };
195 candidates.push(Candidate::try_from(child.clone())?);
196 TransportPayload::Candidates(candidates)
197 } else if child.is("activated", ns::JINGLE_S5B) {
198 if payload.is_some() {
199 return Err(Error::ParseError(
200 "Non-activated child already present in JingleS5B transport element.",
201 ));
202 }
203 let cid = get_attr!(child, "cid", Required);
204 TransportPayload::Activated(cid)
205 } else if child.is("candidate-error", ns::JINGLE_S5B) {
206 if payload.is_some() {
207 return Err(Error::ParseError(
208 "Non-candidate-error child already present in JingleS5B transport element.",
209 ));
210 }
211 TransportPayload::CandidateError
212 } else if child.is("candidate-used", ns::JINGLE_S5B) {
213 if payload.is_some() {
214 return Err(Error::ParseError(
215 "Non-candidate-used child already present in JingleS5B transport element.",
216 ));
217 }
218 let cid = get_attr!(child, "cid", Required);
219 TransportPayload::CandidateUsed(cid)
220 } else if child.is("proxy-error", ns::JINGLE_S5B) {
221 if payload.is_some() {
222 return Err(Error::ParseError(
223 "Non-proxy-error child already present in JingleS5B transport element.",
224 ));
225 }
226 TransportPayload::ProxyError
227 } else {
228 return Err(Error::ParseError(
229 "Unknown child in JingleS5B transport element.",
230 ));
231 });
232 }
233 let payload = payload.unwrap_or(TransportPayload::None);
234 Ok(Transport {
235 sid,
236 dstaddr,
237 mode,
238 payload,
239 })
240 }
241}
242
243impl From<Transport> for Element {
244 fn from(transport: Transport) -> Element {
245 Element::builder("transport")
246 .ns(ns::JINGLE_S5B)
247 .attr("sid", transport.sid)
248 .attr("dstaddr", transport.dstaddr)
249 .attr("mode", transport.mode)
250 .append_all(match transport.payload {
251 TransportPayload::Candidates(candidates) => candidates
252 .into_iter()
253 .map(Element::from)
254 .collect::<Vec<_>>(),
255 TransportPayload::Activated(cid) => vec![Element::builder("activated")
256 .ns(ns::JINGLE_S5B)
257 .attr("cid", cid)
258 .build()],
259 TransportPayload::CandidateError => vec![Element::builder("candidate-error")
260 .ns(ns::JINGLE_S5B)
261 .build()],
262 TransportPayload::CandidateUsed(cid) => vec![Element::builder("candidate-used")
263 .ns(ns::JINGLE_S5B)
264 .attr("cid", cid)
265 .build()],
266 TransportPayload::ProxyError => vec![Element::builder("proxy-error")
267 .ns(ns::JINGLE_S5B)
268 .build()],
269 TransportPayload::None => vec![],
270 })
271 .build()
272 }
273}
274
275#[cfg(test)]
276mod tests {
277 use super::*;
278 use crate::util::compare_elements::NamespaceAwareCompare;
279 use std::str::FromStr;
280
281 #[cfg(target_pointer_width = "32")]
282 #[test]
283 fn test_size() {
284 assert_size!(Type, 1);
285 assert_size!(Mode, 1);
286 assert_size!(CandidateId, 12);
287 assert_size!(StreamId, 12);
288 assert_size!(Candidate, 84);
289 assert_size!(TransportPayload, 16);
290 assert_size!(Transport, 44);
291 }
292
293 #[cfg(target_pointer_width = "64")]
294 #[test]
295 fn test_size() {
296 assert_size!(Type, 1);
297 assert_size!(Mode, 1);
298 assert_size!(CandidateId, 24);
299 assert_size!(StreamId, 24);
300 assert_size!(Candidate, 136);
301 assert_size!(TransportPayload, 32);
302 assert_size!(Transport, 88);
303 }
304
305 #[test]
306 fn test_simple() {
307 let elem: Element = "<transport xmlns='urn:xmpp:jingle:transports:s5b:1' sid='coucou'/>"
308 .parse()
309 .unwrap();
310 let transport = Transport::try_from(elem).unwrap();
311 assert_eq!(transport.sid, StreamId(String::from("coucou")));
312 assert_eq!(transport.dstaddr, None);
313 assert_eq!(transport.mode, Mode::Tcp);
314 match transport.payload {
315 TransportPayload::None => (),
316 _ => panic!("Wrong element inside transport!"),
317 }
318 }
319
320 #[test]
321 fn test_serialise_activated() {
322 let elem: Element = "<transport xmlns='urn:xmpp:jingle:transports:s5b:1' sid='coucou'><activated cid='coucou'/></transport>".parse().unwrap();
323 let transport = Transport {
324 sid: StreamId(String::from("coucou")),
325 dstaddr: None,
326 mode: Mode::Tcp,
327 payload: TransportPayload::Activated(CandidateId(String::from("coucou"))),
328 };
329 let elem2: Element = transport.into();
330 assert!(elem.compare_to(&elem2));
331 }
332
333 #[test]
334 fn test_serialise_candidate() {
335 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();
336 let transport = Transport {
337 sid: StreamId(String::from("coucou")),
338 dstaddr: None,
339 mode: Mode::Tcp,
340 payload: TransportPayload::Candidates(vec![Candidate {
341 cid: CandidateId(String::from("coucou")),
342 host: IpAddr::from_str("127.0.0.1").unwrap(),
343 jid: Jid::from_str("coucou@coucou").unwrap(),
344 port: None,
345 priority: 0u32,
346 type_: Type::Direct,
347 }]),
348 };
349 let elem2: Element = transport.into();
350 assert!(elem.compare_to(&elem2));
351 }
352}