aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEvgeny Khramtsov <xramtsov@gmail.com>2014-09-12 21:55:10 +0400
committerEvgeny Khramtsov <xramtsov@gmail.com>2014-09-12 21:55:10 +0400
commitc90786527e46e485570b2556a5cf2de53482b89a (patch)
tree1dcf843f5750b2cd263d1da6c4e814f79fa1af77
parentMerge pull request #297 from weiss/remove-configure-flag (diff)
parentAdd tests for Client State Indication support (diff)
Merge pull request #298 from weiss/csi
Add support for XEP-0352: Client State Indication (CSI)
-rw-r--r--doc/guide.tex34
-rw-r--r--ejabberd.yml.example3
-rw-r--r--include/ns.hrl1
-rw-r--r--src/ejabberd_c2s.erl101
-rw-r--r--src/mod_client_state.erl91
-rw-r--r--test/ejabberd_SUITE.erl38
-rw-r--r--test/ejabberd_SUITE_data/ejabberd.yml3
-rw-r--r--test/suite.erl2
-rw-r--r--tools/xmpp_codec.erl192
-rw-r--r--tools/xmpp_codec.hrl246
-rw-r--r--tools/xmpp_codec.spec41
11 files changed, 633 insertions, 119 deletions
diff --git a/doc/guide.tex b/doc/guide.tex
index 8e2b91049..27fcfc181 100644
--- a/doc/guide.tex
+++ b/doc/guide.tex
@@ -66,6 +66,7 @@
\newcommand{\module}[1]{\texttt{#1}}
\newcommand{\modadhoc}{\module{mod\_adhoc}}
\newcommand{\modannounce}{\module{mod\_announce}}
+\newcommand{\modclientstate}{\module{mod\_client\_state}}
\newcommand{\modblocking}{\module{mod\_blocking}}
\newcommand{\modcaps}{\module{mod\_caps}}
\newcommand{\modcarboncopy}{\module{mod\_carboncopy}}
@@ -2781,6 +2782,7 @@ The following table lists all modules included in \ejabberd{}.
\hline \modblocking{} & Simple Communications Blocking (\xepref{0191}) & \modprivacy{} \\
\hline \modcaps{} & Entity Capabilities (\xepref{0115}) & \\
\hline \modcarboncopy{} & Message Carbons (\xepref{0280}) & \\
+ \hline \ahrefloc{modclientstate}{\modclientstate{}} & Filter stanzas for inactive clients & \\
\hline \modconfigure{} & Server configuration using Ad-Hoc & \modadhoc{} \\
\hline \ahrefloc{moddisco}{\moddisco{}} & Service Discovery (\xepref{0030}) & \\
\hline \ahrefloc{modecho}{\modecho{}} & Echoes XMPP stanzas & \\
@@ -3001,6 +3003,38 @@ Note that \modannounce{} can be resource intensive on large
deployments as it can broadcast lot of messages. This module should be
disabled for instances of \ejabberd{} with hundreds of thousands users.
+\makesubsection{modclientstate}{\modclientstate{}}
+\ind{modules!\modclientstate{}}\ind{Client State Indication}
+\ind{protocols!XEP-0352: Client State Indication}
+
+This module allows for queueing or dropping certain types of stanzas
+when a client indicates that the user is not actively using the client
+at the moment (see \xepref{0352}). This can save bandwidth and
+resources.
+
+Options:
+\begin{description}
+\titem{drop\_chat\_states: true|false} \ind{options!drop\_chat\_states}
+ Drop most "standalone" Chat State Notifications (as defined in
+ \xepref{0085}) while a client indicates inactivity. The default value
+ is \term{false}.
+\titem{queue\_presence: true|false} \ind{options!queue\_presence}
+ While a client is inactive, queue presence stanzas that indicate
+ (un)availability. The latest queued stanza of each contact is
+ delivered as soon as the client becomes active again. The default
+ value is \term{false}.
+\end{description}
+
+Example:
+\begin{verbatim}
+modules:
+ ...
+ mod_client_state:
+ drop_chat_states: true
+ queue_presence: true
+ ...
+\end{verbatim}
+
\makesubsection{moddisco}{\moddisco{}}
\ind{modules!\moddisco{}}
\ind{protocols!XEP-0030: Service Discovery}
diff --git a/ejabberd.yml.example b/ejabberd.yml.example
index 8f108bf52..4755a7d44 100644
--- a/ejabberd.yml.example
+++ b/ejabberd.yml.example
@@ -558,6 +558,9 @@ modules:
mod_blocking: {} # requires mod_privacy
mod_caps: {}
mod_carboncopy: {}
+ mod_client_state:
+ drop_chat_states: true
+ queue_presence: false
mod_configure: {} # requires mod_adhoc
mod_disco: {}
## mod_echo: {}
diff --git a/include/ns.hrl b/include/ns.hrl
index 6eb54fc3d..3ec19aca8 100644
--- a/include/ns.hrl
+++ b/include/ns.hrl
@@ -147,5 +147,6 @@
-define(NS_CARBONS_2, <<"urn:xmpp:carbons:2">>).
-define(NS_CARBONS_1, <<"urn:xmpp:carbons:1">>).
-define(NS_FORWARD, <<"urn:xmpp:forward:0">>).
+-define(NS_CLIENT_STATE, <<"urn:xmpp:csi:0">>).
-define(NS_STREAM_MGMT_2, <<"urn:xmpp:sm:2">>).
-define(NS_STREAM_MGMT_3, <<"urn:xmpp:sm:3">>).
diff --git a/src/ejabberd_c2s.erl b/src/ejabberd_c2s.erl
index 87f1bbdfb..cb6f9e6d8 100644
--- a/src/ejabberd_c2s.erl
+++ b/src/ejabberd_c2s.erl
@@ -108,6 +108,8 @@
auth_module = unknown,
ip,
aux_fields = [],
+ csi_state = active,
+ csi_queue = [],
mgmt_state,
mgmt_xmlns,
mgmt_queue,
@@ -475,6 +477,10 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
false ->
[]
end,
+ ClientStateFeature =
+ [#xmlel{name = <<"csi">>,
+ attrs = [{<<"xmlns">>, ?NS_CLIENT_STATE}],
+ children = []}],
StreamFeatures = [#xmlel{name = <<"bind">>,
attrs = [{<<"xmlns">>, ?NS_BIND}],
children = []},
@@ -484,6 +490,7 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
++
RosterVersioningFeature ++
StreamManagementFeature ++
+ ClientStateFeature ++
ejabberd_hooks:run_fold(c2s_stream_features,
Server, [], [Server]),
send_element(StateData,
@@ -1165,6 +1172,17 @@ wait_for_session(closed, StateData) ->
session_established({xmlstreamelement, #xmlel{name = Name} = El}, StateData)
when ?IS_STREAM_MGMT_TAG(Name) ->
fsm_next_state(session_established, dispatch_stream_mgmt(El, StateData));
+session_established({xmlstreamelement,
+ #xmlel{name = <<"active">>,
+ attrs = [{<<"xmlns">>, ?NS_CLIENT_STATE}]}},
+ StateData) ->
+ NewStateData = csi_queue_flush(StateData),
+ fsm_next_state(session_established, NewStateData#state{csi_state = active});
+session_established({xmlstreamelement,
+ #xmlel{name = <<"inactive">>,
+ attrs = [{<<"xmlns">>, ?NS_CLIENT_STATE}]}},
+ StateData) ->
+ fsm_next_state(session_established, StateData#state{csi_state = inactive});
session_established({xmlstreamelement, El},
StateData) ->
FromJID = StateData#state.jid,
@@ -1855,6 +1873,8 @@ send_element(StateData, El) when StateData#state.xml_socket ->
send_element(StateData, El) ->
send_text(StateData, xml:element_to_binary(El)).
+send_stanza(StateData, Stanza) when StateData#state.csi_state == inactive ->
+ csi_filter_stanza(StateData, Stanza);
send_stanza(StateData, Stanza) when StateData#state.mgmt_state == pending ->
mgmt_queue_add(StateData, Stanza);
send_stanza(StateData, Stanza) when StateData#state.mgmt_state == active ->
@@ -1869,18 +1889,14 @@ send_stanza(StateData, Stanza) ->
send_element(StateData, Stanza),
StateData.
-send_packet(StateData, Packet) when StateData#state.mgmt_state == active;
- StateData#state.mgmt_state == pending ->
+send_packet(StateData, Packet) ->
case is_stanza(Packet) of
true ->
send_stanza(StateData, Packet);
false ->
send_element(StateData, Packet),
StateData
- end;
-send_packet(StateData, Stanza) ->
- send_element(StateData, Stanza),
- StateData.
+ end.
send_header(StateData, Server, Version, Lang)
when StateData#state.xml_socket ->
@@ -2762,9 +2778,11 @@ handle_resume(StateData, Attrs) ->
#xmlel{name = <<"r">>,
attrs = [{<<"xmlns">>, AttrXmlns}],
children = []}),
+ FlushedState = csi_queue_flush(NewState),
+ NewStateData = FlushedState#state{csi_state = active},
?INFO_MSG("Resumed session for ~s",
- [jlib:jid_to_string(NewState#state.jid)]),
- {ok, NewState};
+ [jlib:jid_to_string(NewStateData#state.jid)]),
+ {ok, NewStateData};
{error, El, Msg} ->
send_element(StateData, El),
?INFO_MSG("Cannot resume session for ~s@~s: ~s",
@@ -2953,6 +2971,8 @@ inherit_session_state(#state{user = U, server = S} = StateData, ResumeID) ->
pres_invis = OldStateData#state.pres_invis,
privacy_list = OldStateData#state.privacy_list,
aux_fields = OldStateData#state.aux_fields,
+ csi_state = OldStateData#state.csi_state,
+ csi_queue = OldStateData#state.csi_queue,
mgmt_xmlns = OldStateData#state.mgmt_xmlns,
mgmt_queue = OldStateData#state.mgmt_queue,
mgmt_timeout = OldStateData#state.mgmt_timeout,
@@ -2977,6 +2997,71 @@ make_resume_id(StateData) ->
jlib:term_to_base64({StateData#state.resource, Time}).
%%%----------------------------------------------------------------------
+%%% XEP-0352
+%%%----------------------------------------------------------------------
+
+csi_filter_stanza(#state{csi_state = CsiState, jid = JID} = StateData,
+ Stanza) ->
+ Action = ejabberd_hooks:run_fold(csi_filter_stanza,
+ StateData#state.server,
+ send, [Stanza]),
+ ?DEBUG("Going to ~p stanza for inactive client ~p",
+ [Action, jlib:jid_to_string(JID)]),
+ case Action of
+ queue -> csi_queue_add(StateData, Stanza);
+ drop -> StateData;
+ send ->
+ From = xml:get_tag_attr_s(<<"from">>, Stanza),
+ StateData1 = csi_queue_send(StateData, From),
+ StateData2 = send_stanza(StateData1#state{csi_state = active},
+ Stanza),
+ StateData2#state{csi_state = CsiState}
+ end.
+
+csi_queue_add(#state{csi_queue = Queue, server = Host} = StateData,
+ #xmlel{children = Els} = Stanza) ->
+ From = xml:get_tag_attr_s(<<"from">>, Stanza),
+ Time = calendar:now_to_universal_time(os:timestamp()),
+ DelayTag = [jlib:timestamp_to_xml(Time, utc,
+ jlib:make_jid(<<"">>, Host, <<"">>),
+ <<"Client Inactive">>)],
+ NewStanza = Stanza#xmlel{children = Els ++ DelayTag},
+ case length(StateData#state.csi_queue) >= csi_max_queue(StateData) of
+ true -> csi_queue_add(csi_queue_flush(StateData), NewStanza);
+ false ->
+ NewQueue = lists:keystore(From, 1, Queue, {From, NewStanza}),
+ StateData#state{csi_queue = NewQueue}
+ end.
+
+csi_queue_send(#state{csi_queue = Queue, csi_state = CsiState} = StateData,
+ From) ->
+ case lists:keytake(From, 1, Queue) of
+ {value, {From, Stanza}, NewQueue} ->
+ NewStateData = send_stanza(StateData#state{csi_state = active},
+ Stanza),
+ NewStateData#state{csi_queue = NewQueue, csi_state = CsiState};
+ false -> StateData
+ end.
+
+csi_queue_flush(#state{csi_queue = Queue, csi_state = CsiState, jid = JID} =
+ StateData) ->
+ ?DEBUG("Flushing CSI queue for ~s", [jlib:jid_to_string(JID)]),
+ NewStateData =
+ lists:foldl(fun({_From, Stanza}, AccState) ->
+ send_stanza(AccState, Stanza)
+ end, StateData#state{csi_state = active}, Queue),
+ NewStateData#state{csi_queue = [], csi_state = CsiState}.
+
+%% Make sure we won't push too many messages to the XEP-0198 queue when the
+%% client becomes 'active' again. Otherwise, the client might not manage to
+%% acknowledge the message flood in time. Also, don't let the queue grow to
+%% more than 100 stanzas.
+csi_max_queue(#state{mgmt_max_queue = infinity}) -> 100;
+csi_max_queue(#state{mgmt_max_queue = Max}) when Max > 200 -> 100;
+csi_max_queue(#state{mgmt_max_queue = Max}) when Max < 2 -> 1;
+csi_max_queue(#state{mgmt_max_queue = Max}) -> Max div 2.
+
+%%%----------------------------------------------------------------------
%%% JID Set memory footprint reduction code
%%%----------------------------------------------------------------------
diff --git a/src/mod_client_state.erl b/src/mod_client_state.erl
new file mode 100644
index 000000000..83363162d
--- /dev/null
+++ b/src/mod_client_state.erl
@@ -0,0 +1,91 @@
+%%%----------------------------------------------------------------------
+%%% File : mod_client_state.erl
+%%% Author : Holger Weiss
+%%% Purpose : Filter stanzas sent to inactive clients (XEP-0352)
+%%% Created : 11 Sep 2014 by Holger Weiss
+%%%
+%%%
+%%% ejabberd, Copyright (C) 2014 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.,
+%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+%%%
+%%%----------------------------------------------------------------------
+
+-module(mod_client_state).
+-author('holger@zedat.fu-berlin.de').
+
+-behavior(gen_mod).
+
+-export([start/2, stop/1, filter_presence/2, filter_chat_states/2]).
+
+-include("ejabberd.hrl").
+-include("logger.hrl").
+-include("jlib.hrl").
+
+start(Host, Opts) ->
+ QueuePresence = gen_mod:get_opt(queue_presence, Opts,
+ fun(true) -> true end, false),
+ DropChatStates = gen_mod:get_opt(drop_chat_states, Opts,
+ fun(true) -> true end, false),
+ if QueuePresence ->
+ ejabberd_hooks:add(csi_filter_stanza, Host, ?MODULE,
+ filter_presence, 50);
+ true -> ok
+ end,
+ if DropChatStates ->
+ ejabberd_hooks:add(csi_filter_stanza, Host, ?MODULE,
+ filter_chat_states, 50);
+ true -> ok
+ end,
+ ok.
+
+stop(Host) ->
+ ejabberd_hooks:delete(csi_filter_stanza, Host, ?MODULE,
+ filter_presence, 50),
+ ejabberd_hooks:delete(csi_filter_stanza, Host, ?MODULE,
+ filter_chat_states, 50),
+ ok.
+
+filter_presence(_Action, #xmlel{name = <<"presence">>, attrs = Attrs}) ->
+ case xml:get_attr(<<"type">>, Attrs) of
+ {value, Type} when Type /= <<"unavailable">> ->
+ ?DEBUG("Got important presence stanza", []),
+ {stop, send};
+ _ ->
+ ?DEBUG("Got availability presence stanza", []),
+ {stop, queue}
+ end;
+filter_presence(Action, _Stanza) -> Action.
+
+filter_chat_states(_Action, #xmlel{name = <<"message">>} = Stanza) ->
+ %% All XEP-0085 chat states except for <gone/>:
+ ChatStates = [<<"active">>, <<"inactive">>, <<"composing">>, <<"paused">>],
+ Stripped =
+ lists:foldl(fun(ChatState, AccStanza) ->
+ xml:remove_subtags(AccStanza, ChatState,
+ {<<"xmlns">>, ?NS_CHATSTATES})
+ end, Stanza, ChatStates),
+ case Stripped of
+ #xmlel{children = [#xmlel{name = <<"thread">>}]} ->
+ ?DEBUG("Got standalone chat state notification", []),
+ {stop, drop};
+ #xmlel{children = []} ->
+ ?DEBUG("Got standalone chat state notification", []),
+ {stop, drop};
+ _ ->
+ ?DEBUG("Got message with chat state notification", []),
+ {stop, send}
+ end;
+filter_chat_states(Action, _Stanza) -> Action.
diff --git a/test/ejabberd_SUITE.erl b/test/ejabberd_SUITE.erl
index fbf2444b6..0cc5afec3 100644
--- a/test/ejabberd_SUITE.erl
+++ b/test/ejabberd_SUITE.erl
@@ -237,6 +237,8 @@ db_tests(mnesia) ->
[offline_master, offline_slave]},
{test_carbons, [parallel],
[carbons_master, carbons_slave]},
+ {test_client_state, [parallel],
+ [client_state_master, client_state_slave]},
{test_muc, [parallel],
[muc_master, muc_slave]},
{test_announce, [sequence],
@@ -1505,6 +1507,42 @@ carbons_slave(Config) ->
#presence{from = Peer, type = unavailable} = recv(),
disconnect(Config).
+client_state_master(Config) ->
+ Peer = ?config(slave, Config),
+ Presence = #presence{to = Peer},
+ Message = #message{to = Peer, thread = <<"1">>,
+ sub_els = [#chatstate_active{}]},
+ wait_for_slave(Config),
+ %% Should be queued (but see below):
+ send(Config, Presence),
+ %% Should be sent immediately, together with the previous presence:
+ send(Config, Message#message{body = [#text{data = <<"body">>}]}),
+ %% Should be dropped:
+ send(Config, Message),
+ %% Should be queued (but see below):
+ send(Config, Presence),
+ %% Should replace the previous presence in the queue:
+ send(Config, Presence#presence{type = unavailable}),
+ wait_for_slave(Config),
+ %% Should be sent immediately, as the client is active again.
+ send(Config, Message),
+ disconnect(Config).
+
+client_state_slave(Config) ->
+ true = ?config(csi, Config),
+ Peer = ?config(master, Config),
+ send(Config, #csi_inactive{}),
+ wait_for_master(Config),
+ #presence{from = Peer, sub_els = [#delay{}]} = recv(),
+ #message{from = Peer, thread = <<"1">>, sub_els = [#chatstate_active{}],
+ body = [#text{data = <<"body">>}]} = recv(),
+ wait_for_master(Config),
+ send(Config, #csi_active{}),
+ ?recv2(#presence{from = Peer, type = unavailable, sub_els = [#delay{}]},
+ #message{from = Peer, thread = <<"1">>,
+ sub_els = [#chatstate_active{}]}),
+ disconnect(Config).
+
%%%===================================================================
%%% Aux functions
%%%===================================================================
diff --git a/test/ejabberd_SUITE_data/ejabberd.yml b/test/ejabberd_SUITE_data/ejabberd.yml
index f096fc53f..597ba5be7 100644
--- a/test/ejabberd_SUITE_data/ejabberd.yml
+++ b/test/ejabberd_SUITE_data/ejabberd.yml
@@ -143,6 +143,9 @@ Welcome to this XMPP server."
db_type: internal
mod_carboncopy:
db_type: internal
+ mod_client_state:
+ drop_chat_states: true
+ queue_presence: true
mod_adhoc: []
mod_configure: []
mod_disco: []
diff --git a/test/suite.erl b/test/suite.erl
index a1043526f..a50bb64df 100644
--- a/test/suite.erl
+++ b/test/suite.erl
@@ -155,6 +155,8 @@ wait_auth_SASL_result(Config) ->
lists:foldl(
fun(#feature_sm{}, ConfigAcc) ->
set_opt(sm, true, ConfigAcc);
+ (#feature_csi{}, ConfigAcc) ->
+ set_opt(csi, true, ConfigAcc);
(_, ConfigAcc) ->
ConfigAcc
end, Config, Fs);
diff --git a/tools/xmpp_codec.erl b/tools/xmpp_codec.erl
index 2b9372973..d106fa8f4 100644
--- a/tools/xmpp_codec.erl
+++ b/tools/xmpp_codec.erl
@@ -47,6 +47,14 @@ decode({xmlel, _name, _attrs, _} = _el, Opts) ->
decode_feature_sm(<<"urn:xmpp:sm:2">>, IgnoreEls, _el);
{<<"sm">>, <<"urn:xmpp:sm:3">>} ->
decode_feature_sm(<<"urn:xmpp:sm:3">>, IgnoreEls, _el);
+ {<<"inactive">>, <<"urn:xmpp:csi:0">>} ->
+ decode_csi_inactive(<<"urn:xmpp:csi:0">>, IgnoreEls,
+ _el);
+ {<<"active">>, <<"urn:xmpp:csi:0">>} ->
+ decode_csi_active(<<"urn:xmpp:csi:0">>, IgnoreEls, _el);
+ {<<"csi">>, <<"urn:xmpp:csi:0">>} ->
+ decode_feature_csi(<<"urn:xmpp:csi:0">>, IgnoreEls,
+ _el);
{<<"sent">>, <<"urn:xmpp:carbons:2">>} ->
decode_carbons_sent(<<"urn:xmpp:carbons:2">>, IgnoreEls,
_el);
@@ -163,6 +171,26 @@ decode({xmlel, _name, _attrs, _} = _el, Opts) ->
_el);
{<<"delay">>, <<"urn:xmpp:delay">>} ->
decode_delay(<<"urn:xmpp:delay">>, IgnoreEls, _el);
+ {<<"paused">>,
+ <<"http://jabber.org/protocol/chatstates">>} ->
+ decode_chatstate_paused(<<"http://jabber.org/protocol/chatstates">>,
+ IgnoreEls, _el);
+ {<<"inactive">>,
+ <<"http://jabber.org/protocol/chatstates">>} ->
+ decode_chatstate_inactive(<<"http://jabber.org/protocol/chatstates">>,
+ IgnoreEls, _el);
+ {<<"gone">>,
+ <<"http://jabber.org/protocol/chatstates">>} ->
+ decode_chatstate_gone(<<"http://jabber.org/protocol/chatstates">>,
+ IgnoreEls, _el);
+ {<<"composing">>,
+ <<"http://jabber.org/protocol/chatstates">>} ->
+ decode_chatstate_composing(<<"http://jabber.org/protocol/chatstates">>,
+ IgnoreEls, _el);
+ {<<"active">>,
+ <<"http://jabber.org/protocol/chatstates">>} ->
+ decode_chatstate_active(<<"http://jabber.org/protocol/chatstates">>,
+ IgnoreEls, _el);
{<<"headers">>,
<<"http://jabber.org/protocol/shim">>} ->
decode_shim_headers(<<"http://jabber.org/protocol/shim">>,
@@ -998,6 +1026,9 @@ is_known_tag({xmlel, _name, _attrs, _} = _el) ->
{<<"enable">>, <<"urn:xmpp:sm:3">>} -> true;
{<<"sm">>, <<"urn:xmpp:sm:2">>} -> true;
{<<"sm">>, <<"urn:xmpp:sm:3">>} -> true;
+ {<<"inactive">>, <<"urn:xmpp:csi:0">>} -> true;
+ {<<"active">>, <<"urn:xmpp:csi:0">>} -> true;
+ {<<"csi">>, <<"urn:xmpp:csi:0">>} -> true;
{<<"sent">>, <<"urn:xmpp:carbons:2">>} -> true;
{<<"received">>, <<"urn:xmpp:carbons:2">>} -> true;
{<<"private">>, <<"urn:xmpp:carbons:2">>} -> true;
@@ -1074,6 +1105,21 @@ is_known_tag({xmlel, _name, _attrs, _} = _el) ->
true;
{<<"x">>, <<"jabber:x:delay">>} -> true;
{<<"delay">>, <<"urn:xmpp:delay">>} -> true;
+ {<<"paused">>,
+ <<"http://jabber.org/protocol/chatstates">>} ->
+ true;
+ {<<"inactive">>,
+ <<"http://jabber.org/protocol/chatstates">>} ->
+ true;
+ {<<"gone">>,
+ <<"http://jabber.org/protocol/chatstates">>} ->
+ true;
+ {<<"composing">>,
+ <<"http://jabber.org/protocol/chatstates">>} ->
+ true;
+ {<<"active">>,
+ <<"http://jabber.org/protocol/chatstates">>} ->
+ true;
{<<"headers">>,
<<"http://jabber.org/protocol/shim">>} ->
true;
@@ -1856,6 +1902,26 @@ encode({pubsub, _, _, _, _, _, _, _, _} = Pubsub) ->
encode({shim, _} = Headers) ->
encode_shim_headers(Headers,
[{<<"xmlns">>, <<"http://jabber.org/protocol/shim">>}]);
+encode({chatstate_active} = Active) ->
+ encode_chatstate_active(Active,
+ [{<<"xmlns">>,
+ <<"http://jabber.org/protocol/chatstates">>}]);
+encode({chatstate_composing} = Composing) ->
+ encode_chatstate_composing(Composing,
+ [{<<"xmlns">>,
+ <<"http://jabber.org/protocol/chatstates">>}]);
+encode({chatstate_gone} = Gone) ->
+ encode_chatstate_gone(Gone,
+ [{<<"xmlns">>,
+ <<"http://jabber.org/protocol/chatstates">>}]);
+encode({chatstate_inactive} = Inactive) ->
+ encode_chatstate_inactive(Inactive,
+ [{<<"xmlns">>,
+ <<"http://jabber.org/protocol/chatstates">>}]);
+encode({chatstate_paused} = Paused) ->
+ encode_chatstate_paused(Paused,
+ [{<<"xmlns">>,
+ <<"http://jabber.org/protocol/chatstates">>}]);
encode({delay, _, _} = Delay) ->
encode_delay(Delay,
[{<<"xmlns">>, <<"urn:xmpp:delay">>}]);
@@ -1926,6 +1992,14 @@ encode({carbons_received, _} = Received) ->
encode({carbons_sent, _} = Sent) ->
encode_carbons_sent(Sent,
[{<<"xmlns">>, <<"urn:xmpp:carbons:2">>}]);
+encode({feature_csi, _} = Csi) ->
+ encode_feature_csi(Csi, []);
+encode({csi_active} = Active) ->
+ encode_csi_active(Active,
+ [{<<"xmlns">>, <<"urn:xmpp:csi:0">>}]);
+encode({csi_inactive} = Inactive) ->
+ encode_csi_inactive(Inactive,
+ [{<<"xmlns">>, <<"urn:xmpp:csi:0">>}]);
encode({feature_sm, _} = Sm) ->
encode_feature_sm(Sm, []);
encode({sm_enable, _, _, _} = Enable) ->
@@ -2084,6 +2158,16 @@ get_ns({pubsub, _, _, _, _, _, _, _, _}) ->
<<"http://jabber.org/protocol/pubsub">>;
get_ns({shim, _}) ->
<<"http://jabber.org/protocol/shim">>;
+get_ns({chatstate_active}) ->
+ <<"http://jabber.org/protocol/chatstates">>;
+get_ns({chatstate_composing}) ->
+ <<"http://jabber.org/protocol/chatstates">>;
+get_ns({chatstate_gone}) ->
+ <<"http://jabber.org/protocol/chatstates">>;
+get_ns({chatstate_inactive}) ->
+ <<"http://jabber.org/protocol/chatstates">>;
+get_ns({chatstate_paused}) ->
+ <<"http://jabber.org/protocol/chatstates">>;
get_ns({delay, _, _}) -> <<"urn:xmpp:delay">>;
get_ns({legacy_delay, _, _}) -> <<"jabber:x:delay">>;
get_ns({streamhost, _, _, _}) ->
@@ -2115,6 +2199,9 @@ get_ns({carbons_private}) -> <<"urn:xmpp:carbons:2">>;
get_ns({carbons_received, _}) ->
<<"urn:xmpp:carbons:2">>;
get_ns({carbons_sent, _}) -> <<"urn:xmpp:carbons:2">>;
+get_ns({feature_csi, _}) -> <<"urn:xmpp:csi:0">>;
+get_ns({csi_active}) -> <<"urn:xmpp:csi:0">>;
+get_ns({csi_inactive}) -> <<"urn:xmpp:csi:0">>;
get_ns(_) -> <<>>.
dec_int(Val) -> dec_int(Val, infinity, infinity).
@@ -2272,6 +2359,11 @@ pp(pubsub, 8) ->
[subscriptions, affiliations, publish, subscribe,
unsubscribe, options, items, retract];
pp(shim, 1) -> [headers];
+pp(chatstate_active, 0) -> [];
+pp(chatstate_composing, 0) -> [];
+pp(chatstate_gone, 0) -> [];
+pp(chatstate_inactive, 0) -> [];
+pp(chatstate_paused, 0) -> [];
pp(delay, 2) -> [stamp, from];
pp(legacy_delay, 2) -> [stamp, from];
pp(streamhost, 3) -> [jid, host, port];
@@ -2298,6 +2390,9 @@ pp(carbons_enable, 0) -> [];
pp(carbons_private, 0) -> [];
pp(carbons_received, 1) -> [forwarded];
pp(carbons_sent, 1) -> [forwarded];
+pp(feature_csi, 1) -> [xmlns];
+pp(csi_active, 0) -> [];
+pp(csi_inactive, 0) -> [];
pp(feature_sm, 1) -> [xmlns];
pp(sm_enable, 3) -> [max, resume, xmlns];
pp(sm_enabled, 5) -> [id, location, max, resume, xmlns];
@@ -3268,6 +3363,54 @@ encode_feature_sm_attr_xmlns(undefined, _acc) -> _acc;
encode_feature_sm_attr_xmlns(_val, _acc) ->
[{<<"xmlns">>, _val} | _acc].
+decode_csi_inactive(__TopXMLNS, __IgnoreEls,
+ {xmlel, <<"inactive">>, _attrs, _els}) ->
+ {csi_inactive}.
+
+encode_csi_inactive({csi_inactive}, _xmlns_attrs) ->
+ _els = [],
+ _attrs = _xmlns_attrs,
+ {xmlel, <<"inactive">>, _attrs, _els}.
+
+decode_csi_active(__TopXMLNS, __IgnoreEls,
+ {xmlel, <<"active">>, _attrs, _els}) ->
+ {csi_active}.
+
+encode_csi_active({csi_active}, _xmlns_attrs) ->
+ _els = [],
+ _attrs = _xmlns_attrs,
+ {xmlel, <<"active">>, _attrs, _els}.
+
+decode_feature_csi(__TopXMLNS, __IgnoreEls,
+ {xmlel, <<"csi">>, _attrs, _els}) ->
+ Xmlns = decode_feature_csi_attrs(__TopXMLNS, _attrs,
+ undefined),
+ {feature_csi, Xmlns}.
+
+decode_feature_csi_attrs(__TopXMLNS,
+ [{<<"xmlns">>, _val} | _attrs], _Xmlns) ->
+ decode_feature_csi_attrs(__TopXMLNS, _attrs, _val);
+decode_feature_csi_attrs(__TopXMLNS, [_ | _attrs],
+ Xmlns) ->
+ decode_feature_csi_attrs(__TopXMLNS, _attrs, Xmlns);
+decode_feature_csi_attrs(__TopXMLNS, [], Xmlns) ->
+ decode_feature_csi_attr_xmlns(__TopXMLNS, Xmlns).
+
+encode_feature_csi({feature_csi, Xmlns},
+ _xmlns_attrs) ->
+ _els = [],
+ _attrs = encode_feature_csi_attr_xmlns(Xmlns,
+ _xmlns_attrs),
+ {xmlel, <<"csi">>, _attrs, _els}.
+
+decode_feature_csi_attr_xmlns(__TopXMLNS, undefined) ->
+ undefined;
+decode_feature_csi_attr_xmlns(__TopXMLNS, _val) -> _val.
+
+encode_feature_csi_attr_xmlns(undefined, _acc) -> _acc;
+encode_feature_csi_attr_xmlns(_val, _acc) ->
+ [{<<"xmlns">>, _val} | _acc].
+
decode_carbons_sent(__TopXMLNS, __IgnoreEls,
{xmlel, <<"sent">>, _attrs, _els}) ->
Forwarded = decode_carbons_sent_els(__TopXMLNS,
@@ -5355,6 +5498,55 @@ encode_delay_attr_from(undefined, _acc) -> _acc;
encode_delay_attr_from(_val, _acc) ->
[{<<"from">>, enc_jid(_val)} | _acc].
+decode_chatstate_paused(__TopXMLNS, __IgnoreEls,
+ {xmlel, <<"paused">>, _attrs, _els}) ->
+ {chatstate_paused}.
+
+encode_chatstate_paused({chatstate_paused},
+ _xmlns_attrs) ->
+ _els = [],
+ _attrs = _xmlns_attrs,
+ {xmlel, <<"paused">>, _attrs, _els}.
+
+decode_chatstate_inactive(__TopXMLNS, __IgnoreEls,
+ {xmlel, <<"inactive">>, _attrs, _els}) ->
+ {chatstate_inactive}.
+
+encode_chatstate_inactive({chatstate_inactive},
+ _xmlns_attrs) ->
+ _els = [],
+ _attrs = _xmlns_attrs,
+ {xmlel, <<"inactive">>, _attrs, _els}.
+
+decode_chatstate_gone(__TopXMLNS, __IgnoreEls,
+ {xmlel, <<"gone">>, _attrs, _els}) ->
+ {chatstate_gone}.
+
+encode_chatstate_gone({chatstate_gone}, _xmlns_attrs) ->
+ _els = [],
+ _attrs = _xmlns_attrs,
+ {xmlel, <<"gone">>, _attrs, _els}.
+
+decode_chatstate_composing(__TopXMLNS, __IgnoreEls,
+ {xmlel, <<"composing">>, _attrs, _els}) ->
+ {chatstate_composing}.
+
+encode_chatstate_composing({chatstate_composing},
+ _xmlns_attrs) ->
+ _els = [],
+ _attrs = _xmlns_attrs,
+ {xmlel, <<"composing">>, _attrs, _els}.
+
+decode_chatstate_active(__TopXMLNS, __IgnoreEls,
+ {xmlel, <<"active">>, _attrs, _els}) ->
+ {chatstate_active}.
+
+encode_chatstate_active({chatstate_active},
+ _xmlns_attrs) ->
+ _els = [],
+ _attrs = _xmlns_attrs,
+ {xmlel, <<"active">>, _attrs, _els}.
+
decode_shim_headers(__TopXMLNS, __IgnoreEls,
{xmlel, <<"headers">>, _attrs, _els}) ->
Headers = decode_shim_headers_els(__TopXMLNS,
diff --git a/tools/xmpp_codec.hrl b/tools/xmpp_codec.hrl
index 549c6fb1b..6e142414f 100644
--- a/tools/xmpp_codec.hrl
+++ b/tools/xmpp_codec.hrl
@@ -8,6 +8,8 @@
-record(text, {lang :: binary(),
data :: binary()}).
+-record(chatstate_paused, {}).
+
-record(streamhost, {jid :: any(),
host :: binary(),
port = 1080 :: non_neg_integer()}).
@@ -24,6 +26,8 @@
jid :: any(),
subid :: binary()}).
+-record(csi_inactive, {}).
+
-record(ping, {}).
-record(delay, {stamp :: any(),
@@ -57,6 +61,8 @@
resume = false :: any(),
xmlns :: binary()}).
+-record(chatstate_gone, {}).
+
-record(starttls_failure, {}).
-record(sasl_challenge, {text :: any()}).
@@ -123,14 +129,20 @@
-record(sasl_response, {text :: any()}).
+-record(chatstate_inactive, {}).
+
-record(pubsub_subscribe, {node :: binary(),
jid :: any()}).
+-record(chatstate_composing, {}).
+
-record(sasl_auth, {mechanism :: binary(),
text :: any()}).
-record(p1_push, {}).
+-record(feature_csi, {xmlns :: binary()}).
+
-record(legacy_delay, {stamp :: binary(),
from :: any()}).
@@ -220,6 +232,8 @@
-record(block_list, {}).
+-record(csi_active, {}).
+
-record(xdata_field, {label :: binary(),
type :: 'boolean' | 'fixed' | 'hidden' | 'jid-multi' | 'jid-single' | 'list-multi' | 'list-single' | 'text-multi' | 'text-private' | 'text-single',
var :: binary(),
@@ -255,6 +269,8 @@
mode = tcp :: 'tcp' | 'udp',
sid :: binary()}).
+-record(chatstate_active, {}).
+
-record(vcard_org, {name :: binary(),
units = [] :: [binary()]}).
@@ -475,114 +491,122 @@
-record(time, {tzo :: any(),
utc :: any()}).
--type xmpp_element() :: #session{} |
- #compression{} |
- #pubsub_subscription{} |
- #version{} |
- #pubsub_affiliation{} |
- #muc_admin{} |
- #sm_a{} |
- #carbons_sent{} |
- #p1_rebind{} |
- #sasl_abort{} |
- #carbons_received{} |
- #pubsub_retract{} |
- #compressed{} |
- #block_list{} |
- #'see-other-host'{} |
- #starttls_proceed{} |
- #sm_resumed{} |
- #forwarded{} |
- #privacy_list{} |
- #text{} |
- #vcard_org{} |
- #feature_sm{} |
- #pubsub_item{} |
- #roster_item{} |
- #pubsub_event_item{} |
- #muc_item{} |
- #shim{} |
- #caps{} |
- #muc{} |
- #stream_features{} |
- #stats{} |
- #pubsub_items{} |
- #pubsub_event_items{} |
- #disco_items{} |
- #pubsub_options{} |
- #starttls{} |
- #sasl_mechanisms{} |
- #sasl_success{} |
- #compress{} |
- #bytestreams{} |
- #vcard_key{} |
- #identity{} |
- #legacy_delay{} |
- #muc_user_destroy{} |
- #muc_owner_destroy{} |
- #privacy{} |
- #delay{} |
- #muc_history{} |
- #bookmark_url{} |
- #vcard_email{} |
- #vcard_label{} |
- #vcard_tel{} |
- #disco_info{} |
- #vcard_logo{} |
- #vcard_geo{} |
- #vcard_photo{} |
- #muc_owner{} |
- #pubsub{} |
- #sm_r{} |
- #muc_actor{} |
- #error{} |
- #stream_error{} |
- #feature_register{} |
- #roster{} |
- #muc_user{} |
- #vcard_adr{} |
- #register{} |
- #muc_invite{} |
- #carbons_disable{} |
- #bookmark_conference{} |
- #time{} |
- #sasl_response{} |
- #pubsub_subscribe{} |
- #presence{} |
- #message{} |
- #sm_enable{} |
- #starttls_failure{} |
- #sasl_challenge{} |
- #gone{} |
- #private{} |
- #compress_failure{} |
- #sasl_failure{} |
- #bookmark_storage{} |
- #vcard_name{} |
- #sm_resume{} |
- #carbons_enable{} |
- #carbons_private{} |
- #pubsub_unsubscribe{} |
- #muc_decline{} |
- #sasl_auth{} |
- #p1_push{} |
- #pubsub_publish{} |
- #unblock{} |
- #p1_ack{} |
- #block{} |
- #xdata{} |
- #iq{} |
- #last{} |
- #redirect{} |
- #sm_enabled{} |
- #pubsub_event{} |
- #vcard_sound{} |
- #streamhost{} |
- #stat{} |
- #xdata_field{} |
- #bind{} |
- #sm_failed{} |
- #vcard{} |
- #ping{} |
- #disco_item{} |
- #privacy_item{}.
+-type xmpp_codec_type() :: #session{} |
+ #compression{} |
+ #pubsub_subscription{} |
+ #version{} |
+ #pubsub_affiliation{} |
+ #muc_admin{} |
+ #sm_a{} |
+ #carbons_sent{} |
+ #p1_rebind{} |
+ #sasl_abort{} |
+ #carbons_received{} |
+ #pubsub_retract{} |
+ #compressed{} |
+ #block_list{} |
+ #'see-other-host'{} |
+ #starttls_proceed{} |
+ #sm_resumed{} |
+ #forwarded{} |
+ #privacy_list{} |
+ #text{} |
+ #vcard_org{} |
+ #feature_sm{} |
+ #pubsub_item{} |
+ #roster_item{} |
+ #pubsub_event_item{} |
+ #muc_item{} |
+ #shim{} |
+ #pubsub_event_items{} |
+ #disco_items{} |
+ #pubsub_options{} |
+ #sasl_success{} |
+ #compress{} |
+ #bytestreams{} |
+ #vcard_key{} |
+ #identity{} |
+ #feature_csi{} |
+ #legacy_delay{} |
+ #muc_user_destroy{} |
+ #muc_owner_destroy{} |
+ #privacy{} |
+ #delay{} |
+ #muc_history{} |
+ #bookmark_url{} |
+ #vcard_email{} |
+ #vcard_label{} |
+ #vcard_tel{} |
+ #vcard_logo{} |
+ #disco_info{} |
+ #vcard_geo{} |
+ #vcard_photo{} |
+ #muc_owner{} |
+ #pubsub{} |
+ #sm_r{} |
+ #muc_actor{} |
+ #error{} |
+ #stream_error{} |
+ #feature_register{} |
+ #roster{} |
+ #muc_user{} |
+ #vcard_adr{} |
+ #register{} |
+ #csi_active{} |
+ #muc_invite{} |
+ #carbons_disable{} |
+ #chatstate_active{} |
+ #bookmark_conference{} |
+ #time{} |
+ #sasl_response{} |
+ #chatstate_inactive{} |
+ #pubsub_subscribe{} |
+ #presence{} |
+ #message{} |
+ #sm_enable{} |
+ #chatstate_gone{} |
+ #starttls_failure{} |
+ #sasl_challenge{} |
+ #gone{} |
+ #private{} |
+ #compress_failure{} |
+ #sasl_failure{} |
+ #bookmark_storage{} |
+ #vcard_name{} |
+ #sm_resume{} |
+ #carbons_enable{} |
+ #carbons_private{} |
+ #pubsub_unsubscribe{} |
+ #csi_inactive{} |
+ #muc_decline{} |
+ #sasl_auth{} |
+ #p1_push{} |
+ #pubsub_publish{} |
+ #unblock{} |
+ #p1_ack{} |
+ #block{} |
+ #xdata{} |
+ #iq{} |
+ #last{} |
+ #redirect{} |
+ #sm_enabled{} |
+ #pubsub_event{} |
+ #vcard_sound{} |
+ #chatstate_paused{} |
+ #streamhost{} |
+ #stat{} |
+ #xdata_field{} |
+ #bind{} |
+ #sm_failed{} |
+ #vcard{} |
+ #chatstate_composing{} |
+ #ping{} |
+ #disco_item{} |
+ #privacy_item{} |
+ #caps{} |
+ #muc{} |
+ #stream_features{} |
+ #stats{} |
+ #pubsub_items{} |
+ #starttls{} |
+ #sasl_mechanisms{}.
diff --git a/tools/xmpp_codec.spec b/tools/xmpp_codec.spec
index 4d218839b..c5d9013ed 100644
--- a/tools/xmpp_codec.spec
+++ b/tools/xmpp_codec.spec
@@ -1757,6 +1757,31 @@
result = {shim, '$headers'},
refs = [#ref{name = shim_header, label = '$headers'}]}).
+-xml(chatstate_active,
+ #elem{name = <<"active">>,
+ xmlns = <<"http://jabber.org/protocol/chatstates">>,
+ result = {chatstate_active}}).
+
+-xml(chatstate_composing,
+ #elem{name = <<"composing">>,
+ xmlns = <<"http://jabber.org/protocol/chatstates">>,
+ result = {chatstate_composing}}).
+
+-xml(chatstate_gone,
+ #elem{name = <<"gone">>,
+ xmlns = <<"http://jabber.org/protocol/chatstates">>,
+ result = {chatstate_gone}}).
+
+-xml(chatstate_inactive,
+ #elem{name = <<"inactive">>,
+ xmlns = <<"http://jabber.org/protocol/chatstates">>,
+ result = {chatstate_inactive}}).
+
+-xml(chatstate_paused,
+ #elem{name = <<"paused">>,
+ xmlns = <<"http://jabber.org/protocol/chatstates">>,
+ result = {chatstate_paused}}).
+
-xml(delay,
#elem{name = <<"delay">>,
xmlns = <<"urn:xmpp:delay">>,
@@ -2070,6 +2095,22 @@
refs = [#ref{name = forwarded, min = 1,
max = 1, label = '$forwarded'}]}).
+-xml(feature_csi,
+ #elem{name = <<"csi">>,
+ xmlns = <<"urn:xmpp:csi:0">>,
+ result = {feature_csi, '$xmlns'},
+ attrs = [#attr{name = <<"xmlns">>}]}).
+
+-xml(csi_active,
+ #elem{name = <<"active">>,
+ xmlns = <<"urn:xmpp:csi:0">>,
+ result = {csi_active}}).
+
+-xml(csi_inactive,
+ #elem{name = <<"inactive">>,
+ xmlns = <<"urn:xmpp:csi:0">>,
+ result = {csi_inactive}}).
+
-xml(feature_sm,
#elem{name = <<"sm">>,
xmlns = [<<"urn:xmpp:sm:2">>, <<"urn:xmpp:sm:3">>],