xmpp_parsers/
stream_features.rs1use alloc::collections::BTreeSet;
8use minidom::Element;
9use xso::{AsXml, FromXml};
10
11use crate::bind::BindFeature;
12use crate::ns;
13use crate::sasl2::Authentication;
14use crate::sasl_cb::SaslChannelBinding;
15use crate::starttls::StartTls;
16use crate::stream_limits::Limits;
17
18#[derive(FromXml, AsXml, PartialEq, Debug, Default, Clone)]
21#[xml(namespace = ns::STREAM, name = "features")]
22pub struct StreamFeatures {
23 #[xml(child(default))]
25 pub starttls: Option<StartTls>,
26
27 #[xml(child(default))]
29 pub bind: Option<BindFeature>,
30
31 #[xml(extract(default, namespace = ns::SASL, name = "mechanisms", fields(
33 extract(n = .., name = "mechanism", fields(text(type_ = String)))
34 )))]
35 pub sasl_mechanisms: BTreeSet<String>,
36
37 #[xml(child(default))]
39 pub limits: Option<Limits>,
40
41 #[xml(child(default))]
43 pub sasl2: Option<Authentication>,
44
45 #[xml(child(default))]
47 pub sasl_cb: Option<SaslChannelBinding>,
48
49 #[xml(child(default))]
51 pub stream_management: Option<crate::sm::StreamManagement>,
52
53 #[xml(flag(namespace = ns::REGISTER_FEATURE, name = "register"))]
55 pub in_band_registration: bool,
56
57 #[xml(element(n = ..))]
62 pub others: Vec<Element>,
63}
64
65impl StreamFeatures {
66 pub fn can_starttls(&self) -> bool {
68 self.starttls.is_some()
69 }
70
71 pub fn can_bind(&self) -> bool {
73 self.bind.is_some()
74 }
75}
76
77#[cfg(test)]
78mod tests {
79 use super::*;
80 use minidom::Element;
81
82 #[cfg(target_pointer_width = "32")]
83 #[test]
84 fn test_size() {
85 assert_size!(StreamFeatures, 92);
86 }
87
88 #[cfg(target_pointer_width = "64")]
89 #[test]
90 fn test_size() {
91 assert_size!(StreamFeatures, 168);
92 }
93
94 #[test]
95 fn test_sasl_mechanisms() {
96 let elem: Element = "<stream:features xmlns:stream='http://etherx.jabber.org/streams'>
97 <mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>
98 <mechanism>PLAIN</mechanism>
99 <mechanism>SCRAM-SHA-1</mechanism>
100 <mechanism>SCRAM-SHA-1-PLUS</mechanism>
101 </mechanisms>
102 </stream:features>"
103 .parse()
104 .unwrap();
105
106 let features = StreamFeatures::try_from(elem).unwrap();
107 assert!(features.sasl_mechanisms.contains("PLAIN"));
108 assert!(features.sasl_mechanisms.contains("SCRAM-SHA-1"));
109 assert!(features.sasl_mechanisms.contains("SCRAM-SHA-1-PLUS"));
110 assert!(!features.sasl_mechanisms.contains("CRAM-MD5"));
111 }
112
113 #[test]
114 fn test_required_starttls() {
115 let elem: Element = "<stream:features xmlns:stream='http://etherx.jabber.org/streams'>
116 <starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'>
117 <required/>
118 </starttls>
119 </stream:features>"
120 .parse()
121 .unwrap();
122
123 let features = StreamFeatures::try_from(elem).unwrap();
124
125 assert_eq!(features.can_bind(), false);
126 assert_eq!(features.sasl_mechanisms.len(), 0);
127 assert_eq!(features.can_starttls(), true);
128 assert_eq!(features.starttls.unwrap().required, true);
129 }
130
131 #[test]
132 fn test_deprecated_compression() {
133 let elem: Element = "<stream:features xmlns:stream='http://etherx.jabber.org/streams'>
134 <bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'/>
135 <compression xmlns='http://jabber.org/features/compress'>
136 <method>zlib</method>
137 <method>lzw</method>
138 </compression>
139 </stream:features>"
140 .parse()
141 .unwrap();
142
143 let features = StreamFeatures::try_from(elem).unwrap();
144
145 assert_eq!(features.can_bind(), true);
146 assert_eq!(features.sasl_mechanisms.len(), 0);
147 assert_eq!(features.can_starttls(), false);
148 assert_eq!(features.others.len(), 1);
149
150 let compression = &features.others[0];
151 assert!(compression.is("compression", "http://jabber.org/features/compress"));
152 let mut children = compression.children();
153
154 let child = children.next().expect("zlib not found");
155 assert_eq!(child.name(), "method");
156 let mut texts = child.texts();
157 assert_eq!(texts.next().unwrap(), "zlib");
158 assert_eq!(texts.next(), None);
159
160 let child = children.next().expect("lzw not found");
161 assert_eq!(child.name(), "method");
162 let mut texts = child.texts();
163 assert_eq!(texts.next().unwrap(), "lzw");
164 assert_eq!(texts.next(), None);
165 }
166
167 #[test]
168 fn test_empty_features() {
169 let elem: Element = "<stream:features xmlns:stream='http://etherx.jabber.org/streams'/>"
170 .parse()
171 .unwrap();
172
173 let features = StreamFeatures::try_from(elem).unwrap();
174
175 assert_eq!(features.can_bind(), false);
176 assert_eq!(features.sasl_mechanisms.len(), 0);
177 assert_eq!(features.can_starttls(), false);
178 }
179}