1use xso::{
8 error::{Error, FromElementError},
9 AsXml, FromXml,
10};
11
12use crate::ns;
13use core::net::IpAddr;
14use jid::Jid;
15use minidom::Element;
16
17generate_attribute!(
18 Type, "type", {
20 Assisted => "assisted",
23
24 Direct => "direct",
26
27 Proxy => "proxy",
29
30 Tunnel => "tunnel",
32 }, Default = Direct
33);
34
35generate_attribute!(
36 Mode, "mode", {
38 Tcp => "tcp",
40
41 Udp => "udp",
43 }, Default = Tcp
44);
45
46generate_id!(
47 CandidateId
49);
50
51generate_id!(
52 StreamId
54);
55
56#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
58#[xml(namespace = ns::JINGLE_S5B, name = "candidate")]
59pub struct Candidate {
60 #[xml(attribute)]
62 cid: CandidateId,
63
64 #[xml(attribute)]
66 host: IpAddr,
67
68 #[xml(attribute)]
70 jid: Jid,
71
72 #[xml(attribute(default))]
74 port: Option<u16>,
75
76 #[xml(attribute)]
79 priority: u32,
80
81 #[xml(attribute(default, name = "type"))]
83 type_: Type,
84}
85
86impl Candidate {
87 pub fn new(cid: CandidateId, host: IpAddr, jid: Jid, priority: u32) -> Candidate {
89 Candidate {
90 cid,
91 host,
92 jid,
93 priority,
94 port: Default::default(),
95 type_: Default::default(),
96 }
97 }
98
99 pub fn with_port(mut self, port: u16) -> Candidate {
101 self.port = Some(port);
102 self
103 }
104
105 pub fn with_type(mut self, type_: Type) -> Candidate {
107 self.type_ = type_;
108 self
109 }
110}
111
112#[derive(Debug, Clone, PartialEq)]
114pub enum TransportPayload {
115 Activated(CandidateId),
118
119 Candidates(Vec<Candidate>),
121
122 CandidateError,
125
126 CandidateUsed(CandidateId),
128
129 ProxyError,
131
132 None,
134}
135
136#[derive(Debug, Clone, PartialEq)]
138pub struct Transport {
139 pub sid: StreamId,
141
142 pub dstaddr: Option<String>,
144
145 pub mode: Mode,
147
148 pub payload: TransportPayload,
150}
151
152impl Transport {
153 pub fn new(sid: StreamId) -> Transport {
155 Transport {
156 sid,
157 dstaddr: None,
158 mode: Default::default(),
159 payload: TransportPayload::None,
160 }
161 }
162
163 pub fn with_dstaddr(mut self, dstaddr: String) -> Transport {
165 self.dstaddr = Some(dstaddr);
166 self
167 }
168
169 pub fn with_mode(mut self, mode: Mode) -> Transport {
171 self.mode = mode;
172 self
173 }
174
175 pub fn with_payload(mut self, payload: TransportPayload) -> Transport {
177 self.payload = payload;
178 self
179 }
180}
181
182impl TryFrom<Element> for Transport {
183 type Error = FromElementError;
184
185 fn try_from(elem: Element) -> Result<Transport, FromElementError> {
186 check_self!(elem, "transport", JINGLE_S5B);
187 check_no_unknown_attributes!(elem, "transport", ["sid", "dstaddr", "mode"]);
188 let sid = get_attr!(elem, "sid", Required);
189 let dstaddr = get_attr!(elem, "dstaddr", Option);
190 let mode = get_attr!(elem, "mode", Default);
191
192 let mut payload = None;
193 for child in elem.children() {
194 payload = Some(if child.is("candidate", ns::JINGLE_S5B) {
195 let mut candidates =
196 match payload {
197 Some(TransportPayload::Candidates(candidates)) => candidates,
198 Some(_) => return Err(Error::Other(
199 "Non-candidate child already present in JingleS5B transport element.",
200 )
201 .into()),
202 None => vec![],
203 };
204 candidates.push(Candidate::try_from(child.clone())?);
205 TransportPayload::Candidates(candidates)
206 } else if child.is("activated", ns::JINGLE_S5B) {
207 if payload.is_some() {
208 return Err(Error::Other(
209 "Non-activated child already present in JingleS5B transport element.",
210 )
211 .into());
212 }
213 let cid = get_attr!(child, "cid", Required);
214 TransportPayload::Activated(cid)
215 } else if child.is("candidate-error", ns::JINGLE_S5B) {
216 if payload.is_some() {
217 return Err(Error::Other(
218 "Non-candidate-error child already present in JingleS5B transport element.",
219 )
220 .into());
221 }
222 TransportPayload::CandidateError
223 } else if child.is("candidate-used", ns::JINGLE_S5B) {
224 if payload.is_some() {
225 return Err(Error::Other(
226 "Non-candidate-used child already present in JingleS5B transport element.",
227 )
228 .into());
229 }
230 let cid = get_attr!(child, "cid", Required);
231 TransportPayload::CandidateUsed(cid)
232 } else if child.is("proxy-error", ns::JINGLE_S5B) {
233 if payload.is_some() {
234 return Err(Error::Other(
235 "Non-proxy-error child already present in JingleS5B transport element.",
236 )
237 .into());
238 }
239 TransportPayload::ProxyError
240 } else {
241 return Err(Error::Other("Unknown child in JingleS5B transport element.").into());
242 });
243 }
244 let payload = payload.unwrap_or(TransportPayload::None);
245 Ok(Transport {
246 sid,
247 dstaddr,
248 mode,
249 payload,
250 })
251 }
252}
253
254impl From<Transport> for Element {
255 fn from(transport: Transport) -> Element {
256 Element::builder("transport", ns::JINGLE_S5B)
257 .attr("sid", transport.sid)
258 .attr("dstaddr", transport.dstaddr)
259 .attr("mode", transport.mode)
260 .append_all(match transport.payload {
261 TransportPayload::Candidates(candidates) => candidates
262 .into_iter()
263 .map(Element::from)
264 .collect::<Vec<_>>(),
265 TransportPayload::Activated(cid) => {
266 vec![Element::builder("activated", ns::JINGLE_S5B)
267 .attr("cid", cid)
268 .build()]
269 }
270 TransportPayload::CandidateError => {
271 vec![Element::builder("candidate-error", ns::JINGLE_S5B).build()]
272 }
273 TransportPayload::CandidateUsed(cid) => {
274 vec![Element::builder("candidate-used", ns::JINGLE_S5B)
275 .attr("cid", cid)
276 .build()]
277 }
278 TransportPayload::ProxyError => {
279 vec![Element::builder("proxy-error", ns::JINGLE_S5B).build()]
280 }
281 TransportPayload::None => vec![],
282 })
283 .build()
284 }
285}
286
287impl ::xso::FromXml for Transport {
288 type Builder = ::xso::minidom_compat::FromEventsViaElement<Transport>;
289
290 fn from_events(
291 qname: ::xso::exports::rxml::QName,
292 attrs: ::xso::exports::rxml::AttrMap,
293 ) -> Result<Self::Builder, ::xso::error::FromEventsError> {
294 if qname.0 != crate::ns::JINGLE_S5B || qname.1 != "transport" {
295 return Err(::xso::error::FromEventsError::Mismatch { name: qname, attrs });
296 }
297 Self::Builder::new(qname, attrs)
298 }
299}
300
301impl ::xso::AsXml for Transport {
302 type ItemIter<'x> = ::xso::minidom_compat::AsItemsViaElement<'x>;
303
304 fn as_xml_iter(&self) -> Result<Self::ItemIter<'_>, ::xso::error::Error> {
305 ::xso::minidom_compat::AsItemsViaElement::new(self.clone())
306 }
307}
308
309#[cfg(test)]
310mod tests {
311 use super::*;
312 use core::str::FromStr;
313
314 #[cfg(target_pointer_width = "32")]
315 #[test]
316 fn test_size() {
317 assert_size!(Type, 1);
318 assert_size!(Mode, 1);
319 assert_size!(CandidateId, 12);
320 assert_size!(StreamId, 12);
321 assert_size!(Candidate, 56);
322 assert_size!(TransportPayload, 16);
323 assert_size!(Transport, 44);
324 }
325
326 #[cfg(target_pointer_width = "64")]
327 #[test]
328 fn test_size() {
329 assert_size!(Type, 1);
330 assert_size!(Mode, 1);
331 assert_size!(CandidateId, 24);
332 assert_size!(StreamId, 24);
333 assert_size!(Candidate, 88);
334 assert_size!(TransportPayload, 32);
335 assert_size!(Transport, 88);
336 }
337
338 #[test]
339 fn test_simple() {
340 let elem: Element = "<transport xmlns='urn:xmpp:jingle:transports:s5b:1' sid='coucou'/>"
341 .parse()
342 .unwrap();
343 let transport = Transport::try_from(elem).unwrap();
344 assert_eq!(transport.sid, StreamId(String::from("coucou")));
345 assert_eq!(transport.dstaddr, None);
346 assert_eq!(transport.mode, Mode::Tcp);
347 match transport.payload {
348 TransportPayload::None => (),
349 _ => panic!("Wrong element inside transport!"),
350 }
351 }
352
353 #[test]
354 fn test_serialise_activated() {
355 let elem: Element = "<transport xmlns='urn:xmpp:jingle:transports:s5b:1' sid='coucou'><activated cid='coucou'/></transport>".parse().unwrap();
356 let transport = Transport {
357 sid: StreamId(String::from("coucou")),
358 dstaddr: None,
359 mode: Mode::Tcp,
360 payload: TransportPayload::Activated(CandidateId(String::from("coucou"))),
361 };
362 let elem2: Element = transport.into();
363 assert_eq!(elem, elem2);
364 }
365
366 #[test]
367 fn test_serialise_candidate() {
368 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();
369 let transport = Transport {
370 sid: StreamId(String::from("coucou")),
371 dstaddr: None,
372 mode: Mode::Tcp,
373 payload: TransportPayload::Candidates(vec![Candidate {
374 cid: CandidateId(String::from("coucou")),
375 host: IpAddr::from_str("127.0.0.1").unwrap(),
376 jid: Jid::new("coucou@coucou").unwrap(),
377 port: None,
378 priority: 0u32,
379 type_: Type::Direct,
380 }]),
381 };
382 let elem2: Element = transport.into();
383 assert_eq!(elem, elem2);
384 }
385}