use minidom::Element;
use xso::{AsXml, FromXml};
use crate::bind::BindFeature;
use crate::ns;
use crate::sasl2::Authentication;
use crate::sasl_cb::SaslChannelBinding;
use crate::starttls::StartTls;
use crate::stream_limits::Limits;
#[derive(FromXml, AsXml, PartialEq, Debug, Default, Clone)]
#[xml(namespace = ns::STREAM, name = "features")]
pub struct StreamFeatures {
#[xml(child(default))]
pub starttls: Option<StartTls>,
#[xml(child(default))]
pub bind: Option<BindFeature>,
#[xml(child(default))]
pub sasl_mechanisms: SaslMechanisms,
#[xml(child(default))]
pub limits: Option<Limits>,
#[xml(child(default))]
pub sasl2: Option<Authentication>,
#[xml(child(default))]
pub sasl_cb: Option<SaslChannelBinding>,
#[xml(element(n = ..))]
pub others: Vec<Element>,
}
#[derive(FromXml, AsXml, PartialEq, Debug, Clone, Default)]
#[xml(namespace = ns::SASL, name = "mechanisms")]
pub struct SaslMechanisms {
#[xml(extract(n = .., name = "mechanism", fields(text(type_ = String))))]
pub mechanisms: Vec<String>,
}
impl StreamFeatures {
pub fn can_starttls(&self) -> bool {
self.starttls.is_some()
}
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!(SaslMechanisms, 12);
assert_size!(StreamFeatures, 92);
}
#[cfg(target_pointer_width = "64")]
#[test]
fn test_size() {
assert_size!(SaslMechanisms, 24);
assert_size!(StreamFeatures, 168);
}
#[test]
fn test_sasl_mechanisms() {
let elem: Element = "<stream:features xmlns:stream='http://etherx.jabber.org/streams'>
<mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>
<mechanism>PLAIN</mechanism>
<mechanism>SCRAM-SHA-1</mechanism>
<mechanism>SCRAM-SHA-1-PLUS</mechanism>
</mechanisms>
</stream:features>"
.parse()
.unwrap();
let features = StreamFeatures::try_from(elem).unwrap();
assert_eq!(
features.sasl_mechanisms.mechanisms,
["PLAIN", "SCRAM-SHA-1", "SCRAM-SHA-1-PLUS"]
);
}
#[test]
fn test_required_starttls() {
let elem: Element = "<stream:features xmlns:stream='http://etherx.jabber.org/streams'>
<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'>
<required/>
</starttls>
</stream:features>"
.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]
fn test_deprecated_compression() {
let elem: Element = "<stream:features xmlns:stream='http://etherx.jabber.org/streams'>
<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'/>
<compression xmlns='http://jabber.org/features/compress'>
<method>zlib</method>
<method>lzw</method>
</compression>
</stream:features>"
.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);
assert_eq!(features.others.len(), 1);
let compression = &features.others[0];
assert!(compression.is("compression", "http://jabber.org/features/compress"));
let mut children = compression.children();
let child = children.next().expect("zlib not found");
assert_eq!(child.name(), "method");
let mut texts = child.texts();
assert_eq!(texts.next().unwrap(), "zlib");
assert_eq!(texts.next(), None);
let child = children.next().expect("lzw not found");
assert_eq!(child.name(), "method");
let mut texts = child.texts();
assert_eq!(texts.next().unwrap(), "lzw");
assert_eq!(texts.next(), None);
}
#[test]
fn test_empty_features() {
let elem: Element = "<stream:features xmlns:stream='http://etherx.jabber.org/streams'/>"
.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);
}
}