xmpp_parsers/
sm.rs

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    /// Sucessful 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}