extdisco.rs

  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}