./ct_report/coverage/mongoose_acc.COVER.html

1 %%%-------------------------------------------------------------------
2 %%% File : mongoose_acc.erl
3 %%% Author : Piotr Nosek <piotr.nosek@erlang-solutions.com>
4 %%% Author : Bartlomiej Gorny <bartlomiej.gorny@erlang-solutions.com>
5 %%% Purpose : Mongoose accumulator implementation
6 %%% Created : 11 Sep 2018 by Piotr Nosek <piotr.nosek@erlang-solutions.com>
7 %%%
8 %%% NS:Key conventions:
9 %%% * hook:result should be used to return hook processing result
10 %%% * iq:* contains useful IQ metadata but must be provided by mongoose_iq.erl
11 %%%-------------------------------------------------------------------
12 -module(mongoose_acc).
13 -author("bartlomiej.gorny@erlang-solutions.com").
14 -author("piotr.nosek@erlang-solutions.com").
15
16 -include("jlib.hrl").
17
18 %% API
19 % Constructor
20 -export([new/1]).
21 % Access to built-in fields
22 -export([
23 ref/1,
24 timestamp/1,
25 lserver/1,
26 host_type/1,
27 element/1,
28 to_jid/1,
29 from_jid/1,
30 packet/1,
31 stanza_name/1,
32 stanza_type/1,
33 stanza_ref/1
34 ]).
35 % Stanza update
36 -export([update_stanza/2]).
37 % C2S accumulator
38 -export([get_statem_acc/1, set_statem_acc/2]).
39 % Access to namespaced fields
40 -export([
41 set/3,
42 set/4,
43 set_permanent/2,
44 set_permanent/3,
45 set_permanent/4,
46 append/4,
47 get_permanent_keys/1,
48 get_permanent_fields/1,
49 get/2,
50 get/3,
51 get/4,
52 delete/3,
53 delete/2,
54 delete_many/3
55 ]).
56 % Strip with or without stanza replacement
57 -export([strip/1, strip/2]).
58
59 -ignore_xref([delete/2, ref/1]).
60
61 %% Note about 'undefined' to_jid and from_jid: these are the special cases when JID may be
62 %% truly unknown: before a client is authorized.
63
64 -type line_number() :: non_neg_integer().
65 -type location() :: #{mfa := mfa(),
66 line := line_number(),
67 file := string()}.
68
69 -type stanza_metadata() :: #{
70 element := exml:element(),
71 from_jid := jid:jid() | undefined,
72 to_jid := jid:jid() | undefined,
73 name := binary(),
74 type := binary(),
75 ref := reference()
76 }.
77
78 %% If it is defined as -opaque then dialyzer fails
79 %% It's still valid in acc 2.0 and gain is probably not worth the effort
80 -type t() :: #{
81 mongoose_acc := true,
82 ref := reference(),
83 timestamp := integer(), %microsecond
84 origin_pid := pid(),
85 origin_location := location(),
86 stanza := stanza_metadata() | undefined,
87 lserver := jid:lserver(),
88 host_type := binary() | undefined,
89 non_strippable := [ns_key()],
90 statem_acc := mongoose_c2s_acc:t(),
91 ns_key() => Value :: any()
92 }.
93
94 -export_type([t/0,
95 new_acc_params/0]).
96
97 -type new_acc_params() :: #{
98 location := location(),
99 lserver => jid:lserver(),
100 element => exml:element() | undefined,
101 host_type => binary() | undefined, % optional
102 from_jid => jid:jid() | undefined, % optional
103 to_jid => jid:jid() | undefined, % optional
104 statem_acc => mongoose_c2s_acc:t() % optional
105 }.
106
107 -type strip_params() :: #{
108 lserver := jid:lserver(),
109 element := exml:element(),
110 host_type => binary() | undefined, % optional
111 from_jid => jid:jid() | undefined, % optional
112 to_jid => jid:jid() | undefined % optional
113 }.
114
115 -type stanza_params() :: #{
116 element := exml:element(),
117 from_jid => jid:jid() | undefined, % optional
118 to_jid => jid:jid() | undefined, % optional
119 _ => _
120 }.
121
122 -type ns_key() :: {NS :: any(), Key :: any()}.
123 -type ns_key_value() :: {Namespace :: any(), K :: any(), V :: any()}.
124
125 %% --------------------------------------------------------
126 %% API
127 %% --------------------------------------------------------
128
129 -spec new(Params :: new_acc_params()) -> t().
130 new(#{ location := Location, lserver := LServer } = Params) ->
131 65404 Stanza = case maps:get(element, Params, undefined) of
132 19835 undefined -> undefined;
133 45569 _Element -> stanza_from_params(Params)
134 end,
135 65404 HostType = get_host_type(Params),
136 65404 #{
137 mongoose_acc => true,
138 ref => make_ref(),
139 timestamp => erlang:system_time(microsecond),
140 origin_pid => self(),
141 origin_location => Location,
142 stanza => Stanza,
143 lserver => LServer,
144 host_type => HostType,
145 statem_acc => get_mongoose_c2s_acc(Params),
146 %% The non_strippable elements must be unique.
147 %% This used to be represented with the sets module, but as the number of elements inserted
148 %% was too small, sets were themselves an overhead, and also annoying when printing
149 non_strippable => []
150 }.
151
152 -spec ref(Acc :: t()) -> reference().
153 ref(#{ mongoose_acc := true, ref := Ref }) ->
154 6 Ref.
155
156 -spec timestamp(Acc :: t()) -> integer().
157 timestamp(#{ mongoose_acc := true, timestamp := TS }) ->
158 9104 TS.
159
160 -spec lserver(Acc :: t()) -> jid:lserver().
161 lserver(#{ mongoose_acc := true, lserver := LServer }) ->
162 43724 LServer.
163
164 -spec host_type(Acc :: t()) -> binary() | undefined.
165 host_type(#{ mongoose_acc := true, host_type := HostType }) ->
166 189199 HostType.
167
168 -spec element(Acc :: t()) -> exml:element() | undefined.
169 element(#{ mongoose_acc := true, stanza := #{ element := El } }) ->
170 183509 El;
171 element(#{ mongoose_acc := true }) ->
172 4 undefined.
173
174 -spec from_jid(Acc :: t()) -> jid:jid() | undefined.
175 from_jid(#{ mongoose_acc := true, stanza := #{ from_jid := FromJID } }) ->
176 13072 FromJID;
177 from_jid(#{ mongoose_acc := true }) ->
178
:-(
undefined.
179
180 -spec to_jid(Acc :: t()) -> jid:jid() | undefined.
181 to_jid(#{ mongoose_acc := true, stanza := #{ to_jid := ToJID } }) ->
182 3479 ToJID;
183 to_jid(#{ mongoose_acc := true }) ->
184
:-(
undefined.
185
186 -spec packet(Acc :: t()) -> mongoose_c2s:packet() | undefined.
187 packet(#{ mongoose_acc := true, stanza := #{ to_jid := ToJID,
188 from_jid := FromJID,
189 element := El } }) ->
190 88247 {FromJID, ToJID, El};
191 packet(#{ mongoose_acc := true }) ->
192
:-(
undefined.
193
194 -spec stanza_name(Acc :: t()) -> binary() | undefined.
195 stanza_name(#{ mongoose_acc := true, stanza := #{ name := Name } }) ->
196 60762 Name;
197 stanza_name(#{ mongoose_acc := true }) ->
198
:-(
undefined.
199
200 -spec stanza_type(Acc :: t()) -> binary() | undefined.
201 stanza_type(#{ mongoose_acc := true, stanza := #{ type := Type } }) ->
202 92799 Type;
203 stanza_type(#{ mongoose_acc := true }) ->
204 9 undefined.
205
206 -spec stanza_ref(Acc :: t()) -> reference() | undefined.
207 stanza_ref(#{ mongoose_acc := true, stanza := #{ ref := StanzaRef } }) ->
208 28096 StanzaRef;
209 stanza_ref(#{ mongoose_acc := true }) ->
210
:-(
undefined.
211
212 -spec update_stanza(NewStanzaParams :: stanza_params(), Acc :: t()) -> t().
213 update_stanza(NewStanzaParams, #{ mongoose_acc := true } = Acc) ->
214 164915 Acc#{ stanza := stanza_from_params(NewStanzaParams) }.
215
216 -spec get_statem_acc(Acc :: t()) -> mongoose_c2s_acc:t().
217 get_statem_acc(#{ mongoose_acc := true, statem_acc := StatemAcc }) ->
218 85424 StatemAcc.
219
220 -spec set_statem_acc(NewStatemAcc :: mongoose_c2s_acc:t(), Acc :: t()) -> t().
221 set_statem_acc(NewStatemAcc, Acc = #{ mongoose_acc := true }) ->
222 32709 Acc#{statem_acc := NewStatemAcc}.
223
224 %% Values set with this function are discarded during 'strip' operation...
225 -spec set(Namespace :: any(), K :: any(), V :: any(), Acc :: t()) -> t().
226 set(NS, K, V, #{ mongoose_acc := true } = Acc) ->
227 119721 Acc#{ {NS, K} => V }.
228
229 -spec set(Namespace :: any(), [{K :: any(), V :: any()}], Acc :: t()) -> t().
230 set(NS, KVs, #{ mongoose_acc := true } = Acc) ->
231 71339 NSKVs = [ {{NS, K}, V} || {K, V} <- KVs ],
232 71339 Input = maps:from_list(NSKVs),
233 71339 maps:merge(Acc, Input).
234
235 -spec set([ns_key_value()], Acc :: t()) -> t().
236 set(NSKVs, #{ mongoose_acc := true } = Acc) ->
237 2620 PropList = [ {{NS, K}, V} || {NS, K, V} <- NSKVs ],
238 2620 Input = maps:from_list(PropList),
239 2620 maps:merge(Acc, Input).
240
241 %% .. while these are not.
242 -spec set_permanent(Namespace :: any(), K :: any(), V :: any(), Acc :: t()) -> t().
243 set_permanent(NS, K, V, #{ mongoose_acc := true, non_strippable := NonStrippable } = Acc) ->
244 5778 Key = {NS, K},
245 5778 NewNonStrippable = [Key | lists:delete(Key, NonStrippable)],
246 5778 Acc#{ Key => V, non_strippable := NewNonStrippable }.
247
248 -spec set_permanent(Namespace :: any(), [{K :: any(), V :: any()}], Acc :: t()) -> t().
249 set_permanent(NS, KVs, #{mongoose_acc := true, non_strippable := NonStrippable} = Acc) ->
250 39257 NewKeys = [{NS, K} || {K, _V} <- KVs, not lists:member({NS, K}, NonStrippable)],
251 39257 set(NS, KVs, Acc#{non_strippable := NewKeys ++ NonStrippable }).
252
253 -spec set_permanent([ns_key_value()], Acc :: t()) -> t().
254 set_permanent(NSKVs, #{mongoose_acc := true, non_strippable := NonStrippable} = Acc) ->
255 2620 NewKeys = [{NS, K} || {NS, K, _V} <- NSKVs, not lists:member({NS, K}, NonStrippable)],
256 2620 set(NSKVs, Acc#{non_strippable := NewKeys ++ NonStrippable }).
257
258 -spec append(NS :: any(), Key :: any(), Val :: any() | [any()], Acc :: t()) -> t().
259 append(NS, Key, Val, Acc) ->
260 44442 OldVal = get(NS, Key, [], Acc),
261 44442 set(NS, Key, append(OldVal, Val), Acc).
262
263 -spec get_permanent_keys(Acc :: t()) -> [ns_key()].
264 get_permanent_keys(#{mongoose_acc := true, non_strippable := NonStrippable}) ->
265 2663 NonStrippable.
266
267 -spec get_permanent_fields(Acc :: t()) -> [ns_key_value()].
268 get_permanent_fields(Acc) ->
269 2663 [{NS, Key, mongoose_acc:get(NS, Key, Acc)} ||
270 2663 {NS, Key} <- mongoose_acc:get_permanent_keys(Acc)].
271
272 -spec get(Namespace :: any(), Acc :: t()) -> [{K :: any(), V :: any()}].
273 get(NS, #{mongoose_acc := true} = Acc) ->
274 20 Fn = fun({Namespace, K}, V, List) when Namespace =:= NS ->
275 40 [{K, V} | List];
276 (_, _, List) ->
277 280 List
278 end,
279 20 maps:fold(Fn, [], Acc).
280
281 -spec get(Namespace :: any(), K :: any(), Acc :: t()) -> V :: any().
282 get(NS, K, #{ mongoose_acc := true } = Acc) ->
283 28545 maps:get({NS, K}, Acc).
284
285 -spec get(Namespace :: any(), K :: any(), Default :: any(), Acc :: t()) -> V :: any().
286 get(NS, K, Default, #{ mongoose_acc := true } = Acc) ->
287 334518 maps:get({NS, K}, Acc, Default).
288
289 -spec delete(Namespace :: any(), K :: any(), Acc :: t()) -> t().
290 delete(NS, K, #{ mongoose_acc := true, non_strippable := NonStrippable } = Acc0) ->
291 5121 Key = {NS, K},
292 5121 Acc1 = maps:remove(Key, Acc0),
293 5121 Acc1#{ non_strippable := lists:delete(Key, NonStrippable) }.
294
295 -spec delete_many(Namespace :: any(), [K :: any()], Acc :: t()) -> t().
296 delete_many(NS, Keys, #{ mongoose_acc := true, non_strippable := NonStrippable } = Acc0) ->
297 133 KVs = [{NS, K} || K <- Keys],
298 133 Acc1 = maps:without(KVs, Acc0),
299 133 Acc1#{ non_strippable := lists:subtract(NonStrippable, KVs) }.
300
301 -spec delete(Namespace :: any(), Acc :: t()) -> t().
302 delete(NS, Acc) ->
303
:-(
KeyList = [K || {K, _} <- get(NS, Acc)],
304
:-(
delete_many(NS, KeyList, Acc).
305
306 -spec strip(Acc :: t()) -> t().
307 strip(#{ mongoose_acc := true, non_strippable := NonStrippable } = Acc) ->
308 43852 Stripped = maps:with(NonStrippable ++ default_non_strippable(), Acc),
309 43852 Stripped#{statem_acc := mongoose_c2s_acc:new()}.
310
311 -spec strip(ParamsToOverwrite :: strip_params(), Acc :: t()) -> t().
312 strip(#{ lserver := NewLServer } = Params, Acc) ->
313 43755 StrippedAcc = strip(Acc),
314 43755 StrippedAcc#{ lserver := NewLServer, host_type := get_host_type(Params),
315 stanza := stanza_from_params(Params) }.
316
317 %% --------------------------------------------------------
318 %% Internal functions
319 %% --------------------------------------------------------
320 -spec get_host_type(new_acc_params() | strip_params()) -> binary() | undefined.
321 get_host_type(#{host_type := HostType}) ->
322 101775 HostType;
323 get_host_type(_) ->
324 7384 undefined.
325
326 -spec get_mongoose_c2s_acc(new_acc_params() | strip_params()) -> mongoose_c2s_acc:t() | undefined.
327 get_mongoose_c2s_acc(#{statem_acc := C2SAcc}) ->
328
:-(
C2SAcc;
329 get_mongoose_c2s_acc(_) ->
330 65404 mongoose_c2s_acc:new().
331
332 -spec stanza_from_params(Params :: stanza_params() | strip_params()) ->
333 stanza_metadata().
334 stanza_from_params(#{ element := El } = Params) ->
335 254239 #{
336 element => El,
337 from_jid => jid_from_params(from_jid, <<"from">>, Params),
338 to_jid => jid_from_params(to_jid, <<"to">>, Params),
339 name => El#xmlel.name,
340 type => exml_query:attr(El, <<"type">>),
341 ref => make_ref()
342 }.
343
344 -spec jid_from_params(MapKey :: to_jid | from_jid,
345 StanzaAttrName :: binary(),
346 Params :: stanza_params()) -> jid:jid().
347 jid_from_params(to_jid, _, #{to_jid := Jid}) ->
348 248243 Jid;
349 jid_from_params(from_jid, _, #{from_jid := Jid}) ->
350 254225 Jid;
351 jid_from_params(_, StanzaAttrName, #{element := El}) ->
352 6010 #jid{} = jid:from_binary(exml_query:attr(El, StanzaAttrName)).
353
354 -spec default_non_strippable() -> [atom()].
355 default_non_strippable() ->
356 43852 [
357 mongoose_acc,
358 ref,
359 timestamp,
360 origin_pid,
361 origin_location,
362 stanza,
363 lserver,
364 host_type,
365 statem_acc,
366 non_strippable
367 ].
368
369 -spec append(OldVal :: list(), Val :: list() | any()) -> list().
370 660 append(OldVal, Val) when is_list(OldVal), is_list(Val) -> OldVal ++ Val;
371 43782 append(OldVal, Val) when is_list(OldVal) -> [Val | OldVal].
Line Hits Source