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 xso::{AsXml, FromXml};
8
9use crate::iq::{IqResultPayload, IqSetPayload};
10use crate::ns;
11use jid::{FullJid, Jid};
12
13/// The bind feature exposed in stream:features.
14#[derive(FromXml, AsXml, Debug, Clone, PartialEq)]
15#[xml(namespace = ns::BIND, name = "bind")]
16pub struct BindFeature {
17 /// Present if bind is required.
18 #[xml(child(default))]
19 required: Option<Required>,
20}
21
22/// Notes that bind is required.
23#[derive(FromXml, AsXml, Debug, Clone, PartialEq)]
24#[xml(namespace = ns::BIND, name = "required")]
25pub struct Required;
26
27/// The request for resource binding, which is the process by which a client
28/// can obtain a full JID and start exchanging on the XMPP network.
29///
30/// See <https://xmpp.org/rfcs/rfc6120.html#bind>
31#[derive(FromXml, AsXml, Debug, Clone, PartialEq)]
32#[xml(namespace = ns::BIND, name = "bind")]
33pub struct BindQuery {
34 /// Requests this resource, the server may associate another one though.
35 ///
36 /// If this is None, we request no particular resource, and a random one
37 /// will be affected by the server.
38 #[xml(extract(default, fields(text(type_ = String))))]
39 resource: Option<String>,
40}
41
42impl BindQuery {
43 /// Creates a resource binding request.
44 pub fn new(resource: Option<String>) -> BindQuery {
45 BindQuery { resource }
46 }
47}
48
49impl IqSetPayload for BindQuery {}
50
51/// The response for resource binding, containing the client’s full JID.
52///
53/// See <https://xmpp.org/rfcs/rfc6120.html#bind>
54#[derive(FromXml, AsXml, Debug, Clone, PartialEq)]
55#[xml(namespace = ns::BIND, name = "bind")]
56pub struct BindResponse {
57 /// The full JID returned by the server for this client.
58 #[xml(extract(fields(text(type_ = FullJid))))]
59 jid: FullJid,
60}
61
62impl IqResultPayload for BindResponse {}
63
64impl From<BindResponse> for FullJid {
65 fn from(bind: BindResponse) -> FullJid {
66 bind.jid
67 }
68}
69
70impl From<BindResponse> for Jid {
71 fn from(bind: BindResponse) -> Jid {
72 Jid::from(bind.jid)
73 }
74}
75
76#[cfg(test)]
77mod tests {
78 use super::*;
79 use minidom::Element;
80 use xso::error::{Error, FromElementError};
81
82 #[cfg(target_pointer_width = "32")]
83 #[test]
84 fn test_size() {
85 assert_size!(BindFeature, 1);
86 assert_size!(Required, 0);
87 assert_size!(BindQuery, 12);
88 assert_size!(BindResponse, 16);
89 }
90
91 #[cfg(target_pointer_width = "64")]
92 #[test]
93 fn test_size() {
94 assert_size!(BindFeature, 1);
95 assert_size!(Required, 0);
96 assert_size!(BindQuery, 24);
97 assert_size!(BindResponse, 32);
98 }
99
100 #[test]
101 fn test_simple() {
102 let elem: Element = "<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'/>"
103 .parse()
104 .unwrap();
105 let bind = BindQuery::try_from(elem).unwrap();
106 assert_eq!(bind.resource, None);
107
108 let elem: Element =
109 "<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'><resource>Hello™</resource></bind>"
110 .parse()
111 .unwrap();
112 let bind = BindQuery::try_from(elem).unwrap();
113 // FIXME: “™” should be resourceprep’d into “TM” here…
114 //assert_eq!(bind.resource.unwrap(), "HelloTM");
115 assert_eq!(bind.resource.unwrap(), "Hello™");
116
117 let elem: Element = "<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'><jid>coucou@linkmauve.fr/Hello™</jid></bind>"
118 .parse()
119 .unwrap();
120 let bind = BindResponse::try_from(elem).unwrap();
121 assert_eq!(
122 bind.jid,
123 FullJid::new("coucou@linkmauve.fr/HelloTM").unwrap()
124 );
125 }
126
127 #[cfg(not(feature = "disable-validation"))]
128 #[test]
129 fn test_invalid_resource() {
130 let elem: Element = "<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'><resource attr='coucou'>resource</resource></bind>"
131 .parse()
132 .unwrap();
133 let error = BindQuery::try_from(elem).unwrap_err();
134 let message = match error {
135 FromElementError::Invalid(Error::Other(string)) => string,
136 _ => panic!(),
137 };
138 assert_eq!(
139 message,
140 "Unknown attribute in extraction for field 'resource' in BindQuery element."
141 );
142
143 let elem: Element = "<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'><resource><hello-world/>resource</resource></bind>"
144 .parse()
145 .unwrap();
146 let error = BindQuery::try_from(elem).unwrap_err();
147 let message = match error {
148 FromElementError::Invalid(Error::Other(string)) => string,
149 _ => panic!(),
150 };
151 assert_eq!(
152 message,
153 "Unknown child in extraction for field 'resource' in BindQuery element."
154 );
155 }
156}