From 83cce468a573d3ca58a0c6653a2ccc17c7b4dbc3 Mon Sep 17 00:00:00 2001 From: Evgeniy Khramtsov Date: Mon, 22 Jun 2015 16:56:08 +0300 Subject: [PATCH] Add MAM (XEP-0313) support --- include/ns.hrl | 2 + sql/lite.sql | 24 + sql/mysql.sql | 25 + sql/pg.sql | 29 +- src/ejabberd_c2s.erl | 32 +- src/mod_caps.erl | 24 +- src/mod_carboncopy.erl | 13 +- src/mod_mam.erl | 823 +++++++++++++++++ src/mod_ping.erl | 7 +- src/mod_service_log.erl | 14 +- test/ejabberd_SUITE.erl | 280 ++++++ test/ejabberd_SUITE_data/ejabberd.yml | 8 + tools/xmpp_codec.erl | 1200 +++++++++++++++++++++++++ tools/xmpp_codec.hrl | 35 + tools/xmpp_codec.spec | 150 ++++ 15 files changed, 2624 insertions(+), 42 deletions(-) create mode 100644 src/mod_mam.erl diff --git a/include/ns.hrl b/include/ns.hrl index f4c8bf3b0..71f454cad 100644 --- a/include/ns.hrl +++ b/include/ns.hrl @@ -141,6 +141,8 @@ -define(NS_CAPTCHA, <<"urn:xmpp:captcha">>). -define(NS_MEDIA, <<"urn:xmpp:media-element">>). -define(NS_BOB, <<"urn:xmpp:bob">>). +-define(NS_MAM_TMP, <<"urn:xmpp:mam:tmp">>). +-define(NS_MAM_0, <<"urn:xmpp:mam:0">>). -define(NS_PING, <<"urn:xmpp:ping">>). -define(NS_CARBONS_2, <<"urn:xmpp:carbons:2">>). -define(NS_CARBONS_1, <<"urn:xmpp:carbons:1">>). diff --git a/sql/lite.sql b/sql/lite.sql index 21e4df929..461686d1a 100644 --- a/sql/lite.sql +++ b/sql/lite.sql @@ -270,3 +270,27 @@ CREATE TABLE caps_features ( ); CREATE INDEX i_caps_features_node_subnode ON caps_features (node, subnode); + +CREATE TABLE archive ( + username text NOT NULL, + timestamp BIGINT UNSIGNED NOT NULL, + peer text NOT NULL, + bare_peer text NOT NULL, + xml text NOT NULL, + txt text, + id INTEGER PRIMARY KEY AUTOINCREMENT, + created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +CREATE INDEX i_username ON archive(username); +CREATE INDEX i_timestamp ON archive(timestamp); +CREATE INDEX i_peer ON archive(peer); +CREATE INDEX i_bare_peer ON archive(bare_peer); + +CREATE TABLE archive_prefs ( + username text NOT NULL PRIMARY KEY, + def text NOT NULL, + always text NOT NULL, + never text NOT NULL, + created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP +); diff --git a/sql/mysql.sql b/sql/mysql.sql index 9596afa7c..fab76b9a2 100644 --- a/sql/mysql.sql +++ b/sql/mysql.sql @@ -85,6 +85,31 @@ CREATE TABLE spool ( CREATE INDEX i_despool USING BTREE ON spool(username); CREATE INDEX i_spool_created_at USING BTREE ON spool(created_at); +CREATE TABLE archive ( + username varchar(250) NOT NULL, + timestamp BIGINT UNSIGNED NOT NULL, + peer varchar(250) NOT NULL, + bare_peer varchar(250) NOT NULL, + xml text NOT NULL, + txt text, + id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE, + created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP +) ENGINE=InnoDB CHARACTER SET utf8; + +CREATE FULLTEXT INDEX i_text ON archive(txt); +CREATE INDEX i_username USING BTREE ON archive(username); +CREATE INDEX i_timestamp USING BTREE ON archive(timestamp); +CREATE INDEX i_peer USING BTREE ON archive(peer); +CREATE INDEX i_bare_peer USING BTREE ON archive(bare_peer); + +CREATE TABLE archive_prefs ( + username varchar(250) NOT NULL PRIMARY KEY, + def text NOT NULL, + always text NOT NULL, + never text NOT NULL, + created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP +) ENGINE=InnoDB CHARACTER SET utf8; + CREATE TABLE vcard ( username varchar(250) PRIMARY KEY, vcard mediumtext NOT NULL, diff --git a/sql/pg.sql b/sql/pg.sql index 736c4f932..2a052d337 100644 --- a/sql/pg.sql +++ b/sql/pg.sql @@ -85,6 +85,29 @@ CREATE TABLE spool ( CREATE INDEX i_despool ON spool USING btree (username); +CREATE TABLE archive ( + username text NOT NULL, + timestamp BIGINT NOT NULL, + peer text NOT NULL, + bare_peer text NOT NULL, + xml text NOT NULL, + txt text, + id SERIAL, + created_at TIMESTAMP NOT NULL DEFAULT now() +); + +CREATE INDEX i_username ON archive USING btree (username); +CREATE INDEX i_timestamp ON archive USING btree (timestamp); +CREATE INDEX i_peer ON archive USING btree (peer); +CREATE INDEX i_bare_peer ON archive USING btree (bare_peer); + +CREATE TABLE archive_prefs ( + username text NOT NULL PRIMARY KEY, + def text NOT NULL, + always text NOT NULL, + never text NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT now() +); CREATE TABLE vcard ( username text PRIMARY KEY, @@ -299,6 +322,6 @@ CREATE TABLE sm ( info text NOT NULL ); -CREATE UNIQUE INDEX i_sid ON sm USING btree (usec, pid); -CREATE INDEX i_node ON sm USING btree (node); -CREATE INDEX i_username ON sm USING btree (username); +CREATE UNIQUE INDEX i_sm_sid ON sm USING btree (usec, pid); +CREATE INDEX i_sm_node ON sm USING btree (node); +CREATE INDEX i_sm_username ON sm USING btree (username); diff --git a/src/ejabberd_c2s.erl b/src/ejabberd_c2s.erl index 9655a2ab1..3869bd54e 100644 --- a/src/ejabberd_c2s.erl +++ b/src/ejabberd_c2s.erl @@ -1260,12 +1260,14 @@ session_established2(El, StateData) -> _ -> case Name of <<"presence">> -> - PresenceEl = + PresenceEl0 = ejabberd_hooks:run_fold(c2s_update_presence, Server, NewEl, [User, Server]), - ejabberd_hooks:run(user_send_packet, Server, - [FromJID, ToJID, PresenceEl]), + PresenceEl = + ejabberd_hooks:run_fold( + user_send_packet, Server, PresenceEl0, + [NewStateData, FromJID, ToJID]), case ToJID of #jid{user = User, server = Server, resource = <<"">>} -> @@ -1285,16 +1287,18 @@ session_established2(El, StateData) -> process_privacy_iq(FromJID, ToJID, IQ, NewStateData); _ -> - ejabberd_hooks:run(user_send_packet, Server, - [FromJID, ToJID, NewEl]), + NewEl0 = ejabberd_hooks:run_fold( + user_send_packet, Server, NewEl, + [NewStateData, FromJID, ToJID]), check_privacy_route(FromJID, NewStateData, - FromJID, ToJID, NewEl) + FromJID, ToJID, NewEl0) end; <<"message">> -> - ejabberd_hooks:run(user_send_packet, Server, - [FromJID, ToJID, NewEl]), + NewEl0 = ejabberd_hooks:run_fold( + user_send_packet, Server, NewEl, + [NewStateData, FromJID, ToJID]), check_privacy_route(FromJID, NewStateData, FromJID, - ToJID, NewEl); + ToJID, NewEl0); _ -> NewStateData end end, @@ -1692,11 +1696,13 @@ handle_info({route, From, To, Attrs2 = jlib:replace_from_to_attrs(jlib:jid_to_string(From), jlib:jid_to_string(To), NewAttrs), - FixedPacket = #xmlel{name = Name, attrs = Attrs2, children = Els}, + FixedPacket0 = #xmlel{name = Name, attrs = Attrs2, children = Els}, + FixedPacket = ejabberd_hooks:run_fold( + user_receive_packet, + NewState#state.server, + FixedPacket0, + [NewState, NewState#state.jid, From, To]), SentStateData = send_packet(NewState, FixedPacket), - ejabberd_hooks:run(user_receive_packet, - SentStateData#state.server, - [SentStateData#state.jid, From, To, FixedPacket]), ejabberd_hooks:run(c2s_loop_debug, [{route, From, To, Packet}]), fsm_next_state(StateName, SentStateData); true -> diff --git a/src/mod_caps.erl b/src/mod_caps.erl index f10ed66c7..4d166ce70 100644 --- a/src/mod_caps.erl +++ b/src/mod_caps.erl @@ -47,7 +47,7 @@ -export([init/1, handle_info/2, handle_call/3, handle_cast/2, terminate/2, code_change/3]). --export([user_send_packet/3, user_receive_packet/4, +-export([user_send_packet/4, user_receive_packet/5, c2s_presence_in/2, c2s_filter_packet/6, c2s_broadcast_recipients/6, mod_opt_type/1]). @@ -143,11 +143,12 @@ read_caps([_ | Tail], Result) -> read_caps(Tail, Result); read_caps([], Result) -> Result. -user_send_packet(#jid{luser = User, lserver = Server} = From, +user_send_packet(#xmlel{name = <<"presence">>, attrs = Attrs, + children = Els} = Pkt, + _C2SState, + #jid{luser = User, lserver = Server} = From, #jid{luser = User, lserver = Server, - lresource = <<"">>}, - #xmlel{name = <<"presence">>, attrs = Attrs, - children = Els} = Pkt) -> + lresource = <<"">>}) -> Type = xml:get_attr_s(<<"type">>, Attrs), if Type == <<"">>; Type == <<"available">> -> case read_caps(Els) of @@ -158,13 +159,14 @@ user_send_packet(#jid{luser = User, lserver = Server} = From, true -> ok end, Pkt; -user_send_packet( _From, _To, Pkt) -> +user_send_packet(Pkt, _C2SState, _From, _To) -> Pkt. -user_receive_packet(#jid{lserver = Server}, - From, _To, - #xmlel{name = <<"presence">>, attrs = Attrs, - children = Els} = Pkt) -> +user_receive_packet(#xmlel{name = <<"presence">>, attrs = Attrs, + children = Els} = Pkt, + _C2SState, + #jid{lserver = Server}, + From, _To) -> Type = xml:get_attr_s(<<"type">>, Attrs), IsRemote = not lists:member(From#jid.lserver, ?MYHOSTS), if IsRemote and @@ -177,7 +179,7 @@ user_receive_packet(#jid{lserver = Server}, true -> ok end, Pkt; -user_receive_packet( _JID, _From, _To, Pkt) -> +user_receive_packet(Pkt, _C2SState, _JID, _From, _To) -> Pkt. -spec caps_stream_features([xmlel()], binary()) -> [xmlel()]. diff --git a/src/mod_carboncopy.erl b/src/mod_carboncopy.erl index 2cc495aec..186aca5e3 100644 --- a/src/mod_carboncopy.erl +++ b/src/mod_carboncopy.erl @@ -34,7 +34,7 @@ -export([start/2, stop/1]). --export([user_send_packet/3, user_receive_packet/4, +-export([user_send_packet/4, user_receive_packet/5, iq_handler2/3, iq_handler1/3, remove_connection/4, is_carbon_copy/1, mod_opt_type/1]). @@ -124,10 +124,10 @@ iq_handler(From, _To, #iq{type=set, sub_el = #xmlel{name = Operation, children iq_handler(_From, _To, IQ, _CC)-> IQ#iq{type=error, sub_el = [?ERR_NOT_ALLOWED]}. -user_send_packet(From, To, Packet) -> +user_send_packet(Packet, _C2SState, From, To) -> check_and_forward(From, To, Packet, sent). -user_receive_packet(JID, _From, To, Packet) -> +user_receive_packet(Packet, _C2SState, JID, _From, To) -> check_and_forward(JID, To, Packet, received). % verifier si le trafic est local @@ -142,14 +142,15 @@ check_and_forward(JID, To, Packet, Direction)-> true -> case is_carbon_copy(Packet) of false -> - send_copies(JID, To, Packet, Direction); + send_copies(JID, To, Packet, Direction), + Packet; true -> %% stop the hook chain, we don't want mod_logdb to register %% this message (duplicate) - stop + {stop, Packet} end; _ -> - ok + Packet end. remove_connection(User, Server, Resource, _Status)-> diff --git a/src/mod_mam.erl b/src/mod_mam.erl new file mode 100644 index 000000000..1de1d9a95 --- /dev/null +++ b/src/mod_mam.erl @@ -0,0 +1,823 @@ +%%%------------------------------------------------------------------- +%%% @author Evgeniy Khramtsov +%%% @doc +%%% Message Archive Management (XEP-0313) +%%% @end +%%% Created : 4 Jul 2013 by Evgeniy Khramtsov +%%% +%%% +%%% ejabberd, Copyright (C) 2013-2015 ProcessOne +%%% +%%% This program is free software; you can redistribute it and/or +%%% modify it under the terms of the GNU General Public License as +%%% published by the Free Software Foundation; either version 2 of the +%%% License, or (at your option) any later version. +%%% +%%% This program is distributed in the hope that it will be useful, +%%% but WITHOUT ANY WARRANTY; without even the implied warranty of +%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +%%% General Public License for more details. +%%% +%%% You should have received a copy of the GNU General Public License +%%% along with this program; if not, write to the Free Software +%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +%%% 02111-1307 USA +%%% +%%%------------------------------------------------------------------- +-module(mod_mam). + +-behaviour(gen_mod). + +%% API +-export([start/2, stop/1]). + +-export([user_send_packet/4, user_receive_packet/5, + process_iq/3, remove_user/2, mod_opt_type/1]). + +-include_lib("stdlib/include/ms_transform.hrl"). +-include("jlib.hrl"). +-include("logger.hrl"). + +-record(archive_msg, + {us = {<<"">>, <<"">>} :: {binary(), binary()} | '$2', + id = <<>> :: binary() | '_', + timestamp = now() :: erlang:timestamp() | '_' | '$1', + peer = {<<"">>, <<"">>, <<"">>} :: ljid() | '_' | '$3', + bare_peer = {<<"">>, <<"">>, <<"">>} :: ljid() | '_' | '$3', + packet = #xmlel{} :: xmlel() | '_'}). + +-record(archive_prefs, + {us = {<<"">>, <<"">>} :: {binary(), binary()}, + default = never :: never | always | roster, + always = [] :: [ljid()], + never = [] :: [ljid()]}). + +%%%=================================================================== +%%% API +%%%=================================================================== +start(Host, Opts) -> + IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1, + one_queue), + DBType = gen_mod:db_type(Host, Opts), + init_db(DBType, Host), + init_cache(DBType, Opts), + gen_iq_handler:add_iq_handler(ejabberd_local, Host, + ?NS_MAM_TMP, ?MODULE, process_iq, IQDisc), + gen_iq_handler:add_iq_handler(ejabberd_sm, Host, + ?NS_MAM_TMP, ?MODULE, process_iq, IQDisc), + gen_iq_handler:add_iq_handler(ejabberd_local, Host, + ?NS_MAM_0, ?MODULE, process_iq, IQDisc), + gen_iq_handler:add_iq_handler(ejabberd_sm, Host, + ?NS_MAM_0, ?MODULE, process_iq, IQDisc), + ejabberd_hooks:add(user_receive_packet, Host, ?MODULE, + user_receive_packet, 500), + ejabberd_hooks:add(user_send_packet, Host, ?MODULE, + user_send_packet, 500), + ejabberd_hooks:add(remove_user, Host, ?MODULE, + remove_user, 50), + ejabberd_hooks:add(anonymous_purge_hook, Host, ?MODULE, + remove_user, 50), + ok. + +init_db(odbc, Host) -> + Muchost = gen_mod:get_module_opt_host(Host, mod_muc, + <<"conference.@HOST@">>), + ets:insert(ejabberd_modules, {ejabberd_module, {mod_mam, Muchost}, + [{db_type, odbc}]}), + mnesia:dirty_write({local_config, {modules,Muchost}, + [{mod_mam, [{db_type, odbc}]}]}); +init_db(mnesia, _Host) -> + mnesia:create_table(archive_msg, + [{disc_only_copies, [node()]}, + {type, bag}, + {attributes, record_info(fields, archive_msg)}]), + mnesia:create_table(archive_prefs, + [{disc_only_copies, [node()]}, + {attributes, record_info(fields, archive_prefs)}]); +init_db(_, _) -> + ok. + +init_cache(_DBType, Opts) -> + MaxSize = gen_mod:get_opt(cache_size, Opts, + fun(I) when is_integer(I), I>0 -> I end, + 1000), + LifeTime = gen_mod:get_opt(cache_life_time, Opts, + fun(I) when is_integer(I), I>0 -> I end, + timer:hours(1) div 1000), + cache_tab:new(archive_prefs, [{max_size, MaxSize}, + {life_time, LifeTime}]). + +stop(Host) -> + ejabberd_hooks:delete(user_send_packet, Host, ?MODULE, + user_send_packet, 500), + ejabberd_hooks:delete(user_receive_packet, Host, ?MODULE, + user_receive_packet, 500), + gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_MAM_TMP), + gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_MAM_TMP), + gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_MAM_0), + gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_MAM_0), + ejabberd_hooks:delete(remove_user, Host, ?MODULE, + remove_user, 50), + ejabberd_hooks:delete(anonymous_purge_hook, Host, + ?MODULE, remove_user, 50), + ok. + +remove_user(User, Server) -> + LUser = jlib:nodeprep(User), + LServer = jlib:nameprep(Server), + remove_user(LUser, LServer, + gen_mod:db_type(LServer, ?MODULE)). + +remove_user(LUser, LServer, mnesia) -> + US = {LUser, LServer}, + F = fun () -> + mnesia:delete({archive_msg, US}), + mnesia:delete({archive_prefs, US}) + end, + mnesia:transaction(F); +remove_user(LUser, LServer, odbc) -> + SUser = ejabberd_odbc:escape(LUser), + ejabberd_odbc:sql_query( + LServer, + [<<"delete from archive where username='">>, SUser, <<"';">>]), + ejabberd_odbc:sql_query( + LServer, + [<<"delete from archive_prefs where username='">>, SUser, <<"';">>]). + +user_receive_packet(Pkt, C2SState, JID, Peer, _To) -> + LUser = JID#jid.luser, + LServer = JID#jid.lserver, + case should_archive(Pkt) of + true -> + NewPkt = strip_my_archived_tag(Pkt, LServer), + case store(C2SState, NewPkt, LUser, LServer, + Peer, true, recv) of + {ok, ID} -> + Archived = #xmlel{name = <<"archived">>, + attrs = [{<<"by">>, LServer}, + {<<"xmlns">>, ?NS_MAM_TMP}, + {<<"id">>, ID}]}, + NewEls = [Archived|NewPkt#xmlel.children], + NewPkt#xmlel{children = NewEls}; + _ -> + NewPkt + end; + muc -> + Pkt; + false -> + Pkt + end. + +user_send_packet(Pkt, C2SState, JID, Peer) -> + LUser = JID#jid.luser, + LServer = JID#jid.lserver, + case should_archive(Pkt) of + S when (S==true) -> + NewPkt = strip_my_archived_tag(Pkt, LServer), + store0(C2SState, jlib:replace_from_to(JID, Peer, NewPkt), + LUser, LServer, Peer, S, send), + NewPkt; + S when (S==muc) -> + NewPkt = strip_my_archived_tag(Pkt, LServer), + case store0(C2SState, jlib:replace_from_to(JID, Peer, NewPkt), + LUser, LServer, Peer, S, send) of + {ok, ID} -> + By = jlib:jid_to_string(Peer), + Archived = #xmlel{name = <<"archived">>, + attrs = [{<<"by">>, By}, {<<"xmlns">>, ?NS_MAM_TMP}, + {<<"id">>, ID}]}, + NewEls = [Archived|NewPkt#xmlel.children], + NewPkt#xmlel{children = NewEls}; + _ -> + NewPkt + end; + false -> + Pkt + end. + +process_iq(#jid{lserver = LServer} = From, + #jid{lserver = LServer} = To, + #iq{type = get, sub_el = #xmlel{name = <<"query">>} = SubEl} = IQ) -> + NS = xml:get_tag_attr_s(<<"xmlns">>, SubEl), + Fs = case NS of + ?NS_MAM_TMP -> + lists:flatmap( + fun(#xmlel{name = <<"start">>} = El) -> + [{<<"start">>, [xml:get_tag_cdata(El)]}]; + (#xmlel{name = <<"end">>} = El) -> + [{<<"end">>, [xml:get_tag_cdata(El)]}]; + (#xmlel{name = <<"with">>} = El) -> + [{<<"with">>, [xml:get_tag_cdata(El)]}]; + (#xmlel{name = <<"withroom">>} = El) -> + [{<<"withroom">>, [xml:get_tag_cdata(El)]}]; + (#xmlel{name = <<"withtext">>} = El) -> + [{<<"withtext">>, [xml:get_tag_cdata(El)]}]; + (#xmlel{name = <<"set">>}) -> + [{<<"set">>, SubEl}]; + (_) -> + [] + end, SubEl#xmlel.children); + ?NS_MAM_0 -> + case {xml:get_subtag_with_xmlns(SubEl, <<"x">>, ?NS_XDATA), + xml:get_subtag_with_xmlns(SubEl, <<"set">>, ?NS_RSM)} of + {#xmlel{} = XData, false} -> + jlib:parse_xdata_submit(XData); + {#xmlel{} = XData, #xmlel{}} -> + [{<<"set">>, SubEl} | jlib:parse_xdata_submit(XData)]; + {false, #xmlel{}} -> + [{<<"set">>, SubEl}]; + {false, false} -> + [] + end + end, + case catch lists:foldl( + fun({<<"start">>, [Data|_]}, {_, End, With, RSM}) -> + {{_, _, _} = jlib:datetime_string_to_timestamp(Data), + End, With, RSM}; + ({<<"end">>, [Data|_]}, {Start, _, With, RSM}) -> + {Start, + {_, _, _} = jlib:datetime_string_to_timestamp(Data), + With, RSM}; + ({<<"with">>, [Data|_]}, {Start, End, _, RSM}) -> + {Start, End, jlib:jid_tolower(jlib:string_to_jid(Data)), RSM}; + ({<<"withroom">>, [Data|_]}, {Start, End, _, RSM}) -> + {Start, End, + {room, jlib:jid_tolower(jlib:string_to_jid(Data))}, + RSM}; + ({<<"withtext">>, [Data|_]}, {Start, End, _, RSM}) -> + {Start, End, {text, Data}, RSM}; + ({<<"set">>, El}, {Start, End, With, _}) -> + {Start, End, With, jlib:rsm_decode(El)}; + (_, Acc) -> + Acc + end, {none, [], none, none}, Fs) of + {'EXIT', _} -> + IQ#iq{type = error, sub_el = [SubEl, ?ERR_BAD_REQUEST]}; + {Start, End, With, RSM} -> + select_and_send(From, To, Start, End, With, RSM, IQ) + end; +process_iq(#jid{luser = LUser, lserver = LServer}, + #jid{lserver = LServer}, + #iq{type = set, sub_el = #xmlel{name = <<"prefs">>} = SubEl} = IQ) -> + try {case xml:get_tag_attr_s(<<"default">>, SubEl) of + <<"always">> -> always; + <<"never">> -> never; + <<"roster">> -> roster + end, + lists:foldl( + fun(#xmlel{name = <<"always">>, children = Els}, {A, N}) -> + {get_jids(Els) ++ A, N}; + (#xmlel{name = <<"never">>, children = Els}, {A, N}) -> + {A, get_jids(Els) ++ N}; + (_, {A, N}) -> + {A, N} + end, {[], []}, SubEl#xmlel.children)} of + {Default, {Always, Never}} -> + case write_prefs(LUser, LServer, LServer, Default, + lists:usort(Always), lists:usort(Never)) of + ok -> + IQ#iq{type = result, sub_el = []}; + _Err -> + IQ#iq{type = error, + sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]} + end + catch _:_ -> + IQ#iq{type = error, sub_el = [SubEl, ?ERR_BAD_REQUEST]} + end; +process_iq(_, _, #iq{sub_el = SubEl} = IQ) -> + IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}. + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== +should_archive(#xmlel{name = <<"message">>} = Pkt) -> + case {xml:get_attr_s(<<"type">>, Pkt#xmlel.attrs), + xml:get_subtag_cdata(Pkt, <<"body">>)} of + {<<"error">>, _} -> + false; + {<<"groupchat">>, _} -> + To = xml:get_attr_s(<<"to">>, Pkt#xmlel.attrs), + case (jlib:string_to_jid(To))#jid.resource of + <<"">> -> muc; + _ -> false + end; + {_, <<>>} -> + %% Empty body + false; + _ -> + true + end; +should_archive(#xmlel{}) -> + false. + +strip_my_archived_tag(Pkt, LServer) -> + NewEls = lists:filter( + fun(#xmlel{name = <<"archived">>, + attrs = Attrs}) -> + case catch jlib:nameprep( + xml:get_attr_s( + <<"by">>, Attrs)) of + LServer -> + false; + _ -> + true + end; + (_) -> + true + end, Pkt#xmlel.children), + Pkt#xmlel{children = NewEls}. + +should_archive_peer(C2SState, + #archive_prefs{default = Default, + always = Always, + never = Never}, + Peer) -> + LPeer = jlib:jid_tolower(Peer), + case lists:member(LPeer, Always) of + true -> + true; + false -> + case lists:member(LPeer, Never) of + true -> + false; + false -> + case Default of + always -> true; + never -> false; + roster -> + case ejabberd_c2s:get_subscription( + LPeer, C2SState) of + both -> true; + from -> true; + to -> true; + _ -> false + end + end + end + end. + +store0(C2SState, Pkt, LUser, LServer, Peer, Type, Dir) -> + case Type of + muc -> store(C2SState, Pkt, Peer#jid.luser, LServer, + jlib:jid_replace_resource(Peer, LUser), Type, Dir); + true -> store(C2SState, Pkt, LUser, LServer, Peer, Type, Dir) + end. + +store(C2SState, Pkt, LUser, LServer, Peer, Type, Dir) -> + Prefs = get_prefs(LUser, LServer), + case should_archive_peer(C2SState, Prefs, Peer) of + true -> + do_store(Pkt, LUser, LServer, Peer, Type, Dir, + gen_mod:db_type(LServer, ?MODULE)); + false -> + pass + end. + +do_store(Pkt, LUser, LServer, Peer, Type, _Dir, mnesia) -> + LPeer = {PUser, PServer, _} = jlib:jid_tolower(Peer), + LServer2 = case Type of muc -> Peer#jid.lserver; _ -> LServer end, + TS = now(), + ID = jlib:integer_to_binary(now_to_usec(TS)), + case mnesia:dirty_write( + #archive_msg{us = {LUser, LServer2}, + id = ID, + timestamp = TS, + peer = LPeer, + bare_peer = {PUser, PServer, <<>>}, + packet = Pkt}) of + ok -> + {ok, ID}; + Err -> + Err + end; +do_store(Pkt, LUser, LServer, Peer, _Type, _Dir, odbc) -> + TSinteger = now_to_usec(now()), + ID = TS = jlib:integer_to_binary(TSinteger), + BarePeer = jlib:jid_to_string( + jlib:jid_tolower( + jlib:jid_remove_resource(Peer))), + LPeer = jlib:jid_to_string( + jlib:jid_tolower(Peer)), + XML = xml:element_to_binary(Pkt), + Body = xml:get_subtag_cdata(Pkt, <<"body">>), + case ejabberd_odbc:sql_query( + LServer, + [<<"insert into archive (username, timestamp, " + "peer, bare_peer, xml, txt) values (">>, + <<"'">>, ejabberd_odbc:escape(LUser), <<"', ">>, + <<"'">>, TS, <<"', ">>, + <<"'">>, ejabberd_odbc:escape(LPeer), <<"', ">>, + <<"'">>, ejabberd_odbc:escape(BarePeer), <<"', ">>, + <<"'">>, ejabberd_odbc:escape(XML), <<"', ">>, + <<"'">>, ejabberd_odbc:escape(Body), <<"');">>]) of + {updated, _} -> + {ok, ID}; + Err -> + Err + end. + +write_prefs(LUser, LServer, Host, Default, Always, Never) -> + DBType = case gen_mod:db_type(Host, ?MODULE) of + odbc -> {odbc, Host}; + DB -> DB + end, + Prefs = #archive_prefs{us = {LUser, LServer}, + default = Default, + always = Always, + never = Never}, + cache_tab:dirty_insert( + archive_prefs, {LUser, LServer}, Prefs, + fun() -> write_prefs(LUser, LServer, Prefs, DBType) end). + +write_prefs(_LUser, _LServer, Prefs, mnesia) -> + mnesia:dirty_write(Prefs); +write_prefs(LUser, _LServer, #archive_prefs{default = Default, + never = Never, + always = Always}, + {odbc, Host}) -> + SUser = ejabberd_odbc:escape(LUser), + SDefault = erlang:atom_to_binary(Default, utf8), + SAlways = ejabberd_odbc:encode_term(Always), + SNever = ejabberd_odbc:encode_term(Never), + case update(Host, <<"archive_prefs">>, + [<<"username">>, <<"def">>, <<"always">>, <<"never">>], + [SUser, SDefault, SAlways, SNever], + [<<"username='">>, SUser, <<"'">>]) of + {updated, _} -> + ok; + Err -> + Err + end. + +get_prefs(LUser, LServer) -> + DBType = gen_mod:db_type(LServer, ?MODULE), + Res = cache_tab:lookup(archive_prefs, {LUser, LServer}, + fun() -> get_prefs(LUser, LServer, + DBType) + end), + case Res of + {ok, Prefs} -> + Prefs; + error -> + Default = gen_mod:get_module_opt( + LServer, ?MODULE, default, + fun(always) -> always; + (never) -> never; + (roster) -> roster + end, never), + #archive_prefs{us = {LUser, LServer}, default = Default} + end. + +get_prefs(LUser, LServer, mnesia) -> + case mnesia:dirty_read(archive_prefs, {LUser, LServer}) of + [Prefs] -> + {ok, Prefs}; + _ -> + error + end; +get_prefs(LUser, LServer, odbc) -> + case ejabberd_odbc:sql_query( + LServer, + [<<"select def, always, never from archive_prefs ">>, + <<"where username='">>, + ejabberd_odbc:escape(LUser), <<"';">>]) of + {selected, _, [[SDefault, SAlways, SNever]]} -> + Default = erlang:binary_to_existing_atom(SDefault, utf8), + Always = ejabberd_odbc:decode_term(SAlways), + Never = ejabberd_odbc:decode_term(SNever), + {ok, #archive_prefs{us = {LUser, LServer}, + default = Default, + always = Always, + never = Never}}; + _ -> + error + end. + +select_and_send(#jid{lserver = LServer} = From, + To, Start, End, With, RSM, IQ) -> + DBType = case gen_mod:db_type(LServer, ?MODULE) of + odbc -> {odbc, LServer}; + DB -> DB + end, + select_and_send(From, To, Start, End, With, RSM, IQ, + DBType). + +select_and_send(From, To, Start, End, With, RSM, IQ, DBType) -> + {Msgs, Count} = select_and_start(From, To, Start, End, With, + RSM, DBType), + SortedMsgs = lists:keysort(2, Msgs), + send(From, To, SortedMsgs, RSM, Count, IQ). + +select_and_start(From, _To, StartUser, End, With, RSM, DB) -> + {JidRequestor, Start, With2} = case With of + {room, {LUserRoom, LServerRoom, <<>>} = WithJid} -> + JR = jlib:make_jid(LUserRoom,LServerRoom,<<>>), + St = StartUser, + {JR, St, WithJid}; + _ -> + {From, StartUser, With} + end, + select(JidRequestor, Start, End, With2, RSM, DB). + +select(#jid{luser = LUser, lserver = LServer} = JidRequestor, + Start, End, With, RSM, mnesia) -> + MS = make_matchspec(LUser, LServer, Start, End, With), + Msgs = mnesia:dirty_select(archive_msg, MS), + FilteredMsgs = filter_by_rsm(Msgs, RSM), + Count = length(Msgs), + {lists:map( + fun(Msg) -> + {Msg#archive_msg.id, + jlib:binary_to_integer(Msg#archive_msg.id), + msg_to_el(Msg, JidRequestor)} + end, FilteredMsgs), Count}; +select(#jid{luser = LUser, lserver = LServer} = JidRequestor, + Start, End, With, RSM, {odbc, Host}) -> + {Query, CountQuery} = make_sql_query(LUser, LServer, + Start, End, With, RSM), + case {ejabberd_odbc:sql_query(Host, Query), + ejabberd_odbc:sql_query(Host, CountQuery)} of + {{selected, _, Res}, {selected, _, [[Count]]}} -> + {lists:map( + fun([TS, XML, PeerBin]) -> + #xmlel{} = El = xml_stream:parse_element(XML), + Now = usec_to_now(jlib:binary_to_integer(TS)), + PeerJid = jlib:jid_tolower(jlib:string_to_jid(PeerBin)), + {TS, jlib:binary_to_integer(TS), + msg_to_el(#archive_msg{timestamp = Now, + packet = El, + peer = PeerJid}, + JidRequestor)} + end, Res), jlib:binary_to_integer(Count)}; + _ -> + {[], 0} + end. + +msg_to_el(#archive_msg{timestamp = TS, packet = Pkt1, peer = Peer}, + JidRequestor) -> + Delay = jlib:now_to_utc_string(TS), + Pkt = maybe_update_from_to(Pkt1, JidRequestor, Peer), + #xmlel{name = <<"forwarded">>, + attrs = [{<<"xmlns">>, ?NS_FORWARD}], + children = [#xmlel{name = <<"delay">>, + attrs = [{<<"xmlns">>, ?NS_DELAY}, + {<<"stamp">>, Delay}]}, + xml:replace_tag_attr( + <<"xmlns">>, <<"jabber:client">>, Pkt)]}. + +maybe_update_from_to(Pkt, _JIDRequestor, undefined) -> + Pkt; +maybe_update_from_to(Pkt, JidRequestor, Peer) -> + case xml:get_attr_s(<<"type">>, Pkt#xmlel.attrs) of + <<"groupchat">> -> + Pkt2 = xml:replace_tag_attr(<<"to">>, + jlib:jid_to_string(JidRequestor), + Pkt), + xml:replace_tag_attr(<<"from">>, jlib:jid_to_string(Peer), + Pkt2); + _ -> Pkt + end. + +send(From, To, Msgs, RSM, Count, #iq{sub_el = SubEl} = IQ) -> + QID = xml:get_tag_attr_s(<<"queryid">>, SubEl), + NS = xml:get_tag_attr_s(<<"xmlns">>, SubEl), + QIDAttr = if QID /= <<>> -> + [{<<"queryid">>, QID}]; + true -> + [] + end, + Els = lists:map( + fun({ID, _IDInt, El}) -> + #xmlel{name = <<"message">>, + children = [#xmlel{name = <<"result">>, + attrs = [{<<"xmlns">>, NS}, + {<<"id">>, ID}|QIDAttr], + children = [El]}]} + end, Msgs), + RSMOut = make_rsm_out(Msgs, RSM, Count, QIDAttr, NS), + case NS of + ?NS_MAM_TMP -> + lists:foreach( + fun(El) -> + ejabberd_router:route(To, From, El) + end, Els), + IQ#iq{type = result, sub_el = RSMOut}; + ?NS_MAM_0 -> + ejabberd_router:route( + To, From, jlib:iq_to_xml(IQ#iq{type = result, sub_el = []})), + lists:foreach( + fun(El) -> + ejabberd_router:route(To, From, El) + end, Els), + ejabberd_router:route( + To, From, #xmlel{name = <<"message">>, + children = RSMOut}), + ignore + end. + + +make_rsm_out(_Msgs, none, _Count, _QIDAttr, ?NS_MAM_TMP) -> + []; +make_rsm_out(_Msgs, none, _Count, QIDAttr, ?NS_MAM_0) -> + [#xmlel{name = <<"fin">>, attrs = [{<<"xmlns">>, ?NS_MAM_0}|QIDAttr]}]; +make_rsm_out([], #rsm_in{}, Count, QIDAttr, NS) -> + Tag = if NS == ?NS_MAM_TMP -> <<"query">>; + true -> <<"fin">> + end, + [#xmlel{name = Tag, attrs = [{<<"xmlns">>, NS}|QIDAttr], + children = jlib:rsm_encode(#rsm_out{count = Count})}]; +make_rsm_out([{FirstID, _, _}|_] = Msgs, #rsm_in{}, Count, QIDAttr, NS) -> + {LastID, _, _} = lists:last(Msgs), + Tag = if NS == ?NS_MAM_TMP -> <<"query">>; + true -> <<"fin">> + end, + [#xmlel{name = Tag, attrs = [{<<"xmlns">>, NS}|QIDAttr], + children = jlib:rsm_encode( + #rsm_out{first = FirstID, count = Count, + last = LastID})}]. + +filter_by_rsm(Msgs, none) -> + Msgs; +filter_by_rsm(_Msgs, #rsm_in{max = Max}) when Max =< 0 -> + []; +filter_by_rsm(Msgs, #rsm_in{max = Max, direction = Direction, id = ID}) -> + NewMsgs = case Direction of + aft -> + lists:filter( + fun(#archive_msg{id = I}) -> + I > ID + end, Msgs); + before -> + lists:foldl( + fun(#archive_msg{id = I} = Msg, Acc) when I < ID -> + [Msg|Acc]; + (_, Acc) -> + Acc + end, [], Msgs); + _ -> + Msgs + end, + filter_by_max(NewMsgs, Max). + +filter_by_max(Msgs, undefined) -> + Msgs; +filter_by_max(Msgs, Len) when is_integer(Len), Len >= 0 -> + lists:sublist(Msgs, Len); +filter_by_max(_Msgs, _Junk) -> + []. + +make_matchspec(LUser, LServer, Start, End, {_, _, <<>>} = With) -> + ets:fun2ms( + fun(#archive_msg{timestamp = TS, + us = US, + bare_peer = BPeer} = Msg) + when Start =< TS, End >= TS, + US == {LUser, LServer}, + BPeer == With -> + Msg + end); +make_matchspec(LUser, LServer, Start, End, {_, _, _} = With) -> + ets:fun2ms( + fun(#archive_msg{timestamp = TS, + us = US, + peer = Peer} = Msg) + when Start =< TS, End >= TS, + US == {LUser, LServer}, + Peer == With -> + Msg + end); +make_matchspec(LUser, LServer, Start, End, none) -> + ets:fun2ms( + fun(#archive_msg{timestamp = TS, + us = US, + peer = Peer} = Msg) + when Start =< TS, End >= TS, + US == {LUser, LServer} -> + Msg + end). + +make_sql_query(LUser, _LServer, Start, End, With, RSM) -> + {Max, Direction, ID} = case RSM of + #rsm_in{} -> + {RSM#rsm_in.max, + RSM#rsm_in.direction, + RSM#rsm_in.id}; + none -> + {none, none, none} + end, + LimitClause = if is_integer(Max), Max >= 0 -> + [<<" limit ">>, jlib:integer_to_binary(Max)]; + true -> + [] + end, + WithClause = case With of + {text, <<>>} -> + []; + {text, Txt} -> + [<<" and match (txt) against ('">>, + ejabberd_odbc:escape(Txt), <<"')">>]; + {_, _, <<>>} -> + [<<" and bare_peer='">>, + ejabberd_odbc:escape(jlib:jid_to_string(With)), + <<"'">>]; + {_, _, _} -> + [<<" and peer='">>, + ejabberd_odbc:escape(jlib:jid_to_string(With)), + <<"'">>]; + none -> + [] + end, + DirectionClause = case catch jlib:binary_to_integer(ID) of + I when is_integer(I), I >= 0 -> + case Direction of + before -> + [<<" and timestamp < ">>, ID, + <<" order by timestamp desc">>]; + aft -> + [<<" and timestamp > ">>, ID, + <<" order by timestamp asc">>]; + _ -> + [] + end; + _ -> + [] + end, + StartClause = case Start of + {_, _, _} -> + [<<" and timestamp >= ">>, + jlib:integer_to_binary(now_to_usec(Start))]; + _ -> + [] + end, + EndClause = case End of + {_, _, _} -> + [<<" and timestamp <= ">>, + jlib:integer_to_binary(now_to_usec(End))]; + _ -> + [] + end, + SUser = ejabberd_odbc:escape(LUser), + {[<<"select timestamp, xml, peer from archive where username='">>, + SUser, <<"'">>] ++ WithClause ++ StartClause ++ EndClause ++ + DirectionClause ++ LimitClause ++ [<<";">>], + [<<"select count(*) from archive where username='">>, + SUser, <<"'">>] ++ WithClause ++ StartClause ++ EndClause ++ [<<";">>]}. + +now_to_usec({MSec, Sec, USec}) -> + (MSec*1000000 + Sec)*1000000 + USec. + +usec_to_now(Int) -> + Secs = Int div 1000000, + USec = Int rem 1000000, + MSec = Secs div 1000000, + Sec = Secs rem 1000000, + {MSec, Sec, USec}. + +get_jids(Els) -> + lists:flatmap( + fun(#xmlel{name = <<"jid">>} = El) -> + J = jlib:string_to_jid(xml:get_tag_cdata(El)), + [jlib:jid_tolower(jlib:jid_remove_resource(J)), + jlib:jid_tolower(J)]; + (_) -> + [] + end, Els). + +update(LServer, Table, Fields, Vals, Where) -> + UPairs = lists:zipwith(fun (A, B) -> + <> + end, + Fields, Vals), + case ejabberd_odbc:sql_query(LServer, + [<<"update ">>, Table, <<" set ">>, + join(UPairs, <<", ">>), <<" where ">>, Where, + <<";">>]) + of + {updated, 1} -> {updated, 1}; + _ -> + ejabberd_odbc:sql_query(LServer, + [<<"insert into ">>, Table, <<"(">>, + join(Fields, <<", ">>), <<") values ('">>, + join(Vals, <<"', '">>), <<"');">>]) + end. + +%% Almost a copy of string:join/2. +join([], _Sep) -> []; +join([H | T], Sep) -> [H, [[Sep, X] || X <- T]]. + +mod_opt_type(cache_life_time) -> + fun (I) when is_integer(I), I > 0 -> I end; +mod_opt_type(cache_size) -> + fun (I) when is_integer(I), I > 0 -> I end; +mod_opt_type(db_type) -> fun gen_mod:v_db/1; +mod_opt_type(default) -> + fun (always) -> always; + (never) -> never; + (roster) -> roster + end; +mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1; +mod_opt_type(store_body_only) -> + fun (B) when is_boolean(B) -> B end; +mod_opt_type(_) -> + [cache_life_time, cache_size, db_type, default, iqdisc, + store_body_only]. diff --git a/src/mod_ping.erl b/src/mod_ping.erl index 0b332edec..7d030685c 100644 --- a/src/mod_ping.erl +++ b/src/mod_ping.erl @@ -57,7 +57,7 @@ handle_cast/2, handle_info/2, code_change/3]). -export([iq_ping/3, user_online/3, user_offline/3, - user_send/3, mod_opt_type/1]). + user_send/4, mod_opt_type/1]). -record(state, {host = <<"">>, @@ -214,8 +214,9 @@ user_online(_SID, JID, _Info) -> user_offline(_SID, JID, _Info) -> stop_ping(JID#jid.lserver, JID). -user_send(JID, _From, _Packet) -> - start_ping(JID#jid.lserver, JID). +user_send(Packet, _C2SState, JID, _From) -> + start_ping(JID#jid.lserver, JID), + Packet. %%==================================================================== %% Internal functions diff --git a/src/mod_service_log.erl b/src/mod_service_log.erl index e145c5ce6..db0f3df3b 100644 --- a/src/mod_service_log.erl +++ b/src/mod_service_log.erl @@ -29,8 +29,8 @@ -behaviour(gen_mod). --export([start/2, stop/1, log_user_send/3, - log_user_receive/4, mod_opt_type/1]). +-export([start/2, stop/1, log_user_send/4, + log_user_receive/5, mod_opt_type/1]). -include("ejabberd.hrl"). -include("logger.hrl"). @@ -51,11 +51,13 @@ stop(Host) -> ?MODULE, log_user_receive, 50), ok. -log_user_send(From, To, Packet) -> - log_packet(From, To, Packet, From#jid.lserver). +log_user_send(Packet, _C2SState, From, To) -> + log_packet(From, To, Packet, From#jid.lserver), + Packet. -log_user_receive(_JID, From, To, Packet) -> - log_packet(From, To, Packet, To#jid.lserver). +log_user_receive(Packet, _C2SState, _JID, From, To) -> + log_packet(From, To, Packet, To#jid.lserver), + Packet. log_packet(From, To, #xmlel{name = Name, attrs = Attrs, children = Els}, diff --git a/test/ejabberd_SUITE.erl b/test/ejabberd_SUITE.erl index 4f0823323..4d1a1fc7b 100644 --- a/test/ejabberd_SUITE.erl +++ b/test/ejabberd_SUITE.erl @@ -247,6 +247,10 @@ db_tests(mnesia) -> roster_subscribe_slave]}, {test_offline, [sequence], [offline_master, offline_slave]}, + {test_old_mam, [parallel], + [mam_old_master, mam_old_slave]}, + {test_new_mam, [parallel], + [mam_new_master, mam_new_slave]}, {test_carbons, [parallel], [carbons_master, carbons_slave]}, {test_client_state, [parallel], @@ -283,6 +287,10 @@ db_tests(_) -> roster_subscribe_slave]}, {test_offline, [sequence], [offline_master, offline_slave]}, + {test_old_mam, [parallel], + [mam_old_master, mam_old_slave]}, + {test_new_mam, [parallel], + [mam_new_master, mam_new_slave]}, {test_muc, [parallel], [muc_master, muc_slave]}, {test_announce, [sequence], @@ -1567,6 +1575,278 @@ carbons_slave(Config) -> ?recv1(#presence{from = Peer, type = unavailable}), disconnect(Config). +mam_old_master(Config) -> + mam_master(Config, ?NS_MAM_TMP). + +mam_new_master(Config) -> + mam_master(Config, ?NS_MAM_0). + +mam_master(Config, NS) -> + true = is_feature_advertised(Config, NS), + MyJID = my_jid(Config), + BareMyJID = jlib:jid_remove_resource(MyJID), + Peer = ?config(slave, Config), + send(Config, #presence{}), + ?recv1(#presence{}), + wait_for_slave(Config), + ?recv1(#presence{from = Peer}), + #iq{type = result, sub_els = []} = + send_recv(Config, + #iq{type = set, + sub_els = [#mam_prefs{xmlns = NS, + default = roster, + never = [MyJID]}]}), + if NS == ?NS_MAM_TMP -> + FakeArchived = #mam_archived{id = randoms:get_string(), + by = server_jid(Config)}, + send(Config, #message{to = MyJID, + sub_els = [FakeArchived], + body = [#text{data = <<"a">>}]}), + send(Config, #message{to = BareMyJID, + sub_els = [FakeArchived], + body = [#text{data = <<"b">>}]}), + %% NOTE: The server should strip fake archived tags, + %% i.e. the sub_els received should be []. + ?recv2(#message{body = [#text{data = <<"a">>}], sub_els = []}, + #message{body = [#text{data = <<"b">>}], sub_els = []}); + true -> + ok + end, + wait_for_slave(Config), + lists:foreach( + fun(N) -> + Text = #text{data = jlib:integer_to_binary(N)}, + send(Config, + #message{to = Peer, body = [Text]}) + end, lists:seq(1, 5)), + ?recv1(#presence{type = unavailable, from = Peer}), + mam_query_all(Config, NS), + mam_query_with(Config, Peer, NS), + %% mam_query_with(Config, jlib:jid_remove_resource(Peer)), + mam_query_rsm(Config, NS), + #iq{type = result, sub_els = []} = + send_recv(Config, #iq{type = set, + sub_els = [#mam_prefs{xmlns = NS, + default = never}]}), + disconnect(Config). + +mam_old_slave(Config) -> + mam_slave(Config, ?NS_MAM_TMP). + +mam_new_slave(Config) -> + mam_slave(Config, ?NS_MAM_0). + +mam_slave(Config, NS) -> + Peer = ?config(master, Config), + ServerJID = server_jid(Config), + wait_for_master(Config), + send(Config, #presence{}), + ?recv2(#presence{}, #presence{from = Peer}), + #iq{type = result, sub_els = []} = + send_recv(Config, + #iq{type = set, + sub_els = [#mam_prefs{xmlns = NS, default = always}]}), + wait_for_master(Config), + lists:foreach( + fun(N) -> + Text = #text{data = jlib:integer_to_binary(N)}, + ?recv1(#message{from = Peer, body = [Text], + sub_els = [#mam_archived{by = ServerJID}]}) + end, lists:seq(1, 5)), + #iq{type = result, sub_els = []} = + send_recv(Config, #iq{type = set, + sub_els = [#mam_prefs{xmlns = NS, default = never}]}), + disconnect(Config). + +mam_query_all(Config, NS) -> + QID = randoms:get_string(), + MyJID = my_jid(Config), + Peer = ?config(slave, Config), + I = send(Config, #iq{type = get, sub_els = [#mam_query{xmlns = NS, id = QID}]}), + maybe_recv_iq_result(NS, I), + Iter = if NS == ?NS_MAM_TMP -> lists:seq(1, 5); + true -> lists:seq(1, 5) ++ lists:seq(1, 5) + end, + lists:foreach( + fun(N) -> + Text = #text{data = jlib:integer_to_binary(N)}, + ?recv1(#message{to = MyJID, + sub_els = + [#mam_result{ + queryid = QID, + sub_els = + [#forwarded{ + delay = #delay{}, + sub_els = + [#message{ + from = MyJID, to = Peer, + body = [Text]}]}]}]}) + end, Iter), + if NS == ?NS_MAM_TMP -> + ?recv1(#iq{type = result, id = I, sub_els = []}); + true -> + ?recv1(#message{sub_els = [#mam_fin{id = QID}]}) + end. + +mam_query_with(Config, JID, NS) -> + MyJID = my_jid(Config), + Peer = ?config(slave, Config), + Query = if NS == ?NS_MAM_TMP -> + #mam_query{xmlns = NS, with = JID}; + true -> + Fs = [#xdata_field{var = <<"jid">>, + values = [jlib:jid_to_string(JID)]}], + #mam_query{xmlns = NS, + xdata = #xdata{type = submit, fields = Fs}} + end, + I = send(Config, #iq{type = get, sub_els = [Query]}), + Iter = if NS == ?NS_MAM_TMP -> lists:seq(1, 5); + true -> lists:seq(1, 5) ++ lists:seq(1, 5) + end, + maybe_recv_iq_result(NS, I), + lists:foreach( + fun(N) -> + Text = #text{data = jlib:integer_to_binary(N)}, + ?recv1(#message{to = MyJID, + sub_els = + [#mam_result{ + sub_els = + [#forwarded{ + delay = #delay{}, + sub_els = + [#message{ + from = MyJID, to = Peer, + body = [Text]}]}]}]}) + end, Iter), + if NS == ?NS_MAM_TMP -> + ?recv1(#iq{type = result, id = I, sub_els = []}); + true -> + ?recv1(#message{sub_els = [#mam_fin{}]}) + end. + +maybe_recv_iq_result(?NS_MAM_0, I1) -> + ?recv1(#iq{type = result, id = I1}); +maybe_recv_iq_result(_, _) -> + ok. + +mam_query_rsm(Config, NS) -> + MyJID = my_jid(Config), + Peer = ?config(slave, Config), + %% Get the first 3 items out of 5 + I1 = send(Config, + #iq{type = get, + sub_els = [#mam_query{xmlns = NS, rsm = #rsm_set{max = 3}}]}), + maybe_recv_iq_result(NS, I1), + lists:foreach( + fun(N) -> + Text = #text{data = jlib:integer_to_binary(N)}, + ?recv1(#message{to = MyJID, + sub_els = + [#mam_result{ + xmlns = NS, + sub_els = + [#forwarded{ + delay = #delay{}, + sub_els = + [#message{ + from = MyJID, to = Peer, + body = [Text]}]}]}]}) + end, lists:seq(1, 3)), + if NS == ?NS_MAM_TMP -> + ?recv1(#iq{type = result, id = I1, + sub_els = [#mam_query{xmlns = NS, + rsm = #rsm_set{last = Last, count = 5}}]}); + true -> + ?recv1(#message{sub_els = [#mam_fin{ + rsm = #rsm_set{last = Last, count = 10}}]}) + end, + %% Get the next items starting from the `Last`. + %% Limit the response to 2 items. + I2 = send(Config, + #iq{type = get, + sub_els = [#mam_query{xmlns = NS, + rsm = #rsm_set{max = 2, + 'after' = Last}}]}), + maybe_recv_iq_result(NS, I2), + lists:foreach( + fun(N) -> + Text = #text{data = jlib:integer_to_binary(N)}, + ?recv1(#message{to = MyJID, + sub_els = + [#mam_result{ + xmlns = NS, + sub_els = + [#forwarded{ + delay = #delay{}, + sub_els = + [#message{ + from = MyJID, to = Peer, + body = [Text]}]}]}]}) + end, lists:seq(4, 5)), + if NS == ?NS_MAM_TMP -> + ?recv1(#iq{type = result, id = I2, + sub_els = [#mam_query{ + xmlns = NS, + rsm = #rsm_set{ + count = 5, + first = #rsm_first{data = First}}}]}); + true -> + ?recv1(#message{ + sub_els = [#mam_fin{ + rsm = #rsm_set{ + count = 10, + first = #rsm_first{data = First}}}]}) + end, + %% Paging back. Should receive 2 elements: 2, 3. + I3 = send(Config, + #iq{type = get, + sub_els = [#mam_query{xmlns = NS, + rsm = #rsm_set{max = 2, + before = First}}]}), + maybe_recv_iq_result(NS, I3), + lists:foreach( + fun(N) -> + Text = #text{data = jlib:integer_to_binary(N)}, + ?recv1(#message{to = MyJID, + sub_els = + [#mam_result{ + xmlns = NS, + sub_els = + [#forwarded{ + delay = #delay{}, + sub_els = + [#message{ + from = MyJID, to = Peer, + body = [Text]}]}]}]}) + end, lists:seq(2, 3)), + if NS == ?NS_MAM_TMP -> + ?recv1(#iq{type = result, id = I3, + sub_els = [#mam_query{xmlns = NS, rsm = #rsm_set{count = 5}}]}); + true -> + ?recv1(#message{ + sub_els = [#mam_fin{rsm = #rsm_set{count = 10}}]}) + end, + %% Getting the item count. Should be 5 (or 10). + I4 = send(Config, + #iq{type = get, + sub_els = [#mam_query{xmlns = NS, + rsm = #rsm_set{max = 0}}]}), + maybe_recv_iq_result(NS, I4), + if NS == ?NS_MAM_TMP -> + ?recv1(#iq{type = result, id = I4, + sub_els = [#mam_query{ + xmlns = NS, + rsm = #rsm_set{count = 5, + first = undefined, + last = undefined}}]}); + true -> + ?recv1(#message{ + sub_els = [#mam_fin{ + rsm = #rsm_set{count = 10, + first = undefined, + last = undefined}}]}) + end. + client_state_master(Config) -> true = ?config(csi, Config), Peer = ?config(slave, Config), diff --git a/test/ejabberd_SUITE_data/ejabberd.yml b/test/ejabberd_SUITE_data/ejabberd.yml index 0a836b805..b1c95ffdf 100644 --- a/test/ejabberd_SUITE_data/ejabberd.yml +++ b/test/ejabberd_SUITE_data/ejabberd.yml @@ -39,6 +39,8 @@ host_config: versioning: true store_current_id: true db_type: odbc + mod_mam: + db_type: odbc mod_vcard: db_type: odbc mod_vcard_xupdate: @@ -90,6 +92,8 @@ Welcome to this XMPP server." versioning: true store_current_id: true db_type: odbc + mod_mam: + db_type: odbc mod_vcard: db_type: odbc mod_vcard_xupdate: @@ -147,6 +151,8 @@ Welcome to this XMPP server." versioning: true store_current_id: true db_type: odbc + mod_mam: + db_type: odbc mod_vcard: db_type: odbc mod_vcard_xupdate: @@ -196,6 +202,8 @@ Welcome to this XMPP server." versioning: true store_current_id: true db_type: internal + mod_mam: + db_type: internal mod_vcard: db_type: internal mod_vcard_xupdate: diff --git a/tools/xmpp_codec.erl b/tools/xmpp_codec.erl index 187098abc..0dbc62808 100644 --- a/tools/xmpp_codec.erl +++ b/tools/xmpp_codec.erl @@ -73,6 +73,65 @@ decode({xmlel, _name, _attrs, _} = _el, Opts) -> {<<"forwarded">>, <<"urn:xmpp:forward:0">>} -> decode_forwarded(<<"urn:xmpp:forward:0">>, IgnoreEls, _el); + {<<"fin">>, <<"urn:xmpp:mam:0">>} -> + decode_mam_fin(<<"urn:xmpp:mam:0">>, IgnoreEls, _el); + {<<"prefs">>, <<"urn:xmpp:mam:0">>} -> + decode_mam_prefs(<<"urn:xmpp:mam:0">>, IgnoreEls, _el); + {<<"prefs">>, <<"urn:xmpp:mam:tmp">>} -> + decode_mam_prefs(<<"urn:xmpp:mam:tmp">>, IgnoreEls, + _el); + {<<"always">>, <<"urn:xmpp:mam:tmp">>} -> + decode_mam_always(<<"urn:xmpp:mam:tmp">>, IgnoreEls, + _el); + {<<"never">>, <<"urn:xmpp:mam:tmp">>} -> + decode_mam_never(<<"urn:xmpp:mam:tmp">>, IgnoreEls, + _el); + {<<"jid">>, <<"urn:xmpp:mam:tmp">>} -> + decode_mam_jid(<<"urn:xmpp:mam:tmp">>, IgnoreEls, _el); + {<<"result">>, <<"urn:xmpp:mam:0">>} -> + decode_mam_result(<<"urn:xmpp:mam:0">>, IgnoreEls, _el); + {<<"result">>, <<"urn:xmpp:mam:tmp">>} -> + decode_mam_result(<<"urn:xmpp:mam:tmp">>, IgnoreEls, + _el); + {<<"archived">>, <<"urn:xmpp:mam:tmp">>} -> + decode_mam_archived(<<"urn:xmpp:mam:tmp">>, IgnoreEls, + _el); + {<<"query">>, <<"urn:xmpp:mam:0">>} -> + decode_mam_query(<<"urn:xmpp:mam:0">>, IgnoreEls, _el); + {<<"query">>, <<"urn:xmpp:mam:tmp">>} -> + decode_mam_query(<<"urn:xmpp:mam:tmp">>, IgnoreEls, + _el); + {<<"with">>, <<"urn:xmpp:mam:tmp">>} -> + decode_mam_with(<<"urn:xmpp:mam:tmp">>, IgnoreEls, _el); + {<<"end">>, <<"urn:xmpp:mam:tmp">>} -> + decode_mam_end(<<"urn:xmpp:mam:tmp">>, IgnoreEls, _el); + {<<"start">>, <<"urn:xmpp:mam:tmp">>} -> + decode_mam_start(<<"urn:xmpp:mam:tmp">>, IgnoreEls, + _el); + {<<"set">>, <<"http://jabber.org/protocol/rsm">>} -> + decode_rsm_set(<<"http://jabber.org/protocol/rsm">>, + IgnoreEls, _el); + {<<"first">>, <<"http://jabber.org/protocol/rsm">>} -> + decode_rsm_first(<<"http://jabber.org/protocol/rsm">>, + IgnoreEls, _el); + {<<"max">>, <<"http://jabber.org/protocol/rsm">>} -> + decode_rsm_max(<<"http://jabber.org/protocol/rsm">>, + IgnoreEls, _el); + {<<"index">>, <<"http://jabber.org/protocol/rsm">>} -> + decode_rsm_index(<<"http://jabber.org/protocol/rsm">>, + IgnoreEls, _el); + {<<"count">>, <<"http://jabber.org/protocol/rsm">>} -> + decode_rsm_count(<<"http://jabber.org/protocol/rsm">>, + IgnoreEls, _el); + {<<"last">>, <<"http://jabber.org/protocol/rsm">>} -> + decode_rsm_last(<<"http://jabber.org/protocol/rsm">>, + IgnoreEls, _el); + {<<"before">>, <<"http://jabber.org/protocol/rsm">>} -> + decode_rsm_before(<<"http://jabber.org/protocol/rsm">>, + IgnoreEls, _el); + {<<"after">>, <<"http://jabber.org/protocol/rsm">>} -> + decode_rsm_after(<<"http://jabber.org/protocol/rsm">>, + IgnoreEls, _el); {<<"x">>, <<"http://jabber.org/protocol/muc">>} -> decode_muc(<<"http://jabber.org/protocol/muc">>, IgnoreEls, _el); @@ -1038,6 +1097,36 @@ is_known_tag({xmlel, _name, _attrs, _} = _el) -> {<<"enable">>, <<"urn:xmpp:carbons:2">>} -> true; {<<"disable">>, <<"urn:xmpp:carbons:2">>} -> true; {<<"forwarded">>, <<"urn:xmpp:forward:0">>} -> true; + {<<"fin">>, <<"urn:xmpp:mam:0">>} -> true; + {<<"prefs">>, <<"urn:xmpp:mam:0">>} -> true; + {<<"prefs">>, <<"urn:xmpp:mam:tmp">>} -> true; + {<<"always">>, <<"urn:xmpp:mam:tmp">>} -> true; + {<<"never">>, <<"urn:xmpp:mam:tmp">>} -> true; + {<<"jid">>, <<"urn:xmpp:mam:tmp">>} -> true; + {<<"result">>, <<"urn:xmpp:mam:0">>} -> true; + {<<"result">>, <<"urn:xmpp:mam:tmp">>} -> true; + {<<"archived">>, <<"urn:xmpp:mam:tmp">>} -> true; + {<<"query">>, <<"urn:xmpp:mam:0">>} -> true; + {<<"query">>, <<"urn:xmpp:mam:tmp">>} -> true; + {<<"with">>, <<"urn:xmpp:mam:tmp">>} -> true; + {<<"end">>, <<"urn:xmpp:mam:tmp">>} -> true; + {<<"start">>, <<"urn:xmpp:mam:tmp">>} -> true; + {<<"set">>, <<"http://jabber.org/protocol/rsm">>} -> + true; + {<<"first">>, <<"http://jabber.org/protocol/rsm">>} -> + true; + {<<"max">>, <<"http://jabber.org/protocol/rsm">>} -> + true; + {<<"index">>, <<"http://jabber.org/protocol/rsm">>} -> + true; + {<<"count">>, <<"http://jabber.org/protocol/rsm">>} -> + true; + {<<"last">>, <<"http://jabber.org/protocol/rsm">>} -> + true; + {<<"before">>, <<"http://jabber.org/protocol/rsm">>} -> + true; + {<<"after">>, <<"http://jabber.org/protocol/rsm">>} -> + true; {<<"x">>, <<"http://jabber.org/protocol/muc">>} -> true; {<<"query">>, <<"http://jabber.org/protocol/muc#admin">>} -> @@ -1978,6 +2067,24 @@ encode({muc_admin, _} = Query) -> encode({muc, _, _} = X) -> encode_muc(X, [{<<"xmlns">>, <<"http://jabber.org/protocol/muc">>}]); +encode({rsm_first, _, _} = First) -> + encode_rsm_first(First, + [{<<"xmlns">>, <<"http://jabber.org/protocol/rsm">>}]); +encode({rsm_set, _, _, _, _, _, _, _} = Set) -> + encode_rsm_set(Set, + [{<<"xmlns">>, <<"http://jabber.org/protocol/rsm">>}]); +encode({mam_query, _, _, _, _, _, _, _} = Query) -> + encode_mam_query(Query, []); +encode({mam_archived, _, _} = Archived) -> + encode_mam_archived(Archived, + [{<<"xmlns">>, <<"urn:xmpp:mam:tmp">>}]); +encode({mam_result, _, _, _, _} = Result) -> + encode_mam_result(Result, []); +encode({mam_prefs, _, _, _, _} = Prefs) -> + encode_mam_prefs(Prefs, []); +encode({mam_fin, _, _} = Fin) -> + encode_mam_fin(Fin, + [{<<"xmlns">>, <<"urn:xmpp:mam:0">>}]); encode({forwarded, _, _} = Forwarded) -> encode_forwarded(Forwarded, [{<<"xmlns">>, <<"urn:xmpp:forward:0">>}]); @@ -2196,6 +2303,12 @@ get_ns({muc_admin, _}) -> <<"http://jabber.org/protocol/muc#admin">>; get_ns({muc, _, _}) -> <<"http://jabber.org/protocol/muc">>; +get_ns({rsm_first, _, _}) -> + <<"http://jabber.org/protocol/rsm">>; +get_ns({rsm_set, _, _, _, _, _, _, _}) -> + <<"http://jabber.org/protocol/rsm">>; +get_ns({mam_archived, _, _}) -> <<"urn:xmpp:mam:tmp">>; +get_ns({mam_fin, _, _}) -> <<"urn:xmpp:mam:0">>; get_ns({forwarded, _, _}) -> <<"urn:xmpp:forward:0">>; get_ns({carbons_disable}) -> <<"urn:xmpp:carbons:2">>; get_ns({carbons_enable}) -> <<"urn:xmpp:carbons:2">>; @@ -2384,6 +2497,15 @@ pp(muc_item, 7) -> pp(muc_actor, 2) -> [jid, nick]; pp(muc_admin, 1) -> [items]; pp(muc, 2) -> [history, password]; +pp(rsm_first, 2) -> [index, data]; +pp(rsm_set, 7) -> + ['after', before, count, first, index, last, max]; +pp(mam_query, 7) -> + [xmlns, id, start, 'end', with, rsm, xdata]; +pp(mam_archived, 2) -> [by, id]; +pp(mam_result, 4) -> [xmlns, queryid, id, sub_els]; +pp(mam_prefs, 4) -> [xmlns, default, always, never]; +pp(mam_fin, 2) -> [id, rsm]; pp(forwarded, 2) -> [delay, sub_els]; pp(carbons_disable, 0) -> []; pp(carbons_enable, 0) -> []; @@ -3583,6 +3705,1084 @@ encode_forwarded({forwarded, Delay, __Els}, [{<<"xmlns">>, <<"urn:xmpp:delay">>}]) | _acc]. +decode_mam_fin(__TopXMLNS, __IgnoreEls, + {xmlel, <<"fin">>, _attrs, _els}) -> + Rsm = decode_mam_fin_els(__TopXMLNS, __IgnoreEls, _els, + undefined), + Id = decode_mam_fin_attrs(__TopXMLNS, _attrs, + undefined), + {mam_fin, Id, Rsm}. + +decode_mam_fin_els(__TopXMLNS, __IgnoreEls, [], Rsm) -> + Rsm; +decode_mam_fin_els(__TopXMLNS, __IgnoreEls, + [{xmlel, <<"set">>, _attrs, _} = _el | _els], Rsm) -> + _xmlns = get_attr(<<"xmlns">>, _attrs), + if _xmlns == <<"http://jabber.org/protocol/rsm">> -> + decode_mam_fin_els(__TopXMLNS, __IgnoreEls, _els, + decode_rsm_set(_xmlns, __IgnoreEls, _el)); + true -> + decode_mam_fin_els(__TopXMLNS, __IgnoreEls, _els, Rsm) + end; +decode_mam_fin_els(__TopXMLNS, __IgnoreEls, [_ | _els], + Rsm) -> + decode_mam_fin_els(__TopXMLNS, __IgnoreEls, _els, Rsm). + +decode_mam_fin_attrs(__TopXMLNS, + [{<<"queryid">>, _val} | _attrs], _Id) -> + decode_mam_fin_attrs(__TopXMLNS, _attrs, _val); +decode_mam_fin_attrs(__TopXMLNS, [_ | _attrs], Id) -> + decode_mam_fin_attrs(__TopXMLNS, _attrs, Id); +decode_mam_fin_attrs(__TopXMLNS, [], Id) -> + decode_mam_fin_attr_queryid(__TopXMLNS, Id). + +encode_mam_fin({mam_fin, Id, Rsm}, _xmlns_attrs) -> + _els = lists:reverse('encode_mam_fin_$rsm'(Rsm, [])), + _attrs = encode_mam_fin_attr_queryid(Id, _xmlns_attrs), + {xmlel, <<"fin">>, _attrs, _els}. + +'encode_mam_fin_$rsm'(undefined, _acc) -> _acc; +'encode_mam_fin_$rsm'(Rsm, _acc) -> + [encode_rsm_set(Rsm, + [{<<"xmlns">>, <<"http://jabber.org/protocol/rsm">>}]) + | _acc]. + +decode_mam_fin_attr_queryid(__TopXMLNS, undefined) -> + undefined; +decode_mam_fin_attr_queryid(__TopXMLNS, _val) -> _val. + +encode_mam_fin_attr_queryid(undefined, _acc) -> _acc; +encode_mam_fin_attr_queryid(_val, _acc) -> + [{<<"queryid">>, _val} | _acc]. + +decode_mam_prefs(__TopXMLNS, __IgnoreEls, + {xmlel, <<"prefs">>, _attrs, _els}) -> + {Never, Always} = decode_mam_prefs_els(__TopXMLNS, + __IgnoreEls, _els, [], []), + {Default, Xmlns} = decode_mam_prefs_attrs(__TopXMLNS, + _attrs, undefined, undefined), + {mam_prefs, Xmlns, Default, Always, Never}. + +decode_mam_prefs_els(__TopXMLNS, __IgnoreEls, [], Never, + Always) -> + {Never, Always}; +decode_mam_prefs_els(__TopXMLNS, __IgnoreEls, + [{xmlel, <<"always">>, _attrs, _} = _el | _els], Never, + Always) -> + _xmlns = get_attr(<<"xmlns">>, _attrs), + if _xmlns == <<>>; _xmlns == __TopXMLNS -> + decode_mam_prefs_els(__TopXMLNS, __IgnoreEls, _els, + Never, + decode_mam_always(__TopXMLNS, __IgnoreEls, + _el)); + true -> + decode_mam_prefs_els(__TopXMLNS, __IgnoreEls, _els, + Never, Always) + end; +decode_mam_prefs_els(__TopXMLNS, __IgnoreEls, + [{xmlel, <<"never">>, _attrs, _} = _el | _els], Never, + Always) -> + _xmlns = get_attr(<<"xmlns">>, _attrs), + if _xmlns == <<>>; _xmlns == __TopXMLNS -> + decode_mam_prefs_els(__TopXMLNS, __IgnoreEls, _els, + decode_mam_never(__TopXMLNS, __IgnoreEls, _el), + Always); + true -> + decode_mam_prefs_els(__TopXMLNS, __IgnoreEls, _els, + Never, Always) + end; +decode_mam_prefs_els(__TopXMLNS, __IgnoreEls, + [_ | _els], Never, Always) -> + decode_mam_prefs_els(__TopXMLNS, __IgnoreEls, _els, + Never, Always). + +decode_mam_prefs_attrs(__TopXMLNS, + [{<<"default">>, _val} | _attrs], _Default, Xmlns) -> + decode_mam_prefs_attrs(__TopXMLNS, _attrs, _val, Xmlns); +decode_mam_prefs_attrs(__TopXMLNS, + [{<<"xmlns">>, _val} | _attrs], Default, _Xmlns) -> + decode_mam_prefs_attrs(__TopXMLNS, _attrs, Default, + _val); +decode_mam_prefs_attrs(__TopXMLNS, [_ | _attrs], + Default, Xmlns) -> + decode_mam_prefs_attrs(__TopXMLNS, _attrs, Default, + Xmlns); +decode_mam_prefs_attrs(__TopXMLNS, [], Default, + Xmlns) -> + {decode_mam_prefs_attr_default(__TopXMLNS, Default), + decode_mam_prefs_attr_xmlns(__TopXMLNS, Xmlns)}. + +encode_mam_prefs({mam_prefs, Xmlns, Default, Always, + Never}, + _xmlns_attrs) -> + _els = lists:reverse('encode_mam_prefs_$never'(Never, + 'encode_mam_prefs_$always'(Always, + []))), + _attrs = encode_mam_prefs_attr_xmlns(Xmlns, + encode_mam_prefs_attr_default(Default, + _xmlns_attrs)), + {xmlel, <<"prefs">>, _attrs, _els}. + +'encode_mam_prefs_$never'([], _acc) -> _acc; +'encode_mam_prefs_$never'(Never, _acc) -> + [encode_mam_never(Never, + [{<<"xmlns">>, <<"urn:xmpp:mam:tmp">>}]) + | _acc]. + +'encode_mam_prefs_$always'([], _acc) -> _acc; +'encode_mam_prefs_$always'(Always, _acc) -> + [encode_mam_always(Always, + [{<<"xmlns">>, <<"urn:xmpp:mam:tmp">>}]) + | _acc]. + +decode_mam_prefs_attr_default(__TopXMLNS, undefined) -> + undefined; +decode_mam_prefs_attr_default(__TopXMLNS, _val) -> + case catch dec_enum(_val, [always, never, roster]) of + {'EXIT', _} -> + erlang:error({xmpp_codec, + {bad_attr_value, <<"default">>, <<"prefs">>, + __TopXMLNS}}); + _res -> _res + end. + +encode_mam_prefs_attr_default(undefined, _acc) -> _acc; +encode_mam_prefs_attr_default(_val, _acc) -> + [{<<"default">>, enc_enum(_val)} | _acc]. + +decode_mam_prefs_attr_xmlns(__TopXMLNS, undefined) -> + undefined; +decode_mam_prefs_attr_xmlns(__TopXMLNS, _val) -> _val. + +encode_mam_prefs_attr_xmlns(undefined, _acc) -> _acc; +encode_mam_prefs_attr_xmlns(_val, _acc) -> + [{<<"xmlns">>, _val} | _acc]. + +decode_mam_always(__TopXMLNS, __IgnoreEls, + {xmlel, <<"always">>, _attrs, _els}) -> + Jids = decode_mam_always_els(__TopXMLNS, __IgnoreEls, + _els, []), + Jids. + +decode_mam_always_els(__TopXMLNS, __IgnoreEls, [], + Jids) -> + lists:reverse(Jids); +decode_mam_always_els(__TopXMLNS, __IgnoreEls, + [{xmlel, <<"jid">>, _attrs, _} = _el | _els], Jids) -> + _xmlns = get_attr(<<"xmlns">>, _attrs), + if _xmlns == <<>>; _xmlns == __TopXMLNS -> + decode_mam_always_els(__TopXMLNS, __IgnoreEls, _els, + case decode_mam_jid(__TopXMLNS, __IgnoreEls, + _el) + of + [] -> Jids; + _new_el -> [_new_el | Jids] + end); + true -> + decode_mam_always_els(__TopXMLNS, __IgnoreEls, _els, + Jids) + end; +decode_mam_always_els(__TopXMLNS, __IgnoreEls, + [_ | _els], Jids) -> + decode_mam_always_els(__TopXMLNS, __IgnoreEls, _els, + Jids). + +encode_mam_always(Jids, _xmlns_attrs) -> + _els = lists:reverse('encode_mam_always_$jids'(Jids, + [])), + _attrs = _xmlns_attrs, + {xmlel, <<"always">>, _attrs, _els}. + +'encode_mam_always_$jids'([], _acc) -> _acc; +'encode_mam_always_$jids'([Jids | _els], _acc) -> + 'encode_mam_always_$jids'(_els, + [encode_mam_jid(Jids, []) | _acc]). + +decode_mam_never(__TopXMLNS, __IgnoreEls, + {xmlel, <<"never">>, _attrs, _els}) -> + Jids = decode_mam_never_els(__TopXMLNS, __IgnoreEls, + _els, []), + Jids. + +decode_mam_never_els(__TopXMLNS, __IgnoreEls, [], + Jids) -> + lists:reverse(Jids); +decode_mam_never_els(__TopXMLNS, __IgnoreEls, + [{xmlel, <<"jid">>, _attrs, _} = _el | _els], Jids) -> + _xmlns = get_attr(<<"xmlns">>, _attrs), + if _xmlns == <<>>; _xmlns == __TopXMLNS -> + decode_mam_never_els(__TopXMLNS, __IgnoreEls, _els, + case decode_mam_jid(__TopXMLNS, __IgnoreEls, + _el) + of + [] -> Jids; + _new_el -> [_new_el | Jids] + end); + true -> + decode_mam_never_els(__TopXMLNS, __IgnoreEls, _els, + Jids) + end; +decode_mam_never_els(__TopXMLNS, __IgnoreEls, + [_ | _els], Jids) -> + decode_mam_never_els(__TopXMLNS, __IgnoreEls, _els, + Jids). + +encode_mam_never(Jids, _xmlns_attrs) -> + _els = lists:reverse('encode_mam_never_$jids'(Jids, + [])), + _attrs = _xmlns_attrs, + {xmlel, <<"never">>, _attrs, _els}. + +'encode_mam_never_$jids'([], _acc) -> _acc; +'encode_mam_never_$jids'([Jids | _els], _acc) -> + 'encode_mam_never_$jids'(_els, + [encode_mam_jid(Jids, []) | _acc]). + +decode_mam_jid(__TopXMLNS, __IgnoreEls, + {xmlel, <<"jid">>, _attrs, _els}) -> + Cdata = decode_mam_jid_els(__TopXMLNS, __IgnoreEls, + _els, <<>>), + Cdata. + +decode_mam_jid_els(__TopXMLNS, __IgnoreEls, [], + Cdata) -> + decode_mam_jid_cdata(__TopXMLNS, Cdata); +decode_mam_jid_els(__TopXMLNS, __IgnoreEls, + [{xmlcdata, _data} | _els], Cdata) -> + decode_mam_jid_els(__TopXMLNS, __IgnoreEls, _els, + <>); +decode_mam_jid_els(__TopXMLNS, __IgnoreEls, [_ | _els], + Cdata) -> + decode_mam_jid_els(__TopXMLNS, __IgnoreEls, _els, + Cdata). + +encode_mam_jid(Cdata, _xmlns_attrs) -> + _els = encode_mam_jid_cdata(Cdata, []), + _attrs = _xmlns_attrs, + {xmlel, <<"jid">>, _attrs, _els}. + +decode_mam_jid_cdata(__TopXMLNS, <<>>) -> + erlang:error({xmpp_codec, + {missing_cdata, <<>>, <<"jid">>, __TopXMLNS}}); +decode_mam_jid_cdata(__TopXMLNS, _val) -> + case catch dec_jid(_val) of + {'EXIT', _} -> + erlang:error({xmpp_codec, + {bad_cdata_value, <<>>, <<"jid">>, __TopXMLNS}}); + _res -> _res + end. + +encode_mam_jid_cdata(_val, _acc) -> + [{xmlcdata, enc_jid(_val)} | _acc]. + +decode_mam_result(__TopXMLNS, __IgnoreEls, + {xmlel, <<"result">>, _attrs, _els}) -> + __Els = decode_mam_result_els(__TopXMLNS, __IgnoreEls, + _els, []), + {Queryid, Xmlns, Id} = + decode_mam_result_attrs(__TopXMLNS, _attrs, undefined, + undefined, undefined), + {mam_result, Xmlns, Queryid, Id, __Els}. + +decode_mam_result_els(__TopXMLNS, __IgnoreEls, [], + __Els) -> + lists:reverse(__Els); +decode_mam_result_els(__TopXMLNS, __IgnoreEls, + [{xmlel, _, _, _} = _el | _els], __Els) -> + if __IgnoreEls -> + decode_mam_result_els(__TopXMLNS, __IgnoreEls, _els, + [_el | __Els]); + true -> + case is_known_tag(_el) of + true -> + decode_mam_result_els(__TopXMLNS, __IgnoreEls, _els, + [decode(_el) | __Els]); + false -> + decode_mam_result_els(__TopXMLNS, __IgnoreEls, _els, + __Els) + end + end; +decode_mam_result_els(__TopXMLNS, __IgnoreEls, + [_ | _els], __Els) -> + decode_mam_result_els(__TopXMLNS, __IgnoreEls, _els, + __Els). + +decode_mam_result_attrs(__TopXMLNS, + [{<<"queryid">>, _val} | _attrs], _Queryid, Xmlns, + Id) -> + decode_mam_result_attrs(__TopXMLNS, _attrs, _val, Xmlns, + Id); +decode_mam_result_attrs(__TopXMLNS, + [{<<"xmlns">>, _val} | _attrs], Queryid, _Xmlns, Id) -> + decode_mam_result_attrs(__TopXMLNS, _attrs, Queryid, + _val, Id); +decode_mam_result_attrs(__TopXMLNS, + [{<<"id">>, _val} | _attrs], Queryid, Xmlns, _Id) -> + decode_mam_result_attrs(__TopXMLNS, _attrs, Queryid, + Xmlns, _val); +decode_mam_result_attrs(__TopXMLNS, [_ | _attrs], + Queryid, Xmlns, Id) -> + decode_mam_result_attrs(__TopXMLNS, _attrs, Queryid, + Xmlns, Id); +decode_mam_result_attrs(__TopXMLNS, [], Queryid, Xmlns, + Id) -> + {decode_mam_result_attr_queryid(__TopXMLNS, Queryid), + decode_mam_result_attr_xmlns(__TopXMLNS, Xmlns), + decode_mam_result_attr_id(__TopXMLNS, Id)}. + +encode_mam_result({mam_result, Xmlns, Queryid, Id, + __Els}, + _xmlns_attrs) -> + _els = [encode(_el) || _el <- __Els], + _attrs = encode_mam_result_attr_id(Id, + encode_mam_result_attr_xmlns(Xmlns, + encode_mam_result_attr_queryid(Queryid, + _xmlns_attrs))), + {xmlel, <<"result">>, _attrs, _els}. + +decode_mam_result_attr_queryid(__TopXMLNS, undefined) -> + undefined; +decode_mam_result_attr_queryid(__TopXMLNS, _val) -> + _val. + +encode_mam_result_attr_queryid(undefined, _acc) -> _acc; +encode_mam_result_attr_queryid(_val, _acc) -> + [{<<"queryid">>, _val} | _acc]. + +decode_mam_result_attr_xmlns(__TopXMLNS, undefined) -> + undefined; +decode_mam_result_attr_xmlns(__TopXMLNS, _val) -> _val. + +encode_mam_result_attr_xmlns(undefined, _acc) -> _acc; +encode_mam_result_attr_xmlns(_val, _acc) -> + [{<<"xmlns">>, _val} | _acc]. + +decode_mam_result_attr_id(__TopXMLNS, undefined) -> + undefined; +decode_mam_result_attr_id(__TopXMLNS, _val) -> _val. + +encode_mam_result_attr_id(undefined, _acc) -> _acc; +encode_mam_result_attr_id(_val, _acc) -> + [{<<"id">>, _val} | _acc]. + +decode_mam_archived(__TopXMLNS, __IgnoreEls, + {xmlel, <<"archived">>, _attrs, _els}) -> + {Id, By} = decode_mam_archived_attrs(__TopXMLNS, _attrs, + undefined, undefined), + {mam_archived, By, Id}. + +decode_mam_archived_attrs(__TopXMLNS, + [{<<"id">>, _val} | _attrs], _Id, By) -> + decode_mam_archived_attrs(__TopXMLNS, _attrs, _val, By); +decode_mam_archived_attrs(__TopXMLNS, + [{<<"by">>, _val} | _attrs], Id, _By) -> + decode_mam_archived_attrs(__TopXMLNS, _attrs, Id, _val); +decode_mam_archived_attrs(__TopXMLNS, [_ | _attrs], Id, + By) -> + decode_mam_archived_attrs(__TopXMLNS, _attrs, Id, By); +decode_mam_archived_attrs(__TopXMLNS, [], Id, By) -> + {decode_mam_archived_attr_id(__TopXMLNS, Id), + decode_mam_archived_attr_by(__TopXMLNS, By)}. + +encode_mam_archived({mam_archived, By, Id}, + _xmlns_attrs) -> + _els = [], + _attrs = encode_mam_archived_attr_by(By, + encode_mam_archived_attr_id(Id, + _xmlns_attrs)), + {xmlel, <<"archived">>, _attrs, _els}. + +decode_mam_archived_attr_id(__TopXMLNS, undefined) -> + undefined; +decode_mam_archived_attr_id(__TopXMLNS, _val) -> _val. + +encode_mam_archived_attr_id(undefined, _acc) -> _acc; +encode_mam_archived_attr_id(_val, _acc) -> + [{<<"id">>, _val} | _acc]. + +decode_mam_archived_attr_by(__TopXMLNS, undefined) -> + erlang:error({xmpp_codec, + {missing_attr, <<"by">>, <<"archived">>, __TopXMLNS}}); +decode_mam_archived_attr_by(__TopXMLNS, _val) -> + case catch dec_jid(_val) of + {'EXIT', _} -> + erlang:error({xmpp_codec, + {bad_attr_value, <<"by">>, <<"archived">>, + __TopXMLNS}}); + _res -> _res + end. + +encode_mam_archived_attr_by(_val, _acc) -> + [{<<"by">>, enc_jid(_val)} | _acc]. + +decode_mam_query(__TopXMLNS, __IgnoreEls, + {xmlel, <<"query">>, _attrs, _els}) -> + {Xdata, End, Start, With, Rsm} = + decode_mam_query_els(__TopXMLNS, __IgnoreEls, _els, + undefined, undefined, undefined, undefined, + undefined), + {Id, Xmlns} = decode_mam_query_attrs(__TopXMLNS, _attrs, + undefined, undefined), + {mam_query, Xmlns, Id, Start, End, With, Rsm, Xdata}. + +decode_mam_query_els(__TopXMLNS, __IgnoreEls, [], Xdata, + End, Start, With, Rsm) -> + {Xdata, End, Start, With, Rsm}; +decode_mam_query_els(__TopXMLNS, __IgnoreEls, + [{xmlel, <<"start">>, _attrs, _} = _el | _els], Xdata, + End, Start, With, Rsm) -> + _xmlns = get_attr(<<"xmlns">>, _attrs), + if _xmlns == <<>>; _xmlns == __TopXMLNS -> + decode_mam_query_els(__TopXMLNS, __IgnoreEls, _els, + Xdata, End, + decode_mam_start(__TopXMLNS, __IgnoreEls, _el), + With, Rsm); + true -> + decode_mam_query_els(__TopXMLNS, __IgnoreEls, _els, + Xdata, End, Start, With, Rsm) + end; +decode_mam_query_els(__TopXMLNS, __IgnoreEls, + [{xmlel, <<"end">>, _attrs, _} = _el | _els], Xdata, + End, Start, With, Rsm) -> + _xmlns = get_attr(<<"xmlns">>, _attrs), + if _xmlns == <<>>; _xmlns == __TopXMLNS -> + decode_mam_query_els(__TopXMLNS, __IgnoreEls, _els, + Xdata, + decode_mam_end(__TopXMLNS, __IgnoreEls, _el), + Start, With, Rsm); + true -> + decode_mam_query_els(__TopXMLNS, __IgnoreEls, _els, + Xdata, End, Start, With, Rsm) + end; +decode_mam_query_els(__TopXMLNS, __IgnoreEls, + [{xmlel, <<"with">>, _attrs, _} = _el | _els], Xdata, + End, Start, With, Rsm) -> + _xmlns = get_attr(<<"xmlns">>, _attrs), + if _xmlns == <<>>; _xmlns == __TopXMLNS -> + decode_mam_query_els(__TopXMLNS, __IgnoreEls, _els, + Xdata, End, Start, + decode_mam_with(__TopXMLNS, __IgnoreEls, _el), + Rsm); + true -> + decode_mam_query_els(__TopXMLNS, __IgnoreEls, _els, + Xdata, End, Start, With, Rsm) + end; +decode_mam_query_els(__TopXMLNS, __IgnoreEls, + [{xmlel, <<"set">>, _attrs, _} = _el | _els], Xdata, + End, Start, With, Rsm) -> + _xmlns = get_attr(<<"xmlns">>, _attrs), + if _xmlns == <<"http://jabber.org/protocol/rsm">> -> + decode_mam_query_els(__TopXMLNS, __IgnoreEls, _els, + Xdata, End, Start, With, + decode_rsm_set(_xmlns, __IgnoreEls, _el)); + true -> + decode_mam_query_els(__TopXMLNS, __IgnoreEls, _els, + Xdata, End, Start, With, Rsm) + end; +decode_mam_query_els(__TopXMLNS, __IgnoreEls, + [{xmlel, <<"x">>, _attrs, _} = _el | _els], Xdata, End, + Start, With, Rsm) -> + _xmlns = get_attr(<<"xmlns">>, _attrs), + if _xmlns == <<"jabber:x:data">> -> + decode_mam_query_els(__TopXMLNS, __IgnoreEls, _els, + decode_xdata(_xmlns, __IgnoreEls, _el), End, + Start, With, Rsm); + true -> + decode_mam_query_els(__TopXMLNS, __IgnoreEls, _els, + Xdata, End, Start, With, Rsm) + end; +decode_mam_query_els(__TopXMLNS, __IgnoreEls, + [_ | _els], Xdata, End, Start, With, Rsm) -> + decode_mam_query_els(__TopXMLNS, __IgnoreEls, _els, + Xdata, End, Start, With, Rsm). + +decode_mam_query_attrs(__TopXMLNS, + [{<<"queryid">>, _val} | _attrs], _Id, Xmlns) -> + decode_mam_query_attrs(__TopXMLNS, _attrs, _val, Xmlns); +decode_mam_query_attrs(__TopXMLNS, + [{<<"xmlns">>, _val} | _attrs], Id, _Xmlns) -> + decode_mam_query_attrs(__TopXMLNS, _attrs, Id, _val); +decode_mam_query_attrs(__TopXMLNS, [_ | _attrs], Id, + Xmlns) -> + decode_mam_query_attrs(__TopXMLNS, _attrs, Id, Xmlns); +decode_mam_query_attrs(__TopXMLNS, [], Id, Xmlns) -> + {decode_mam_query_attr_queryid(__TopXMLNS, Id), + decode_mam_query_attr_xmlns(__TopXMLNS, Xmlns)}. + +encode_mam_query({mam_query, Xmlns, Id, Start, End, + With, Rsm, Xdata}, + _xmlns_attrs) -> + _els = lists:reverse('encode_mam_query_$xdata'(Xdata, + 'encode_mam_query_$end'(End, + 'encode_mam_query_$start'(Start, + 'encode_mam_query_$with'(With, + 'encode_mam_query_$rsm'(Rsm, + [])))))), + _attrs = encode_mam_query_attr_xmlns(Xmlns, + encode_mam_query_attr_queryid(Id, + _xmlns_attrs)), + {xmlel, <<"query">>, _attrs, _els}. + +'encode_mam_query_$xdata'(undefined, _acc) -> _acc; +'encode_mam_query_$xdata'(Xdata, _acc) -> + [encode_xdata(Xdata, + [{<<"xmlns">>, <<"jabber:x:data">>}]) + | _acc]. + +'encode_mam_query_$end'(undefined, _acc) -> _acc; +'encode_mam_query_$end'(End, _acc) -> + [encode_mam_end(End, + [{<<"xmlns">>, <<"urn:xmpp:mam:tmp">>}]) + | _acc]. + +'encode_mam_query_$start'(undefined, _acc) -> _acc; +'encode_mam_query_$start'(Start, _acc) -> + [encode_mam_start(Start, + [{<<"xmlns">>, <<"urn:xmpp:mam:tmp">>}]) + | _acc]. + +'encode_mam_query_$with'(undefined, _acc) -> _acc; +'encode_mam_query_$with'(With, _acc) -> + [encode_mam_with(With, + [{<<"xmlns">>, <<"urn:xmpp:mam:tmp">>}]) + | _acc]. + +'encode_mam_query_$rsm'(undefined, _acc) -> _acc; +'encode_mam_query_$rsm'(Rsm, _acc) -> + [encode_rsm_set(Rsm, + [{<<"xmlns">>, <<"http://jabber.org/protocol/rsm">>}]) + | _acc]. + +decode_mam_query_attr_queryid(__TopXMLNS, undefined) -> + undefined; +decode_mam_query_attr_queryid(__TopXMLNS, _val) -> _val. + +encode_mam_query_attr_queryid(undefined, _acc) -> _acc; +encode_mam_query_attr_queryid(_val, _acc) -> + [{<<"queryid">>, _val} | _acc]. + +decode_mam_query_attr_xmlns(__TopXMLNS, undefined) -> + undefined; +decode_mam_query_attr_xmlns(__TopXMLNS, _val) -> _val. + +encode_mam_query_attr_xmlns(undefined, _acc) -> _acc; +encode_mam_query_attr_xmlns(_val, _acc) -> + [{<<"xmlns">>, _val} | _acc]. + +decode_mam_with(__TopXMLNS, __IgnoreEls, + {xmlel, <<"with">>, _attrs, _els}) -> + Cdata = decode_mam_with_els(__TopXMLNS, __IgnoreEls, + _els, <<>>), + Cdata. + +decode_mam_with_els(__TopXMLNS, __IgnoreEls, [], + Cdata) -> + decode_mam_with_cdata(__TopXMLNS, Cdata); +decode_mam_with_els(__TopXMLNS, __IgnoreEls, + [{xmlcdata, _data} | _els], Cdata) -> + decode_mam_with_els(__TopXMLNS, __IgnoreEls, _els, + <>); +decode_mam_with_els(__TopXMLNS, __IgnoreEls, [_ | _els], + Cdata) -> + decode_mam_with_els(__TopXMLNS, __IgnoreEls, _els, + Cdata). + +encode_mam_with(Cdata, _xmlns_attrs) -> + _els = encode_mam_with_cdata(Cdata, []), + _attrs = _xmlns_attrs, + {xmlel, <<"with">>, _attrs, _els}. + +decode_mam_with_cdata(__TopXMLNS, <<>>) -> + erlang:error({xmpp_codec, + {missing_cdata, <<>>, <<"with">>, __TopXMLNS}}); +decode_mam_with_cdata(__TopXMLNS, _val) -> + case catch dec_jid(_val) of + {'EXIT', _} -> + erlang:error({xmpp_codec, + {bad_cdata_value, <<>>, <<"with">>, __TopXMLNS}}); + _res -> _res + end. + +encode_mam_with_cdata(_val, _acc) -> + [{xmlcdata, enc_jid(_val)} | _acc]. + +decode_mam_end(__TopXMLNS, __IgnoreEls, + {xmlel, <<"end">>, _attrs, _els}) -> + Cdata = decode_mam_end_els(__TopXMLNS, __IgnoreEls, + _els, <<>>), + Cdata. + +decode_mam_end_els(__TopXMLNS, __IgnoreEls, [], + Cdata) -> + decode_mam_end_cdata(__TopXMLNS, Cdata); +decode_mam_end_els(__TopXMLNS, __IgnoreEls, + [{xmlcdata, _data} | _els], Cdata) -> + decode_mam_end_els(__TopXMLNS, __IgnoreEls, _els, + <>); +decode_mam_end_els(__TopXMLNS, __IgnoreEls, [_ | _els], + Cdata) -> + decode_mam_end_els(__TopXMLNS, __IgnoreEls, _els, + Cdata). + +encode_mam_end(Cdata, _xmlns_attrs) -> + _els = encode_mam_end_cdata(Cdata, []), + _attrs = _xmlns_attrs, + {xmlel, <<"end">>, _attrs, _els}. + +decode_mam_end_cdata(__TopXMLNS, <<>>) -> + erlang:error({xmpp_codec, + {missing_cdata, <<>>, <<"end">>, __TopXMLNS}}); +decode_mam_end_cdata(__TopXMLNS, _val) -> + case catch dec_utc(_val) of + {'EXIT', _} -> + erlang:error({xmpp_codec, + {bad_cdata_value, <<>>, <<"end">>, __TopXMLNS}}); + _res -> _res + end. + +encode_mam_end_cdata(_val, _acc) -> + [{xmlcdata, enc_utc(_val)} | _acc]. + +decode_mam_start(__TopXMLNS, __IgnoreEls, + {xmlel, <<"start">>, _attrs, _els}) -> + Cdata = decode_mam_start_els(__TopXMLNS, __IgnoreEls, + _els, <<>>), + Cdata. + +decode_mam_start_els(__TopXMLNS, __IgnoreEls, [], + Cdata) -> + decode_mam_start_cdata(__TopXMLNS, Cdata); +decode_mam_start_els(__TopXMLNS, __IgnoreEls, + [{xmlcdata, _data} | _els], Cdata) -> + decode_mam_start_els(__TopXMLNS, __IgnoreEls, _els, + <>); +decode_mam_start_els(__TopXMLNS, __IgnoreEls, + [_ | _els], Cdata) -> + decode_mam_start_els(__TopXMLNS, __IgnoreEls, _els, + Cdata). + +encode_mam_start(Cdata, _xmlns_attrs) -> + _els = encode_mam_start_cdata(Cdata, []), + _attrs = _xmlns_attrs, + {xmlel, <<"start">>, _attrs, _els}. + +decode_mam_start_cdata(__TopXMLNS, <<>>) -> + erlang:error({xmpp_codec, + {missing_cdata, <<>>, <<"start">>, __TopXMLNS}}); +decode_mam_start_cdata(__TopXMLNS, _val) -> + case catch dec_utc(_val) of + {'EXIT', _} -> + erlang:error({xmpp_codec, + {bad_cdata_value, <<>>, <<"start">>, __TopXMLNS}}); + _res -> _res + end. + +encode_mam_start_cdata(_val, _acc) -> + [{xmlcdata, enc_utc(_val)} | _acc]. + +decode_rsm_set(__TopXMLNS, __IgnoreEls, + {xmlel, <<"set">>, _attrs, _els}) -> + {After, Last, First, Count, Before, Max, Index} = + decode_rsm_set_els(__TopXMLNS, __IgnoreEls, _els, + undefined, undefined, undefined, undefined, + undefined, undefined, undefined), + {rsm_set, After, Before, Count, First, Index, Last, + Max}. + +decode_rsm_set_els(__TopXMLNS, __IgnoreEls, [], After, + Last, First, Count, Before, Max, Index) -> + {After, Last, First, Count, Before, Max, Index}; +decode_rsm_set_els(__TopXMLNS, __IgnoreEls, + [{xmlel, <<"after">>, _attrs, _} = _el | _els], After, + Last, First, Count, Before, Max, Index) -> + _xmlns = get_attr(<<"xmlns">>, _attrs), + if _xmlns == <<>>; _xmlns == __TopXMLNS -> + decode_rsm_set_els(__TopXMLNS, __IgnoreEls, _els, + decode_rsm_after(__TopXMLNS, __IgnoreEls, _el), + Last, First, Count, Before, Max, Index); + true -> + decode_rsm_set_els(__TopXMLNS, __IgnoreEls, _els, After, + Last, First, Count, Before, Max, Index) + end; +decode_rsm_set_els(__TopXMLNS, __IgnoreEls, + [{xmlel, <<"before">>, _attrs, _} = _el | _els], After, + Last, First, Count, Before, Max, Index) -> + _xmlns = get_attr(<<"xmlns">>, _attrs), + if _xmlns == <<>>; _xmlns == __TopXMLNS -> + decode_rsm_set_els(__TopXMLNS, __IgnoreEls, _els, After, + Last, First, Count, + decode_rsm_before(__TopXMLNS, __IgnoreEls, _el), + Max, Index); + true -> + decode_rsm_set_els(__TopXMLNS, __IgnoreEls, _els, After, + Last, First, Count, Before, Max, Index) + end; +decode_rsm_set_els(__TopXMLNS, __IgnoreEls, + [{xmlel, <<"count">>, _attrs, _} = _el | _els], After, + Last, First, Count, Before, Max, Index) -> + _xmlns = get_attr(<<"xmlns">>, _attrs), + if _xmlns == <<>>; _xmlns == __TopXMLNS -> + decode_rsm_set_els(__TopXMLNS, __IgnoreEls, _els, After, + Last, First, + decode_rsm_count(__TopXMLNS, __IgnoreEls, _el), + Before, Max, Index); + true -> + decode_rsm_set_els(__TopXMLNS, __IgnoreEls, _els, After, + Last, First, Count, Before, Max, Index) + end; +decode_rsm_set_els(__TopXMLNS, __IgnoreEls, + [{xmlel, <<"first">>, _attrs, _} = _el | _els], After, + Last, First, Count, Before, Max, Index) -> + _xmlns = get_attr(<<"xmlns">>, _attrs), + if _xmlns == <<>>; _xmlns == __TopXMLNS -> + decode_rsm_set_els(__TopXMLNS, __IgnoreEls, _els, After, + Last, + decode_rsm_first(__TopXMLNS, __IgnoreEls, _el), + Count, Before, Max, Index); + true -> + decode_rsm_set_els(__TopXMLNS, __IgnoreEls, _els, After, + Last, First, Count, Before, Max, Index) + end; +decode_rsm_set_els(__TopXMLNS, __IgnoreEls, + [{xmlel, <<"index">>, _attrs, _} = _el | _els], After, + Last, First, Count, Before, Max, Index) -> + _xmlns = get_attr(<<"xmlns">>, _attrs), + if _xmlns == <<>>; _xmlns == __TopXMLNS -> + decode_rsm_set_els(__TopXMLNS, __IgnoreEls, _els, After, + Last, First, Count, Before, Max, + decode_rsm_index(__TopXMLNS, __IgnoreEls, _el)); + true -> + decode_rsm_set_els(__TopXMLNS, __IgnoreEls, _els, After, + Last, First, Count, Before, Max, Index) + end; +decode_rsm_set_els(__TopXMLNS, __IgnoreEls, + [{xmlel, <<"last">>, _attrs, _} = _el | _els], After, + Last, First, Count, Before, Max, Index) -> + _xmlns = get_attr(<<"xmlns">>, _attrs), + if _xmlns == <<>>; _xmlns == __TopXMLNS -> + decode_rsm_set_els(__TopXMLNS, __IgnoreEls, _els, After, + decode_rsm_last(__TopXMLNS, __IgnoreEls, _el), + First, Count, Before, Max, Index); + true -> + decode_rsm_set_els(__TopXMLNS, __IgnoreEls, _els, After, + Last, First, Count, Before, Max, Index) + end; +decode_rsm_set_els(__TopXMLNS, __IgnoreEls, + [{xmlel, <<"max">>, _attrs, _} = _el | _els], After, + Last, First, Count, Before, Max, Index) -> + _xmlns = get_attr(<<"xmlns">>, _attrs), + if _xmlns == <<>>; _xmlns == __TopXMLNS -> + decode_rsm_set_els(__TopXMLNS, __IgnoreEls, _els, After, + Last, First, Count, Before, + decode_rsm_max(__TopXMLNS, __IgnoreEls, _el), + Index); + true -> + decode_rsm_set_els(__TopXMLNS, __IgnoreEls, _els, After, + Last, First, Count, Before, Max, Index) + end; +decode_rsm_set_els(__TopXMLNS, __IgnoreEls, [_ | _els], + After, Last, First, Count, Before, Max, Index) -> + decode_rsm_set_els(__TopXMLNS, __IgnoreEls, _els, After, + Last, First, Count, Before, Max, Index). + +encode_rsm_set({rsm_set, After, Before, Count, First, + Index, Last, Max}, + _xmlns_attrs) -> + _els = lists:reverse('encode_rsm_set_$after'(After, + 'encode_rsm_set_$last'(Last, + 'encode_rsm_set_$first'(First, + 'encode_rsm_set_$count'(Count, + 'encode_rsm_set_$before'(Before, + 'encode_rsm_set_$max'(Max, + 'encode_rsm_set_$index'(Index, + [])))))))), + _attrs = _xmlns_attrs, + {xmlel, <<"set">>, _attrs, _els}. + +'encode_rsm_set_$after'(undefined, _acc) -> _acc; +'encode_rsm_set_$after'(After, _acc) -> + [encode_rsm_after(After, []) | _acc]. + +'encode_rsm_set_$last'(undefined, _acc) -> _acc; +'encode_rsm_set_$last'(Last, _acc) -> + [encode_rsm_last(Last, []) | _acc]. + +'encode_rsm_set_$first'(undefined, _acc) -> _acc; +'encode_rsm_set_$first'(First, _acc) -> + [encode_rsm_first(First, []) | _acc]. + +'encode_rsm_set_$count'(undefined, _acc) -> _acc; +'encode_rsm_set_$count'(Count, _acc) -> + [encode_rsm_count(Count, []) | _acc]. + +'encode_rsm_set_$before'(undefined, _acc) -> _acc; +'encode_rsm_set_$before'(Before, _acc) -> + [encode_rsm_before(Before, []) | _acc]. + +'encode_rsm_set_$max'(undefined, _acc) -> _acc; +'encode_rsm_set_$max'(Max, _acc) -> + [encode_rsm_max(Max, []) | _acc]. + +'encode_rsm_set_$index'(undefined, _acc) -> _acc; +'encode_rsm_set_$index'(Index, _acc) -> + [encode_rsm_index(Index, []) | _acc]. + +decode_rsm_first(__TopXMLNS, __IgnoreEls, + {xmlel, <<"first">>, _attrs, _els}) -> + Data = decode_rsm_first_els(__TopXMLNS, __IgnoreEls, + _els, <<>>), + Index = decode_rsm_first_attrs(__TopXMLNS, _attrs, + undefined), + {rsm_first, Index, Data}. + +decode_rsm_first_els(__TopXMLNS, __IgnoreEls, [], + Data) -> + decode_rsm_first_cdata(__TopXMLNS, Data); +decode_rsm_first_els(__TopXMLNS, __IgnoreEls, + [{xmlcdata, _data} | _els], Data) -> + decode_rsm_first_els(__TopXMLNS, __IgnoreEls, _els, + <>); +decode_rsm_first_els(__TopXMLNS, __IgnoreEls, + [_ | _els], Data) -> + decode_rsm_first_els(__TopXMLNS, __IgnoreEls, _els, + Data). + +decode_rsm_first_attrs(__TopXMLNS, + [{<<"index">>, _val} | _attrs], _Index) -> + decode_rsm_first_attrs(__TopXMLNS, _attrs, _val); +decode_rsm_first_attrs(__TopXMLNS, [_ | _attrs], + Index) -> + decode_rsm_first_attrs(__TopXMLNS, _attrs, Index); +decode_rsm_first_attrs(__TopXMLNS, [], Index) -> + decode_rsm_first_attr_index(__TopXMLNS, Index). + +encode_rsm_first({rsm_first, Index, Data}, + _xmlns_attrs) -> + _els = encode_rsm_first_cdata(Data, []), + _attrs = encode_rsm_first_attr_index(Index, + _xmlns_attrs), + {xmlel, <<"first">>, _attrs, _els}. + +decode_rsm_first_attr_index(__TopXMLNS, undefined) -> + undefined; +decode_rsm_first_attr_index(__TopXMLNS, _val) -> + case catch dec_int(_val, 0, infinity) of + {'EXIT', _} -> + erlang:error({xmpp_codec, + {bad_attr_value, <<"index">>, <<"first">>, + __TopXMLNS}}); + _res -> _res + end. + +encode_rsm_first_attr_index(undefined, _acc) -> _acc; +encode_rsm_first_attr_index(_val, _acc) -> + [{<<"index">>, enc_int(_val)} | _acc]. + +decode_rsm_first_cdata(__TopXMLNS, <<>>) -> undefined; +decode_rsm_first_cdata(__TopXMLNS, _val) -> _val. + +encode_rsm_first_cdata(undefined, _acc) -> _acc; +encode_rsm_first_cdata(_val, _acc) -> + [{xmlcdata, _val} | _acc]. + +decode_rsm_max(__TopXMLNS, __IgnoreEls, + {xmlel, <<"max">>, _attrs, _els}) -> + Cdata = decode_rsm_max_els(__TopXMLNS, __IgnoreEls, + _els, <<>>), + Cdata. + +decode_rsm_max_els(__TopXMLNS, __IgnoreEls, [], + Cdata) -> + decode_rsm_max_cdata(__TopXMLNS, Cdata); +decode_rsm_max_els(__TopXMLNS, __IgnoreEls, + [{xmlcdata, _data} | _els], Cdata) -> + decode_rsm_max_els(__TopXMLNS, __IgnoreEls, _els, + <>); +decode_rsm_max_els(__TopXMLNS, __IgnoreEls, [_ | _els], + Cdata) -> + decode_rsm_max_els(__TopXMLNS, __IgnoreEls, _els, + Cdata). + +encode_rsm_max(Cdata, _xmlns_attrs) -> + _els = encode_rsm_max_cdata(Cdata, []), + _attrs = _xmlns_attrs, + {xmlel, <<"max">>, _attrs, _els}. + +decode_rsm_max_cdata(__TopXMLNS, <<>>) -> undefined; +decode_rsm_max_cdata(__TopXMLNS, _val) -> + case catch dec_int(_val, 0, infinity) of + {'EXIT', _} -> + erlang:error({xmpp_codec, + {bad_cdata_value, <<>>, <<"max">>, __TopXMLNS}}); + _res -> _res + end. + +encode_rsm_max_cdata(undefined, _acc) -> _acc; +encode_rsm_max_cdata(_val, _acc) -> + [{xmlcdata, enc_int(_val)} | _acc]. + +decode_rsm_index(__TopXMLNS, __IgnoreEls, + {xmlel, <<"index">>, _attrs, _els}) -> + Cdata = decode_rsm_index_els(__TopXMLNS, __IgnoreEls, + _els, <<>>), + Cdata. + +decode_rsm_index_els(__TopXMLNS, __IgnoreEls, [], + Cdata) -> + decode_rsm_index_cdata(__TopXMLNS, Cdata); +decode_rsm_index_els(__TopXMLNS, __IgnoreEls, + [{xmlcdata, _data} | _els], Cdata) -> + decode_rsm_index_els(__TopXMLNS, __IgnoreEls, _els, + <>); +decode_rsm_index_els(__TopXMLNS, __IgnoreEls, + [_ | _els], Cdata) -> + decode_rsm_index_els(__TopXMLNS, __IgnoreEls, _els, + Cdata). + +encode_rsm_index(Cdata, _xmlns_attrs) -> + _els = encode_rsm_index_cdata(Cdata, []), + _attrs = _xmlns_attrs, + {xmlel, <<"index">>, _attrs, _els}. + +decode_rsm_index_cdata(__TopXMLNS, <<>>) -> undefined; +decode_rsm_index_cdata(__TopXMLNS, _val) -> + case catch dec_int(_val, 0, infinity) of + {'EXIT', _} -> + erlang:error({xmpp_codec, + {bad_cdata_value, <<>>, <<"index">>, __TopXMLNS}}); + _res -> _res + end. + +encode_rsm_index_cdata(undefined, _acc) -> _acc; +encode_rsm_index_cdata(_val, _acc) -> + [{xmlcdata, enc_int(_val)} | _acc]. + +decode_rsm_count(__TopXMLNS, __IgnoreEls, + {xmlel, <<"count">>, _attrs, _els}) -> + Cdata = decode_rsm_count_els(__TopXMLNS, __IgnoreEls, + _els, <<>>), + Cdata. + +decode_rsm_count_els(__TopXMLNS, __IgnoreEls, [], + Cdata) -> + decode_rsm_count_cdata(__TopXMLNS, Cdata); +decode_rsm_count_els(__TopXMLNS, __IgnoreEls, + [{xmlcdata, _data} | _els], Cdata) -> + decode_rsm_count_els(__TopXMLNS, __IgnoreEls, _els, + <>); +decode_rsm_count_els(__TopXMLNS, __IgnoreEls, + [_ | _els], Cdata) -> + decode_rsm_count_els(__TopXMLNS, __IgnoreEls, _els, + Cdata). + +encode_rsm_count(Cdata, _xmlns_attrs) -> + _els = encode_rsm_count_cdata(Cdata, []), + _attrs = _xmlns_attrs, + {xmlel, <<"count">>, _attrs, _els}. + +decode_rsm_count_cdata(__TopXMLNS, <<>>) -> undefined; +decode_rsm_count_cdata(__TopXMLNS, _val) -> + case catch dec_int(_val, 0, infinity) of + {'EXIT', _} -> + erlang:error({xmpp_codec, + {bad_cdata_value, <<>>, <<"count">>, __TopXMLNS}}); + _res -> _res + end. + +encode_rsm_count_cdata(undefined, _acc) -> _acc; +encode_rsm_count_cdata(_val, _acc) -> + [{xmlcdata, enc_int(_val)} | _acc]. + +decode_rsm_last(__TopXMLNS, __IgnoreEls, + {xmlel, <<"last">>, _attrs, _els}) -> + Cdata = decode_rsm_last_els(__TopXMLNS, __IgnoreEls, + _els, <<>>), + Cdata. + +decode_rsm_last_els(__TopXMLNS, __IgnoreEls, [], + Cdata) -> + decode_rsm_last_cdata(__TopXMLNS, Cdata); +decode_rsm_last_els(__TopXMLNS, __IgnoreEls, + [{xmlcdata, _data} | _els], Cdata) -> + decode_rsm_last_els(__TopXMLNS, __IgnoreEls, _els, + <>); +decode_rsm_last_els(__TopXMLNS, __IgnoreEls, [_ | _els], + Cdata) -> + decode_rsm_last_els(__TopXMLNS, __IgnoreEls, _els, + Cdata). + +encode_rsm_last(Cdata, _xmlns_attrs) -> + _els = encode_rsm_last_cdata(Cdata, []), + _attrs = _xmlns_attrs, + {xmlel, <<"last">>, _attrs, _els}. + +decode_rsm_last_cdata(__TopXMLNS, <<>>) -> undefined; +decode_rsm_last_cdata(__TopXMLNS, _val) -> _val. + +encode_rsm_last_cdata(undefined, _acc) -> _acc; +encode_rsm_last_cdata(_val, _acc) -> + [{xmlcdata, _val} | _acc]. + +decode_rsm_before(__TopXMLNS, __IgnoreEls, + {xmlel, <<"before">>, _attrs, _els}) -> + Cdata = decode_rsm_before_els(__TopXMLNS, __IgnoreEls, + _els, <<>>), + Cdata. + +decode_rsm_before_els(__TopXMLNS, __IgnoreEls, [], + Cdata) -> + decode_rsm_before_cdata(__TopXMLNS, Cdata); +decode_rsm_before_els(__TopXMLNS, __IgnoreEls, + [{xmlcdata, _data} | _els], Cdata) -> + decode_rsm_before_els(__TopXMLNS, __IgnoreEls, _els, + <>); +decode_rsm_before_els(__TopXMLNS, __IgnoreEls, + [_ | _els], Cdata) -> + decode_rsm_before_els(__TopXMLNS, __IgnoreEls, _els, + Cdata). + +encode_rsm_before(Cdata, _xmlns_attrs) -> + _els = encode_rsm_before_cdata(Cdata, []), + _attrs = _xmlns_attrs, + {xmlel, <<"before">>, _attrs, _els}. + +decode_rsm_before_cdata(__TopXMLNS, <<>>) -> undefined; +decode_rsm_before_cdata(__TopXMLNS, _val) -> _val. + +encode_rsm_before_cdata(undefined, _acc) -> _acc; +encode_rsm_before_cdata(_val, _acc) -> + [{xmlcdata, _val} | _acc]. + +decode_rsm_after(__TopXMLNS, __IgnoreEls, + {xmlel, <<"after">>, _attrs, _els}) -> + Cdata = decode_rsm_after_els(__TopXMLNS, __IgnoreEls, + _els, <<>>), + Cdata. + +decode_rsm_after_els(__TopXMLNS, __IgnoreEls, [], + Cdata) -> + decode_rsm_after_cdata(__TopXMLNS, Cdata); +decode_rsm_after_els(__TopXMLNS, __IgnoreEls, + [{xmlcdata, _data} | _els], Cdata) -> + decode_rsm_after_els(__TopXMLNS, __IgnoreEls, _els, + <>); +decode_rsm_after_els(__TopXMLNS, __IgnoreEls, + [_ | _els], Cdata) -> + decode_rsm_after_els(__TopXMLNS, __IgnoreEls, _els, + Cdata). + +encode_rsm_after(Cdata, _xmlns_attrs) -> + _els = encode_rsm_after_cdata(Cdata, []), + _attrs = _xmlns_attrs, + {xmlel, <<"after">>, _attrs, _els}. + +decode_rsm_after_cdata(__TopXMLNS, <<>>) -> undefined; +decode_rsm_after_cdata(__TopXMLNS, _val) -> _val. + +encode_rsm_after_cdata(undefined, _acc) -> _acc; +encode_rsm_after_cdata(_val, _acc) -> + [{xmlcdata, _val} | _acc]. + decode_muc(__TopXMLNS, __IgnoreEls, {xmlel, <<"x">>, _attrs, _els}) -> History = decode_muc_els(__TopXMLNS, __IgnoreEls, _els, diff --git a/tools/xmpp_codec.hrl b/tools/xmpp_codec.hrl index af5903c93..567946fe2 100644 --- a/tools/xmpp_codec.hrl +++ b/tools/xmpp_codec.hrl @@ -9,6 +9,14 @@ -record(sasl_success, {text :: any()}). +-record(mam_result, {xmlns :: binary(), + queryid :: binary(), + id :: binary(), + sub_els = [] :: [any()]}). + +-record(rsm_first, {index :: non_neg_integer(), + data :: binary()}). + -record(text, {lang :: binary(), data :: binary()}). @@ -176,6 +184,11 @@ -record(shim, {headers = [] :: [{binary(),'undefined' | binary()}]}). +-record(mam_prefs, {xmlns :: binary(), + default :: 'always' | 'never' | 'roster', + always = [] :: [any()], + never = [] :: [any()]}). + -record(caps, {hash :: binary(), node :: binary(), ver :: any()}). @@ -194,6 +207,9 @@ -record(carbons_sent, {forwarded :: #forwarded{}}). +-record(mam_archived, {by :: any(), + id :: binary()}). + -record(p1_rebind, {}). -record(compress_failure, {reason :: 'processing-failed' | 'setup-failed' | 'unsupported-method'}). @@ -263,6 +279,17 @@ -record(vcard_org, {name :: binary(), units = [] :: [binary()]}). +-record(rsm_set, {'after' :: binary(), + before :: binary(), + count :: non_neg_integer(), + first :: #rsm_first{}, + index :: non_neg_integer(), + last :: binary(), + max :: non_neg_integer()}). + +-record(mam_fin, {id :: binary(), + rsm :: #rsm_set{}}). + -record(vcard_tel, {home = false :: boolean(), work = false :: boolean(), voice = false :: boolean(), @@ -343,6 +370,14 @@ items = [] :: [[#xdata_field{}]], fields = [] :: [#xdata_field{}]}). +-record(mam_query, {xmlns :: binary(), + id :: binary(), + start :: any(), + 'end' :: any(), + with :: any(), + rsm :: #rsm_set{}, + xdata :: #xdata{}}). + -record(muc_owner, {destroy :: #muc_owner_destroy{}, config :: #xdata{}}). diff --git a/tools/xmpp_codec.spec b/tools/xmpp_codec.spec index e8cf0612b..dfa516ef5 100644 --- a/tools/xmpp_codec.spec +++ b/tools/xmpp_codec.spec @@ -2063,6 +2063,156 @@ refs = [#ref{name = muc_history, min = 0, max = 1, label = '$history'}]}). +-xml(rsm_after, + #elem{name = <<"after">>, + xmlns = <<"http://jabber.org/protocol/rsm">>, + result = '$cdata'}). + +-xml(rsm_before, + #elem{name = <<"before">>, + xmlns = <<"http://jabber.org/protocol/rsm">>, + result = '$cdata'}). + +-xml(rsm_last, + #elem{name = <<"last">>, + xmlns = <<"http://jabber.org/protocol/rsm">>, + result = '$cdata'}). + +-xml(rsm_count, + #elem{name = <<"count">>, result = '$cdata', + xmlns = <<"http://jabber.org/protocol/rsm">>, + cdata = #cdata{dec = {dec_int, [0, infinity]}, + enc = {enc_int, []}}}). + +-xml(rsm_index, + #elem{name = <<"index">>, result = '$cdata', + xmlns = <<"http://jabber.org/protocol/rsm">>, + cdata = #cdata{dec = {dec_int, [0, infinity]}, + enc = {enc_int, []}}}). + +-xml(rsm_max, + #elem{name = <<"max">>, result = '$cdata', + xmlns = <<"http://jabber.org/protocol/rsm">>, + cdata = #cdata{dec = {dec_int, [0, infinity]}, + enc = {enc_int, []}}}). + +-xml(rsm_first, + #elem{name = <<"first">>, + xmlns = <<"http://jabber.org/protocol/rsm">>, + result = {rsm_first, '$index', '$data'}, + cdata = #cdata{label = '$data'}, + attrs = [#attr{name = <<"index">>, + dec = {dec_int, [0, infinity]}, + enc = {enc_int, []}}]}). + +-xml(rsm_set, + #elem{name = <<"set">>, + xmlns = <<"http://jabber.org/protocol/rsm">>, + result = {rsm_set, '$after', '$before', '$count', + '$first', '$index', '$last', '$max'}, + refs = [#ref{name = rsm_after, label = '$after', min = 0, max = 1}, + #ref{name = rsm_before, label = '$before', min = 0, max = 1}, + #ref{name = rsm_count, label = '$count', min = 0, max = 1}, + #ref{name = rsm_first, label = '$first', min = 0, max = 1}, + #ref{name = rsm_index, label = '$index', min = 0, max = 1}, + #ref{name = rsm_last, label = '$last', min = 0, max = 1}, + #ref{name = rsm_max, label = '$max', min = 0, max = 1}]}). + +-xml(mam_start, + #elem{name = <<"start">>, + xmlns = <<"urn:xmpp:mam:tmp">>, + result = '$cdata', + cdata = #cdata{required = true, + dec = {dec_utc, []}, + enc = {enc_utc, []}}}). + +-xml(mam_end, + #elem{name = <<"end">>, + xmlns = <<"urn:xmpp:mam:tmp">>, + result = '$cdata', + cdata = #cdata{required = true, + dec = {dec_utc, []}, + enc = {enc_utc, []}}}). + +-xml(mam_with, + #elem{name = <<"with">>, + xmlns = <<"urn:xmpp:mam:tmp">>, + result = '$cdata', + cdata = #cdata{required = true, + dec = {dec_jid, []}, + enc = {enc_jid, []}}}). + +-xml(mam_query, + #elem{name = <<"query">>, + xmlns = [<<"urn:xmpp:mam:0">>, <<"urn:xmpp:mam:tmp">>], + result = {mam_query, '$xmlns', '$id', '$start', '$end', '$with', + '$rsm', '$xdata'}, + attrs = [#attr{name = <<"queryid">>, label = '$id'}, + #attr{name = <<"xmlns">>}], + refs = [#ref{name = mam_start, min = 0, max = 1, label = '$start'}, + #ref{name = mam_end, min = 0, max = 1, label = '$end'}, + #ref{name = mam_with, min = 0, max = 1, label = '$with'}, + #ref{name = rsm_set, min = 0, max = 1, label = '$rsm'}, + #ref{name = xdata, min = 0, max = 1, label = '$xdata'}]}). + +-xml(mam_archived, + #elem{name = <<"archived">>, + xmlns = <<"urn:xmpp:mam:tmp">>, + result = {mam_archived, '$by', '$id'}, + attrs = [#attr{name = <<"id">>}, + #attr{name = <<"by">>, + required = true, + dec = {dec_jid, []}, + enc = {enc_jid, []}}]}). + +-xml(mam_result, + #elem{name = <<"result">>, + xmlns = [<<"urn:xmpp:mam:0">>, <<"urn:xmpp:mam:tmp">>], + result = {mam_result, '$xmlns', '$queryid', '$id', '$_els'}, + attrs = [#attr{name = <<"queryid">>}, + #attr{name = <<"xmlns">>}, + #attr{name = <<"id">>}]}). + +-xml(mam_jid, + #elem{name = <<"jid">>, + xmlns = <<"urn:xmpp:mam:tmp">>, + result = '$cdata', + cdata = #cdata{required = true, + dec = {dec_jid, []}, + enc = {enc_jid, []}}}). + +-xml(mam_never, + #elem{name = <<"never">>, + xmlns = <<"urn:xmpp:mam:tmp">>, + result = '$jids', + refs = [#ref{name = mam_jid, label = '$jids', default = []}]}). + +-xml(mam_always, + #elem{name = <<"always">>, + xmlns = <<"urn:xmpp:mam:tmp">>, + result = '$jids', + refs = [#ref{name = mam_jid, label = '$jids', default = []}]}). + +-xml(mam_prefs, + #elem{name = <<"prefs">>, + xmlns = [<<"urn:xmpp:mam:0">>, <<"urn:xmpp:mam:tmp">>], + result = {mam_prefs, '$xmlns', '$default', '$always', '$never'}, + attrs = [#attr{name = <<"default">>, + dec = {dec_enum, [[always, never, roster]]}, + enc = {enc_enum, []}}, + #attr{name = <<"xmlns">>}], + refs = [#ref{name = mam_always, label = '$always', + min = 0, max = 1, default = []}, + #ref{name = mam_never, label = '$never', + min = 0, max = 1, default = []}]}). + +-xml(mam_fin, + #elem{name = <<"fin">>, + xmlns = <<"urn:xmpp:mam:0">>, + result = {mam_fin, '$id', '$rsm'}, + attrs = [#attr{name = <<"queryid">>, label = '$id'}], + refs = [#ref{name = rsm_set, min = 0, max = 1, label = '$rsm'}]}). + -xml(forwarded, #elem{name = <<"forwarded">>, xmlns = <<"urn:xmpp:forward:0">>,