stream_features.rs

  1// Copyright (c) 2024 xmpp-rs contributors
  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::ns;
 10
 11/// Wraps `<stream:features/>`, usually the very first nonza of a
 12/// XMPP stream. Indicates which features are supported.
 13#[derive(FromXml, AsXml, PartialEq, Debug, Default, Clone)]
 14#[xml(namespace = ns::STREAM, name = "features")]
 15pub struct StreamFeatures {
 16    /// StartTLS is supported, and may be mandatory.
 17    #[xml(child(default))]
 18    pub starttls: Option<StartTls>,
 19
 20    /// Bind is supported.
 21    #[xml(child(default))]
 22    pub bind: Option<Bind>,
 23
 24    /// List of supported SASL mechanisms
 25    #[xml(child(default))]
 26    pub sasl_mechanisms: SaslMechanisms,
 27}
 28
 29/// StartTLS is supported, and may be mandatory.
 30#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 31#[xml(namespace = ns::TLS, name = "starttls")]
 32pub struct StartTls {
 33    /// Marker for mandatory StartTLS.
 34    #[xml(child(default))]
 35    pub required: Option<RequiredStartTls>,
 36}
 37
 38/// Marker for mandatory StartTLS.
 39#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 40#[xml(namespace = ns::TLS, name = "required")]
 41pub struct RequiredStartTls;
 42
 43/// Bind is supported.
 44#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 45#[xml(namespace = ns::BIND, name = "bind")]
 46pub struct Bind;
 47
 48generate_element!(
 49    /// List of supported SASL mechanisms
 50    #[derive(Default)]
 51    SaslMechanisms, "mechanisms", SASL,
 52    children: [
 53        /// List of information elements describing this avatar.
 54        mechanisms: Vec<SaslMechanism> = ("mechanism", SASL) => SaslMechanism,
 55    ]
 56);
 57
 58// TODO: Uncomment me when xso supports collections, see
 59// https://gitlab.com/xmpp-rs/xmpp-rs/-/issues/136
 60// #[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 61// #[xml(namespace = ns::SASL, name = "mechanisms")]
 62// pub struct SaslMechanisms {
 63//     #[xml(child(default))]
 64//     mechanisms: Vec<SaslMechanism>,
 65// }
 66
 67/// The name of a SASL mechanism.
 68#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 69#[xml(namespace = ns::SASL, name = "mechanism")]
 70pub struct SaslMechanism {
 71    /// The stringy name of the mechanism.
 72    #[xml(text)]
 73    pub mechanism: String,
 74}
 75
 76impl StreamFeatures {
 77    /// Can initiate TLS session with this server?
 78    pub fn can_starttls(&self) -> bool {
 79        self.starttls.is_some()
 80    }
 81
 82    /// Does server support user resource binding?
 83    pub fn can_bind(&self) -> bool {
 84        self.bind.is_some()
 85    }
 86}
 87
 88#[cfg(test)]
 89mod tests {
 90    use super::*;
 91    use minidom::Element;
 92
 93    #[cfg(target_pointer_width = "32")]
 94    #[test]
 95    fn test_size() {
 96        assert_size!(SaslMechanism, 12);
 97        assert_size!(SaslMechanisms, 12);
 98        assert_size!(Bind, 0);
 99        assert_size!(RequiredStartTls, 0);
100        assert_size!(StartTls, 1);
101        assert_size!(StreamFeatures, 16);
102    }
103
104    #[cfg(target_pointer_width = "64")]
105    #[test]
106    fn test_size() {
107        assert_size!(SaslMechanism, 24);
108        assert_size!(SaslMechanisms, 24);
109        assert_size!(Bind, 0);
110        assert_size!(RequiredStartTls, 0);
111        assert_size!(StartTls, 1);
112        assert_size!(StreamFeatures, 32);
113    }
114
115    #[test]
116    fn test_required_starttls() {
117        let elem: Element = "<stream:features xmlns:stream='http://etherx.jabber.org/streams'>
118                                 <starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'>
119                                     <required/>
120                                 </starttls>
121                             </stream:features>"
122            .parse()
123            .unwrap();
124
125        let features = StreamFeatures::try_from(elem).unwrap();
126
127        assert_eq!(features.can_bind(), false);
128        assert_eq!(features.sasl_mechanisms.mechanisms.len(), 0);
129        assert_eq!(features.can_starttls(), true);
130        assert_eq!(features.starttls.unwrap().required.is_some(), true);
131    }
132
133    #[test]
134    // TODO: Unignore me when xso supports collections of unknown children, see
135    // https://gitlab.com/xmpp-rs/xmpp-rs/-/issues/136
136    #[ignore]
137    fn test_deprecated_compression() {
138        let elem: Element = "<stream:features xmlns:stream='http://etherx.jabber.org/streams'>
139                                 <bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'/>
140                                 <compression xmlns='http://jabber.org/features/compress'>
141                                     <method>zlib</method>
142                                     <method>lzw</method>
143                                 </compression>
144                             </stream:features>"
145            .parse()
146            .unwrap();
147
148        let features = StreamFeatures::try_from(elem).unwrap();
149
150        assert_eq!(features.can_bind(), true);
151        assert_eq!(features.sasl_mechanisms.mechanisms.len(), 0);
152        assert_eq!(features.can_starttls(), false);
153    }
154
155    #[test]
156    fn test_empty_features() {
157        let elem: Element = "<stream:features xmlns:stream='http://etherx.jabber.org/streams'/>"
158            .parse()
159            .unwrap();
160
161        let features = StreamFeatures::try_from(elem).unwrap();
162
163        assert_eq!(features.can_bind(), false);
164        assert_eq!(features.sasl_mechanisms.mechanisms.len(), 0);
165        assert_eq!(features.can_starttls(), false);
166    }
167}