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