// Copyright (c) 2017-2021 Emmanuel Gil Peyrot // // 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::{ error::{Error, FromElementError}, AsXml, FromXml, }; use crate::data_forms::DataForm; use crate::date::DateTime; use crate::forwarding::Forwarded; use crate::iq::{IqGetPayload, IqResultPayload, IqSetPayload}; use crate::message::MessagePayload; use crate::ns; use crate::pubsub::NodeName; use crate::rsm::{SetQuery, SetResult}; use minidom::Element; use minidom::Node; generate_id!( /// An identifier matching a result message to the query requesting it. QueryId ); /// Starts a query to the archive. #[derive(Debug)] pub struct Query { /// An optional identifier for matching forwarded messages to this /// query. pub queryid: Option, /// Must be set to Some when querying a PubSub node’s archive. pub node: Option, /// Used for filtering the results. pub form: Option, /// Used for paging through results. pub set: Option, /// Used for reversing the order of the results. pub flip_page: bool, } impl IqGetPayload for Query {} impl IqSetPayload for Query {} impl IqResultPayload for Query {} impl TryFrom for Query { type Error = FromElementError; fn try_from(elem: Element) -> Result { check_self!(elem, "query", MAM); check_no_unknown_attributes!(elem, "query", ["queryid", "node"]); let mut form = None; let mut set = None; let mut flip_page = false; for child in elem.children() { if child.is("x", ns::DATA_FORMS) { if form.is_some() { return Err( Error::Other("Element query must not have more than one x child.").into(), ); } form = Some(DataForm::try_from(child.clone())?); continue; } if child.is("set", ns::RSM) { if set.is_some() { return Err(Error::Other( "Element query must not have more than one set child.", ) .into()); } set = Some(SetQuery::try_from(child.clone())?); continue; } if child.is("flip-page", ns::MAM) { if flip_page { return Err(Error::Other( "Element query must not have more than one flip-page child.", ) .into()); } flip_page = true; continue; } return Err(Error::Other("Unknown child in query element.").into()); } Ok(Query { queryid: match elem.attr("queryid") { Some(value) => Some(value.parse()?), None => None, }, node: match elem.attr("node") { Some(value) => Some(value.parse()?), None => None, }, form, set, flip_page, }) } } impl From for Element { fn from(elem: Query) -> Element { let mut builder = Element::builder("query", ns::MAM); builder = builder.attr("queryid", elem.queryid); builder = builder.attr("node", elem.node); builder = builder.append_all(elem.form.map(|elem| Node::Element(Element::from(elem)))); builder = builder.append_all(elem.set.map(|elem| Node::Element(Element::from(elem)))); if elem.flip_page { let flip_page = Element::builder("flip-page", ns::MAM).build(); builder = builder.append(Node::Element(flip_page)); } builder.build() } } /// The wrapper around forwarded stanzas. #[derive(FromXml, AsXml, PartialEq, Debug, Clone)] #[xml(namespace = ns::MAM, name = "result")] pub struct Result_ { /// The stanza-id under which the archive stored this stanza. #[xml(attribute)] pub id: String, /// The same queryid as the one requested in the /// [query](struct.Query.html). #[xml(attribute(default))] pub queryid: Option, /// The actual stanza being forwarded. #[xml(child)] pub forwarded: Forwarded, } impl MessagePayload for Result_ {} generate_attribute!( /// True when the end of a MAM query has been reached. Complete, "complete", bool ); /// Notes the end of a page in a query. #[derive(FromXml, AsXml, Debug, Clone, PartialEq)] #[xml(namespace = ns::MAM, name = "fin")] pub struct Fin { /// True when the end of a MAM query has been reached. #[xml(attribute(default))] pub complete: Complete, /// Describes the current page, it should contain at least [first] /// (with an [index]) and [last], and generally [count]. /// /// [first]: ../rsm/struct.SetResult.html#structfield.first /// [index]: ../rsm/struct.SetResult.html#structfield.first_index /// [last]: ../rsm/struct.SetResult.html#structfield.last /// [count]: ../rsm/struct.SetResult.html#structfield.count #[xml(child)] pub set: SetResult, } impl IqResultPayload for Fin {} /// Metadata of the first message in the archive. #[derive(FromXml, AsXml, Debug, Clone, PartialEq)] #[xml(namespace = ns::MAM, name = "start")] pub struct Start { /// The id of the first message in the archive. #[xml(attribute)] pub id: String, /// Time at which that message was sent. #[xml(attribute)] pub timestamp: DateTime, } /// Metadata of the last message in the archive. #[derive(FromXml, AsXml, Debug, Clone, PartialEq)] #[xml(namespace = ns::MAM, name = "end")] pub struct End { /// The id of the last message in the archive. #[xml(attribute)] pub id: String, /// Time at which that message was sent. #[xml(attribute)] pub timestamp: DateTime, } /// Request an archive for its metadata. #[derive(FromXml, AsXml, Debug, Clone, PartialEq)] #[xml(namespace = ns::MAM, name = "metadata")] pub struct MetadataQuery; impl IqGetPayload for MetadataQuery {} /// Response from the archive, containing the start and end metadata if it isn’t empty. #[derive(FromXml, AsXml, Debug, Clone, PartialEq)] #[xml(namespace = ns::MAM, name = "metadata")] pub struct MetadataResponse { /// Metadata about the first message in the archive. #[xml(child(default))] pub start: Option, /// Metadata about the last message in the archive. #[xml(child(default))] pub end: Option, } impl IqResultPayload for MetadataResponse {} #[cfg(test)] mod tests { use super::*; #[cfg(target_pointer_width = "32")] #[test] fn test_size() { assert_size!(QueryId, 12); assert_size!(Query, 120); assert_size!(Result_, 164); assert_size!(Complete, 1); assert_size!(Fin, 44); assert_size!(Start, 28); assert_size!(End, 28); assert_size!(MetadataQuery, 0); assert_size!(MetadataResponse, 56); } #[cfg(target_pointer_width = "64")] #[test] fn test_size() { assert_size!(QueryId, 24); assert_size!(Query, 240); assert_size!(Result_, 312); assert_size!(Complete, 1); assert_size!(Fin, 88); assert_size!(Start, 40); assert_size!(End, 40); assert_size!(MetadataQuery, 0); assert_size!(MetadataResponse, 80); } #[test] fn test_query() { let elem: Element = "".parse().unwrap(); Query::try_from(elem).unwrap(); } #[test] fn test_result() { #[cfg(not(feature = "component"))] let elem: Element = r#" Hail to thee "# .parse() .unwrap(); #[cfg(feature = "component")] let elem: Element = r#" Hail to thee "#.parse().unwrap(); Result_::try_from(elem).unwrap(); } #[test] fn test_fin() { let elem: Element = r#" 28482-98726-73623 09af3-cc343-b409f "# .parse() .unwrap(); Fin::try_from(elem).unwrap(); } #[test] fn test_query_x() { let elem: Element = r#" urn:xmpp:mam:2 juliet@capulet.lit "# .parse() .unwrap(); Query::try_from(elem).unwrap(); } #[test] fn test_query_x_set() { let elem: Element = r#" urn:xmpp:mam:2 2010-08-07T00:00:00Z 10 "# .parse() .unwrap(); Query::try_from(elem).unwrap(); } #[test] fn test_query_x_set_flipped() { let elem: Element = r#" urn:xmpp:mam:2 2010-08-07T00:00:00Z 10 "# .parse() .unwrap(); Query::try_from(elem).unwrap(); } #[test] fn test_metadata() { let elem: Element = r"".parse().unwrap(); MetadataQuery::try_from(elem).unwrap(); let elem: Element = r" " .parse() .unwrap(); let metadata = MetadataResponse::try_from(elem).unwrap(); let start = metadata.start.unwrap(); let end = metadata.end.unwrap(); assert_eq!(start.id, "YWxwaGEg"); assert_eq!(start.timestamp.0.timestamp(), 1219439344); assert_eq!(end.id, "b21lZ2Eg"); assert_eq!(end.timestamp.0.timestamp(), 1587393261); } #[test] fn test_invalid_child() { let elem: Element = "" .parse() .unwrap(); let error = Query::try_from(elem).unwrap_err(); let message = match error { FromElementError::Invalid(Error::Other(string)) => string, _ => panic!(), }; assert_eq!(message, "Unknown child in query element."); } #[test] fn test_serialise_empty() { let elem: Element = "".parse().unwrap(); let replace = Query { queryid: None, node: None, form: None, set: None, flip_page: false, }; let elem2 = replace.into(); assert_eq!(elem, elem2); } #[test] fn test_serialize_query_with_form() { let reference: Element = "urn:xmpp:mam:2juliet@capulet.lit" .parse() .unwrap(); let elem: Element = "urn:xmpp:mam:2juliet@capulet.lit" .parse() .unwrap(); let form = DataForm::try_from(elem).unwrap(); let query = Query { queryid: None, node: None, set: None, form: Some(form), flip_page: true, }; let serialized: Element = query.into(); assert_eq!(serialized, reference); } #[test] fn test_serialize_result() { let reference: Element = "" .parse() .unwrap(); let elem: Element = "" .parse() .unwrap(); let forwarded = Forwarded::try_from(elem).unwrap(); let result = Result_ { id: String::from("28482-98726-73623"), queryid: Some(QueryId(String::from("f27"))), forwarded, }; let serialized: Element = result.into(); assert_eq!(serialized, reference); } #[test] fn test_serialize_fin() { let reference: Element = "28482-98726-7362309af3-cc343-b409f" .parse() .unwrap(); let elem: Element = "28482-98726-7362309af3-cc343-b409f" .parse() .unwrap(); let set = SetResult::try_from(elem).unwrap(); let fin = Fin { set, complete: Complete::default(), }; let serialized: Element = fin.into(); assert_eq!(serialized, reference); } }