1// Copyright (c) 2018 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 crate::util::error::Error;
8use crate::iq::{IqResultPayload, IqSetPayload};
9use crate::ns;
10use jid::Jid;
11use minidom::Element;
12use std::str::FromStr;
13use std::convert::TryFrom;
14
15/// The request for resource binding, which is the process by which a client
16/// can obtain a full JID and start exchanging on the XMPP network.
17///
18/// See https://xmpp.org/rfcs/rfc6120.html#bind
19#[derive(Debug, Clone, PartialEq)]
20pub enum Bind {
21 /// Requests no particular resource, a random one will be affected by the
22 /// server.
23 None,
24
25 /// Requests this resource, the server may associate another one though.
26 Resource(String),
27
28 /// The full JID returned by the server for this client.
29 Jid(Jid),
30}
31
32impl Bind {
33 /// Creates a resource binding request.
34 pub fn new(resource: Option<String>) -> Bind {
35 match resource {
36 None => Bind::None,
37 Some(resource) => Bind::Resource(resource),
38 }
39 }
40}
41
42impl IqSetPayload for Bind {}
43impl IqResultPayload for Bind {}
44
45impl TryFrom<Element> for Bind {
46 type Error = Error;
47
48 fn try_from(elem: Element) -> Result<Bind, Error> {
49 check_self!(elem, "bind", BIND);
50 check_no_attributes!(elem, "bind");
51
52 let mut bind = Bind::None;
53 for child in elem.children() {
54 if bind != Bind::None {
55 return Err(Error::ParseError("Bind can only have one child."));
56 }
57 if child.is("resource", ns::BIND) {
58 check_no_attributes!(child, "resource");
59 check_no_children!(child, "resource");
60 bind = Bind::Resource(child.text());
61 } else if child.is("jid", ns::BIND) {
62 check_no_attributes!(child, "jid");
63 check_no_children!(child, "jid");
64 bind = Bind::Jid(Jid::from_str(&child.text())?);
65 } else {
66 return Err(Error::ParseError("Unknown element in bind."));
67 }
68 }
69
70 Ok(bind)
71 }
72}
73
74impl From<Bind> for Element {
75 fn from(bind: Bind) -> Element {
76 Element::builder("bind")
77 .ns(ns::BIND)
78 .append(match bind {
79 Bind::None => vec![],
80 Bind::Resource(resource) => vec![Element::builder("resource")
81 .ns(ns::BIND)
82 .append(resource)
83 .build()],
84 Bind::Jid(jid) => vec![Element::builder("jid").ns(ns::BIND).append(jid).build()],
85 })
86 .build()
87 }
88}
89
90#[cfg(test)]
91mod tests {
92 use super::*;
93
94 #[cfg(target_pointer_width = "32")]
95 #[test]
96 fn test_size() {
97 assert_size!(Bind, 40);
98 }
99
100 #[cfg(target_pointer_width = "64")]
101 #[test]
102 fn test_size() {
103 assert_size!(Bind, 80);
104 }
105
106 #[test]
107 fn test_simple() {
108 let elem: Element = "<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'/>"
109 .parse()
110 .unwrap();
111 let bind = Bind::try_from(elem).unwrap();
112 assert_eq!(bind, Bind::None);
113 }
114
115 #[cfg(not(feature = "disable-validation"))]
116 #[test]
117 fn test_invalid_resource() {
118 let elem: Element = "<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'><resource attr='coucou'>resource</resource></bind>"
119 .parse()
120 .unwrap();
121 let error = Bind::try_from(elem).unwrap_err();
122 let message = match error {
123 Error::ParseError(string) => string,
124 _ => panic!(),
125 };
126 assert_eq!(message, "Unknown attribute in resource element.");
127
128 let elem: Element = "<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'><resource><hello-world/>resource</resource></bind>"
129 .parse()
130 .unwrap();
131 let error = Bind::try_from(elem).unwrap_err();
132 let message = match error {
133 Error::ParseError(string) => string,
134 _ => panic!(),
135 };
136 assert_eq!(message, "Unknown child in resource element.");
137 }
138}