// Copyright (c) 2017 Emmanuel Gil Peyrot // Copyright (c) 2017 Maxime “pep” Buquet // // 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 crate::ns; use crate::stanza_error::StanzaError; use jid::Jid; use minidom::Element; use xso::{AsXml, FromXml}; /// Should be implemented on every known payload of an ``. pub trait IqGetPayload: TryFrom + Into {} /// Should be implemented on every known payload of an ``. pub trait IqSetPayload: TryFrom + Into {} /// Should be implemented on every known payload of an ``. pub trait IqResultPayload: TryFrom + Into {} /// 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), /// Payload of a type='set' stanza. Set(Element), /// Payload of a type='result' stanza. Result(Option), /// The error carride in a type='error' stanza. Error(StanzaError), } 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(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::Get { from: None, to: None, id: id.into(), payload: payload.into(), } } /// Creates an `` stanza containing a set request. pub fn from_set>(id: S, payload: impl IqSetPayload) -> Iq { Iq::Set { from: None, to: None, id: id.into(), payload: payload.into(), } } /// Creates an empty `` stanza. pub fn empty_result>(to: Jid, id: S) -> Iq { Iq::Result { from: None, to: Some(to), id: id.into(), payload: None, } } /// Creates an `` stanza containing a result. pub fn from_result>(id: S, payload: Option) -> Iq { Iq::Result { from: None, to: None, id: id.into(), payload: payload.map(Into::into), } } /// Creates an `` stanza containing an error. pub fn from_error>(id: S, payload: StanzaError) -> Iq { Iq::Error { from: None, to: None, id: id.into(), error: payload, payload: None, } } /// Sets the recipient of this stanza. pub fn with_to(mut self, to: Jid) -> Iq { *self.to_mut() = Some(to); self } /// Sets the emitter of this stanza. pub fn with_from(mut self, from: Jid) -> Iq { *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_mut() = id; self } /// 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(), } } /// 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, } } /// 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(), } } /// 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, } } /// 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(), } } /// 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, } } /// 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)), } } /// 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 } } #[cfg(test)] 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!(IqHeader, 44); assert_size!(IqPayload, 108); assert_size!(Iq, 212); } #[cfg(target_pointer_width = "64")] #[test] fn test_size() { assert_size!(IqHeader, 88); assert_size!(IqPayload, 216); assert_size!(Iq, 424); } #[test] fn test_require_type() { #[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, "Missing discriminator attribute."); } #[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] fn test_get() { #[cfg(not(feature = "component"))] let elem: Element = " " .parse() .unwrap(); #[cfg(feature = "component")] let elem: Element = " " .parse() .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 { Iq::Get { payload, .. } => payload == query, _ => false, }); } #[test] fn test_set() { #[cfg(not(feature = "component"))] let elem: Element = " " .parse() .unwrap(); #[cfg(feature = "component")] let elem: Element = " " .parse() .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 { Iq::Set { payload, .. } => payload == vcard, _ => false, }); } #[test] fn test_result_empty() { #[cfg(not(feature = "component"))] let elem: Element = "" .parse() .unwrap(); #[cfg(feature = "component")] let elem: Element = "" .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 { Iq::Result { payload: None, .. } => true, _ => false, }); } #[test] fn test_result() { #[cfg(not(feature = "component"))] let elem: Element = " " .parse() .unwrap(); #[cfg(feature = "component")] let elem: Element = " " .parse() .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(), "res"); assert!(match iq { Iq::Result { payload: Some(element), .. } => element == query, _ => false, }); } #[test] fn test_error() { #[cfg(not(feature = "component"))] let elem: Element = " " .parse() .unwrap(); #[cfg(feature = "component")] let elem: Element = " " .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 { Iq::Error { error, .. } => { assert_eq!(error.type_, ErrorType::Cancel); assert_eq!(error.by, None); assert_eq!( error.defined_condition, DefinedCondition::ServiceUnavailable ); assert_eq!(error.texts.len(), 0); assert_eq!(error.other, None); } _ => panic!(), } } #[test] fn test_children_invalid() { #[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, "Missing child field 'error' in Iq::Error element."); } #[test] fn test_serialise() { #[cfg(not(feature = "component"))] let elem: Element = "" .parse() .unwrap(); #[cfg(feature = "component")] let elem: Element = "" .parse() .unwrap(); let iq2 = Iq::Result { from: None, to: None, id: String::from("res"), payload: None, }; let elem2 = iq2.into(); assert_eq!(elem, elem2); } #[test] fn test_disco() { #[cfg(not(feature = "component"))] let elem: Element = "".parse().unwrap(); #[cfg(feature = "component")] let elem: Element = "".parse().unwrap(); let iq = Iq::try_from(elem).unwrap(); let disco_info = match iq { Iq::Get { payload, .. } => DiscoInfoQuery::try_from(payload).unwrap(), _ => panic!(), }; assert!(disco_info.node.is_none()); } }