./ct_report/coverage/mongoose_s2s_cets.COVER.html

1 -module(mongoose_s2s_cets).
2 -behaviour(mongoose_s2s_backend).
3
4 -export([init/1,
5 get_s2s_out_pids/1,
6 try_register/2,
7 remove_connection/2,
8 node_cleanup/1]).
9
10 -export([register_secret/2,
11 get_shared_secret/1]).
12
13 %% Internal usage (export so the callback would survive multiple code reloads)
14 -export([handle_secret_conflict/2]).
15
16 -define(TABLE, cets_s2s_session).
17 -define(SECRET_TABLE, cets_s2s_secret).
18
19 -type secret_tuple() :: {HostType :: mongooseim:host_type(), TS :: integer(), Secret :: ejabberd_s2s:base16_secret()}.
20
21 -spec init(map()) -> ok.
22 init(_) ->
23 35 cets:start(?TABLE, #{}),
24 %% Non-random, non-node-specific keys
25 %% This means that default merging would not work
26 35 cets:start(?SECRET_TABLE, #{handle_conflict => fun ?MODULE:handle_secret_conflict/2}),
27 35 cets_discovery:add_table(mongoose_cets_discovery, ?TABLE),
28 35 cets_discovery:add_table(mongoose_cets_discovery, ?SECRET_TABLE),
29 35 ok.
30
31 %% Chooses the most recent value of two.
32 %% Even if we choose the wrong record - nothing bad would happen
33 %% (we still need to choose one).
34 %% Choosing the record with the highest timestamp is just a logical behaviour
35 %% (it also matches the logic of mongoose_s2s_lib:check_shared_secret/2, where updated secret
36 %% in the config is updated across all nodes in the cluster).
37 %% Example call:
38 %% handle_secret_conflict({<<"localhost">>, 1689858975612268, <<"4e48dc4898b23f512059">>},
39 %% {<<"localhost">>, 1689859177195451, <<"56fdcd3ec63ff8299eb0">>}).
40 -spec handle_secret_conflict(secret_tuple(), secret_tuple()) -> secret_tuple().
41 handle_secret_conflict(Rec1, Rec2) when Rec1 > Rec2 ->
42 170 Rec1;
43 handle_secret_conflict(_Rec1, Rec2) ->
44 3 Rec2.
45
46 %% Pid lists
47 -spec get_s2s_out_pids(ejabberd_s2s:fromto()) -> ejabberd_s2s:s2s_pids().
48 get_s2s_out_pids(FromTo) ->
49 164 R = {{FromTo, '$1'}},
50 164 ets:select(?TABLE, [{R, [], ['$1']}]).
51
52 -spec try_register(Pid :: pid(),
53 FromTo :: ejabberd_s2s:fromto()) -> boolean().
54 try_register(Pid, FromTo) ->
55 37 Pids = get_s2s_out_pids(FromTo),
56 37 case mongoose_s2s_lib:need_more_connections(FromTo, Pids) of
57 true ->
58 24 cets:insert(?TABLE, {{FromTo, Pid}}),
59 24 true;
60 false ->
61 13 false
62 end.
63
64 -spec remove_connection(FromTo :: ejabberd_s2s:fromto(), Pid :: pid()) -> ok.
65 remove_connection(FromTo, Pid) ->
66 24 cets:delete(?TABLE, {FromTo, Pid}),
67 24 ok.
68
69 %% node_cleanup is called on each node in the cluster, when Node is down
70 -spec node_cleanup(Node :: node()) -> ok.
71 node_cleanup(Node) ->
72
:-(
KeyPattern = {'_', '$1'},
73
:-(
R = {KeyPattern},
74
:-(
Guard = {'==', {node, '$1'}, Node},
75
:-(
Objects = ets:select(?TABLE, [{R, [Guard], ['$_']}]),
76
:-(
cets:delete_objects(?TABLE, Objects).
77
78 %% Secrets
79 -spec register_secret(HostType :: mongooseim:host_type(),
80 Secret :: ejabberd_s2s:base16_secret()) -> ok.
81 register_secret(HostType, Secret) ->
82 %% We store timestamp so we could use it when merging two tables when clustering.
83 %% Secrets is a very small table and get_shared_secret is called rarely,
84 %% so having an extra field is not a problem.
85 186 TS = erlang:system_time(microsecond),
86 186 cets:insert(?SECRET_TABLE, {HostType, TS, Secret}),
87 186 ok.
88
89 -spec get_shared_secret(mongooseim:host_type()) ->
90 {ok, ejabberd_s2s:base16_secret()} | {error, not_found}.
91 get_shared_secret(HostType) ->
92 222 case ets:lookup(?SECRET_TABLE, HostType) of
93 [{_HostType, _TS, Secret}] ->
94 38 {ok, Secret};
95 [] ->
96 184 {error, not_found}
97 end.
Line Hits Source