diff --git a/parsers/ChangeLog b/parsers/ChangeLog index 179a667b..30ad6819 100644 --- a/parsers/ChangeLog +++ b/parsers/ChangeLog @@ -1,3 +1,8 @@ +Version NEXT: +XXXX-YY-ZZ RELEASER + * New parsers/serialisers: + - Stream Features (RFC 6120) (!400) + Version 0.21.0: 2024-07-25 Emmanuel Gil Peyrot * New parsers/serialisers: diff --git a/parsers/src/lib.rs b/parsers/src/lib.rs index 550592e3..2cd51888 100644 --- a/parsers/src/lib.rs +++ b/parsers/src/lib.rs @@ -55,6 +55,8 @@ pub mod sasl; pub mod stanza_error; /// RFC 6120: Extensible Messaging and Presence Protocol (XMPP): Core pub mod stream; +/// RFC 6120: Extensible Messaging and Presence Protocol (XMPP): Core +pub mod stream_features; /// RFC 6121: Extensible Messaging and Presence Protocol (XMPP): Instant Messaging and Presence pub mod roster; diff --git a/parsers/src/stream_features.rs b/parsers/src/stream_features.rs new file mode 100644 index 00000000..fad98bd7 --- /dev/null +++ b/parsers/src/stream_features.rs @@ -0,0 +1,167 @@ +// Copyright (c) 2024 xmpp-rs contributors +// +// 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::{AsXml, FromXml}; + +use crate::ns; + +/// Wraps ``, usually the very first nonza of a +/// XMPP stream. Indicates which features are supported. +#[derive(FromXml, AsXml, PartialEq, Debug, Default, Clone)] +#[xml(namespace = ns::STREAM, name = "features")] +pub struct StreamFeatures { + /// StartTLS is supported, and may be mandatory. + #[xml(child(default))] + pub starttls: Option, + + /// Bind is supported. + #[xml(child(default))] + pub bind: Option, + + /// List of supported SASL mechanisms + #[xml(child(default))] + pub sasl_mechanisms: SaslMechanisms, +} + +/// StartTLS is supported, and may be mandatory. +#[derive(FromXml, AsXml, PartialEq, Debug, Clone)] +#[xml(namespace = ns::TLS, name = "starttls")] +pub struct StartTls { + /// Marker for mandatory StartTLS. + #[xml(child(default))] + pub required: Option, +} + +/// Marker for mandatory StartTLS. +#[derive(FromXml, AsXml, PartialEq, Debug, Clone)] +#[xml(namespace = ns::TLS, name = "required")] +pub struct RequiredStartTls; + +/// Bind is supported. +#[derive(FromXml, AsXml, PartialEq, Debug, Clone)] +#[xml(namespace = ns::BIND, name = "bind")] +pub struct Bind; + +generate_element!( + /// List of supported SASL mechanisms + #[derive(Default)] + SaslMechanisms, "mechanisms", SASL, + children: [ + /// List of information elements describing this avatar. + mechanisms: Vec = ("mechanism", SASL) => SaslMechanism, + ] +); + +// TODO: Uncomment me when xso supports collections, see +// https://gitlab.com/xmpp-rs/xmpp-rs/-/issues/136 +// #[derive(FromXml, AsXml, PartialEq, Debug, Clone)] +// #[xml(namespace = ns::SASL, name = "mechanisms")] +// pub struct SaslMechanisms { +// #[xml(child(default))] +// mechanisms: Vec, +// } + +/// The name of a SASL mechanism. +#[derive(FromXml, AsXml, PartialEq, Debug, Clone)] +#[xml(namespace = ns::SASL, name = "mechanism")] +pub struct SaslMechanism { + /// The stringy name of the mechanism. + #[xml(text)] + pub mechanism: String, +} + +impl StreamFeatures { + /// Can initiate TLS session with this server? + pub fn can_starttls(&self) -> bool { + self.starttls.is_some() + } + + /// Does server support user resource binding? + pub fn can_bind(&self) -> bool { + self.bind.is_some() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use minidom::Element; + + #[cfg(target_pointer_width = "32")] + #[test] + fn test_size() { + assert_size!(SaslMechanism, 12); + assert_size!(SaslMechanisms, 12); + assert_size!(Bind, 0); + assert_size!(RequiredStartTls, 0); + assert_size!(StartTls, 1); + assert_size!(StreamFeatures, 16); + } + + #[cfg(target_pointer_width = "64")] + #[test] + fn test_size() { + assert_size!(SaslMechanism, 24); + assert_size!(SaslMechanisms, 24); + assert_size!(Bind, 0); + assert_size!(RequiredStartTls, 0); + assert_size!(StartTls, 1); + assert_size!(StreamFeatures, 32); + } + + #[test] + fn test_required_starttls() { + let elem: Element = " + + + + " + .parse() + .unwrap(); + + let features = StreamFeatures::try_from(elem).unwrap(); + + assert_eq!(features.can_bind(), false); + assert_eq!(features.sasl_mechanisms.mechanisms.len(), 0); + assert_eq!(features.can_starttls(), true); + assert_eq!(features.starttls.unwrap().required.is_some(), true); + } + + #[test] + // TODO: Unignore me when xso supports collections of unknown children, see + // https://gitlab.com/xmpp-rs/xmpp-rs/-/issues/136 + #[ignore] + fn test_deprecated_compression() { + let elem: Element = " + + + zlib + lzw + + " + .parse() + .unwrap(); + + let features = StreamFeatures::try_from(elem).unwrap(); + + assert_eq!(features.can_bind(), true); + assert_eq!(features.sasl_mechanisms.mechanisms.len(), 0); + assert_eq!(features.can_starttls(), false); + } + + #[test] + fn test_empty_features() { + let elem: Element = "" + .parse() + .unwrap(); + + let features = StreamFeatures::try_from(elem).unwrap(); + + assert_eq!(features.can_bind(), false); + assert_eq!(features.sasl_mechanisms.mechanisms.len(), 0); + assert_eq!(features.can_starttls(), false); + } +}