Merge pull request #298 from weiss/csi

Add support for XEP-0352: Client State Indication (CSI)
This commit is contained in:
Evgeny Khramtsov 2014-09-12 21:55:10 +04:00
commit c90786527e
11 changed files with 633 additions and 119 deletions

View File

@ -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}

View File

@ -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: {}

View File

@ -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">>).

View File

@ -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,
@ -2976,6 +2996,71 @@ make_resume_id(StateData) ->
{Time, _} = StateData#state.sid,
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
%%%----------------------------------------------------------------------

91
src/mod_client_state.erl Normal file
View File

@ -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.

View File

@ -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
%%%===================================================================

View File

@ -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: []

View File

@ -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);

View File

@ -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,

View File

@ -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{}.

View File

@ -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">>],