diff --git a/test/ejabberd_SUITE.erl b/test/ejabberd_SUITE.erl index 1b8d5f477..f17bfeb4c 100644 --- a/test/ejabberd_SUITE.erl +++ b/test/ejabberd_SUITE.erl @@ -12,8 +12,8 @@ -import(suite, [init_config/1, connect/1, disconnect/1, recv/0, send/2, send_recv/2, my_jid/1, server_jid/1, - pubsub_jid/1, proxy_jid/1, muc_jid/1, - muc_room_jid/1, get_features/2, re_register/1, + pubsub_jid/1, proxy_jid/1, muc_jid/1, muc_room_jid/1, + mix_jid/1, mix_room_jid/1, get_features/2, re_register/1, is_feature_advertised/2, subscribe_to_events/1, is_feature_advertised/3, set_opt/3, auth_SASL/2, wait_for_master/1, wait_for_slave/1, @@ -249,6 +249,8 @@ db_tests(DB) when DB == mnesia; DB == redis -> test_unregister]}, {test_muc_register, [sequence], [muc_register_master, muc_register_slave]}, + {test_mix, [parallel], + [mix_master, mix_slave]}, {test_roster_subscribe, [parallel], [roster_subscribe_master, roster_subscribe_slave]}, @@ -882,6 +884,90 @@ pubsub(Config) -> jid = my_jid(Config)}}]}), disconnect(Config). +mix_master(Config) -> + MIX = mix_jid(Config), + Room = mix_room_jid(Config), + MyJID = my_jid(Config), + MyBareJID = jid:remove_resource(MyJID), + true = is_feature_advertised(Config, ?NS_MIX_0, MIX), + #iq{type = result, + sub_els = + [#disco_info{ + identities = [#identity{category = <<"conference">>, + type = <<"text">>}], + xdata = [#xdata{type = result, fields = XFields}]}]} = + send_recv(Config, #iq{type = get, to = MIX, sub_els = [#disco_info{}]}), + true = lists:any( + fun(#xdata_field{var = <<"FORM_TYPE">>, + values = [?NS_MIX_SERVICEINFO_0]}) -> true; + (_) -> false + end, XFields), + %% Joining + Nodes = [?NS_MIX_NODES_MESSAGES, ?NS_MIX_NODES_PRESENCE, + ?NS_MIX_NODES_PARTICIPANTS, ?NS_MIX_NODES_SUBJECT, + ?NS_MIX_NODES_CONFIG], + I0 = send(Config, #iq{type = set, to = Room, + sub_els = [#mix_join{subscribe = Nodes}]}), + {_, #message{sub_els = + [#pubsub_event{ + items = [#pubsub_event_items{ + node = ?NS_MIX_NODES_PARTICIPANTS, + items = [#pubsub_event_item{ + id = ParticipantID, + xml_els = [PXML]}]}]}]}} = + ?recv2(#iq{type = result, id = I0, + sub_els = [#mix_join{subscribe = Nodes, jid = MyBareJID}]}, + #message{from = Room}), + #mix_participant{jid = MyBareJID} = xmpp_codec:decode(PXML), + %% Coming online + PresenceID = randoms:get_string(), + Presence = xmpp_codec:encode(#presence{}), + I1 = send( + Config, + #iq{type = set, to = Room, + sub_els = + [#pubsub{ + publish = #pubsub_publish{ + node = ?NS_MIX_NODES_PRESENCE, + items = [#pubsub_item{ + id = PresenceID, + xml_els = [Presence]}]}}]}), + ?recv2(#iq{type = result, id = I1, + sub_els = + [#pubsub{ + publish = #pubsub_publish{ + node = ?NS_MIX_NODES_PRESENCE, + items = [#pubsub_item{id = PresenceID}]}}]}, + #message{from = Room, + sub_els = + [#pubsub_event{ + items = [#pubsub_event_items{ + node = ?NS_MIX_NODES_PRESENCE, + items = [#pubsub_event_item{ + id = PresenceID, + xml_els = [Presence]}]}]}]}), + %% Coming offline + send(Config, #presence{type = unavailable, to = Room}), + %% Receiving presence retract event + #message{from = Room, + sub_els = [#pubsub_event{ + items = [#pubsub_event_items{ + node = ?NS_MIX_NODES_PRESENCE, + retract = [PresenceID]}]}]} = recv(), + %% Leaving + I2 = send(Config, #iq{type = set, to = Room, sub_els = [#mix_leave{}]}), + ?recv2(#iq{type = result, id = I2, sub_els = []}, + #message{from = Room, + sub_els = + [#pubsub_event{ + items = [#pubsub_event_items{ + node = ?NS_MIX_NODES_PARTICIPANTS, + retract = [ParticipantID]}]}]}), + disconnect(Config). + +mix_slave(Config) -> + disconnect(Config). + roster_subscribe_master(Config) -> send(Config, #presence{}), ?recv1(#presence{}), diff --git a/test/ejabberd_SUITE_data/ejabberd.yml b/test/ejabberd_SUITE_data/ejabberd.yml index 5507900b8..96453f77b 100644 --- a/test/ejabberd_SUITE_data/ejabberd.yml +++ b/test/ejabberd_SUITE_data/ejabberd.yml @@ -197,6 +197,7 @@ Welcome to this XMPP server." - "flat" - "hometree" - "pep" + mod_mix: [] mod_roster: versioning: true store_current_id: true @@ -252,6 +253,7 @@ Welcome to this XMPP server." - "flat" - "hometree" - "pep" + mod_mix: [] mod_roster: versioning: true store_current_id: true diff --git a/test/suite.erl b/test/suite.erl index a0e0a4823..bc094fa31 100644 --- a/test/suite.erl +++ b/test/suite.erl @@ -354,6 +354,14 @@ muc_room_jid(Config) -> Server = ?config(server, Config), jid:make(<<"test">>, <<"conference.", Server/binary>>, <<>>). +mix_jid(Config) -> + Server = ?config(server, Config), + jid:make(<<>>, <<"mix.", Server/binary>>, <<>>). + +mix_room_jid(Config) -> + Server = ?config(server, Config), + jid:make(<<"test">>, <<"mix.", Server/binary>>, <<>>). + id() -> id(undefined). diff --git a/tools/xmpp_codec.erl b/tools/xmpp_codec.erl index 917418f55..01e0676ae 100644 --- a/tools/xmpp_codec.erl +++ b/tools/xmpp_codec.erl @@ -15,6 +15,16 @@ decode(_el) -> decode(_el, []). decode({xmlel, _name, _attrs, _} = _el, Opts) -> IgnoreEls = proplists:get_bool(ignore_els, Opts), case {_name, get_attr(<<"xmlns">>, _attrs)} of + {<<"participant">>, <<"urn:xmpp:mix:0">>} -> + decode_mix_participant(<<"urn:xmpp:mix:0">>, IgnoreEls, + _el); + {<<"leave">>, <<"urn:xmpp:mix:0">>} -> + decode_mix_leave(<<"urn:xmpp:mix:0">>, IgnoreEls, _el); + {<<"join">>, <<"urn:xmpp:mix:0">>} -> + decode_mix_join(<<"urn:xmpp:mix:0">>, IgnoreEls, _el); + {<<"subscribe">>, <<"urn:xmpp:mix:0">>} -> + decode_mix_subscribe(<<"urn:xmpp:mix:0">>, IgnoreEls, + _el); {<<"offline">>, <<"http://jabber.org/protocol/offline">>} -> decode_offline(<<"http://jabber.org/protocol/offline">>, @@ -1088,6 +1098,10 @@ decode({xmlel, _name, _attrs, _} = _el, Opts) -> is_known_tag({xmlel, _name, _attrs, _} = _el) -> case {_name, get_attr(<<"xmlns">>, _attrs)} of + {<<"participant">>, <<"urn:xmpp:mix:0">>} -> true; + {<<"leave">>, <<"urn:xmpp:mix:0">>} -> true; + {<<"join">>, <<"urn:xmpp:mix:0">>} -> true; + {<<"subscribe">>, <<"urn:xmpp:mix:0">>} -> true; {<<"offline">>, <<"http://jabber.org/protocol/offline">>} -> true; @@ -1987,7 +2001,7 @@ encode({pubsub_items, _, _, _, _} = Items) -> encode_pubsub_items(Items, [{<<"xmlns">>, <<"http://jabber.org/protocol/pubsub">>}]); -encode({pubsub_event_item, _, _, _} = Item) -> +encode({pubsub_event_item, _, _, _, _} = Item) -> encode_pubsub_event_item(Item, [{<<"xmlns">>, <<"http://jabber.org/protocol/pubsub#event">>}]); @@ -2160,7 +2174,16 @@ encode({offline_item, _, _} = Item) -> encode({offline, _, _, _} = Offline) -> encode_offline(Offline, [{<<"xmlns">>, - <<"http://jabber.org/protocol/offline">>}]). + <<"http://jabber.org/protocol/offline">>}]); +encode({mix_join, _, _} = Join) -> + encode_mix_join(Join, + [{<<"xmlns">>, <<"urn:xmpp:mix:0">>}]); +encode({mix_leave} = Leave) -> + encode_mix_leave(Leave, + [{<<"xmlns">>, <<"urn:xmpp:mix:0">>}]); +encode({mix_participant, _, _} = Participant) -> + encode_mix_participant(Participant, + [{<<"xmlns">>, <<"urn:xmpp:mix:0">>}]). get_ns({last, _, _}) -> <<"jabber:iq:last">>; get_ns({version, _, _, _}) -> <<"jabber:iq:version">>; @@ -2286,7 +2309,7 @@ get_ns({pubsub_item, _, _}) -> <<"http://jabber.org/protocol/pubsub">>; get_ns({pubsub_items, _, _, _, _}) -> <<"http://jabber.org/protocol/pubsub">>; -get_ns({pubsub_event_item, _, _, _}) -> +get_ns({pubsub_event_item, _, _, _, _}) -> <<"http://jabber.org/protocol/pubsub#event">>; get_ns({pubsub_event_items, _, _, _}) -> <<"http://jabber.org/protocol/pubsub#event">>; @@ -2359,6 +2382,9 @@ get_ns({offline_item, _, _}) -> <<"http://jabber.org/protocol/offline">>; get_ns({offline, _, _, _}) -> <<"http://jabber.org/protocol/offline">>; +get_ns({mix_join, _, _}) -> <<"urn:xmpp:mix:0">>; +get_ns({mix_leave}) -> <<"urn:xmpp:mix:0">>; +get_ns({mix_participant, _, _}) -> <<"urn:xmpp:mix:0">>; get_ns(_) -> <<>>. dec_int(Val) -> dec_int(Val, infinity, infinity). @@ -2505,7 +2531,8 @@ pp(pubsub_subscription, 4) -> [jid, node, subid, type]; pp(pubsub_affiliation, 2) -> [node, type]; pp(pubsub_item, 2) -> [id, xml_els]; pp(pubsub_items, 4) -> [node, max_items, subid, items]; -pp(pubsub_event_item, 3) -> [id, node, publisher]; +pp(pubsub_event_item, 4) -> + [id, node, publisher, xml_els]; pp(pubsub_event_items, 3) -> [node, retract, items]; pp(pubsub_event, 1) -> [items]; pp(pubsub_subscribe, 2) -> [node, jid]; @@ -2564,6 +2591,9 @@ pp(sm_a, 2) -> [h, xmlns]; pp(sm_failed, 2) -> [reason, xmlns]; pp(offline_item, 2) -> [node, action]; pp(offline, 3) -> [items, purge, fetch]; +pp(mix_join, 2) -> [jid, subscribe]; +pp(mix_leave, 0) -> []; +pp(mix_participant, 2) -> [jid, nick]; pp(_, _) -> no. enc_bool(false) -> <<"false">>; @@ -2606,6 +2636,170 @@ dec_tzo(Val) -> M = jlib:binary_to_integer(M1), if H >= -12, H =< 12, M >= 0, M < 60 -> {H, M} end. +decode_mix_participant(__TopXMLNS, __IgnoreEls, + {xmlel, <<"participant">>, _attrs, _els}) -> + {Jid, Nick} = decode_mix_participant_attrs(__TopXMLNS, + _attrs, undefined, undefined), + {mix_participant, Jid, Nick}. + +decode_mix_participant_attrs(__TopXMLNS, + [{<<"jid">>, _val} | _attrs], _Jid, Nick) -> + decode_mix_participant_attrs(__TopXMLNS, _attrs, _val, + Nick); +decode_mix_participant_attrs(__TopXMLNS, + [{<<"nick">>, _val} | _attrs], Jid, _Nick) -> + decode_mix_participant_attrs(__TopXMLNS, _attrs, Jid, + _val); +decode_mix_participant_attrs(__TopXMLNS, [_ | _attrs], + Jid, Nick) -> + decode_mix_participant_attrs(__TopXMLNS, _attrs, Jid, + Nick); +decode_mix_participant_attrs(__TopXMLNS, [], Jid, + Nick) -> + {decode_mix_participant_attr_jid(__TopXMLNS, Jid), + decode_mix_participant_attr_nick(__TopXMLNS, Nick)}. + +encode_mix_participant({mix_participant, Jid, Nick}, + _xmlns_attrs) -> + _els = [], + _attrs = encode_mix_participant_attr_nick(Nick, + encode_mix_participant_attr_jid(Jid, + _xmlns_attrs)), + {xmlel, <<"participant">>, _attrs, _els}. + +decode_mix_participant_attr_jid(__TopXMLNS, + undefined) -> + erlang:error({xmpp_codec, + {missing_attr, <<"jid">>, <<"participant">>, + __TopXMLNS}}); +decode_mix_participant_attr_jid(__TopXMLNS, _val) -> + case catch dec_jid(_val) of + {'EXIT', _} -> + erlang:error({xmpp_codec, + {bad_attr_value, <<"jid">>, <<"participant">>, + __TopXMLNS}}); + _res -> _res + end. + +encode_mix_participant_attr_jid(_val, _acc) -> + [{<<"jid">>, enc_jid(_val)} | _acc]. + +decode_mix_participant_attr_nick(__TopXMLNS, + undefined) -> + undefined; +decode_mix_participant_attr_nick(__TopXMLNS, _val) -> + _val. + +encode_mix_participant_attr_nick(undefined, _acc) -> + _acc; +encode_mix_participant_attr_nick(_val, _acc) -> + [{<<"nick">>, _val} | _acc]. + +decode_mix_leave(__TopXMLNS, __IgnoreEls, + {xmlel, <<"leave">>, _attrs, _els}) -> + {mix_leave}. + +encode_mix_leave({mix_leave}, _xmlns_attrs) -> + _els = [], + _attrs = _xmlns_attrs, + {xmlel, <<"leave">>, _attrs, _els}. + +decode_mix_join(__TopXMLNS, __IgnoreEls, + {xmlel, <<"join">>, _attrs, _els}) -> + Subscribe = decode_mix_join_els(__TopXMLNS, __IgnoreEls, + _els, []), + Jid = decode_mix_join_attrs(__TopXMLNS, _attrs, + undefined), + {mix_join, Jid, Subscribe}. + +decode_mix_join_els(__TopXMLNS, __IgnoreEls, [], + Subscribe) -> + lists:reverse(Subscribe); +decode_mix_join_els(__TopXMLNS, __IgnoreEls, + [{xmlel, <<"subscribe">>, _attrs, _} = _el | _els], + Subscribe) -> + _xmlns = get_attr(<<"xmlns">>, _attrs), + if _xmlns == <<>>; _xmlns == __TopXMLNS -> + decode_mix_join_els(__TopXMLNS, __IgnoreEls, _els, + [decode_mix_subscribe(__TopXMLNS, __IgnoreEls, + _el) + | Subscribe]); + true -> + decode_mix_join_els(__TopXMLNS, __IgnoreEls, _els, + Subscribe) + end; +decode_mix_join_els(__TopXMLNS, __IgnoreEls, [_ | _els], + Subscribe) -> + decode_mix_join_els(__TopXMLNS, __IgnoreEls, _els, + Subscribe). + +decode_mix_join_attrs(__TopXMLNS, + [{<<"jid">>, _val} | _attrs], _Jid) -> + decode_mix_join_attrs(__TopXMLNS, _attrs, _val); +decode_mix_join_attrs(__TopXMLNS, [_ | _attrs], Jid) -> + decode_mix_join_attrs(__TopXMLNS, _attrs, Jid); +decode_mix_join_attrs(__TopXMLNS, [], Jid) -> + decode_mix_join_attr_jid(__TopXMLNS, Jid). + +encode_mix_join({mix_join, Jid, Subscribe}, + _xmlns_attrs) -> + _els = + lists:reverse('encode_mix_join_$subscribe'(Subscribe, + [])), + _attrs = encode_mix_join_attr_jid(Jid, _xmlns_attrs), + {xmlel, <<"join">>, _attrs, _els}. + +'encode_mix_join_$subscribe'([], _acc) -> _acc; +'encode_mix_join_$subscribe'([Subscribe | _els], + _acc) -> + 'encode_mix_join_$subscribe'(_els, + [encode_mix_subscribe(Subscribe, []) | _acc]). + +decode_mix_join_attr_jid(__TopXMLNS, undefined) -> + undefined; +decode_mix_join_attr_jid(__TopXMLNS, _val) -> + case catch dec_jid(_val) of + {'EXIT', _} -> + erlang:error({xmpp_codec, + {bad_attr_value, <<"jid">>, <<"join">>, __TopXMLNS}}); + _res -> _res + end. + +encode_mix_join_attr_jid(undefined, _acc) -> _acc; +encode_mix_join_attr_jid(_val, _acc) -> + [{<<"jid">>, enc_jid(_val)} | _acc]. + +decode_mix_subscribe(__TopXMLNS, __IgnoreEls, + {xmlel, <<"subscribe">>, _attrs, _els}) -> + Node = decode_mix_subscribe_attrs(__TopXMLNS, _attrs, + undefined), + Node. + +decode_mix_subscribe_attrs(__TopXMLNS, + [{<<"node">>, _val} | _attrs], _Node) -> + decode_mix_subscribe_attrs(__TopXMLNS, _attrs, _val); +decode_mix_subscribe_attrs(__TopXMLNS, [_ | _attrs], + Node) -> + decode_mix_subscribe_attrs(__TopXMLNS, _attrs, Node); +decode_mix_subscribe_attrs(__TopXMLNS, [], Node) -> + decode_mix_subscribe_attr_node(__TopXMLNS, Node). + +encode_mix_subscribe(Node, _xmlns_attrs) -> + _els = [], + _attrs = encode_mix_subscribe_attr_node(Node, + _xmlns_attrs), + {xmlel, <<"subscribe">>, _attrs, _els}. + +decode_mix_subscribe_attr_node(__TopXMLNS, undefined) -> + erlang:error({xmpp_codec, + {missing_attr, <<"node">>, <<"subscribe">>, + __TopXMLNS}}); +decode_mix_subscribe_attr_node(__TopXMLNS, _val) -> + _val. + +encode_mix_subscribe_attr_node(_val, _acc) -> + [{<<"node">>, _val} | _acc]. + decode_offline(__TopXMLNS, __IgnoreEls, {xmlel, <<"offline">>, _attrs, _els}) -> {Items, Purge, Fetch} = decode_offline_els(__TopXMLNS, @@ -7883,10 +8077,24 @@ encode_pubsub_event_items_attr_node(_val, _acc) -> decode_pubsub_event_item(__TopXMLNS, __IgnoreEls, {xmlel, <<"item">>, _attrs, _els}) -> + __Xmls = decode_pubsub_event_item_els(__TopXMLNS, + __IgnoreEls, _els, []), {Id, Node, Publisher} = decode_pubsub_event_item_attrs(__TopXMLNS, _attrs, undefined, undefined, undefined), - {pubsub_event_item, Id, Node, Publisher}. + {pubsub_event_item, Id, Node, Publisher, __Xmls}. + +decode_pubsub_event_item_els(__TopXMLNS, __IgnoreEls, + [], __Xmls) -> + lists:reverse(__Xmls); +decode_pubsub_event_item_els(__TopXMLNS, __IgnoreEls, + [{xmlel, _, _, _} = _el | _els], __Xmls) -> + decode_pubsub_event_item_els(__TopXMLNS, __IgnoreEls, + _els, [_el | __Xmls]); +decode_pubsub_event_item_els(__TopXMLNS, __IgnoreEls, + [_ | _els], __Xmls) -> + decode_pubsub_event_item_els(__TopXMLNS, __IgnoreEls, + _els, __Xmls). decode_pubsub_event_item_attrs(__TopXMLNS, [{<<"id">>, _val} | _attrs], _Id, Node, @@ -7915,9 +8123,9 @@ decode_pubsub_event_item_attrs(__TopXMLNS, [], Id, Node, Publisher)}. encode_pubsub_event_item({pubsub_event_item, Id, Node, - Publisher}, + Publisher, __Xmls}, _xmlns_attrs) -> - _els = [], + _els = __Xmls, _attrs = encode_pubsub_event_item_attr_publisher(Publisher, encode_pubsub_event_item_attr_node(Node, diff --git a/tools/xmpp_codec.hrl b/tools/xmpp_codec.hrl index 1426287bf..b2773a6c1 100644 --- a/tools/xmpp_codec.hrl +++ b/tools/xmpp_codec.hrl @@ -36,6 +36,8 @@ jid :: any(), subid :: binary()}). +-record(mix_leave, {}). + -record(ping, {}). -record(delay, {stamp :: any(), @@ -98,7 +100,8 @@ -record(pubsub_event_item, {id :: binary(), node :: binary(), - publisher :: binary()}). + publisher :: binary(), + xml_els = [] :: [any()]}). -record(sm_r, {xmlns :: binary()}). @@ -229,6 +232,9 @@ notify = false :: any(), items = [] :: [#pubsub_item{}]}). +-record(mix_participant, {jid :: any(), + nick :: binary()}). + -record(vcard_geo, {lat :: binary(), lon :: binary()}). @@ -471,6 +477,9 @@ error :: #error{}, sub_els = [] :: [any()]}). +-record(mix_join, {jid :: any(), + subscribe = [] :: [binary()]}). + -record(privacy_item, {order :: non_neg_integer(), action :: 'allow' | 'deny', type :: 'group' | 'jid' | 'subscription', diff --git a/tools/xmpp_codec.spec b/tools/xmpp_codec.spec index 129a4efba..e61b951c5 100644 --- a/tools/xmpp_codec.spec +++ b/tools/xmpp_codec.spec @@ -1645,7 +1645,7 @@ -xml(pubsub_event_item, #elem{name = <<"item">>, xmlns = <<"http://jabber.org/protocol/pubsub#event">>, - result = {pubsub_event_item, '$id', '$node', '$publisher'}, + result = {pubsub_event_item, '$id', '$node', '$publisher', '$_xmls'}, attrs = [#attr{name = <<"id">>}, #attr{name = <<"node">>}, #attr{name = <<"publisher">>}]}). @@ -2427,6 +2427,41 @@ label = '$fetch', default = false}, #ref{name = offline_item, min = 0, label = '$items'}]}). +-xml(mix_subscribe, + #elem{name = <<"subscribe">>, + xmlns = <<"urn:xmpp:mix:0">>, + result = '$node', + attrs = [#attr{name = <<"node">>, + required = true, + label = '$node'}]}). + +-xml(mix_join, + #elem{name = <<"join">>, + xmlns = <<"urn:xmpp:mix:0">>, + result = {mix_join, '$jid', '$subscribe'}, + attrs = [#attr{name = <<"jid">>, + label = '$jid', + dec = {dec_jid, []}, + enc = {enc_jid, []}}], + refs = [#ref{name = mix_subscribe, min = 0, label = '$subscribe'}]}). + +-xml(mix_leave, + #elem{name = <<"leave">>, + xmlns = <<"urn:xmpp:mix:0">>, + result = {mix_leave}}). + +-xml(mix_participant, + #elem{name = <<"participant">>, + xmlns = <<"urn:xmpp:mix:0">>, + result = {mix_participant, '$jid', '$nick'}, + attrs = [#attr{name = <<"jid">>, + required = true, + label = '$jid', + dec = {dec_jid, []}, + enc = {enc_jid, []}}, + #attr{name = <<"nick">>, + label = '$nick'}]}). + dec_tzo(Val) -> [H1, M1] = str:tokens(Val, <<":">>), H = jlib:binary_to_integer(H1),