xmpp-parsers: Implement XEP-0440: SASL Channel-Binding Type Capability

Emmanuel Gil Peyrot created

Change summary

parsers/ChangeLog      |  1 
parsers/doap.xml       |  8 +++
parsers/src/lib.rs     |  3 +
parsers/src/ns.rs      |  3 +
parsers/src/sasl_cb.rs | 89 +++++++++++++++++++++++++++++++++++++++++++
5 files changed, 104 insertions(+)

Detailed changes

parsers/ChangeLog 🔗

@@ -17,6 +17,7 @@ XXXX-YY-ZZ RELEASER <admin@example.com>
         (!416)
     * New parsers/serialisers:
         - Stream Features (RFC 6120) (!400)
+        - SASL Channel-Binding Type Capability (XEP-0440)
 
 Version 0.21.0:
 2024-07-25 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>

parsers/doap.xml 🔗

@@ -626,6 +626,14 @@
             <xmpp:since>0.16.0</xmpp:since>
         </xmpp:SupportedXep>
     </implements>
+    <implements>
+        <xmpp:SupportedXep>
+            <xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0440.html"/>
+            <xmpp:status>complete</xmpp:status>
+            <xmpp:version>0.4.2</xmpp:version>
+            <xmpp:since>NEXT</xmpp:since>
+        </xmpp:SupportedXep>
+    </implements>
     <implements>
         <xmpp:SupportedXep>
             <xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0441.html"/>

parsers/src/lib.rs 🔗

@@ -272,6 +272,9 @@ pub mod occupant_id;
 /// XEP-0441: Message Archive Management Preferences
 pub mod mam_prefs;
 
+/// XEP-0440: SASL Channel-Binding Type Capability
+pub mod sasl_cb;
+
 /// XEP-0444: Message Reactions
 pub mod reactions;
 

parsers/src/ns.rs 🔗

@@ -293,6 +293,9 @@ pub const BOOKMARKS2_COMPAT_PEP: &str = "urn:xmpp:bookmarks:1#compat-pep";
 /// XEP-0421: Anonymous unique occupant identifiers for MUCs
 pub const OID: &str = "urn:xmpp:occupant-id:0";
 
+/// XEP-0440: SASL Channel-Binding Type Capability
+pub const SASL_CB: &str = "urn:xmpp:sasl-cb:0";
+
 /// XEP-0444: Message Reactions
 pub const REACTIONS: &str = "urn:xmpp:reactions:0";
 

parsers/src/sasl_cb.rs 🔗

@@ -0,0 +1,89 @@
+// Copyright (c) 2024 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 xso::{error::Error, AsXml, AsXmlText, FromXml, FromXmlText};
+
+use crate::ns;
+use std::borrow::Cow;
+
+/// One type of channel-binding, as defined by the IANA:
+/// https://www.iana.org/assignments/channel-binding-types/channel-binding-types.xhtml
+#[derive(Debug, Clone, PartialEq)]
+pub enum Type {
+    /// The tls-unique channel binding.
+    TlsUnique,
+
+    /// The tls-server-end-point channel binding.
+    TlsServerEndPoint,
+
+    /// The tls-unique-for-telnet channel binding.
+    TlsUniqueForTelnet,
+
+    /// The EKM value obtained from the current TLS connection.
+    ///
+    /// See RFC9266.
+    TlsExporter,
+}
+
+impl FromXmlText for Type {
+    fn from_xml_text(s: String) -> Result<Type, Error> {
+        Ok(match s.as_ref() {
+            "tls-unique" => Type::TlsUnique,
+            "tls-server-end-point" => Type::TlsServerEndPoint,
+            "tls-unique-for-telnet" => Type::TlsUniqueForTelnet,
+            "tls-exporter" => Type::TlsExporter,
+
+            _ => return Err(Error::Other("Unknown value '{s}' for 'type' attribute.")),
+        })
+    }
+}
+
+impl AsXmlText for Type {
+    fn as_xml_text(&self) -> Result<Cow<'_, str>, Error> {
+        Ok(Cow::Borrowed(match self {
+            Type::TlsUnique => "tls-unique",
+            Type::TlsServerEndPoint => "tls-server-end-point",
+            Type::TlsUniqueForTelnet => "tls-unique-for-telnet",
+            Type::TlsExporter => "tls-exporter",
+        }))
+    }
+}
+
+/// Stream feature listing the channel-binding types supported by the server.
+#[derive(FromXml, AsXml, Debug, Clone, PartialEq)]
+#[xml(namespace = ns::SASL_CB, name = "sasl-channel-binding")]
+pub struct SaslChannelBinding {
+    /// The list of channel-binding types supported by the server.
+    #[xml(extract(n = .., name = "channel-binding", fields(attribute(name = "type", type_ = Type))))]
+    pub types: Vec<Type>,
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use minidom::Element;
+
+    #[cfg(target_pointer_width = "64")]
+    #[test]
+    fn test_size() {
+        assert_size!(Type, 1);
+        assert_size!(SaslChannelBinding, 24);
+    }
+
+    #[cfg(target_pointer_width = "32")]
+    #[test]
+    fn test_size() {
+        assert_size!(Type, 1);
+        assert_size!(SaslChannelBinding, 12);
+    }
+
+    #[test]
+    fn test_simple() {
+        let elem: Element = "<sasl-channel-binding xmlns='urn:xmpp:sasl-cb:0'><channel-binding type='tls-server-end-point'/><channel-binding type='tls-exporter'/></sasl-channel-binding>".parse().unwrap();
+        let sasl_cb = SaslChannelBinding::try_from(elem).unwrap();
+        assert_eq!(sasl_cb.types, [Type::TlsServerEndPoint, Type::TlsExporter]);
+    }
+}