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::ns;
8use crate::util::error::Error;
9use crate::Element;
10use jid::Jid;
11use std::convert::TryFrom;
12use std::net::IpAddr;
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, PartialEq)]
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, PartialEq)]
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", ns::JINGLE_S5B)
246 .attr("sid", transport.sid)
247 .attr("dstaddr", transport.dstaddr)
248 .attr("mode", transport.mode)
249 .append_all(match transport.payload {
250 TransportPayload::Candidates(candidates) => candidates
251 .into_iter()
252 .map(Element::from)
253 .collect::<Vec<_>>(),
254 TransportPayload::Activated(cid) => {
255 vec![Element::builder("activated", ns::JINGLE_S5B)
256 .attr("cid", cid)
257 .build()]
258 }
259 TransportPayload::CandidateError => {
260 vec![Element::builder("candidate-error", ns::JINGLE_S5B).build()]
261 }
262 TransportPayload::CandidateUsed(cid) => {
263 vec![Element::builder("candidate-used", ns::JINGLE_S5B)
264 .attr("cid", cid)
265 .build()]
266 }
267 TransportPayload::ProxyError => {
268 vec![Element::builder("proxy-error", ns::JINGLE_S5B).build()]
269 }
270 TransportPayload::None => vec![],
271 })
272 .build()
273 }
274}
275
276#[cfg(test)]
277mod tests {
278 use super::*;
279 use jid::BareJid;
280 use std::str::FromStr;
281
282 #[cfg(target_pointer_width = "32")]
283 #[test]
284 fn test_size() {
285 assert_size!(Type, 1);
286 assert_size!(Mode, 1);
287 assert_size!(CandidateId, 12);
288 assert_size!(StreamId, 12);
289 assert_size!(Candidate, 84);
290 assert_size!(TransportPayload, 16);
291 assert_size!(Transport, 44);
292 }
293
294 #[cfg(target_pointer_width = "64")]
295 #[test]
296 fn test_size() {
297 assert_size!(Type, 1);
298 assert_size!(Mode, 1);
299 assert_size!(CandidateId, 24);
300 assert_size!(StreamId, 24);
301 assert_size!(Candidate, 136);
302 assert_size!(TransportPayload, 32);
303 assert_size!(Transport, 88);
304 }
305
306 #[test]
307 fn test_simple() {
308 let elem: Element = "<transport xmlns='urn:xmpp:jingle:transports:s5b:1' sid='coucou'/>"
309 .parse()
310 .unwrap();
311 let transport = Transport::try_from(elem).unwrap();
312 assert_eq!(transport.sid, StreamId(String::from("coucou")));
313 assert_eq!(transport.dstaddr, None);
314 assert_eq!(transport.mode, Mode::Tcp);
315 match transport.payload {
316 TransportPayload::None => (),
317 _ => panic!("Wrong element inside transport!"),
318 }
319 }
320
321 #[test]
322 fn test_serialise_activated() {
323 let elem: Element = "<transport xmlns='urn:xmpp:jingle:transports:s5b:1' sid='coucou'><activated cid='coucou'/></transport>".parse().unwrap();
324 let transport = Transport {
325 sid: StreamId(String::from("coucou")),
326 dstaddr: None,
327 mode: Mode::Tcp,
328 payload: TransportPayload::Activated(CandidateId(String::from("coucou"))),
329 };
330 let elem2: Element = transport.into();
331 assert_eq!(elem, elem2);
332 }
333
334 #[test]
335 fn test_serialise_candidate() {
336 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();
337 let transport = Transport {
338 sid: StreamId(String::from("coucou")),
339 dstaddr: None,
340 mode: Mode::Tcp,
341 payload: TransportPayload::Candidates(vec![Candidate {
342 cid: CandidateId(String::from("coucou")),
343 host: IpAddr::from_str("127.0.0.1").unwrap(),
344 jid: Jid::Bare(BareJid::new("coucou", "coucou")),
345 port: None,
346 priority: 0u32,
347 type_: Type::Direct,
348 }]),
349 };
350 let elem2: Element = transport.into();
351 assert_eq!(elem, elem2);
352 }
353}