diff --git a/parsers/ChangeLog b/parsers/ChangeLog index bbc8ab154c89eb6196fed0bb6b787b9791c1cb70..216f8b617dcee1d2d83215dc57bc472d0093a86b 100644 --- a/parsers/ChangeLog +++ b/parsers/ChangeLog @@ -85,6 +85,17 @@ XXXX-YY-ZZ RELEASER optional `parent` attribute. - presence::Presence has been ported to use xso (!477). It also uses the message::Lang newtype since !561. + - iq::Iq has been ported to use xso (!572). That entails a couple + breaking changes: + + - The IqType type has been removed and loosely replaced by the + IqPayload type. + - The Iq type is now an enum. Access to `from`, `to` and `id` needs + to happen via the provided accessor functions, by `match`-ing the + enum variant or by splitting the Iq into its IqHeader and IqPayload + using the `split()` method. + - The inverse is possible through the `assemble()` methods on + IqPayload, IqHeader and Iq, whichever is more convenient. * New parsers/serialisers: - Stream Features (RFC 6120) (!400) - Spam Reporting (XEP-0377) (!506) diff --git a/parsers/src/iq.rs b/parsers/src/iq.rs index bfd63e19444213e68de42147ef46262096ca60bc..77bfd8c17e5b5c684b04717f1fd918167b48d822 100644 --- a/parsers/src/iq.rs +++ b/parsers/src/iq.rs @@ -9,8 +9,7 @@ use crate::ns; use crate::stanza_error::StanzaError; use jid::Jid; use minidom::Element; -use minidom::IntoAttributeValue; -use xso::error::{Error, FromElementError}; +use xso::{AsXml, FromXml}; /// Should be implemented on every known payload of an ``. pub trait IqGetPayload: TryFrom + Into {} @@ -21,228 +20,354 @@ pub trait IqSetPayload: TryFrom + Into {} /// Should be implemented on every known payload of an ``. pub trait IqResultPayload: TryFrom + Into {} -/// Represents one of the four possible iq types. -#[derive(Debug, Clone, PartialEq)] -pub enum IqType { - /// This is a request for accessing some data. +/// Metadata of an IQ stanza. +pub struct IqHeader { + /// The sender JID. + pub from: Option, + + /// The reciepient JID. + pub to: Option, + + /// The stanza's ID. + pub id: String, +} + +impl IqHeader { + /// Combine a header with [`IqPayload`] to create a full [`Iq`] stanza. + pub fn assemble(self, data: IqPayload) -> Iq { + data.assemble(self) + } +} + +/// Payload of an IQ stanza, by type. +pub enum IqPayload { + /// Payload of a type='get' stanza. Get(Element), - /// This is a request for modifying some data. + /// Payload of a type='set' stanza. Set(Element), - /// This is a result containing some data. + /// Payload of a type='result' stanza. Result(Option), - /// A get or set request failed. + /// The error carride in a type='error' stanza. Error(StanzaError), } -impl IntoAttributeValue for &IqType { - fn into_attribute_value(self) -> Option { - Some( - match *self { - IqType::Get(_) => "get", - IqType::Set(_) => "set", - IqType::Result(_) => "result", - IqType::Error(_) => "error", - } - .to_owned(), - ) +impl IqPayload { + /// Combine the data with an [`IqHeader`] to create a full [`Iq`] stanza. + pub fn assemble(self, IqHeader { from, to, id }: IqHeader) -> Iq { + match self { + Self::Get(payload) => Iq::Get { + from, + to, + id, + payload, + }, + Self::Set(payload) => Iq::Set { + from, + to, + id, + payload, + }, + Self::Result(payload) => Iq::Result { + from, + to, + id, + payload, + }, + Self::Error(error) => Iq::Error { + from, + to, + id, + payload: None, + error, + }, + } } } /// The main structure representing the `` stanza. -#[derive(Debug, Clone, PartialEq)] -pub struct Iq { - /// The JID emitting this stanza. - pub from: Option, - - /// The recipient of this stanza. - pub to: Option, - - /// The @id attribute of this stanza, which is required in order to match a - /// request with its result/error. - pub id: String, - - /// The payload content of this stanza. - pub payload: IqType, +#[derive(FromXml, AsXml, Debug, Clone, PartialEq)] +#[xml(namespace = ns::DEFAULT_NS, name = "iq", attribute = "type", exhaustive)] +pub enum Iq { + /// An `` stanza. + #[xml(value = "get")] + Get { + /// The JID emitting this stanza. + #[xml(attribute(default))] + from: Option, + + /// The recipient of this stanza. + #[xml(attribute(default))] + to: Option, + + /// The @id attribute of this stanza, which is required in order to match a + /// request with its result/error. + #[xml(attribute)] + id: String, + + /// The payload content of this stanza. + #[xml(element(n = 1))] + payload: Element, + }, + + /// An `` stanza. + #[xml(value = "set")] + Set { + /// The JID emitting this stanza. + #[xml(attribute(default))] + from: Option, + + /// The recipient of this stanza. + #[xml(attribute(default))] + to: Option, + + /// The @id attribute of this stanza, which is required in order to match a + /// request with its result/error. + #[xml(attribute)] + id: String, + + /// The payload content of this stanza. + #[xml(element(n = 1))] + payload: Element, + }, + + /// An `` stanza. + #[xml(value = "result")] + Result { + /// The JID emitting this stanza. + #[xml(attribute(default))] + from: Option, + + /// The recipient of this stanza. + #[xml(attribute(default))] + to: Option, + + /// The @id attribute of this stanza, which is required in order to match a + /// request with its result/error. + #[xml(attribute)] + id: String, + + /// The payload content of this stanza. + #[xml(element(n = 1, default))] + payload: Option, + }, + + /// An `` stanza. + #[xml(value = "error")] + Error { + /// The JID emitting this stanza. + #[xml(attribute(default))] + from: Option, + + /// The recipient of this stanza. + #[xml(attribute(default))] + to: Option, + + /// The @id attribute of this stanza, which is required in order to match a + /// request with its result/error. + #[xml(attribute)] + id: String, + + /// The error carried by this stanza. + #[xml(child)] + error: StanzaError, + + /// The optional payload content which caused the error. + /// + /// As per + /// [RFC 6120 ยง 8.3.1](https://datatracker.ietf.org/doc/html/rfc6120#section-8.3.1), + /// the emitter of an error stanza MAY include the original XML which + /// caused the error. However, recipients MUST NOT rely on this. + #[xml(element(n = 1, default))] + payload: Option, + }, } impl Iq { + /// Assemble a new Iq stanza from an [`IqHeader`] and the given + /// [`IqPayload`]. + pub fn assemble(header: IqHeader, data: IqPayload) -> Self { + data.assemble(header) + } + /// Creates an `` stanza containing a get request. pub fn from_get>(id: S, payload: impl IqGetPayload) -> Iq { - Iq { + Iq::Get { from: None, to: None, id: id.into(), - payload: IqType::Get(payload.into()), + payload: payload.into(), } } /// Creates an `` stanza containing a set request. pub fn from_set>(id: S, payload: impl IqSetPayload) -> Iq { - Iq { + Iq::Set { from: None, to: None, id: id.into(), - payload: IqType::Set(payload.into()), + payload: payload.into(), } } /// Creates an empty `` stanza. pub fn empty_result>(to: Jid, id: S) -> Iq { - Iq { + Iq::Result { from: None, to: Some(to), id: id.into(), - payload: IqType::Result(None), + payload: None, } } /// Creates an `` stanza containing a result. pub fn from_result>(id: S, payload: Option) -> Iq { - Iq { + Iq::Result { from: None, to: None, id: id.into(), - payload: IqType::Result(payload.map(Into::into)), + payload: payload.map(Into::into), } } /// Creates an `` stanza containing an error. pub fn from_error>(id: S, payload: StanzaError) -> Iq { - Iq { + Iq::Error { from: None, to: None, id: id.into(), - payload: IqType::Error(payload), + error: payload, + payload: None, } } /// Sets the recipient of this stanza. pub fn with_to(mut self, to: Jid) -> Iq { - self.to = Some(to); + *self.to_mut() = Some(to); self } /// Sets the emitter of this stanza. pub fn with_from(mut self, from: Jid) -> Iq { - self.from = Some(from); + *self.from_mut() = Some(from); self } /// Sets the id of this stanza, in order to later match its response. pub fn with_id(mut self, id: String) -> Iq { - self.id = id; + *self.id_mut() = id; self } -} -impl TryFrom for Iq { - type Error = FromElementError; - - fn try_from(root: Element) -> Result { - check_self!(root, "iq", DEFAULT_NS); - let from = get_attr!(root, "from", Option); - let to = get_attr!(root, "to", Option); - let id = get_attr!(root, "id", Required); - let type_: String = get_attr!(root, "type", Required); - - let mut payload = None; - let mut error_payload = None; - for elem in root.children() { - if payload.is_some() { - return Err(Error::Other("Wrong number of children in iq element.").into()); - } - if type_ == "error" { - if elem.is("error", ns::DEFAULT_NS) { - if error_payload.is_some() { - return Err(Error::Other("Wrong number of children in iq element.").into()); - } - error_payload = Some(StanzaError::try_from(elem.clone())?); - } else if root.children().count() != 2 { - return Err(Error::Other("Wrong number of children in iq element.").into()); - } - } else { - payload = Some(elem.clone()); - } + /// Access the sender address. + pub fn from(&self) -> Option<&Jid> { + match self { + Self::Get { from, .. } + | Self::Set { from, .. } + | Self::Result { from, .. } + | Self::Error { from, .. } => from.as_ref(), } + } - let type_ = if type_ == "get" { - if let Some(payload) = payload { - IqType::Get(payload) - } else { - return Err(Error::Other("Wrong number of children in iq element.").into()); - } - } else if type_ == "set" { - if let Some(payload) = payload { - IqType::Set(payload) - } else { - return Err(Error::Other("Wrong number of children in iq element.").into()); - } - } else if type_ == "result" { - if let Some(payload) = payload { - IqType::Result(Some(payload)) - } else { - IqType::Result(None) - } - } else if type_ == "error" { - if let Some(payload) = error_payload { - IqType::Error(payload) - } else { - return Err(Error::Other("Wrong number of children in iq element.").into()); - } - } else { - return Err(Error::Other("Unknown iq type.").into()); - }; + /// Access the sender address, mutably. + pub fn from_mut(&mut self) -> &mut Option { + match self { + Self::Get { ref mut from, .. } + | Self::Set { ref mut from, .. } + | Self::Result { ref mut from, .. } + | Self::Error { ref mut from, .. } => from, + } + } - Ok(Iq { - from, - to, - id, - payload: type_, - }) + /// Access the recipient address. + pub fn to(&self) -> Option<&Jid> { + match self { + Self::Get { to, .. } + | Self::Set { to, .. } + | Self::Result { to, .. } + | Self::Error { to, .. } => to.as_ref(), + } } -} -impl From for Element { - fn from(iq: Iq) -> Element { - let mut stanza = Element::builder("iq", ns::DEFAULT_NS) - .attr("from", iq.from) - .attr("to", iq.to) - .attr("id", iq.id) - .attr("type", &iq.payload) - .build(); - let elem = match iq.payload { - IqType::Get(elem) | IqType::Set(elem) | IqType::Result(Some(elem)) => elem, - IqType::Error(error) => error.into(), - IqType::Result(None) => return stanza, - }; - stanza.append_child(elem); - stanza + /// Access the recipient address, mutably. + pub fn to_mut(&mut self) -> &mut Option { + match self { + Self::Get { ref mut to, .. } + | Self::Set { ref mut to, .. } + | Self::Result { ref mut to, .. } + | Self::Error { ref mut to, .. } => to, + } } -} -impl ::xso::FromXml for Iq { - type Builder = ::xso::minidom_compat::FromEventsViaElement; + /// Access the id. + pub fn id(&self) -> &str { + match self { + Self::Get { id, .. } + | Self::Set { id, .. } + | Self::Result { id, .. } + | Self::Error { id, .. } => id.as_str(), + } + } - fn from_events( - qname: ::xso::exports::rxml::QName, - attrs: ::xso::exports::rxml::AttrMap, - _ctx: &::xso::Context<'_>, - ) -> Result { - if qname.0 != crate::ns::DEFAULT_NS || qname.1 != "iq" { - return Err(::xso::error::FromEventsError::Mismatch { name: qname, attrs }); + /// Access the id mutably. + pub fn id_mut(&mut self) -> &mut String { + match self { + Self::Get { ref mut id, .. } + | Self::Set { ref mut id, .. } + | Self::Result { ref mut id, .. } + | Self::Error { ref mut id, .. } => id, } - Self::Builder::new(qname, attrs) } -} -impl ::xso::AsXml for Iq { - type ItemIter<'x> = ::xso::minidom_compat::AsItemsViaElement<'x>; + /// Split the IQ stanza in its metadata and data. + /// + /// Note that this discards the optional original error-inducing + /// [`payload`][`Self::Error::payload`] of the + /// [`Iq::Error`][`Self::Error`] variant. + pub fn split(self) -> (IqHeader, IqPayload) { + match self { + Self::Get { + from, + to, + id, + payload, + } => (IqHeader { from, to, id }, IqPayload::Get(payload)), + Self::Set { + from, + to, + id, + payload, + } => (IqHeader { from, to, id }, IqPayload::Set(payload)), + Self::Result { + from, + to, + id, + payload, + } => (IqHeader { from, to, id }, IqPayload::Result(payload)), + Self::Error { + from, + to, + id, + error, + payload: _, + } => (IqHeader { from, to, id }, IqPayload::Error(error)), + } + } - fn as_xml_iter(&self) -> Result, ::xso::error::Error> { - ::xso::minidom_compat::AsItemsViaElement::new(self.clone()) + /// Return the [`IqHeader`] of this stanza, discarding the payload. + pub fn into_header(self) -> IqHeader { + self.split().0 + } + + /// Return the [`IqPayload`] of this stanza, discarding the header. + /// + /// Note that this also discards the optional original error-inducing + /// [`payload`][`Self::Error::payload`] of the + /// [`Iq::Error`][`Self::Error`] variant. + pub fn into_payload(self) -> IqPayload { + self.split().1 } } @@ -251,19 +376,22 @@ mod tests { use super::*; use crate::disco::DiscoInfoQuery; use crate::stanza_error::{DefinedCondition, ErrorType}; + use xso::error::{Error, FromElementError}; #[cfg(target_pointer_width = "32")] #[test] fn test_size() { - assert_size!(IqType, 108); - assert_size!(Iq, 152); + assert_size!(IqHeader, 44); + assert_size!(IqPayload, 108); + assert_size!(Iq, 212); } #[cfg(target_pointer_width = "64")] #[test] fn test_size() { - assert_size!(IqType, 216); - assert_size!(Iq, 304); + assert_size!(IqHeader, 88); + assert_size!(IqPayload, 216); + assert_size!(Iq, 424); } #[test] @@ -277,20 +405,29 @@ mod tests { FromElementError::Invalid(Error::Other(string)) => string, _ => panic!(), }; - assert_eq!(message, "Required attribute 'id' missing."); + assert_eq!(message, "Missing discriminator attribute."); + } - #[cfg(not(feature = "component"))] - let elem: Element = "".parse().unwrap(); - #[cfg(feature = "component")] - let elem: Element = "" - .parse() - .unwrap(); - let error = Iq::try_from(elem).unwrap_err(); - let message = match error { - FromElementError::Invalid(Error::Other(string)) => string, - _ => panic!(), - }; - assert_eq!(message, "Required attribute 'type' missing."); + #[test] + fn test_require_id() { + for type_ in ["get", "set", "result", "error"] { + #[cfg(not(feature = "component"))] + let elem: Element = format!("", type_) + .parse() + .unwrap(); + #[cfg(feature = "component")] + let elem: Element = format!("", type_) + .parse() + .unwrap(); + let error = Iq::try_from(elem).unwrap_err(); + let message = match error { + FromElementError::Invalid(Error::Other(string)) => string, + _ => panic!(), + }; + // Slicing here, because the rest of the error message is specific + // about the enum variant. + assert_eq!(&message[..33], "Required attribute field 'id' on "); + } } #[test] @@ -309,11 +446,11 @@ mod tests { .unwrap(); let iq = Iq::try_from(elem).unwrap(); let query: Element = "".parse().unwrap(); - assert_eq!(iq.from, None); - assert_eq!(iq.to, None); - assert_eq!(&iq.id, "foo"); - assert!(match iq.payload { - IqType::Get(element) => element == query, + assert_eq!(iq.from(), None); + assert_eq!(iq.to(), None); + assert_eq!(iq.id(), "foo"); + assert!(match iq { + Iq::Get { payload, .. } => payload == query, _ => false, }); } @@ -334,11 +471,11 @@ mod tests { .unwrap(); let iq = Iq::try_from(elem).unwrap(); let vcard: Element = "".parse().unwrap(); - assert_eq!(iq.from, None); - assert_eq!(iq.to, None); - assert_eq!(&iq.id, "vcard"); - assert!(match iq.payload { - IqType::Set(element) => element == vcard, + assert_eq!(iq.from(), None); + assert_eq!(iq.to(), None); + assert_eq!(iq.id(), "vcard"); + assert!(match iq { + Iq::Set { payload, .. } => payload == vcard, _ => false, }); } @@ -354,11 +491,11 @@ mod tests { .parse() .unwrap(); let iq = Iq::try_from(elem).unwrap(); - assert_eq!(iq.from, None); - assert_eq!(iq.to, None); - assert_eq!(&iq.id, "res"); - assert!(match iq.payload { - IqType::Result(None) => true, + assert_eq!(iq.from(), None); + assert_eq!(iq.to(), None); + assert_eq!(iq.id(), "res"); + assert!(match iq { + Iq::Result { payload: None, .. } => true, _ => false, }); } @@ -381,11 +518,14 @@ mod tests { let query: Element = "" .parse() .unwrap(); - assert_eq!(iq.from, None); - assert_eq!(iq.to, None); - assert_eq!(&iq.id, "res"); - assert!(match iq.payload { - IqType::Result(Some(element)) => element == query, + assert_eq!(iq.from(), None); + assert_eq!(iq.to(), None); + assert_eq!(iq.id(), "res"); + assert!(match iq { + Iq::Result { + payload: Some(element), + .. + } => element == query, _ => false, }); } @@ -411,11 +551,11 @@ mod tests { .parse() .unwrap(); let iq = Iq::try_from(elem).unwrap(); - assert_eq!(iq.from, None); - assert_eq!(iq.to, None); - assert_eq!(iq.id, "err1"); - match iq.payload { - IqType::Error(error) => { + assert_eq!(iq.from(), None); + assert_eq!(iq.to(), None); + assert_eq!(iq.id(), "err1"); + match iq { + Iq::Error { error, .. } => { assert_eq!(error.type_, ErrorType::Cancel); assert_eq!(error.by, None); assert_eq!( @@ -444,7 +584,7 @@ mod tests { FromElementError::Invalid(Error::Other(string)) => string, _ => panic!(), }; - assert_eq!(message, "Wrong number of children in iq element."); + assert_eq!(message, "Missing child field 'error' in Iq::Error element."); } #[test] @@ -457,11 +597,11 @@ mod tests { let elem: Element = "" .parse() .unwrap(); - let iq2 = Iq { + let iq2 = Iq::Result { from: None, to: None, id: String::from("res"), - payload: IqType::Result(None), + payload: None, }; let elem2 = iq2.into(); assert_eq!(elem, elem2); @@ -474,8 +614,8 @@ mod tests { #[cfg(feature = "component")] let elem: Element = "".parse().unwrap(); let iq = Iq::try_from(elem).unwrap(); - let disco_info = match iq.payload { - IqType::Get(payload) => DiscoInfoQuery::try_from(payload).unwrap(), + let disco_info = match iq { + Iq::Get { payload, .. } => DiscoInfoQuery::try_from(payload).unwrap(), _ => panic!(), }; assert!(disco_info.node.is_none()); diff --git a/tokio-xmpp/examples/download_avatars.rs b/tokio-xmpp/examples/download_avatars.rs index 8113c674eb81a33603c307643f534bb80db027cd..4af37373ac906e79ad40862adbe9565a0e00cf74 100644 --- a/tokio-xmpp/examples/download_avatars.rs +++ b/tokio-xmpp/examples/download_avatars.rs @@ -11,7 +11,7 @@ use xmpp_parsers::{ caps::{compute_disco, hash_caps, Caps}, disco::{DiscoInfoQuery, DiscoInfoResult, Feature, Identity}, hashes::Algo, - iq::{Iq, IqType}, + iq::Iq, jid::{BareJid, Jid}, ns, presence::{Presence, Type as PresenceType}, @@ -54,63 +54,41 @@ async fn main() { client.send_stanza(presence.into()).await.unwrap(); } else if let Some(stanza) = event.into_stanza() { match stanza { - Stanza::Iq(iq) => { - if let IqType::Get(payload) = iq.payload { - if payload.is("query", ns::DISCO_INFO) { - let query = DiscoInfoQuery::try_from(payload); - match query { - Ok(query) => { - let mut disco = disco_info.clone(); - disco.node = query.node; - let iq = Iq::from_result(iq.id, Some(disco)) - .with_to(iq.from.unwrap()); - client.send_stanza(iq.into()).await.unwrap(); - } - Err(err) => { - client - .send_stanza( - make_error( - iq.from.unwrap(), - iq.id, - ErrorType::Modify, - DefinedCondition::BadRequest, - &format!("{}", err), - ) - .into(), - ) - .await - .unwrap(); - } + Stanza::Iq(Iq::Get { + payload, id, from, .. + }) => { + if payload.is("query", ns::DISCO_INFO) { + let query = DiscoInfoQuery::try_from(payload); + match query { + Ok(query) => { + let mut disco = disco_info.clone(); + disco.node = query.node; + let iq = Iq::from_result(id, Some(disco)).with_to(from.unwrap()); + client.send_stanza(iq.into()).await.unwrap(); } - } else { - // We MUST answer unhandled get iqs with a service-unavailable error. - client - .send_stanza( - make_error( - iq.from.unwrap(), - iq.id, - ErrorType::Cancel, - DefinedCondition::ServiceUnavailable, - "No handler defined for this kind of iq.", + Err(err) => { + client + .send_stanza( + make_error( + from.unwrap(), + id, + ErrorType::Modify, + DefinedCondition::BadRequest, + &format!("{}", err), + ) + .into(), ) - .into(), - ) - .await - .unwrap(); - } - } else if let IqType::Result(Some(payload)) = iq.payload { - if payload.is("pubsub", ns::PUBSUB) { - let pubsub = PubSub::try_from(payload).unwrap(); - let from = iq.from.clone().unwrap_or(jid.clone().into()); - handle_iq_result(pubsub, &from); + .await + .unwrap(); + } } - } else if let IqType::Set(_) = iq.payload { - // We MUST answer unhandled set iqs with a service-unavailable error. + } else { + // We MUST answer unhandled get iqs with a service-unavailable error. client .send_stanza( make_error( - iq.from.unwrap(), - iq.id, + from.unwrap(), + id, ErrorType::Cancel, DefinedCondition::ServiceUnavailable, "No handler defined for this kind of iq.", @@ -121,6 +99,34 @@ async fn main() { .unwrap(); } } + Stanza::Iq(Iq::Result { + payload: Some(payload), + from, + .. + }) => { + if payload.is("pubsub", ns::PUBSUB) { + let pubsub = PubSub::try_from(payload).unwrap(); + let from = from.unwrap_or(jid.clone().into()); + handle_iq_result(pubsub, &from); + } + } + Stanza::Iq(Iq::Set { from, id, .. }) => { + // We MUST answer unhandled set iqs with a service-unavailable error. + client + .send_stanza( + make_error( + from.unwrap(), + id, + ErrorType::Cancel, + DefinedCondition::ServiceUnavailable, + "No handler defined for this kind of iq.", + ) + .into(), + ) + .await + .unwrap(); + } + Stanza::Iq(Iq::Error { .. }) | Stanza::Iq(Iq::Result { payload: None, .. }) => (), Stanza::Message(message) => { let from = message.from.clone().unwrap(); if let Some(body) = message.get_best_body(vec!["en"]) { diff --git a/tokio-xmpp/examples/keep_connection.rs b/tokio-xmpp/examples/keep_connection.rs index 4e78b408093c40d86c67a6c551dee37e52fb24e6..7f3b577d502cd92adf4f37f4b376982da7bf7b82 100644 --- a/tokio-xmpp/examples/keep_connection.rs +++ b/tokio-xmpp/examples/keep_connection.rs @@ -88,8 +88,7 @@ async fn main() { _ = ping_timer.tick() => { log::info!("sending ping for fun & profit"); ping_ctr = ping_ctr.wrapping_add(1); - let mut iq = Iq::from_get(format!("ping-{}", ping_ctr), ping::Ping); - iq.to = Some(domain.clone()); + let iq = Iq::from_get(format!("ping-{}", ping_ctr), ping::Ping).with_to(domain.clone()); stream.send(Box::new(iq.into())).await; } ev = stream.next() => match ev { diff --git a/tokio-xmpp/src/client/iq.rs b/tokio-xmpp/src/client/iq.rs index 6703956e41066c13d16c7b359581ea2b677e5fdc..a5c7599d669a2137d1980dcd3b01b424aaf37efd 100644 --- a/tokio-xmpp/src/client/iq.rs +++ b/tokio-xmpp/src/client/iq.rs @@ -18,10 +18,7 @@ use std::sync::Mutex; use futures::Stream; use tokio::sync::oneshot; -use xmpp_parsers::{ - iq::{Iq, IqType}, - stanza_error::StanzaError, -}; +use xmpp_parsers::{iq::Iq, stanza_error::StanzaError}; use crate::{ event::make_id, @@ -40,11 +37,21 @@ pub enum IqRequest { Set(Element), } -impl From for IqType { - fn from(other: IqRequest) -> IqType { - match other { - IqRequest::Get(v) => Self::Get(v), - IqRequest::Set(v) => Self::Set(v), +impl IqRequest { + fn into_iq(self, from: Option, to: Option, id: String) -> Iq { + match self { + Self::Get(payload) => Iq::Get { + from, + to, + id, + payload, + }, + Self::Set(payload) => Iq::Set { + from, + to, + id, + payload, + }, } } } @@ -59,11 +66,22 @@ pub enum IqResponse { Error(StanzaError), } -impl From for IqType { - fn from(other: IqResponse) -> IqType { - match other { - IqResponse::Result(v) => Self::Result(v), - IqResponse::Error(v) => Self::Error(v), +impl IqResponse { + fn into_iq(self, from: Option, to: Option, id: String) -> Iq { + match self { + Self::Error(error) => Iq::Error { + from, + to, + id, + error, + payload: None, + }, + Self::Result(payload) => Iq::Result { + from, + to, + id, + payload, + }, } } } @@ -251,22 +269,28 @@ impl IqResponseTracker { /// Returns the IQ stanza unharmed if it is not an IQ response matching /// any request which is still being tracked. pub fn handle_iq(&self, iq: Iq) -> ControlFlow<(), Iq> { - let payload = match iq.payload { - IqType::Error(error) => IqResponse::Error(error), - IqType::Result(result) => IqResponse::Result(result), + let (from, to, id, payload) = match iq { + Iq::Error { + from, + to, + id, + error, + payload: _, + } => (from, to, id, IqResponse::Error(error)), + Iq::Result { + from, + to, + id, + payload, + } => (from, to, id, IqResponse::Result(payload)), _ => return ControlFlow::Continue(iq), }; - let key = (iq.from, iq.id); + let key = (from, id); let mut map = self.map.lock().unwrap(); match map.remove(&key) { None => { log::trace!("not handling IQ response from {:?} with id {:?}: no active tracker for this tuple", key.0, key.1); - ControlFlow::Continue(Iq { - from: key.0, - id: key.1, - to: iq.to, - payload: payload.into(), - }) + ControlFlow::Continue(payload.into_iq(key.0, to, key.1)) } Some(sink) => { sink.complete(payload); @@ -298,14 +322,6 @@ impl IqResponseTracker { inner: rx, }; map.insert(key.clone(), sink); - ( - Iq { - from, - to: key.0, - id: key.1, - payload: req.into(), - }, - token, - ) + (req.into_iq(from, key.0, key.1), token) } } diff --git a/tokio-xmpp/src/event.rs b/tokio-xmpp/src/event.rs index 96db58993e64fb02c4d67e9348a62c71ef021cab..742a23e8709b2c120e54e776898af5b62331c697 100644 --- a/tokio-xmpp/src/event.rs +++ b/tokio-xmpp/src/event.rs @@ -43,10 +43,11 @@ impl Stanza { pub fn ensure_id(&mut self) -> &str { match self { Self::Iq(iq) => { - if iq.id.is_empty() { - iq.id = make_id(); + let id = iq.id_mut(); + if id.is_empty() { + *id = make_id(); } - &iq.id + id } Self::Message(message) => message.id.get_or_insert_with(|| Id(make_id())).0.as_ref(), Self::Presence(presence) => presence.id.get_or_insert_with(make_id), @@ -97,7 +98,7 @@ impl TryFrom for Presence { impl TryFrom for Iq { type Error = Stanza; - fn try_from(other: Stanza) -> Result { + fn try_from(other: Stanza) -> Result { match other { Stanza::Iq(st) => Ok(st), other => Err(other), diff --git a/tokio-xmpp/src/stanzastream/negotiation.rs b/tokio-xmpp/src/stanzastream/negotiation.rs index d0f10fa1f6d83149364b3579e13b068d6d7ad6e9..72893e5a4edd107be0b7886b52b5641cbed42c78 100644 --- a/tokio-xmpp/src/stanzastream/negotiation.rs +++ b/tokio-xmpp/src/stanzastream/negotiation.rs @@ -13,7 +13,7 @@ use futures::{ready, Sink, Stream}; use xmpp_parsers::{ bind::{BindQuery, BindResponse}, - iq::{Iq, IqType}, + iq::Iq, jid::{FullJid, Jid}, sm, stream_error::{DefinedCondition, StreamError}, @@ -200,31 +200,34 @@ impl NegotiationState { match item { Ok(XmppStreamElement::Stanza(data)) => match data { - Stanza::Iq(iq) if iq.id == BIND_REQ_ID => { - let error = match iq.payload { - IqType::Result(Some(payload)) => { - match BindResponse::try_from(payload) { - Ok(v) => { - let bound_jid = v.into(); - if *sm_supported { - *self = Self::SendSmRequest { + Stanza::Iq(iq) if iq.id() == BIND_REQ_ID => { + let error = match iq { + Iq::Result { + payload: Some(payload), + .. + } => match BindResponse::try_from(payload) { + Ok(v) => { + let bound_jid = v.into(); + if *sm_supported { + *self = Self::SendSmRequest { + sm_state: None, + bound_jid: Some(bound_jid), + }; + return Poll::Ready(Continue(None)); + } else { + return Poll::Ready(Break( + NegotiationResult::StreamReset { sm_state: None, - bound_jid: Some(bound_jid), - }; - return Poll::Ready(Continue(None)); - } else { - return Poll::Ready(Break( - NegotiationResult::StreamReset { - sm_state: None, - bound_jid: Jid::from(bound_jid), - }, - )); - } + bound_jid: Jid::from(bound_jid), + }, + )); } - Err(e) => e.to_string(), } + Err(e) => e.to_string(), + }, + Iq::Result { payload: None, .. } => { + "Bind response has no payload".to_owned() } - IqType::Result(None) => "Bind response has no payload".to_owned(), _ => "Unexpected IQ type in response to bind request".to_owned(), }; log::warn!("Received IQ matching the bind request, but parsing failed ({error})! Emitting stream error."); diff --git a/xmpp/src/iq/mod.rs b/xmpp/src/iq/mod.rs index 54db1bd083c9173524f8c0a0e49ff2ac5309c5a1..f20d1339fbc4e3730d54ba31ccf064da64eaee4e 100644 --- a/xmpp/src/iq/mod.rs +++ b/xmpp/src/iq/mod.rs @@ -4,7 +4,7 @@ // 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 tokio_xmpp::parsers::iq::{Iq, IqType}; +use tokio_xmpp::parsers::iq::{Iq, IqHeader, IqPayload}; use crate::{Agent, Event}; @@ -14,16 +14,14 @@ pub mod set; pub async fn handle_iq(agent: &mut Agent, iq: Iq) -> Vec { let mut events = vec![]; - let from = iq - .from - .clone() - .unwrap_or_else(|| agent.client.bound_jid().unwrap().to_bare().into()); - if let IqType::Get(payload) = iq.payload { - get::handle_iq_get(agent, &mut events, from, iq.to, iq.id, payload).await; - } else if let IqType::Result(Some(payload)) = iq.payload { - result::handle_iq_result(agent, &mut events, from, iq.to, iq.id, payload).await; - } else if let IqType::Set(payload) = iq.payload { - set::handle_iq_set(agent, &mut events, from, iq.to, iq.id, payload).await; + let (IqHeader { from, to, id }, data) = iq.split(); + let from = from.unwrap_or_else(|| agent.client.bound_jid().unwrap().to_bare().into()); + if let IqPayload::Get(payload) = data { + get::handle_iq_get(agent, &mut events, from, to, id, payload).await; + } else if let IqPayload::Result(Some(payload)) = data { + result::handle_iq_result(agent, &mut events, from, to, id, payload).await; + } else if let IqPayload::Set(payload) = data { + set::handle_iq_set(agent, &mut events, from, to, id, payload).await; } events }