1// Copyright (c) 2018 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::ns;
10use crate::stanza_error::DefinedCondition;
11
12/// Acknowledgement of the currently received stanzas.
13#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
14#[xml(namespace = ns::SM, name = "a")]
15pub struct A {
16 /// The last handled stanza.
17 #[xml(attribute)]
18 pub h: u32,
19}
20
21impl A {
22 /// Generates a new `<a/>` element.
23 pub fn new(h: u32) -> A {
24 A { h }
25 }
26}
27
28/// Client request for enabling stream management.
29#[derive(FromXml, AsXml, PartialEq, Debug, Clone, Default)]
30#[xml(namespace = ns::SM, name = "enable")]
31pub struct Enable {
32 /// The preferred resumption time in seconds by the client.
33 // TODO: should be the infinite integer set ≥ 1.
34 #[xml(attribute(default))]
35 pub max: Option<u32>,
36
37 /// Whether the client wants to be allowed to resume the stream.
38 #[xml(attribute(default))]
39 pub resume: bool,
40}
41
42impl Enable {
43 /// Generates a new `<enable/>` element.
44 pub fn new() -> Self {
45 Enable::default()
46 }
47
48 /// Sets the preferred resumption time in seconds.
49 pub fn with_max(mut self, max: u32) -> Self {
50 self.max = Some(max);
51 self
52 }
53
54 /// Asks for resumption to be possible.
55 pub fn with_resume(mut self) -> Self {
56 self.resume = true;
57 self
58 }
59}
60
61generate_id!(
62 /// A random identifier used for stream resumption.
63 StreamId
64);
65
66/// Server response once stream management is enabled.
67#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
68#[xml(namespace = ns::SM, name = "enabled")]
69pub struct Enabled {
70 /// A random identifier used for stream resumption.
71 #[xml(attribute(default))]
72 pub id: Option<StreamId>,
73
74 /// The preferred IP, domain, IP:port or domain:port location for
75 /// resumption.
76 #[xml(attribute(default))]
77 pub location: Option<String>,
78
79 /// The preferred resumption time in seconds by the server.
80 // TODO: should be the infinite integer set ≥ 1.
81 #[xml(attribute(default))]
82 pub max: Option<u32>,
83
84 /// Whether stream resumption is allowed.
85 #[xml(attribute(default))]
86 pub resume: bool,
87}
88
89/// A stream management error happened.
90#[derive(FromXml, AsXml, Debug, PartialEq, Clone)]
91#[xml(namespace = ns::SM, name = "failed")]
92pub struct Failed {
93 /// The last handled stanza.
94 #[xml(attribute)]
95 pub h: Option<u32>,
96
97 /// The error returned.
98 // XXX: implement the * handling.
99 #[xml(child(default))]
100 pub error: Option<DefinedCondition>,
101}
102
103/// Requests the currently received stanzas by the other party.
104#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
105#[xml(namespace = ns::SM, name = "r")]
106pub struct R;
107
108/// Requests a stream resumption.
109#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
110#[xml(namespace = ns::SM, name = "resume")]
111pub struct Resume {
112 /// The last handled stanza.
113 #[xml(attribute)]
114 pub h: u32,
115
116 /// The previous id given by the server on
117 /// [enabled](struct.Enabled.html).
118 #[xml(attribute)]
119 pub previd: StreamId,
120}
121
122/// The response by the server for a successfully resumed stream.
123#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
124#[xml(namespace = ns::SM, name = "resumed")]
125pub struct Resumed {
126 /// The last handled stanza.
127 #[xml(attribute)]
128 pub h: u32,
129
130 /// The previous id given by the server on
131 /// [enabled](struct.Enabled.html).
132 #[xml(attribute)]
133 pub previd: StreamId,
134}
135
136/// Represents availability of Stream Management in `<stream:features/>`.
137#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
138#[xml(namespace = ns::SM, name = "sm")]
139pub struct StreamManagement {
140 /// Undocumented `<optional/>` flag.
141 // TODO: Remove this flag once servers in the wild have been updated to not send it, as it is
142 // completely undocumented in XEP-0198, only appearing in the XML Schema before 1.6.3.
143 #[xml(flag)]
144 pub optional: bool,
145}
146
147/// Application-specific error condition to use when the peer acknowledges
148/// more stanzas than the local side has sent.
149#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
150#[xml(namespace = ns::SM, name = "handled-count-too-high")]
151pub struct HandledCountTooHigh {
152 /// The `h` value received by the peer.
153 #[xml(attribute)]
154 pub h: u32,
155
156 /// The number of stanzas which were in fact sent.
157 #[xml(attribute = "send-count")]
158 pub send_count: u32,
159}
160
161impl From<HandledCountTooHigh> for crate::stream_error::StreamError {
162 fn from(other: HandledCountTooHigh) -> Self {
163 Self::new(
164 crate::stream_error::DefinedCondition::UndefinedCondition,
165 "en",
166 format!(
167 "You acknowledged {} stanza(s), while I only sent {} so far.",
168 other.h, other.send_count
169 ),
170 )
171 .with_application_specific(vec![other.into()])
172 }
173}
174
175/// Enum which allows parsing/serialising any XEP-0198 element.
176#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
177#[xml()]
178pub enum Nonza {
179 /// Request to enable SM
180 #[xml(transparent)]
181 Enable(Enable),
182
183 /// Successful SM enablement response
184 #[xml(transparent)]
185 Enabled(Enabled),
186
187 /// Request to resume SM
188 #[xml(transparent)]
189 Resume(Resume),
190
191 /// Successful SM resumption response
192 #[xml(transparent)]
193 Resumed(Resumed),
194
195 /// Error response
196 #[xml(transparent)]
197 Failed(Failed),
198
199 /// Acknowledgement
200 #[xml(transparent)]
201 Ack(A),
202
203 /// Request for an acknowledgement
204 #[xml(transparent)]
205 Req(R),
206}
207
208#[cfg(test)]
209mod tests {
210 use super::*;
211 use minidom::Element;
212
213 #[cfg(target_pointer_width = "32")]
214 #[test]
215 fn test_size() {
216 assert_size!(A, 4);
217 assert_size!(Enable, 12);
218 assert_size!(StreamId, 12);
219 assert_size!(Enabled, 36);
220 assert_size!(Failed, 24);
221 assert_size!(R, 0);
222 assert_size!(Resume, 16);
223 assert_size!(Resumed, 16);
224 assert_size!(StreamManagement, 1);
225 assert_size!(HandledCountTooHigh, 8);
226 }
227
228 #[cfg(target_pointer_width = "64")]
229 #[test]
230 fn test_size() {
231 assert_size!(A, 4);
232 assert_size!(Enable, 12);
233 assert_size!(StreamId, 24);
234 assert_size!(Enabled, 64);
235 assert_size!(Failed, 40);
236 assert_size!(R, 0);
237 assert_size!(Resume, 32);
238 assert_size!(Resumed, 32);
239 assert_size!(StreamManagement, 1);
240 assert_size!(HandledCountTooHigh, 8);
241 }
242
243 #[test]
244 fn a() {
245 let elem: Element = "<a xmlns='urn:xmpp:sm:3' h='5'/>".parse().unwrap();
246 let a = A::try_from(elem).unwrap();
247 assert_eq!(a.h, 5);
248 }
249
250 #[test]
251 fn stream_feature() {
252 let elem: Element = "<sm xmlns='urn:xmpp:sm:3'/>".parse().unwrap();
253 StreamManagement::try_from(elem).unwrap();
254 }
255
256 #[test]
257 fn handle_count_too_high() {
258 let elem: Element = "<handled-count-too-high xmlns='urn:xmpp:sm:3' h='10' send-count='8'/>"
259 .parse()
260 .unwrap();
261 let elem = HandledCountTooHigh::try_from(elem).unwrap();
262 assert_eq!(elem.h, 10);
263 assert_eq!(elem.send_count, 8);
264 }
265
266 #[test]
267 fn resume() {
268 let elem: Element = "<enable xmlns='urn:xmpp:sm:3' resume='true'/>"
269 .parse()
270 .unwrap();
271 let enable = Enable::try_from(elem).unwrap();
272 assert_eq!(enable.max, None);
273 assert_eq!(enable.resume, true);
274
275 let elem: Element = "<enabled xmlns='urn:xmpp:sm:3' resume='true' id='coucou' max='600'/>"
276 .parse()
277 .unwrap();
278 let enabled = Enabled::try_from(elem).unwrap();
279 let previd = enabled.id.unwrap();
280 assert_eq!(enabled.resume, true);
281 assert_eq!(previd, StreamId(String::from("coucou")));
282 assert_eq!(enabled.max, Some(600));
283 assert_eq!(enabled.location, None);
284
285 let elem: Element = "<resume xmlns='urn:xmpp:sm:3' h='5' previd='coucou'/>"
286 .parse()
287 .unwrap();
288 let resume = Resume::try_from(elem).unwrap();
289 assert_eq!(resume.h, 5);
290 assert_eq!(resume.previd, previd);
291
292 let elem: Element = "<resumed xmlns='urn:xmpp:sm:3' h='5' previd='coucou'/>"
293 .parse()
294 .unwrap();
295 let resumed = Resumed::try_from(elem).unwrap();
296 assert_eq!(resumed.h, 5);
297 assert_eq!(resumed.previd, previd);
298 }
299
300 #[test]
301 fn test_serialize_failed() {
302 let reference: Element = "<failed xmlns='urn:xmpp:sm:3'><unexpected-request xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/></failed>"
303 .parse()
304 .unwrap();
305
306 let elem: Element = "<unexpected-request xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>"
307 .parse()
308 .unwrap();
309
310 let error = DefinedCondition::try_from(elem).unwrap();
311
312 let failed = Failed {
313 h: None,
314 error: Some(error),
315 };
316 let serialized: Element = failed.into();
317 assert_eq!(serialized, reference);
318 }
319}