xmpp_parsers/
jingle_s5b.rs

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 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    /// The type of the connection being proposed by this candidate.
20    Type, "type", {
21        /// Direct connection using NAT assisting technologies like NAT-PMP or
22        /// UPnP-IGD.
23        Assisted => "assisted",
24
25        /// Direct connection using the given interface.
26        Direct => "direct",
27
28        /// SOCKS5 relay.
29        Proxy => "proxy",
30
31        /// Tunnel protocol such as Teredo.
32        Tunnel => "tunnel",
33    }, Default = Direct
34);
35
36generate_attribute!(
37    /// Which mode to use for the connection.
38    Mode, "mode", {
39        /// Use TCP, which is the default.
40        Tcp => "tcp",
41
42        /// Use UDP.
43        Udp => "udp",
44    }, Default = Tcp
45);
46
47generate_id!(
48    /// An identifier for a candidate.
49    CandidateId
50);
51
52generate_id!(
53    /// An identifier for a stream.
54    StreamId
55);
56
57/// A candidate for a connection.
58#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
59#[xml(namespace = ns::JINGLE_S5B, name = "candidate")]
60pub struct Candidate {
61    /// The identifier for this candidate.
62    #[xml(attribute)]
63    cid: CandidateId,
64
65    /// The host to connect to.
66    #[xml(attribute)]
67    host: IpAddr,
68
69    /// The JID to request at the given end.
70    #[xml(attribute)]
71    jid: Jid,
72
73    /// The port to connect to.
74    #[xml(attribute(default))]
75    port: Option<u16>,
76
77    /// The priority of this candidate, computed using this formula:
78    /// priority = (2^16)*(type preference) + (local preference)
79    #[xml(attribute)]
80    priority: u32,
81
82    /// The type of the connection being proposed by this candidate.
83    #[xml(attribute(default, name = "type"))]
84    type_: Type,
85}
86
87impl Candidate {
88    /// Creates a new candidate with the given parameters.
89    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    /// Sets the port of this candidate.
101    pub fn with_port(mut self, port: u16) -> Candidate {
102        self.port = Some(port);
103        self
104    }
105
106    /// Sets the type of this candidate.
107    pub fn with_type(mut self, type_: Type) -> Candidate {
108        self.type_ = type_;
109        self
110    }
111}
112
113/// The payload of a transport.
114#[derive(Debug, Clone, PartialEq)]
115pub enum TransportPayload {
116    /// The responder informs the initiator that the bytestream pointed by this
117    /// candidate has been activated.
118    Activated(CandidateId),
119
120    /// A list of suggested candidates.
121    Candidates(Vec<Candidate>),
122
123    /// Both parties failed to use a candidate, they should fallback to another
124    /// transport.
125    CandidateError,
126
127    /// The candidate pointed here should be used by both parties.
128    CandidateUsed(CandidateId),
129
130    /// This entity can’t connect to the SOCKS5 proxy.
131    ProxyError,
132
133    /// XXX: Invalid, should not be found in the wild.
134    None,
135}
136
137/// Describes a Jingle transport using a direct or proxied connection.
138#[derive(Debug, Clone, PartialEq)]
139pub struct Transport {
140    /// The stream identifier for this transport.
141    pub sid: StreamId,
142
143    /// The destination address.
144    pub dstaddr: Option<String>,
145
146    /// The mode to be used for the transfer.
147    pub mode: Mode,
148
149    /// The payload of this transport.
150    pub payload: TransportPayload,
151}
152
153impl Transport {
154    /// Creates a new transport element.
155    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    /// Sets the destination address of this transport.
165    pub fn with_dstaddr(mut self, dstaddr: String) -> Transport {
166        self.dstaddr = Some(dstaddr);
167        self
168    }
169
170    /// Sets the mode of this transport.
171    pub fn with_mode(mut self, mode: Mode) -> Transport {
172        self.mode = mode;
173        self
174    }
175
176    /// Sets the payload of this transport.
177    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}