xmpp-rs/parsers/src/sm.rs

329 lines
9.3 KiB
Rust
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Copyright (c) 2018 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
//
// 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;
use crate::stanza_error::DefinedCondition;
/// Acknowledgement of the currently received stanzas.
#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
#[xml(namespace = ns::SM, name = "a")]
pub struct A {
/// The last handled stanza.
#[xml(attribute)]
pub h: u32,
}
impl A {
/// Generates a new `<a/>` element.
pub fn new(h: u32) -> A {
A { h }
}
}
/// Client request for enabling stream management.
#[derive(FromXml, AsXml, PartialEq, Debug, Clone, Default)]
#[xml(namespace = ns::SM, name = "enable")]
pub struct Enable {
/// The preferred resumption time in seconds by the client.
// TODO: should be the infinite integer set ≥ 1.
#[xml(attribute(default))]
pub max: Option<u32>,
/// Whether the client wants to be allowed to resume the stream.
#[xml(attribute(default))]
pub resume: bool,
}
impl Enable {
/// Generates a new `<enable/>` element.
pub fn new() -> Self {
Enable::default()
}
/// Sets the preferred resumption time in seconds.
pub fn with_max(mut self, max: u32) -> Self {
self.max = Some(max);
self
}
/// Asks for resumption to be possible.
pub fn with_resume(mut self) -> Self {
self.resume = true;
self
}
}
generate_id!(
/// A random identifier used for stream resumption.
StreamId
);
/// Server response once stream management is enabled.
#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
#[xml(namespace = ns::SM, name = "enabled")]
pub struct Enabled {
/// A random identifier used for stream resumption.
#[xml(attribute(default))]
pub id: Option<StreamId>,
/// The preferred IP, domain, IP:port or domain:port location for
/// resumption.
#[xml(attribute(default))]
pub location: Option<String>,
/// The preferred resumption time in seconds by the server.
// TODO: should be the infinite integer set ≥ 1.
#[xml(attribute(default))]
pub max: Option<u32>,
/// Whether stream resumption is allowed.
#[xml(attribute(default))]
pub resume: bool,
}
/// A stream management error happened.
#[derive(FromXml, AsXml, Debug, PartialEq, Clone)]
#[xml(namespace = ns::SM, name = "failed")]
pub struct Failed {
/// The last handled stanza.
#[xml(attribute)]
pub h: Option<u32>,
/// The error returned.
// XXX: implement the * handling.
#[xml(child(default))]
pub error: Option<DefinedCondition>,
}
/// Requests the currently received stanzas by the other party.
#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
#[xml(namespace = ns::SM, name = "r")]
pub struct R;
/// Requests a stream resumption.
#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
#[xml(namespace = ns::SM, name = "resume")]
pub struct Resume {
/// The last handled stanza.
#[xml(attribute)]
pub h: u32,
/// The previous id given by the server on
/// [enabled](struct.Enabled.html).
#[xml(attribute)]
pub previd: StreamId,
}
/// The response by the server for a successfully resumed stream.
#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
#[xml(namespace = ns::SM, name = "resumed")]
pub struct Resumed {
/// The last handled stanza.
#[xml(attribute)]
pub h: u32,
/// The previous id given by the server on
/// [enabled](struct.Enabled.html).
#[xml(attribute)]
pub previd: StreamId,
}
/// Marker whose presence indicates that negotiating stream management is
/// optional.
#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
#[xml(namespace = ns::SM, name = "optional")]
pub struct Optional;
// TODO: add support for optional and required.
/// Represents availability of Stream Management in `<stream:features/>`.
#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
#[xml(namespace = ns::SM, name = "sm")]
pub struct StreamManagement {
// TODO: replace with #[xml(flag)] once we have it.
/// `<optional/>` flag.
#[xml(child(default))]
pub optional: Option<Optional>,
}
/// Application-specific error condition to use when the peer acknowledges
/// more stanzas than the local side has sent.
#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
#[xml(namespace = ns::SM, name = "handled-count-too-high")]
pub struct HandledCountTooHigh {
/// The `h` value received by the peer.
#[xml(attribute)]
pub h: u32,
/// The number of stanzas which were in fact sent.
#[xml(attribute = "send-count")]
pub send_count: u32,
}
impl From<HandledCountTooHigh> for crate::stream_error::StreamError {
fn from(other: HandledCountTooHigh) -> Self {
Self {
condition: crate::stream_error::DefinedCondition::UndefinedCondition,
text: Some((
None,
format!(
"You acknowledged {} stanza(s), while I only sent {} so far.",
other.h, other.send_count
),
)),
application_specific: vec![other.into()],
}
}
}
/// Enum which allows parsing/serialising any XEP-0198 element.
#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
#[xml()]
pub enum Nonza {
/// Request to enable SM
#[xml(transparent)]
Enable(Enable),
/// Successful SM enablement response
#[xml(transparent)]
Enabled(Enabled),
/// Request to resume SM
#[xml(transparent)]
Resume(Resume),
/// Sucessful SM resumption response
#[xml(transparent)]
Resumed(Resumed),
/// Error response
#[xml(transparent)]
Failed(Failed),
/// Acknowledgement
#[xml(transparent)]
Ack(A),
/// Request for an acknowledgement
#[xml(transparent)]
Req(R),
}
#[cfg(test)]
mod tests {
use super::*;
use minidom::Element;
#[cfg(target_pointer_width = "32")]
#[test]
fn test_size() {
assert_size!(A, 4);
assert_size!(Enable, 12);
assert_size!(StreamId, 12);
assert_size!(Enabled, 36);
assert_size!(Failed, 24);
assert_size!(R, 0);
assert_size!(Resume, 16);
assert_size!(Resumed, 16);
assert_size!(StreamManagement, 1);
assert_size!(Optional, 0);
assert_size!(HandledCountTooHigh, 8);
}
#[cfg(target_pointer_width = "64")]
#[test]
fn test_size() {
assert_size!(A, 4);
assert_size!(Enable, 12);
assert_size!(StreamId, 24);
assert_size!(Enabled, 64);
assert_size!(Failed, 40);
assert_size!(R, 0);
assert_size!(Resume, 32);
assert_size!(Resumed, 32);
assert_size!(StreamManagement, 1);
assert_size!(Optional, 0);
assert_size!(HandledCountTooHigh, 8);
}
#[test]
fn a() {
let elem: Element = "<a xmlns='urn:xmpp:sm:3' h='5'/>".parse().unwrap();
let a = A::try_from(elem).unwrap();
assert_eq!(a.h, 5);
}
#[test]
fn stream_feature() {
let elem: Element = "<sm xmlns='urn:xmpp:sm:3'/>".parse().unwrap();
StreamManagement::try_from(elem).unwrap();
}
#[test]
fn handle_count_too_high() {
let elem: Element = "<handled-count-too-high xmlns='urn:xmpp:sm:3' h='10' send-count='8'/>"
.parse()
.unwrap();
let elem = HandledCountTooHigh::try_from(elem).unwrap();
assert_eq!(elem.h, 10);
assert_eq!(elem.send_count, 8);
}
#[test]
fn resume() {
let elem: Element = "<enable xmlns='urn:xmpp:sm:3' resume='true'/>"
.parse()
.unwrap();
let enable = Enable::try_from(elem).unwrap();
assert_eq!(enable.max, None);
assert_eq!(enable.resume, true);
let elem: Element = "<enabled xmlns='urn:xmpp:sm:3' resume='true' id='coucou' max='600'/>"
.parse()
.unwrap();
let enabled = Enabled::try_from(elem).unwrap();
let previd = enabled.id.unwrap();
assert_eq!(enabled.resume, true);
assert_eq!(previd, StreamId(String::from("coucou")));
assert_eq!(enabled.max, Some(600));
assert_eq!(enabled.location, None);
let elem: Element = "<resume xmlns='urn:xmpp:sm:3' h='5' previd='coucou'/>"
.parse()
.unwrap();
let resume = Resume::try_from(elem).unwrap();
assert_eq!(resume.h, 5);
assert_eq!(resume.previd, previd);
let elem: Element = "<resumed xmlns='urn:xmpp:sm:3' h='5' previd='coucou'/>"
.parse()
.unwrap();
let resumed = Resumed::try_from(elem).unwrap();
assert_eq!(resumed.h, 5);
assert_eq!(resumed.previd, previd);
}
#[test]
fn test_serialize_failed() {
let reference: Element = "<failed xmlns='urn:xmpp:sm:3'><unexpected-request xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/></failed>"
.parse()
.unwrap();
let elem: Element = "<unexpected-request xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>"
.parse()
.unwrap();
let error = DefinedCondition::try_from(elem).unwrap();
let failed = Failed {
h: None,
error: Some(error),
};
let serialized: Element = failed.into();
assert_eq!(serialized, reference);
}
}