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 std::convert::TryFrom;
8use std::str::FromStr;
9
10use minidom::{Element, IntoAttributeValue};
11
12use error::Error;
13
14use ns;
15
16#[derive(Debug, Clone, PartialEq)]
17pub enum Type {
18 Assisted,
19 Direct,
20 Proxy,
21 Tunnel,
22}
23
24impl Default for Type {
25 fn default() -> Type {
26 Type::Direct
27 }
28}
29
30impl FromStr for Type {
31 type Err = Error;
32
33 fn from_str(s: &str) -> Result<Type, Error> {
34 Ok(match s {
35 "assisted" => Type::Assisted,
36 "direct" => Type::Direct,
37 "proxy" => Type::Proxy,
38 "tunnel" => Type::Tunnel,
39
40 _ => return Err(Error::ParseError("Invalid 'type' attribute in candidate element.")),
41 })
42 }
43}
44
45impl IntoAttributeValue for Type {
46 fn into_attribute_value(self) -> Option<String> {
47 Some(match self {
48 Type::Assisted => String::from("assisted"),
49 Type::Direct => return None,
50 Type::Proxy => String::from("proxy"),
51 Type::Tunnel => String::from("tunnel"),
52 })
53 }
54}
55
56#[derive(Debug, Clone)]
57pub struct Candidate {
58 pub cid: String,
59 pub host: String,
60 pub jid: String,
61 pub port: Option<u16>,
62 pub priority: u32,
63 pub type_: Type,
64}
65
66impl Into<Element> for Candidate {
67 fn into(self) -> Element {
68 Element::builder("candidate")
69 .ns(ns::JINGLE_S5B)
70 .attr("cid", self.cid)
71 .attr("host", self.host)
72 .attr("jid", self.jid)
73 .attr("port", match self.port { Some(port) => Some(format!("{}", port)), None => None })
74 .attr("priority", format!("{}", self.priority))
75 .attr("type", self.type_)
76 .build()
77 }
78}
79
80#[derive(Debug, Clone, PartialEq)]
81pub enum Mode {
82 Tcp,
83 Udp,
84}
85
86impl Default for Mode {
87 fn default() -> Mode {
88 Mode::Tcp
89 }
90}
91
92impl FromStr for Mode {
93 type Err = Error;
94
95 fn from_str(s: &str) -> Result<Mode, Error> {
96 Ok(match s {
97 "tcp" => Mode::Tcp,
98 "udp" => Mode::Udp,
99
100 _ => return Err(Error::ParseError("Invalid 'mode' attribute.")),
101 })
102 }
103}
104
105impl IntoAttributeValue for Mode {
106 fn into_attribute_value(self) -> Option<String> {
107 match self {
108 Mode::Tcp => None,
109 Mode::Udp => Some(String::from("udp")),
110 }
111 }
112}
113
114#[derive(Debug, Clone)]
115pub enum TransportPayload {
116 Activated(String),
117 Candidates(Vec<Candidate>),
118 CandidateError,
119 CandidateUsed(String),
120 ProxyError,
121 None,
122}
123
124#[derive(Debug, Clone)]
125pub struct Transport {
126 pub sid: String,
127 pub dstaddr: Option<String>,
128 pub mode: Mode,
129 pub payload: TransportPayload,
130}
131
132impl TryFrom<Element> for Transport {
133 type Error = Error;
134
135 fn try_from(elem: Element) -> Result<Transport, Error> {
136 if elem.is("transport", ns::JINGLE_S5B) {
137 let sid = get_attr!(elem, "sid", required);
138 let dstaddr = get_attr!(elem, "dstaddr", optional);
139 let mode = get_attr!(elem, "mode", default);
140
141 let mut payload = None;
142 for child in elem.children() {
143 payload = Some(if child.is("candidate", ns::JINGLE_S5B) {
144 let mut candidates = match payload {
145 Some(TransportPayload::Candidates(candidates)) => candidates,
146 Some(_) => return Err(Error::ParseError("Non-candidate child already present in JingleS5B transport element.")),
147 None => vec!(),
148 };
149 candidates.push(Candidate {
150 cid: get_attr!(child, "cid", required),
151 host: get_attr!(child, "host", required),
152 jid: get_attr!(child, "jid", required),
153 port: get_attr!(child, "port", optional),
154 priority: get_attr!(child, "priority", required),
155 type_: get_attr!(child, "type", default),
156 });
157 TransportPayload::Candidates(candidates)
158 } else if child.is("activated", ns::JINGLE_S5B) {
159 if payload.is_some() {
160 return Err(Error::ParseError("Non-activated child already present in JingleS5B transport element."));
161 }
162 let cid = get_attr!(child, "cid", required);
163 TransportPayload::Activated(cid)
164 } else if child.is("candidate-error", ns::JINGLE_S5B) {
165 if payload.is_some() {
166 return Err(Error::ParseError("Non-candidate-error child already present in JingleS5B transport element."));
167 }
168 TransportPayload::CandidateError
169 } else if child.is("candidate-used", ns::JINGLE_S5B) {
170 if payload.is_some() {
171 return Err(Error::ParseError("Non-candidate-used child already present in JingleS5B transport element."));
172 }
173 let cid = get_attr!(child, "cid", required);
174 TransportPayload::CandidateUsed(cid)
175 } else if child.is("proxy-error", ns::JINGLE_S5B) {
176 if payload.is_some() {
177 return Err(Error::ParseError("Non-proxy-error child already present in JingleS5B transport element."));
178 }
179 TransportPayload::ProxyError
180 } else {
181 return Err(Error::ParseError("Unknown child in JingleS5B transport element."));
182 });
183 }
184 let payload = payload.unwrap_or(TransportPayload::None);
185 Ok(Transport {
186 sid: sid,
187 dstaddr: dstaddr,
188 mode: mode,
189 payload: payload,
190 })
191 } else {
192 Err(Error::ParseError("This is not an JingleS5B transport element."))
193 }
194 }
195}
196
197impl Into<Element> for Transport {
198 fn into(self) -> Element {
199 Element::builder("transport")
200 .ns(ns::JINGLE_S5B)
201 .attr("sid", self.sid)
202 .attr("dstaddr", self.dstaddr)
203 .attr("mode", self.mode)
204 .append(match self.payload {
205 TransportPayload::Candidates(mut candidates) => {
206 candidates.drain(..)
207 .map(|candidate| candidate.into())
208 .collect::<Vec<Element>>()
209 },
210 TransportPayload::Activated(cid) => {
211 vec!(Element::builder("activated")
212 .ns(ns::JINGLE_S5B)
213 .attr("cid", cid)
214 .build())
215 },
216 TransportPayload::CandidateError => {
217 vec!(Element::builder("candidate-error")
218 .ns(ns::JINGLE_S5B)
219 .build())
220 },
221 TransportPayload::CandidateUsed(ref cid) => {
222 vec!(Element::builder("candidate-used")
223 .ns(ns::JINGLE_S5B)
224 .attr("cid", cid.to_owned())
225 .build())
226 },
227 TransportPayload::ProxyError => {
228 vec!(Element::builder("proxy-error")
229 .ns(ns::JINGLE_S5B)
230 .build())
231 },
232 TransportPayload::None => vec!(),
233 })
234 .build()
235 }
236}
237
238#[cfg(test)]
239mod tests {
240 use super::*;
241
242 #[test]
243 fn test_simple() {
244 let elem: Element = "<transport xmlns='urn:xmpp:jingle:transports:s5b:1' sid='coucou'/>".parse().unwrap();
245 let transport = Transport::try_from(elem).unwrap();
246 assert_eq!(transport.sid, "coucou");
247 assert_eq!(transport.dstaddr, None);
248 assert_eq!(transport.mode, Mode::Tcp);
249 match transport.payload {
250 TransportPayload::None => (),
251 _ => panic!("Wrong element inside transport!"),
252 }
253 }
254
255 #[test]
256 fn test_serialise_activated() {
257 let elem: Element = "<transport xmlns='urn:xmpp:jingle:transports:s5b:1' sid='coucou'><activated cid='coucou'/></transport>".parse().unwrap();
258 let transport = Transport {
259 sid: String::from("coucou"),
260 dstaddr: None,
261 mode: Mode::Tcp,
262 payload: TransportPayload::Activated(String::from("coucou")),
263 };
264 let elem2: Element = transport.into();
265 assert_eq!(elem, elem2);
266 }
267
268 #[test]
269 fn test_serialise_candidate() {
270 let elem: Element = "<transport xmlns='urn:xmpp:jingle:transports:s5b:1' sid='coucou'><candidate cid='coucou' host='coucou' jid='coucou@coucou' priority='0'/></transport>".parse().unwrap();
271 let transport = Transport {
272 sid: String::from("coucou"),
273 dstaddr: None,
274 mode: Mode::Tcp,
275 payload: TransportPayload::Candidates(vec!(Candidate {
276 cid: String::from("coucou"),
277 host: String::from("coucou"),
278 jid: String::from("coucou@coucou"),
279 port: None,
280 priority: 0u32,
281 type_: Type::Direct,
282 })),
283 };
284 let elem2: Element = transport.into();
285 assert_eq!(elem, elem2);
286 }
287}