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}