./ct_report/coverage/mod_stream_management.COVER.html

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 5755 lists:keystore(<<"sm">>, #xmlel.name, Acc, sm()).
125
126 sm() ->
127 5755 #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 2645 HostType = mongoose_acc:host_type(Acc),
139 2645 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 2645 H = mongoose_acc:get(stream_mgmt, h, undefined, Acc),
151 2645 MaybeSMID = case mnesia:dirty_index_read(sm_session, SID, #sm_session.sid) of
152 2602 [] -> {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 2645 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 38 MaybeModOpts = gen_mod:get_module_opt(HostType, ?MODULE, stale_h, []),
192 38 case proplists:get_value(enabled, MaybeModOpts, false) of
193 1 false -> {error, smid_not_found};
194 37 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.
Line Hits Source