1 |
|
-module(mod_stream_management). |
2 |
|
-xep([{xep, 198}, {version, "1.6"}]). |
3 |
|
-behaviour(gen_mod). |
4 |
|
-behaviour(mongoose_module_metrics). |
5 |
|
|
6 |
|
%% `gen_mod' callbacks |
7 |
|
-export([start/2, |
8 |
|
stop/1, |
9 |
|
config_spec/0, |
10 |
|
supported_features/0, |
11 |
|
process_buffer_and_ack/1]). |
12 |
|
|
13 |
|
%% hooks handlers |
14 |
|
-export([c2s_stream_features/3, |
15 |
|
remove_smid/5, |
16 |
|
session_cleanup/5]). |
17 |
|
|
18 |
|
%% API for `ejabberd_c2s' |
19 |
|
-export([make_smid/0, |
20 |
|
get_session_from_smid/2, |
21 |
|
get_buffer_max/2, |
22 |
|
get_ack_freq/2, |
23 |
|
get_resume_timeout/2, |
24 |
|
register_smid/2]). |
25 |
|
|
26 |
|
%% API for inspection and tests |
27 |
|
-export([get_sid/1, |
28 |
|
get_stale_h/2, |
29 |
|
register_stale_smid_h/3, |
30 |
|
remove_stale_smid_h/2]). |
31 |
|
|
32 |
|
-ignore_xref([c2s_stream_features/3, get_sid/1, get_stale_h/2, remove_smid/5, |
33 |
|
register_stale_smid_h/3, remove_stale_smid_h/2, session_cleanup/5]). |
34 |
|
|
35 |
|
-type smid() :: base64:ascii_binary(). |
36 |
|
|
37 |
|
-include("mongoose.hrl"). |
38 |
|
-include("jlib.hrl"). |
39 |
|
-include("mongoose_config_spec.hrl"). |
40 |
|
|
41 |
|
-record(sm_session, |
42 |
|
{smid :: smid(), |
43 |
|
sid :: ejabberd_sm:sid() |
44 |
|
}). |
45 |
|
|
46 |
|
-type buffer_max() :: pos_integer() | infinity | no_buffer. |
47 |
|
-type ack_freq() :: pos_integer() | never. |
48 |
|
%% |
49 |
|
%% `gen_mod' callbacks |
50 |
|
%% |
51 |
|
|
52 |
|
start(HostType, Opts) -> |
53 |
375 |
?LOG_INFO(#{what => stream_management_starting}), |
54 |
375 |
ejabberd_hooks:add(hooks(HostType)), |
55 |
375 |
mnesia:create_table(sm_session, [{ram_copies, [node()]}, |
56 |
|
{attributes, record_info(fields, sm_session)}]), |
57 |
375 |
mnesia:add_table_index(sm_session, sid), |
58 |
375 |
mnesia:add_table_copy(sm_session, node(), ram_copies), |
59 |
375 |
stream_management_stale_h:maybe_start(Opts), |
60 |
375 |
ok. |
61 |
|
|
62 |
|
stop(HostType) -> |
63 |
375 |
?LOG_INFO(#{what => stream_management_stopping}), |
64 |
375 |
ejabberd_hooks:delete(hooks(HostType)), |
65 |
375 |
ok. |
66 |
|
|
67 |
|
hooks(HostType) -> |
68 |
750 |
[{sm_remove_connection_hook, HostType, ?MODULE, remove_smid, 50}, |
69 |
|
{c2s_stream_features, HostType, ?MODULE, c2s_stream_features, 50}, |
70 |
|
{session_cleanup, HostType, ?MODULE, session_cleanup, 50}]. |
71 |
|
|
72 |
|
-spec config_spec() -> mongoose_config_spec:config_section(). |
73 |
|
config_spec() -> |
74 |
160 |
#section{ |
75 |
|
items = #{<<"buffer">> => #option{type = boolean}, |
76 |
|
<<"buffer_max">> => #option{type = int_or_infinity, |
77 |
|
validate = positive}, |
78 |
|
<<"ack">> => #option{type = boolean}, |
79 |
|
<<"ack_freq">> => #option{type = integer, |
80 |
|
validate = positive}, |
81 |
|
<<"resume_timeout">> => #option{type = integer, |
82 |
|
validate = positive}, |
83 |
|
<<"stale_h">> => stale_h_config_spec() |
84 |
|
}, |
85 |
|
process = fun ?MODULE:process_buffer_and_ack/1 |
86 |
|
}. |
87 |
|
|
88 |
142 |
supported_features() -> [dynamic_domains]. |
89 |
|
|
90 |
|
process_buffer_and_ack(KVs) -> |
91 |
80 |
{[Buffer, Ack], Opts} = proplists:split(KVs, [buffer, ack]), |
92 |
80 |
OptsWithBuffer = check_buffer(Buffer, Opts), |
93 |
80 |
check_ack(Ack, OptsWithBuffer). |
94 |
|
|
95 |
|
check_buffer([{buffer, false}], Opts) -> |
96 |
:-( |
lists:ukeysort(1, [{buffer_max, no_buffer}] ++ Opts); |
97 |
|
check_buffer(_, Opts) -> |
98 |
80 |
Opts. |
99 |
|
|
100 |
|
check_ack([{ack, false}], Opts) -> |
101 |
:-( |
lists:ukeysort(1, [{ack_freq, never}] ++ Opts); |
102 |
|
check_ack(_, Opts) -> |
103 |
80 |
Opts. |
104 |
|
|
105 |
|
stale_h_config_spec() -> |
106 |
160 |
#section{ |
107 |
|
items = #{<<"enabled">> => #option{type = boolean}, |
108 |
|
<<"repeat_after">> => #option{type = integer, |
109 |
|
validate = positive, |
110 |
|
wrap = {kv, stale_h_repeat_after}}, |
111 |
|
<<"geriatric">> => #option{type = integer, |
112 |
|
validate = positive, |
113 |
|
wrap = {kv, stale_h_geriatric}} |
114 |
|
} |
115 |
|
}. |
116 |
|
|
117 |
|
%% |
118 |
|
%% hooks handlers |
119 |
|
%% |
120 |
|
|
121 |
|
-spec c2s_stream_features([exml:element()], mongooseim:host_type(), jid:lserver()) -> |
122 |
|
[exml:element()]. |
123 |
|
c2s_stream_features(Acc, _HostType, _Lserver) -> |
124 |
5663 |
lists:keystore(<<"sm">>, #xmlel.name, Acc, sm()). |
125 |
|
|
126 |
|
sm() -> |
127 |
5663 |
#xmlel{name = <<"sm">>, |
128 |
|
attrs = [{<<"xmlns">>, ?NS_STREAM_MGNT_3}]}. |
129 |
|
|
130 |
|
-spec remove_smid(Acc, SID, JID, Info, Reason) -> Acc1 when |
131 |
|
Acc :: mongoose_acc:t(), |
132 |
|
SID :: ejabberd_sm:sid(), |
133 |
|
JID :: undefined | jid:jid(), |
134 |
|
Info :: undefined | [any()], |
135 |
|
Reason :: undefined | ejabberd_sm:close_reason(), |
136 |
|
Acc1 :: mongoose_acc:t(). |
137 |
|
remove_smid(Acc, SID, _JID, _Info, _Reason) -> |
138 |
2603 |
HostType = mongoose_acc:host_type(Acc), |
139 |
2603 |
do_remove_smid(Acc, HostType, SID). |
140 |
|
|
141 |
|
-spec session_cleanup(Acc :: map(), LUser :: jid:luser(), LServer :: jid:lserver(), |
142 |
|
LResource :: jid:lresource(), SID :: ejabberd_sm:sid()) -> any(). |
143 |
|
session_cleanup(Acc, _LUser, _LServer, _LResource, SID) -> |
144 |
:-( |
HostType = mongoose_acc:host_type(Acc), |
145 |
:-( |
do_remove_smid(Acc, HostType, SID). |
146 |
|
|
147 |
|
-spec do_remove_smid(mongoose_acc:t(), mongooseim:host_type(), ejabberd_sm:sid()) -> |
148 |
|
mongoose_acc:t(). |
149 |
|
do_remove_smid(Acc, HostType, SID) -> |
150 |
2603 |
H = mongoose_acc:get(stream_mgmt, h, undefined, Acc), |
151 |
2603 |
MaybeSMID = case mnesia:dirty_index_read(sm_session, SID, #sm_session.sid) of |
152 |
2560 |
[] -> {error, smid_not_found}; |
153 |
|
[#sm_session{smid = SMID}] -> |
154 |
43 |
mnesia:dirty_delete(sm_session, SMID), |
155 |
43 |
case H of |
156 |
11 |
undefined -> ok; |
157 |
32 |
_ -> register_stale_smid_h(HostType, SMID, H) |
158 |
|
end, |
159 |
43 |
{ok, SMID} |
160 |
|
end, |
161 |
2603 |
mongoose_acc:set(stream_mgmt, smid, MaybeSMID, Acc). |
162 |
|
|
163 |
|
%% |
164 |
|
%% API for `ejabberd_c2s' |
165 |
|
%% |
166 |
|
|
167 |
|
-spec make_smid() -> smid(). |
168 |
|
make_smid() -> |
169 |
33 |
base64:encode(crypto:strong_rand_bytes(21)). |
170 |
|
|
171 |
|
%% Getters |
172 |
|
-spec get_session_from_smid(mongooseim:host_type(), smid()) -> |
173 |
|
{sid, ejabberd_sm:sid()} | {stale_h, non_neg_integer()} | {error, smid_not_found}. |
174 |
|
get_session_from_smid(HostType, SMID) -> |
175 |
17 |
case get_sid(SMID) of |
176 |
12 |
{sid, SID} -> {sid, SID}; |
177 |
5 |
{error, smid_not_found} -> get_stale_h(HostType, SMID) |
178 |
|
end. |
179 |
|
|
180 |
|
-spec get_sid(smid()) -> |
181 |
|
{sid, ejabberd_sm:sid()} | {error, smid_not_found}. |
182 |
|
get_sid(SMID) -> |
183 |
18 |
case mnesia:dirty_read(sm_session, SMID) of |
184 |
12 |
[#sm_session{sid = SID}] -> {sid, SID}; |
185 |
6 |
[] -> {error, smid_not_found} |
186 |
|
end. |
187 |
|
|
188 |
|
-spec get_stale_h(mongooseim:host_type(), SMID :: smid()) -> |
189 |
|
{stale_h, non_neg_integer()} | {error, smid_not_found}. |
190 |
|
get_stale_h(HostType, SMID) -> |
191 |
39 |
MaybeModOpts = gen_mod:get_module_opt(HostType, ?MODULE, stale_h, []), |
192 |
39 |
case proplists:get_value(enabled, MaybeModOpts, false) of |
193 |
1 |
false -> {error, smid_not_found}; |
194 |
38 |
true -> stream_management_stale_h:read_stale_h(SMID) |
195 |
|
end. |
196 |
|
|
197 |
|
-spec get_buffer_max(mongooseim:host_type(), buffer_max()) -> buffer_max(). |
198 |
|
get_buffer_max(HostType, Default) -> |
199 |
60 |
gen_mod:get_module_opt(HostType, ?MODULE, buffer_max, Default). |
200 |
|
|
201 |
|
-spec get_ack_freq(mongooseim:host_type(), ack_freq()) -> ack_freq(). |
202 |
|
get_ack_freq(HostType, Default) -> |
203 |
60 |
gen_mod:get_module_opt(HostType, ?MODULE, ack_freq, Default). |
204 |
|
|
205 |
|
-spec get_resume_timeout(mongooseim:host_type(), pos_integer()) -> pos_integer(). |
206 |
|
get_resume_timeout(HostType, Default) -> |
207 |
60 |
gen_mod:get_module_opt(HostType, ?MODULE, resume_timeout, Default). |
208 |
|
|
209 |
|
%% Setters |
210 |
|
register_smid(SMID, SID) -> |
211 |
44 |
try |
212 |
44 |
mnesia:sync_dirty(fun mnesia:write/1, |
213 |
|
[#sm_session{smid = SMID, sid = SID}]), |
214 |
44 |
ok |
215 |
|
catch exit:Reason -> |
216 |
:-( |
{error, Reason} |
217 |
|
end. |
218 |
|
|
219 |
|
register_stale_smid_h(HostType, SMID, H) -> |
220 |
38 |
MaybeModOpts = gen_mod:get_module_opt(HostType, ?MODULE, stale_h, []), |
221 |
38 |
case proplists:get_value(enabled, MaybeModOpts, false) of |
222 |
30 |
false -> ok; |
223 |
8 |
true -> stream_management_stale_h:write_stale_h(SMID, H) |
224 |
|
end. |
225 |
|
|
226 |
|
remove_stale_smid_h(HostType, SMID) -> |
227 |
:-( |
MaybeModOpts = gen_mod:get_module_opt(HostType, ?MODULE, stale_h, []), |
228 |
:-( |
case proplists:get_value(enabled, MaybeModOpts, false) of |
229 |
:-( |
false -> ok; |
230 |
:-( |
true -> stream_management_stale_h:delete_stale_h(SMID) |
231 |
|
end. |