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 _ctx: &::xso::Context<'_>,
294 ) -> Result<Self::Builder, ::xso::error::FromEventsError> {
295 if qname.0 != crate::ns::JINGLE_S5B || qname.1 != "transport" {
296 return Err(::xso::error::FromEventsError::Mismatch { name: qname, attrs });
297 }
298 Self::Builder::new(qname, attrs)
299 }
300}
301
302impl ::xso::AsXml for Transport {
303 type ItemIter<'x> = ::xso::minidom_compat::AsItemsViaElement<'x>;
304
305 fn as_xml_iter(&self) -> Result<Self::ItemIter<'_>, ::xso::error::Error> {
306 ::xso::minidom_compat::AsItemsViaElement::new(self.clone())
307 }
308}
309
310#[cfg(test)]
311mod tests {
312 use super::*;
313 use core::str::FromStr;
314
315 #[cfg(target_pointer_width = "32")]
316 #[test]
317 fn test_size() {
318 assert_size!(Type, 1);
319 assert_size!(Mode, 1);
320 assert_size!(CandidateId, 12);
321 assert_size!(StreamId, 12);
322 assert_size!(Candidate, 56);
323 assert_size!(TransportPayload, 16);
324 assert_size!(Transport, 44);
325 }
326
327 #[cfg(target_pointer_width = "64")]
328 #[test]
329 fn test_size() {
330 assert_size!(Type, 1);
331 assert_size!(Mode, 1);
332 assert_size!(CandidateId, 24);
333 assert_size!(StreamId, 24);
334 assert_size!(Candidate, 88);
335 assert_size!(TransportPayload, 32);
336 assert_size!(Transport, 88);
337 }
338
339 #[test]
340 fn test_simple() {
341 let elem: Element = "<transport xmlns='urn:xmpp:jingle:transports:s5b:1' sid='coucou'/>"
342 .parse()
343 .unwrap();
344 let transport = Transport::try_from(elem).unwrap();
345 assert_eq!(transport.sid, StreamId(String::from("coucou")));
346 assert_eq!(transport.dstaddr, None);
347 assert_eq!(transport.mode, Mode::Tcp);
348 match transport.payload {
349 TransportPayload::None => (),
350 _ => panic!("Wrong element inside transport!"),
351 }
352 }
353
354 #[test]
355 fn test_serialise_activated() {
356 let elem: Element = "<transport xmlns='urn:xmpp:jingle:transports:s5b:1' sid='coucou'><activated cid='coucou'/></transport>".parse().unwrap();
357 let transport = Transport {
358 sid: StreamId(String::from("coucou")),
359 dstaddr: None,
360 mode: Mode::Tcp,
361 payload: TransportPayload::Activated(CandidateId(String::from("coucou"))),
362 };
363 let elem2: Element = transport.into();
364 assert_eq!(elem, elem2);
365 }
366
367 #[test]
368 fn test_serialise_candidate() {
369 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();
370 let transport = Transport {
371 sid: StreamId(String::from("coucou")),
372 dstaddr: None,
373 mode: Mode::Tcp,
374 payload: TransportPayload::Candidates(vec![Candidate {
375 cid: CandidateId(String::from("coucou")),
376 host: IpAddr::from_str("127.0.0.1").unwrap(),
377 jid: Jid::new("coucou@coucou").unwrap(),
378 port: None,
379 priority: 0u32,
380 type_: Type::Direct,
381 }]),
382 };
383 let elem2: Element = transport.into();
384 assert_eq!(elem, elem2);
385 }
386}