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