%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
%%% 02111-1307 USA
%%%
+%%% 2009, improvements from Process-One to support correct PEP handling
+%%% through s2s, use less memory, and speedup global caps handling
%%%----------------------------------------------------------------------
-module(mod_caps).
-define(CAPS_QUERY_TIMEOUT, 60000). % 1mn without answer, consider client never answer
-record(caps, {node, version, exts}).
--record(caps_features, {node_pair, features}).
+-record(caps_features, {node_pair, features = []}).
-record(user_caps, {jid, caps}).
-record(user_caps_resources, {uid, resource}).
-record(state, {host,
%% get_caps reads user caps from database
get_caps(JID) ->
- case catch mnesia:dirty_read({user_caps, list_to_binary(jlib:jid_to_string(JID))}) of
+ case catch mnesia:dirty_read({user_caps, jid_to_binary(JID)}) of
[#user_caps{caps=Caps}] ->
Caps;
_ ->
%% clear_caps removes user caps from database
clear_caps(JID) ->
{U, S, R} = jlib:jid_tolower(JID),
- BJID = list_to_binary(jlib:jid_to_string(JID)),
- BUID = list_to_binary(jlib:jid_to_string({U, S, []})),
+ BJID = jid_to_binary(JID),
+ BUID = jid_to_binary({U, S, []}),
catch mnesia:dirty_delete({user_caps, BJID}),
catch mnesia:dirty_delete_object(#user_caps_resources{uid = BUID, resource = list_to_binary(R)}),
ok.
%% give default user resource
get_user_resources(LUser, LServer) ->
- BUID = list_to_binary(jlib:jid_to_string({LUser, LServer, []})),
- case catch mnesia:dirty_read({user_caps_resources, BUID}) of
+ case catch mnesia:dirty_read({user_caps_resources, jid_to_binary({LUser, LServer, []})}) of
{'EXIT', _} ->
[];
Resources ->
receive_packet(_JID, From, To, Packet) ->
receive_packet(From, To, Packet).
+jid_to_binary(JID) ->
+ list_to_binary(jlib:jid_to_string(JID)).
+
+caps_to_binary(#caps{node = Node, version = Version, exts = Exts}) ->
+ BExts = [list_to_binary(Ext) || Ext <- Exts],
+ #caps{node = list_to_binary(Node), version = list_to_binary(Version), exts = BExts}.
+
+node_to_binary(Node, SubNode) ->
+ {list_to_binary(Node), list_to_binary(SubNode)}.
+
+features_to_binary(L) -> [list_to_binary(I) || I <- L].
+binary_to_features(L) -> [binary_to_list(I) || I <- L].
+
%%====================================================================
%% gen_server callbacks
%%====================================================================
init([Host, _Opts]) ->
mnesia:create_table(caps_features,
- [{ram_copies, [node()]},
+ [{disc_copies, [node()]},
{attributes, record_info(fields, caps_features)}]),
mnesia:create_table(user_caps,
- [{disc_copies, [node()]},
+ [{ram_copies, [node()]},
{attributes, record_info(fields, user_caps)}]),
mnesia:create_table(user_caps_resources,
- [{disc_copies, [node()]},
+ [{ram_copies, [node()]},
{type, bag},
{attributes, record_info(fields, user_caps_resources)}]),
mnesia:delete_table(user_caps_default),
maybe_get_features(#caps{node = Node, version = Version, exts = Exts}) ->
SubNodes = [Version | Exts],
- F = fun() ->
- %% Make sure that we have all nodes we need to know.
- %% If a single one is missing, we wait for more disco
- %% responses.
- lists:foldl(fun(SubNode, Acc) ->
- case Acc of
- fail -> fail;
- _ ->
- case mnesia:read({caps_features, {Node, SubNode}}) of
- [] -> fail;
- [#caps_features{features = Features}] -> Features ++ Acc
- end
- end
- end, [], SubNodes)
- end,
- case mnesia:transaction(F) of
- {atomic, fail} ->
- wait;
- {atomic, Features} ->
- {ok, Features}
+ %% Make sure that we have all nodes we need to know.
+ %% If a single one is missing, we wait for more disco
+ %% responses.
+ case lists:foldl(fun(SubNode, Acc) ->
+ case Acc of
+ fail -> fail;
+ _ ->
+ case mnesia:dirty_read({caps_features, {Node, SubNode}}) of
+ [] -> fail;
+ [#caps_features{features = Features}] -> Features ++ Acc %% TODO binary
+ end
+ end
+ end, [], SubNodes) of
+ fail -> wait;
+ Features -> {ok, Features}
end.
timestamp() ->
handle_call({get_features, Caps}, From, State) ->
case maybe_get_features(Caps) of
{ok, Features} ->
- {reply, Features, State};
+ {reply, binary_to_features(Features), State};
wait ->
gen_server:cast(self(), visit_feature_queries),
Timeout = timestamp() + 10,
%% XXX: this leads to race conditions where ejabberd will send
%% lots of caps disco requests.
{U, S, R} = jlib:jid_tolower(From),
- BJID = list_to_binary(jlib:jid_to_string(From)),
- mnesia:dirty_write(#user_caps{jid = BJID, caps = Caps}),
+ BJID = jid_to_binary(From),
+ mnesia:dirty_write(#user_caps{jid = BJID, caps = caps_to_binary(Caps)}),
case ejabberd_sm:get_user_resources(U, S) of
[] ->
% only store resources of caps aware external contacts
- BUID = list_to_binary(jlib:jid_to_string(jlib:jid_remove_resource(From))),
+ BUID = jid_to_binary({U, S, []}),
mnesia:dirty_write(#user_caps_resources{uid = BUID, resource = list_to_binary(R)});
_ ->
ok
end,
- SubNodes = [Version | Exts],
%% Now, find which of these are not already in the database.
- Fun = fun() ->
- lists:foldl(fun(SubNode, Acc) ->
- case mnesia:read({caps_features, {Node, SubNode}}) of
- [] ->
- [SubNode | Acc];
- _ ->
- Acc
- end
- end, [], SubNodes)
- end,
- case mnesia:transaction(Fun) of
- {atomic, Missing} ->
+ SubNodes = [Version | Exts],
+ case lists:foldl(fun(SubNode, Acc) ->
+ case mnesia:dirty_read({caps_features, node_to_binary(Node, SubNode)}) of
+ [] ->
+ [SubNode | Acc];
+ _ ->
+ Acc
+ end
+ end, [], SubNodes) of
+ [] ->
+ {noreply, State};
+ Missing ->
%% For each unknown caps "subnode", we send a disco request.
NewRequests = lists:foldl(
fun(SubNode, Dict) ->
(Host, ID, ?MODULE, handle_disco_response),
ejabberd_router:route(jlib:make_jid("", Host, ""), From, Stanza),
timer:send_after(?CAPS_QUERY_TIMEOUT, self(), {disco_timeout, ID}),
- ?DICT:store(ID, {Node, SubNode}, Dict)
+ ?DICT:store(ID, node_to_binary(Node, SubNode), Dict)
end, Requests, Missing),
- {noreply, State#state{disco_requests = NewRequests}};
- Error ->
- ?ERROR_MSG("Transaction failed: ~p", [Error]),
- {noreply, State}
+ {noreply, State#state{disco_requests = NewRequests}}
end;
handle_cast({disco_response, From, _To,
#iq{type = Type, id = ID,
case {Type, SubEls} of
{result, [{xmlelement, "query", _Attrs, Els}]} ->
case ?DICT:find(ID, Requests) of
- {ok, {Node, SubNode}} ->
+ {ok, BinaryNode} ->
Features =
lists:flatmap(fun({xmlelement, "feature", FAttrs, _}) ->
[xml:get_attr_s("var", FAttrs)];
(_) ->
[]
end, Els),
- mnesia:transaction(
- fun() ->
- mnesia:write(#caps_features{node_pair = {Node, SubNode},
- features = Features})
- end),
+ mnesia:dirty_write(#caps_features{node_pair = BinaryNode, features = features_to_binary(Features)}),
gen_server:cast(self(), visit_feature_queries);
error ->
?ERROR_MSG("ID '~s' matches no query", [ID])
{error, _} ->
%% XXX: if we get error, we cache empty feature not to probe the client continuously
case ?DICT:find(ID, Requests) of
- {ok, {Node, SubNode}} ->
- Features = [],
- mnesia:transaction(
- fun() ->
- mnesia:write(#caps_features{node_pair = {Node, SubNode},
- features = Features})
- end),
+ {ok, BinaryNode} ->
+ mnesia:write(#caps_features{node_pair = BinaryNode}),
gen_server:cast(self(), visit_feature_queries);
error ->
?ERROR_MSG("ID '~s' matches no query", [ID])