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