xmpp-rs/parsers/src/sm.rs
Link Mauve 9d91b02dfd Revert "xmpp-parsers: Remove optional child from sm element"
This reverts commit 0a1b291574 once again.

This is currently required to connect to Prosody, otherwise the parser
will choke on the extra undocumented child.

I still wonder if required (4e0cd5d96b)
would be useful to support then, as it might also be found in the wild.
2025-09-08 11:37:24 +02:00

319 lines
9 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,
}
/// Represents availability of Stream Management in `<stream:features/>`.
#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
#[xml(namespace = ns::SM, name = "sm")]
pub struct StreamManagement {
/// Undocumented `<optional/>` flag.
// TODO: Remove this flag once servers in the wild have been updated to not send it, as it is
// completely undocumented in XEP-0198, only appearing in the XMLSchema before 1.6.3.
#[xml(flag)]
pub optional: bool,
}
/// 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::new(
crate::stream_error::DefinedCondition::UndefinedCondition,
"en",
format!(
"You acknowledged {} stanza(s), while I only sent {} so far.",
other.h, other.send_count
),
)
.with_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!(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!(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);
}
}