bind2.rs

  1// Copyright (c) 2024 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::mam;
 10use crate::ns;
 11use minidom::Element;
 12
 13/// Represents the `<bind/>` element, as sent by the server in SASL 2 to advertise which features
 14/// can be enabled during the binding step.
 15#[derive(FromXml, AsXml, Debug, Clone, PartialEq)]
 16#[xml(namespace = ns::BIND2, name = "bind")]
 17pub struct BindFeature {
 18    /// The features that can be enabled by the client.
 19    #[xml(extract(default, name = "inline", fields(extract(n = .., name = "feature", fields(attribute(name = "var", type_ = String))))))]
 20    pub inline_features: Vec<String>,
 21}
 22
 23/// Represents a `<bind/>` element, as sent by the client inline in the `<authenticate/>` SASL 2
 24/// element, to perform the binding at the same time as the authentication.
 25#[derive(FromXml, AsXml, Debug, Clone, PartialEq)]
 26#[xml(namespace = ns::BIND2, name = "bind")]
 27pub struct BindQuery {
 28    /// Short text string that typically identifies the software the user is using, mostly useful
 29    /// for diagnostic purposes for users, operators and developers.  This tag may be visible to
 30    /// other entities on the XMPP network.
 31    #[xml(extract(default, fields(text(type_ = String))))]
 32    pub tag: Option<String>,
 33
 34    /// Features that the client requests to be automatically enabled for its new session.
 35    #[xml(element(n = ..))]
 36    pub payloads: Vec<Element>,
 37}
 38
 39/// Represents a `<bound/>` element, which tells the client its resource is bound, alongside other
 40/// requests.
 41#[derive(FromXml, AsXml, Debug, Clone, PartialEq)]
 42#[xml(namespace = ns::BIND2, name = "bound")]
 43pub struct Bound {
 44    /// Indicates which messages got missed by this particular device, start is the oldest message
 45    /// and end is the newest, before this connection.
 46    #[xml(child(default))]
 47    pub mam_metadata: Option<mam::MetadataResponse>,
 48
 49    /// Additional payloads which happened during the binding process.
 50    #[xml(element(n = ..))]
 51    pub payloads: Vec<Element>,
 52}
 53
 54#[cfg(test)]
 55mod tests {
 56    use super::*;
 57
 58    #[cfg(target_pointer_width = "32")]
 59    #[test]
 60    fn test_size() {
 61        assert_size!(BindFeature, 12);
 62        assert_size!(BindQuery, 24);
 63        assert_size!(Bound, 68);
 64    }
 65
 66    #[cfg(target_pointer_width = "64")]
 67    #[test]
 68    fn test_size() {
 69        assert_size!(BindFeature, 24);
 70        assert_size!(BindQuery, 48);
 71        assert_size!(Bound, 104);
 72    }
 73
 74    #[test]
 75    fn test_empty() {
 76        let elem: Element = "<bind xmlns='urn:xmpp:bind:0'/>".parse().unwrap();
 77        let bind = BindQuery::try_from(elem).unwrap();
 78        assert_eq!(bind.tag, None);
 79        assert_eq!(bind.payloads.len(), 0);
 80    }
 81
 82    #[test]
 83    fn test_simple() {
 84        // Example 1
 85        let elem: Element =
 86            "<bind xmlns='urn:xmpp:bind:0'><inline><feature var='urn:xmpp:carbons:2'/><feature var='urn:xmpp:csi:0'/><feature var='urn:xmpp:sm:3'/></inline></bind>"
 87                .parse()
 88                .unwrap();
 89        let bind = BindFeature::try_from(elem.clone()).unwrap();
 90        assert_eq!(bind.inline_features.len(), 3);
 91        assert_eq!(bind.inline_features[0], "urn:xmpp:carbons:2");
 92        assert_eq!(bind.inline_features[1], "urn:xmpp:csi:0");
 93        assert_eq!(bind.inline_features[2], "urn:xmpp:sm:3");
 94        let elem2 = bind.into();
 95        assert_eq!(elem, elem2);
 96
 97        // Example 2
 98        let elem: Element = "<bind xmlns='urn:xmpp:bind:0'><tag>AwesomeXMPP</tag></bind>"
 99            .parse()
100            .unwrap();
101        let bind = BindQuery::try_from(elem).unwrap();
102        assert_eq!(bind.tag.unwrap(), "AwesomeXMPP");
103        assert_eq!(bind.payloads.len(), 0);
104
105        // Example 3
106        let elem: Element = "<bind xmlns='urn:xmpp:bind:0'><tag>AwesomeXMPP</tag><enable xmlns='urn:xmpp:carbons:2'/><enable xmlns='urn:xmpp:sm:3'/><inactive xmlns='urn:xmpp:csi:0'/></bind>".parse().unwrap();
107        let bind = BindQuery::try_from(elem).unwrap();
108        assert_eq!(bind.tag.unwrap(), "AwesomeXMPP");
109        assert_eq!(bind.payloads.len(), 3);
110
111        // Example 4
112        let elem: Element = "<bound xmlns='urn:xmpp:bind:0'><metadata xmlns='urn:xmpp:mam:2'><start id='YWxwaGEg' timestamp='2008-08-22T21:09:04Z'/><end id='b21lZ2Eg' timestamp='2020-04-20T14:34:21Z'/></metadata></bound>".parse().unwrap();
113        let bound = Bound::try_from(elem).unwrap();
114        assert!(bound.mam_metadata.is_some());
115        assert_eq!(bound.payloads.len(), 0);
116    }
117}