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;
9
10use minidom::{Element, IntoAttributeValue};
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
33#[derive(Debug, Clone)]
34pub struct Candidate {
35 pub cid: CandidateId,
36 pub host: String,
37 pub jid: Jid,
38 pub port: Option<u16>,
39 pub priority: u32,
40 pub type_: Type,
41}
42
43impl From<Candidate> for Element {
44 fn from(candidate: Candidate) -> Element {
45 Element::builder("candidate")
46 .ns(ns::JINGLE_S5B)
47 .attr("cid", candidate.cid)
48 .attr("host", candidate.host)
49 .attr("jid", String::from(candidate.jid))
50 .attr("port", candidate.port)
51 .attr("priority", candidate.priority)
52 .attr("type", candidate.type_)
53 .build()
54 }
55}
56
57#[derive(Debug, Clone)]
58pub enum TransportPayload {
59 Activated(String),
60 Candidates(Vec<Candidate>),
61 CandidateError,
62 CandidateUsed(String),
63 ProxyError,
64 None,
65}
66
67#[derive(Debug, Clone)]
68pub struct Transport {
69 pub sid: StreamId,
70 pub dstaddr: Option<String>,
71 pub mode: Mode,
72 pub payload: TransportPayload,
73}
74
75impl TryFrom<Element> for Transport {
76 type Err = Error;
77
78 fn try_from(elem: Element) -> Result<Transport, Error> {
79 if elem.is("transport", ns::JINGLE_S5B) {
80 let sid = get_attr!(elem, "sid", required);
81 let dstaddr = get_attr!(elem, "dstaddr", optional);
82 let mode = get_attr!(elem, "mode", default);
83
84 let mut payload = None;
85 for child in elem.children() {
86 payload = Some(if child.is("candidate", ns::JINGLE_S5B) {
87 let mut candidates = match payload {
88 Some(TransportPayload::Candidates(candidates)) => candidates,
89 Some(_) => return Err(Error::ParseError("Non-candidate child already present in JingleS5B transport element.")),
90 None => vec!(),
91 };
92 candidates.push(Candidate {
93 cid: get_attr!(child, "cid", required),
94 host: get_attr!(child, "host", required),
95 jid: get_attr!(child, "jid", required),
96 port: get_attr!(child, "port", optional),
97 priority: get_attr!(child, "priority", required),
98 type_: get_attr!(child, "type", default),
99 });
100 TransportPayload::Candidates(candidates)
101 } else if child.is("activated", ns::JINGLE_S5B) {
102 if payload.is_some() {
103 return Err(Error::ParseError("Non-activated child already present in JingleS5B transport element."));
104 }
105 let cid = get_attr!(child, "cid", required);
106 TransportPayload::Activated(cid)
107 } else if child.is("candidate-error", ns::JINGLE_S5B) {
108 if payload.is_some() {
109 return Err(Error::ParseError("Non-candidate-error child already present in JingleS5B transport element."));
110 }
111 TransportPayload::CandidateError
112 } else if child.is("candidate-used", ns::JINGLE_S5B) {
113 if payload.is_some() {
114 return Err(Error::ParseError("Non-candidate-used child already present in JingleS5B transport element."));
115 }
116 let cid = get_attr!(child, "cid", required);
117 TransportPayload::CandidateUsed(cid)
118 } else if child.is("proxy-error", ns::JINGLE_S5B) {
119 if payload.is_some() {
120 return Err(Error::ParseError("Non-proxy-error child already present in JingleS5B transport element."));
121 }
122 TransportPayload::ProxyError
123 } else {
124 return Err(Error::ParseError("Unknown child in JingleS5B transport element."));
125 });
126 }
127 let payload = payload.unwrap_or(TransportPayload::None);
128 Ok(Transport {
129 sid: sid,
130 dstaddr: dstaddr,
131 mode: mode,
132 payload: payload,
133 })
134 } else {
135 Err(Error::ParseError("This is not an JingleS5B transport element."))
136 }
137 }
138}
139
140impl From<Transport> for Element {
141 fn from(transport: Transport) -> Element {
142 Element::builder("transport")
143 .ns(ns::JINGLE_S5B)
144 .attr("sid", transport.sid)
145 .attr("dstaddr", transport.dstaddr)
146 .attr("mode", transport.mode)
147 .append(match transport.payload {
148 TransportPayload::Candidates(candidates) => {
149 candidates.into_iter()
150 .map(Element::from)
151 .collect::<Vec<_>>()
152 },
153 TransportPayload::Activated(cid) => {
154 vec!(Element::builder("activated")
155 .ns(ns::JINGLE_S5B)
156 .attr("cid", cid)
157 .build())
158 },
159 TransportPayload::CandidateError => {
160 vec!(Element::builder("candidate-error")
161 .ns(ns::JINGLE_S5B)
162 .build())
163 },
164 TransportPayload::CandidateUsed(cid) => {
165 vec!(Element::builder("candidate-used")
166 .ns(ns::JINGLE_S5B)
167 .attr("cid", cid)
168 .build())
169 },
170 TransportPayload::ProxyError => {
171 vec!(Element::builder("proxy-error")
172 .ns(ns::JINGLE_S5B)
173 .build())
174 },
175 TransportPayload::None => vec!(),
176 })
177 .build()
178 }
179}
180
181#[cfg(test)]
182mod tests {
183 use super::*;
184 use compare_elements::NamespaceAwareCompare;
185
186 #[test]
187 fn test_simple() {
188 let elem: Element = "<transport xmlns='urn:xmpp:jingle:transports:s5b:1' sid='coucou'/>".parse().unwrap();
189 let transport = Transport::try_from(elem).unwrap();
190 assert_eq!(transport.sid, StreamId(String::from("coucou")));
191 assert_eq!(transport.dstaddr, None);
192 assert_eq!(transport.mode, Mode::Tcp);
193 match transport.payload {
194 TransportPayload::None => (),
195 _ => panic!("Wrong element inside transport!"),
196 }
197 }
198
199 #[test]
200 fn test_serialise_activated() {
201 let elem: Element = "<transport xmlns='urn:xmpp:jingle:transports:s5b:1' sid='coucou'><activated cid='coucou'/></transport>".parse().unwrap();
202 let transport = Transport {
203 sid: StreamId(String::from("coucou")),
204 dstaddr: None,
205 mode: Mode::Tcp,
206 payload: TransportPayload::Activated(String::from("coucou")),
207 };
208 let elem2: Element = transport.into();
209 assert!(elem.compare_to(&elem2));
210 }
211
212 #[test]
213 fn test_serialise_candidate() {
214 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();
215 let transport = Transport {
216 sid: StreamId(String::from("coucou")),
217 dstaddr: None,
218 mode: Mode::Tcp,
219 payload: TransportPayload::Candidates(vec!(Candidate {
220 cid: CandidateId(String::from("coucou")),
221 host: String::from("coucou"),
222 jid: Jid::from_str("coucou@coucou").unwrap(),
223 port: None,
224 priority: 0u32,
225 type_: Type::Direct,
226 })),
227 };
228 let elem2: Element = transport.into();
229 assert!(elem.compare_to(&elem2));
230 }
231}