1// Copyright (c) 2021 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::data_forms::DataForm;
8use crate::date::DateTime;
9use crate::iq::{IqGetPayload, IqResultPayload, IqSetPayload};
10
11generate_attribute!(
12 /// When sending a push update, the action value indicates if the service is being added or
13 /// deleted from the set of known services (or simply being modified).
14 Action, "action", {
15 /// The service is being added from the set of known services.
16 Add => "add",
17
18 /// The service is being removed from the set of known services.
19 Remove => "remove",
20
21 /// The service is being modified.
22 Modify => "modify",
23 }, Default = Add
24);
25
26generate_attribute!(
27 /// The underlying transport protocol to be used when communicating with the service.
28 Transport, "transport", {
29 /// Use TCP as a transport protocol.
30 Tcp => "tcp",
31
32 /// Use UDP as a transport protocol.
33 Udp => "udp",
34 }
35);
36
37generate_attribute!(
38 /// The service type as registered with the XMPP Registrar.
39 Type, "type", {
40 /// A server that provides Session Traversal Utilities for NAT (STUN).
41 Stun => "stun",
42
43 /// A server that provides Traversal Using Relays around NAT (TURN).
44 Turn => "turn",
45 }
46);
47
48generate_attribute!(
49 /// Username and password credentials are required and will need to be requested if not already
50 /// provided.
51 Restricted,
52 "restricted",
53 bool
54);
55
56generate_element!(
57 /// Structure representing a `<service xmlns='urn:xmpp:extdisco:2'/>` element.
58 Service, "service", EXT_DISCO,
59 attributes: [
60 /// When sending a push update, the action value indicates if the service is being added or
61 /// deleted from the set of known services (or simply being modified).
62 action: Default<Action> = "action",
63
64 /// A timestamp indicating when the provided username and password credentials will expire.
65 expires: Option<DateTime> = "expires",
66
67 /// Either a fully qualified domain name (FQDN) or an IP address (IPv4 or IPv6).
68 host: Required<String> = "host",
69
70 /// A friendly (human-readable) name or label for the service.
71 name: Option<String> = "name",
72
73 /// A service- or server-generated password for use at the service.
74 password: Option<String> = "password",
75
76 /// The communications port to be used at the host.
77 port: Option<u16> = "port",
78
79 /// A boolean value indicating that username and password credentials are required and will
80 /// need to be requested if not already provided.
81 restricted: Default<Restricted> = "restricted",
82
83 /// The underlying transport protocol to be used when communicating with the service (typically
84 /// either TCP or UDP).
85 transport: Option<Transport> = "transport",
86
87 /// The service type as registered with the XMPP Registrar.
88 type_: Required<Type> = "type",
89
90 /// A service- or server-generated username for use at the service.
91 username: Option<String> = "username",
92 ], children: [
93 /// Extended information
94 ext_info: Vec<DataForm> = ("x", DATA_FORMS) => DataForm
95 ]
96);
97
98impl IqGetPayload for Service {}
99
100generate_element!(
101 /// Structure representing a `<services xmlns='urn:xmpp:extdisco:2'/>` element.
102 ServicesQuery, "services", EXT_DISCO,
103 attributes: [
104 /// TODO
105 type_: Option<Type> = "type",
106 ]
107);
108
109impl IqGetPayload for ServicesQuery {}
110
111generate_element!(
112 /// Structure representing a `<services xmlns='urn:xmpp:extdisco:2'/>` element.
113 ServicesResult, "services", EXT_DISCO,
114 attributes: [
115 /// TODO
116 type_: Option<Type> = "type",
117 ],
118 children: [
119 /// List of services.
120 services: Vec<Service> = ("service", EXT_DISCO) => Service
121 ]
122);
123
124impl IqResultPayload for ServicesResult {}
125impl IqSetPayload for ServicesResult {}
126
127generate_element!(
128 /// Structure representing a `<credentials xmlns='urn:xmpp:extdisco:2'/>` element.
129 Credentials, "credentials", EXT_DISCO,
130 children: [
131 /// List of services.
132 services: Vec<Service> = ("service", EXT_DISCO) => Service
133 ]
134);
135
136impl IqGetPayload for Credentials {}
137impl IqResultPayload for Credentials {}
138
139#[cfg(test)]
140mod tests {
141 use super::*;
142 use crate::ns;
143 use crate::Element;
144 use std::convert::TryFrom;
145
146 #[cfg(target_pointer_width = "32")]
147 #[test]
148 fn test_size() {
149 assert_size!(Action, 1);
150 assert_size!(Transport, 1);
151 assert_size!(Restricted, 1);
152 assert_size!(Type, 1);
153 assert_size!(Service, 88);
154 assert_size!(ServicesQuery, 1);
155 assert_size!(ServicesResult, 16);
156 assert_size!(Credentials, 12);
157 }
158
159 #[cfg(target_pointer_width = "64")]
160 #[test]
161 fn test_size() {
162 assert_size!(Action, 1);
163 assert_size!(Transport, 1);
164 assert_size!(Restricted, 1);
165 assert_size!(Type, 1);
166 assert_size!(Service, 152);
167 assert_size!(ServicesQuery, 1);
168 assert_size!(ServicesResult, 32);
169 assert_size!(Credentials, 24);
170 }
171
172 #[test]
173 fn test_simple() {
174 let elem: Element = "<service xmlns='urn:xmpp:extdisco:2' host='stun.shakespeare.lit' port='9998' transport='udp' type='stun'/>".parse().unwrap();
175 let service = Service::try_from(elem).unwrap();
176 assert_eq!(service.action, Action::Add);
177 assert!(service.expires.is_none());
178 assert_eq!(service.host, "stun.shakespeare.lit");
179 assert!(service.name.is_none());
180 assert!(service.password.is_none());
181 assert_eq!(service.port.unwrap(), 9998);
182 assert_eq!(service.restricted, Restricted::False);
183 assert_eq!(service.transport.unwrap(), Transport::Udp);
184 assert_eq!(service.type_, Type::Stun);
185 assert!(service.username.is_none());
186 assert!(service.ext_info.is_empty());
187 }
188
189 #[test]
190 fn test_service_query() {
191 let query = ServicesQuery { type_: None };
192 let elem = Element::from(query);
193 assert!(elem.is("services", ns::EXT_DISCO));
194 assert_eq!(elem.attrs().next(), None);
195 assert_eq!(elem.nodes().next(), None);
196 }
197
198 #[test]
199 fn test_service_result() {
200 let elem: Element = "<services xmlns='urn:xmpp:extdisco:2' type='stun'><service host='stun.shakespeare.lit' port='9998' transport='udp' type='stun'/></services>".parse().unwrap();
201 let services = ServicesResult::try_from(elem).unwrap();
202 assert_eq!(services.type_, Some(Type::Stun));
203 assert_eq!(services.services.len(), 1);
204 }
205}