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/1, |
22 |
|
get_ack_freq/1, |
23 |
|
get_resume_timeout/1, |
24 |
|
register_smid/3]). |
25 |
|
|
26 |
|
%% API for inspection and tests |
27 |
|
-export([get_sid/2, |
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/2, 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 |
|
-export_type([smid/0]). |
38 |
|
|
39 |
|
-include("mongoose.hrl"). |
40 |
|
-include("jlib.hrl"). |
41 |
|
-include("mongoose_config_spec.hrl"). |
42 |
|
|
43 |
|
-type buffer_max() :: pos_integer() | infinity | no_buffer. |
44 |
|
-type ack_freq() :: pos_integer() | never. |
45 |
|
%% |
46 |
|
%% `gen_mod' callbacks |
47 |
|
%% |
48 |
|
|
49 |
|
start(HostType, Opts) -> |
50 |
387 |
mod_stream_management_backend:init(HostType, Opts), |
51 |
387 |
?LOG_INFO(#{what => stream_management_starting}), |
52 |
387 |
ejabberd_hooks:add(hooks(HostType)), |
53 |
387 |
ok. |
54 |
|
|
55 |
|
stop(HostType) -> |
56 |
387 |
?LOG_INFO(#{what => stream_management_stopping}), |
57 |
387 |
ejabberd_hooks:delete(hooks(HostType)), |
58 |
387 |
ok. |
59 |
|
|
60 |
|
hooks(HostType) -> |
61 |
774 |
[{sm_remove_connection_hook, HostType, ?MODULE, remove_smid, 50}, |
62 |
|
{c2s_stream_features, HostType, ?MODULE, c2s_stream_features, 50}, |
63 |
|
{session_cleanup, HostType, ?MODULE, session_cleanup, 50}]. |
64 |
|
|
65 |
|
-spec config_spec() -> mongoose_config_spec:config_section(). |
66 |
|
config_spec() -> |
67 |
166 |
#section{ |
68 |
|
items = #{<<"backend">> => #option{type = atom, validate = {module, ?MODULE}}, |
69 |
|
<<"buffer">> => #option{type = boolean}, |
70 |
|
<<"buffer_max">> => #option{type = int_or_infinity, |
71 |
|
validate = positive}, |
72 |
|
<<"ack">> => #option{type = boolean}, |
73 |
|
<<"ack_freq">> => #option{type = integer, |
74 |
|
validate = positive}, |
75 |
|
<<"resume_timeout">> => #option{type = integer, |
76 |
|
validate = positive}, |
77 |
|
<<"stale_h">> => stale_h_config_spec() |
78 |
|
}, |
79 |
|
process = fun ?MODULE:process_buffer_and_ack/1, |
80 |
|
format_items = map, |
81 |
|
defaults = #{<<"backend">> => mnesia, |
82 |
|
<<"buffer">> => true, |
83 |
|
<<"buffer_max">> => 100, |
84 |
|
<<"ack">> => true, |
85 |
|
<<"ack_freq">> => 1, |
86 |
|
<<"resume_timeout">> => 600 % seconds |
87 |
|
} |
88 |
|
}. |
89 |
|
|
90 |
148 |
supported_features() -> [dynamic_domains]. |
91 |
|
|
92 |
|
process_buffer_and_ack(Opts = #{buffer := Buffer, ack := Ack}) -> |
93 |
83 |
OptsWithBuffer = check_buffer(Buffer, Opts), |
94 |
83 |
check_ack(Ack, OptsWithBuffer). |
95 |
|
|
96 |
|
check_buffer(false, Opts) -> |
97 |
:-( |
Opts#{buffer_max => no_buffer}; |
98 |
|
check_buffer(_, Opts) -> |
99 |
83 |
Opts. |
100 |
|
|
101 |
|
check_ack(false, Opts) -> |
102 |
:-( |
Opts#{ack_freq => never}; |
103 |
|
check_ack(_, Opts) -> |
104 |
83 |
Opts. |
105 |
|
|
106 |
|
stale_h_config_spec() -> |
107 |
166 |
#section{ |
108 |
|
items = #{<<"enabled">> => #option{type = boolean}, |
109 |
|
<<"repeat_after">> => #option{type = integer, |
110 |
|
validate = positive}, |
111 |
|
<<"geriatric">> => #option{type = integer, |
112 |
|
validate = positive}}, |
113 |
|
format_items = map, |
114 |
|
include = always, |
115 |
|
defaults = #{<<"enabled">> => false, |
116 |
|
<<"repeat_after">> => 1800, % seconds |
117 |
|
<<"geriatric">> => 3600 % seconds |
118 |
|
} |
119 |
|
}. |
120 |
|
|
121 |
|
%% |
122 |
|
%% hooks handlers |
123 |
|
%% |
124 |
|
|
125 |
|
-spec c2s_stream_features([exml:element()], mongooseim:host_type(), jid:lserver()) -> |
126 |
|
[exml:element()]. |
127 |
|
c2s_stream_features(Acc, _HostType, _Lserver) -> |
128 |
6067 |
lists:keystore(<<"sm">>, #xmlel.name, Acc, sm()). |
129 |
|
|
130 |
|
sm() -> |
131 |
6067 |
#xmlel{name = <<"sm">>, |
132 |
|
attrs = [{<<"xmlns">>, ?NS_STREAM_MGNT_3}]}. |
133 |
|
|
134 |
|
-spec remove_smid(Acc, SID, JID, Info, Reason) -> Acc1 when |
135 |
|
Acc :: mongoose_acc:t(), |
136 |
|
SID :: ejabberd_sm:sid(), |
137 |
|
JID :: undefined | jid:jid(), |
138 |
|
Info :: undefined | [any()], |
139 |
|
Reason :: undefined | ejabberd_sm:close_reason(), |
140 |
|
Acc1 :: mongoose_acc:t(). |
141 |
|
remove_smid(Acc, SID, _JID, _Info, _Reason) -> |
142 |
2801 |
HostType = mongoose_acc:host_type(Acc), |
143 |
2801 |
do_remove_smid(Acc, HostType, SID). |
144 |
|
|
145 |
|
-spec session_cleanup(Acc :: map(), LUser :: jid:luser(), LServer :: jid:lserver(), |
146 |
|
LResource :: jid:lresource(), SID :: ejabberd_sm:sid()) -> any(). |
147 |
|
session_cleanup(Acc, _LUser, _LServer, _LResource, SID) -> |
148 |
:-( |
HostType = mongoose_acc:host_type(Acc), |
149 |
:-( |
do_remove_smid(Acc, HostType, SID). |
150 |
|
|
151 |
|
-spec do_remove_smid(mongoose_acc:t(), mongooseim:host_type(), ejabberd_sm:sid()) -> |
152 |
|
mongoose_acc:t(). |
153 |
|
do_remove_smid(Acc, HostType, SID) -> |
154 |
2801 |
H = mongoose_acc:get(stream_mgmt, h, undefined, Acc), |
155 |
2801 |
MaybeSMID = unregister_smid(HostType, SID), |
156 |
2801 |
case MaybeSMID of |
157 |
|
{ok, SMID} when H =/= undefined -> |
158 |
32 |
register_stale_smid_h(HostType, SMID, H); |
159 |
|
_ -> |
160 |
2769 |
ok |
161 |
|
end, |
162 |
2801 |
mongoose_acc:set(stream_mgmt, smid, MaybeSMID, Acc). |
163 |
|
|
164 |
|
%% |
165 |
|
%% API for `ejabberd_c2s' |
166 |
|
%% |
167 |
|
|
168 |
|
-spec make_smid() -> smid(). |
169 |
|
make_smid() -> |
170 |
33 |
base64:encode(crypto:strong_rand_bytes(21)). |
171 |
|
|
172 |
|
%% Getters |
173 |
|
-spec get_session_from_smid(mongooseim:host_type(), smid()) -> |
174 |
|
{sid, ejabberd_sm:sid()} | {stale_h, non_neg_integer()} | {error, smid_not_found}. |
175 |
|
get_session_from_smid(HostType, SMID) -> |
176 |
17 |
case get_sid(HostType, SMID) of |
177 |
12 |
{sid, SID} -> {sid, SID}; |
178 |
5 |
{error, smid_not_found} -> get_stale_h(HostType, SMID) |
179 |
|
end. |
180 |
|
|
181 |
|
-spec get_stale_h(mongooseim:host_type(), SMID :: smid()) -> |
182 |
|
{stale_h, non_neg_integer()} | {error, smid_not_found}. |
183 |
|
get_stale_h(HostType, SMID) -> |
184 |
25 |
case is_stale_h_enabled(HostType) of |
185 |
1 |
false -> {error, smid_not_found}; |
186 |
24 |
true -> read_stale_h(HostType, SMID) |
187 |
|
end. |
188 |
|
|
189 |
|
-spec get_buffer_max(mongooseim:host_type()) -> buffer_max(). |
190 |
|
get_buffer_max(HostType) -> |
191 |
60 |
gen_mod:get_module_opt(HostType, ?MODULE, buffer_max). |
192 |
|
|
193 |
|
-spec get_ack_freq(mongooseim:host_type()) -> ack_freq(). |
194 |
|
get_ack_freq(HostType) -> |
195 |
60 |
gen_mod:get_module_opt(HostType, ?MODULE, ack_freq). |
196 |
|
|
197 |
|
-spec get_resume_timeout(mongooseim:host_type()) -> pos_integer(). |
198 |
|
get_resume_timeout(HostType) -> |
199 |
60 |
gen_mod:get_module_opt(HostType, ?MODULE, resume_timeout). |
200 |
|
|
201 |
|
|
202 |
|
register_stale_smid_h(HostType, SMID, H) -> |
203 |
38 |
case is_stale_h_enabled(HostType) of |
204 |
30 |
false -> ok; |
205 |
8 |
true -> write_stale_h(HostType, SMID, H) |
206 |
|
end. |
207 |
|
|
208 |
|
remove_stale_smid_h(HostType, SMID) -> |
209 |
:-( |
case is_stale_h_enabled(HostType) of |
210 |
:-( |
false -> ok; |
211 |
:-( |
true -> delete_stale_h(HostType, SMID) |
212 |
|
end. |
213 |
|
|
214 |
|
is_stale_h_enabled(HostType) -> |
215 |
63 |
gen_mod:get_module_opt(HostType, ?MODULE, [stale_h, enabled]). |
216 |
|
|
217 |
|
%% Backend operations |
218 |
|
|
219 |
|
-spec register_smid(HostType, SMID, SID) -> |
220 |
|
ok | {error, term()} when |
221 |
|
HostType :: mongooseim:host_type(), |
222 |
|
SMID :: mod_stream_management:smid(), |
223 |
|
SID :: ejabberd_sm:sid(). |
224 |
|
register_smid(HostType, SMID, SID) -> |
225 |
44 |
mod_stream_management_backend:register_smid(HostType, SMID, SID). |
226 |
|
|
227 |
|
-spec unregister_smid(mongooseim:host_type(), ejabberd_sm:sid()) -> |
228 |
|
{ok, SMID :: mod_stream_management:smid()} | {error, smid_not_found}. |
229 |
|
unregister_smid(HostType, SID) -> |
230 |
2801 |
mod_stream_management_backend:unregister_smid(HostType, SID). |
231 |
|
|
232 |
|
-spec get_sid(mongooseim:host_type(), mod_stream_management:smid()) -> |
233 |
|
{sid, ejabberd_sm:sid()} | {error, smid_not_found}. |
234 |
|
get_sid(HostType, SMID) -> |
235 |
18 |
mod_stream_management_backend:get_sid(HostType, SMID). |
236 |
|
|
237 |
|
%% stale_h |
238 |
|
|
239 |
|
-spec write_stale_h(HostType, SMID, H) -> ok | {error, any()} when |
240 |
|
HostType :: mongooseim:host_type(), |
241 |
|
SMID :: mod_stream_management:smid(), |
242 |
|
H :: non_neg_integer(). |
243 |
|
write_stale_h(HostType, SMID, H) -> |
244 |
8 |
mod_stream_management_backend:write_stale_h(HostType, SMID, H). |
245 |
|
|
246 |
|
-spec delete_stale_h(HostType, SMID) -> ok | {error, any()} when |
247 |
|
HostType :: mongooseim:host_type(), |
248 |
|
SMID :: mod_stream_management:smid(). |
249 |
|
delete_stale_h(HostType, SMID) -> |
250 |
:-( |
mod_stream_management_backend:delete_stale_h(HostType, SMID). |
251 |
|
|
252 |
|
-spec read_stale_h(HostType, SMID) -> |
253 |
|
{stale_h, non_neg_integer()} | {error, smid_not_found} when |
254 |
|
HostType :: mongooseim:host_type(), |
255 |
|
SMID :: mod_stream_management:smid(). |
256 |
|
read_stale_h(HostType, SMID) -> |
257 |
24 |
mod_stream_management_backend:read_stale_h(HostType, SMID). |