parsers: Add support for XEP-0215: External Service Discovery

Emmanuel Gil Peyrot created

Change summary

parsers/doap.xml        |   8 +
parsers/src/extdisco.rs | 204 ++++++++++++++++++++++++++++++++++++++++++
parsers/src/lib.rs      |   3 
parsers/src/ns.rs       |   3 
4 files changed, 218 insertions(+)

Detailed changes

parsers/doap.xml 🔗

@@ -296,6 +296,14 @@
             <xmpp:since>0.1.0</xmpp:since>
         </xmpp:SupportedXep>
     </implements>
+    <implements>
+        <xmpp:SupportedXep>
+            <xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0215.html"/>
+            <xmpp:status>complete</xmpp:status>
+            <xmpp:version>0.7.1</xmpp:version>
+            <xmpp:since>NEXT</xmpp:since>
+        </xmpp:SupportedXep>
+    </implements>
     <implements>
         <xmpp:SupportedXep>
             <xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0221.html"/>

parsers/src/extdisco.rs 🔗

@@ -0,0 +1,204 @@
+// Copyright (c) 2021 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+use crate::data_forms::DataForm;
+use crate::date::DateTime;
+use crate::iq::{IqGetPayload, IqResultPayload, IqSetPayload};
+
+generate_attribute!(
+    /// When sending a push update, the action value indicates if the service is being added or
+    /// deleted from the set of known services (or simply being modified).
+    Action, "action", {
+        /// The service is being added from the set of known services.
+        Add => "add",
+
+        /// The service is being removed from the set of known services.
+        Remove => "remove",
+
+        /// The service is being modified.
+        Modify => "modify",
+    }, Default = Add
+);
+
+generate_attribute!(
+    /// TODO
+    Transport, "transport", {
+        /// TODO
+        Tcp => "tcp",
+
+        /// TODO
+        Udp => "udp",
+    }
+);
+
+generate_attribute!(
+    /// TODO
+    Type, "type", {
+        /// A server that provides Session Traversal Utilities for NAT (STUN).
+        Stun => "stun",
+
+        /// A server that provides Traversal Using Relays around NAT (TURN).
+        Turn => "turn",
+    }
+);
+
+generate_attribute!(
+    /// TODO
+    Restricted,
+    "restricted",
+    bool
+);
+
+generate_element!(
+    /// Structure representing a `<service xmlns='urn:xmpp:extdisco:2'/>` element.
+    Service, "service", EXT_DISCO,
+    attributes: [
+        /// When sending a push update, the action value indicates if the service is being added or
+        /// deleted from the set of known services (or simply being modified).
+        action: Default<Action> = "action",
+
+        /// A timestamp indicating when the provided username and password credentials will expire.
+        expires: Option<DateTime> = "expires",
+
+        /// Either a fully qualified domain name (FQDN) or an IP address (IPv4 or IPv6).
+        host: Required<String> = "host",
+
+        /// A friendly (human-readable) name or label for the service.
+        name: Option<String> = "name",
+
+        /// A service- or server-generated password for use at the service.
+        password: Option<String> = "password",
+
+        /// The communications port to be used at the host.
+        port: Option<u16> = "port",
+
+        /// A boolean value indicating that username and password credentials are required and will
+        /// need to be requested if not already provided.
+        restricted: Default<Restricted> = "restricted",
+
+        /// The underlying transport protocol to be used when communicating with the service (typically
+        /// either TCP or UDP).
+        transport: Option<Transport> = "transport",
+
+        /// The service type as registered with the XMPP Registrar.
+        type_: Required<Type> = "type",
+
+        /// A service- or server-generated username for use at the service.
+        username: Option<String> = "username",
+    ], children: [
+        /// Extended information
+        ext_info: Vec<DataForm> = ("x", DATA_FORMS) => DataForm
+    ]
+);
+
+impl IqGetPayload for Service {}
+
+generate_element!(
+    /// Structure representing a `<services xmlns='urn:xmpp:extdisco:2'/>` element.
+    ServicesQuery, "services", EXT_DISCO,
+    attributes: [
+        /// TODO
+        type_: Option<Type> = "type",
+    ]
+);
+
+impl IqGetPayload for ServicesQuery {}
+
+generate_element!(
+    /// Structure representing a `<services xmlns='urn:xmpp:extdisco:2'/>` element.
+    ServicesResult, "services", EXT_DISCO,
+    attributes: [
+        /// TODO
+        type_: Option<Type> = "type",
+    ],
+    children: [
+        /// List of services.
+        services: Vec<Service> = ("service", EXT_DISCO) => Service
+    ]
+);
+
+impl IqResultPayload for ServicesResult {}
+impl IqSetPayload for ServicesResult {}
+
+generate_element!(
+    /// Structure representing a `<credentials xmlns='urn:xmpp:extdisco:2'/>` element.
+    Credentials, "credentials", EXT_DISCO,
+    children: [
+        /// List of services.
+        services: Vec<Service> = ("service", EXT_DISCO) => Service
+    ]
+);
+
+impl IqGetPayload for Credentials {}
+impl IqResultPayload for Credentials {}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::ns;
+    use crate::Element;
+    use std::convert::TryFrom;
+
+    #[cfg(target_pointer_width = "32")]
+    #[test]
+    fn test_size() {
+        assert_size!(Action, 1);
+        assert_size!(Transport, 1);
+        assert_size!(Restricted, 1);
+        assert_size!(Type, 1);
+        assert_size!(Service, 100); // XXX
+        assert_size!(ServicesQuery, 1);
+        assert_size!(ServicesResult, 16);
+        assert_size!(Credentials, 12);
+    }
+
+    #[cfg(target_pointer_width = "64")]
+    #[test]
+    fn test_size() {
+        assert_size!(Action, 1);
+        assert_size!(Transport, 1);
+        assert_size!(Restricted, 1);
+        assert_size!(Type, 1);
+        assert_size!(Service, 152);
+        assert_size!(ServicesQuery, 1);
+        assert_size!(ServicesResult, 32);
+        assert_size!(Credentials, 24);
+    }
+
+    #[test]
+    fn test_simple() {
+        let elem: Element = "<service xmlns='urn:xmpp:extdisco:2' host='stun.shakespeare.lit' port='9998' transport='udp' type='stun'/>".parse().unwrap();
+        let service = Service::try_from(elem).unwrap();
+        assert_eq!(service.action, Action::Add);
+        assert!(service.expires.is_none());
+        assert_eq!(service.host, "stun.shakespeare.lit");
+        assert!(service.name.is_none());
+        assert!(service.password.is_none());
+        assert_eq!(service.port.unwrap(), 9998);
+        assert_eq!(service.restricted, Restricted::False);
+        assert_eq!(service.transport.unwrap(), Transport::Udp);
+        assert_eq!(service.type_, Type::Stun);
+        assert!(service.username.is_none());
+        assert!(service.ext_info.is_empty());
+    }
+
+    #[test]
+    fn test_service_query() {
+        let query = ServicesQuery { type_: None };
+        let elem = Element::from(query);
+        assert!(elem.is("services", ns::EXT_DISCO));
+        assert_eq!(elem.attrs().next(), None);
+        assert_eq!(elem.nodes().next(), None);
+    }
+
+    #[test]
+    fn test_service_result() {
+        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();
+        let services = ServicesResult::try_from(elem).unwrap();
+        assert_eq!(services.type_, Some(Type::Stun));
+        assert_eq!(services.services.len(), 1);
+    }
+}

parsers/src/lib.rs 🔗

@@ -141,6 +141,9 @@ pub mod time;
 /// XEP-0203: Delayed Delivery
 pub mod delay;
 
+/// XEP-0215: External Service Discovery
+pub mod extdisco;
+
 /// XEP-0221: Data Forms Media Element
 pub mod media_element;
 

parsers/src/ns.rs 🔗

@@ -133,6 +133,9 @@ pub const TIME: &str = "urn:xmpp:time";
 /// XEP-0203: Delayed Delivery
 pub const DELAY: &str = "urn:xmpp:delay";
 
+/// XEP-0215: External Service Discovery
+pub const EXT_DISCO: &str = "urn:xmpp:extdisco:2";
+
 /// XEP-0221: Data Forms Media Element
 pub const MEDIA_ELEMENT: &str = "urn:xmpp:media-element";