aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/ns.hrl2
-rw-r--r--sql/lite.sql24
-rw-r--r--sql/mysql.sql25
-rw-r--r--sql/pg.sql29
-rw-r--r--src/ejabberd_c2s.erl32
-rw-r--r--src/mod_caps.erl24
-rw-r--r--src/mod_carboncopy.erl13
-rw-r--r--src/mod_mam.erl823
-rw-r--r--src/mod_ping.erl7
-rw-r--r--src/mod_service_log.erl14
-rw-r--r--test/ejabberd_SUITE.erl280
-rw-r--r--test/ejabberd_SUITE_data/ejabberd.yml8
-rw-r--r--tools/xmpp_codec.erl1200
-rw-r--r--tools/xmpp_codec.hrl35
-rw-r--r--tools/xmpp_codec.spec150
15 files changed, 2624 insertions, 42 deletions
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 <ekhramtsov@process-one.net>
+%%% @doc
+%%% Message Archive Management (XEP-0313)
+%%% @end
+%%% Created : 4 Jul 2013 by Evgeniy Khramtsov <ekhramtsov@process-one.net>
+%%%
+%%%
+%%% 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) ->
+ <<A/binary, "='", B/binary, "'">>
+ 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,
+ <<Cdata/binary, _data/binary>>);
+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,
+ <<Cdata/binary, _data/binary>>);
+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,
+ <<Cdata/binary, _data/binary>>);
+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,
+ <<Cdata/binary, _data/binary>>);
+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,
+ <<Data/binary, _data/binary>>);
+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,
+ <<Cdata/binary, _data/binary>>);
+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,
+ <<Cdata/binary, _data/binary>>);
+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,
+ <<Cdata/binary, _data/binary>>);
+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,
+ <<Cdata/binary, _data/binary>>);
+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,
+ <<Cdata/binary, _data/binary>>);
+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,
+ <<Cdata/binary, _data/binary>>);
+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">>,