./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 stanza_metadata/0,
96 new_acc_params/0]).
97
98 -type new_acc_params() :: #{
99 location := location(),
100 lserver => jid:lserver(),
101 element => exml:element() | undefined,
102 host_type => binary() | undefined, % optional
103 from_jid => jid:jid() | undefined, % optional
104 to_jid => jid:jid() | undefined, % optional
105 statem_acc => mongoose_c2s_acc:t() % optional
106 }.
107
108 -type strip_params() :: #{
109 lserver := jid:lserver(),
110 element := exml:element(),
111 host_type => binary() | undefined, % optional
112 from_jid => jid:jid() | undefined, % optional
113 to_jid => jid:jid() | undefined % optional
114 }.
115
116 -type stanza_params() :: #{
117 element := exml:element(),
118 from_jid => jid:jid() | undefined, % optional
119 to_jid => jid:jid() | undefined, % optional
120 _ => _
121 }.
122
123 -type ns_key() :: {NS :: any(), Key :: any()}.
124 -type ns_key_value() :: {Namespace :: any(), K :: any(), V :: any()}.
125
126 %% --------------------------------------------------------
127 %% API
128 %% --------------------------------------------------------
129
130 -spec new(Params :: new_acc_params()) -> t().
131 new(#{ location := Location, lserver := LServer } = Params) ->
132 95595 Stanza = case maps:get(element, Params, undefined) of
133 31401 undefined -> undefined;
134 64194 _Element -> stanza_from_params(Params)
135 end,
136 95595 HostType = get_host_type(Params),
137 95595 #{
138 mongoose_acc => true,
139 ref => make_ref(),
140 timestamp => erlang:system_time(microsecond),
141 origin_pid => self(),
142 origin_location => Location,
143 stanza => Stanza,
144 lserver => LServer,
145 host_type => HostType,
146 statem_acc => get_mongoose_c2s_acc(Params),
147 %% The non_strippable elements must be unique.
148 %% This used to be represented with the sets module, but as the number of elements inserted
149 %% was too small, sets were themselves an overhead, and also annoying when printing
150 non_strippable => []
151 }.
152
153 -spec ref(Acc :: t()) -> reference().
154 ref(#{ mongoose_acc := true, ref := Ref }) ->
155 6 Ref.
156
157 -spec timestamp(Acc :: t()) -> integer().
158 timestamp(#{ mongoose_acc := true, timestamp := TS }) ->
159 9757 TS.
160
161 -spec lserver(Acc :: t()) -> jid:lserver().
162 lserver(#{ mongoose_acc := true, lserver := LServer }) ->
163 56662 LServer.
164
165 -spec host_type(Acc :: t()) -> binary() | undefined.
166 host_type(#{ mongoose_acc := true, host_type := HostType }) ->
167 239075 HostType.
168
169 -spec element(Acc :: t()) -> exml:element() | undefined.
170 element(#{ mongoose_acc := true, stanza := #{ element := El } }) ->
171 250947 El;
172 element(#{ mongoose_acc := true }) ->
173 4 undefined.
174
175 -spec from_jid(Acc :: t()) -> jid:jid() | undefined.
176 from_jid(#{ mongoose_acc := true, stanza := #{ from_jid := FromJID } }) ->
177 14082 FromJID;
178 from_jid(#{ mongoose_acc := true }) ->
179
:-(
undefined.
180
181 -spec to_jid(Acc :: t()) -> jid:jid() | undefined.
182 to_jid(#{ mongoose_acc := true, stanza := #{ to_jid := ToJID } }) ->
183 3719 ToJID;
184 to_jid(#{ mongoose_acc := true }) ->
185
:-(
undefined.
186
187 -spec packet(Acc :: t()) -> mongoose_c2s:packet() | undefined.
188 packet(#{ mongoose_acc := true, stanza := #{ to_jid := ToJID,
189 from_jid := FromJID,
190 element := El } }) ->
191 109246 {FromJID, ToJID, El};
192 packet(#{ mongoose_acc := true }) ->
193
:-(
undefined.
194
195 -spec stanza_name(Acc :: t()) -> binary() | undefined.
196 stanza_name(#{ mongoose_acc := true, stanza := #{ name := Name } }) ->
197 86251 Name;
198 stanza_name(#{ mongoose_acc := true }) ->
199
:-(
undefined.
200
201 -spec stanza_type(Acc :: t()) -> binary() | undefined.
202 stanza_type(#{ mongoose_acc := true, stanza := #{ type := Type } }) ->
203 118427 Type;
204 stanza_type(#{ mongoose_acc := true }) ->
205 9 undefined.
206
207 -spec stanza_ref(Acc :: t()) -> reference() | undefined.
208 stanza_ref(#{ mongoose_acc := true, stanza := #{ ref := StanzaRef } }) ->
209 39044 StanzaRef;
210 stanza_ref(#{ mongoose_acc := true }) ->
211
:-(
undefined.
212
213 -spec update_stanza(NewStanzaParams :: stanza_params(), Acc :: t()) -> t().
214 update_stanza(NewStanzaParams, #{ mongoose_acc := true } = Acc) ->
215 210775 Acc#{ stanza := stanza_from_params(NewStanzaParams) }.
216
217 -spec get_statem_acc(Acc :: t()) -> mongoose_c2s_acc:t().
218 get_statem_acc(#{ mongoose_acc := true, statem_acc := StatemAcc }) ->
219 132281 StatemAcc.
220
221 -spec set_statem_acc(NewStatemAcc :: mongoose_c2s_acc:t(), Acc :: t()) -> t().
222 set_statem_acc(NewStatemAcc, Acc = #{ mongoose_acc := true }) ->
223 60485 Acc#{statem_acc := NewStatemAcc}.
224
225 %% Values set with this function are discarded during 'strip' operation...
226 -spec set(Namespace :: any(), K :: any(), V :: any(), Acc :: t()) -> t().
227 set(NS, K, V, #{ mongoose_acc := true } = Acc) ->
228 161983 Acc#{ {NS, K} => V }.
229
230 -spec set(Namespace :: any(), [{K :: any(), V :: any()}], Acc :: t()) -> t().
231 set(NS, KVs, #{ mongoose_acc := true } = Acc) ->
232 103277 NSKVs = [ {{NS, K}, V} || {K, V} <- KVs ],
233 103277 Input = maps:from_list(NSKVs),
234 103277 maps:merge(Acc, Input).
235
236 -spec set([ns_key_value()], Acc :: t()) -> t().
237 set(NSKVs, #{ mongoose_acc := true } = Acc) ->
238 2803 PropList = [ {{NS, K}, V} || {NS, K, V} <- NSKVs ],
239 2803 Input = maps:from_list(PropList),
240 2803 maps:merge(Acc, Input).
241
242 %% .. while these are not.
243 -spec set_permanent(Namespace :: any(), K :: any(), V :: any(), Acc :: t()) -> t().
244 set_permanent(NS, K, V, #{ mongoose_acc := true, non_strippable := NonStrippable } = Acc) ->
245 11922 Key = {NS, K},
246 11922 NewNonStrippable = [Key | lists:delete(Key, NonStrippable)],
247 11922 Acc#{ Key => V, non_strippable := NewNonStrippable }.
248
249 -spec set_permanent(Namespace :: any(), [{K :: any(), V :: any()}], Acc :: t()) -> t().
250 set_permanent(NS, KVs, #{mongoose_acc := true, non_strippable := NonStrippable} = Acc) ->
251 57328 NewKeys = [{NS, K} || {K, _V} <- KVs, not lists:member({NS, K}, NonStrippable)],
252 57328 set(NS, KVs, Acc#{non_strippable := NewKeys ++ NonStrippable }).
253
254 -spec set_permanent([ns_key_value()], Acc :: t()) -> t().
255 set_permanent(NSKVs, #{mongoose_acc := true, non_strippable := NonStrippable} = Acc) ->
256 2803 NewKeys = [{NS, K} || {NS, K, _V} <- NSKVs, not lists:member({NS, K}, NonStrippable)],
257 2803 set(NSKVs, Acc#{non_strippable := NewKeys ++ NonStrippable }).
258
259 -spec append(NS :: any(), Key :: any(), Val :: any() | [any()], Acc :: t()) -> t().
260 append(NS, Key, Val, Acc) ->
261 59955 OldVal = get(NS, Key, [], Acc),
262 59955 set(NS, Key, append(OldVal, Val), Acc).
263
264 -spec get_permanent_keys(Acc :: t()) -> [ns_key()].
265 get_permanent_keys(#{mongoose_acc := true, non_strippable := NonStrippable}) ->
266 2855 NonStrippable.
267
268 -spec get_permanent_fields(Acc :: t()) -> [ns_key_value()].
269 get_permanent_fields(Acc) ->
270 2855 [{NS, Key, mongoose_acc:get(NS, Key, Acc)} ||
271 2855 {NS, Key} <- mongoose_acc:get_permanent_keys(Acc)].
272
273 -spec get(Namespace :: any(), Acc :: t()) -> [{K :: any(), V :: any()}].
274 get(NS, #{mongoose_acc := true} = Acc) ->
275 1444 Fn = fun({Namespace, K}, V, List) when Namespace =:= NS ->
276 186 [{K, V} | List];
277 (_, _, List) ->
278 18671 List
279 end,
280 1444 maps:fold(Fn, [], Acc).
281
282 -spec get(Namespace :: any(), K :: any(), Acc :: t()) -> V :: any().
283 get(NS, K, #{ mongoose_acc := true } = Acc) ->
284 36815 maps:get({NS, K}, Acc).
285
286 -spec get(Namespace :: any(), K :: any(), Default :: any(), Acc :: t()) -> V :: any().
287 get(NS, K, Default, #{ mongoose_acc := true } = Acc) ->
288 442078 maps:get({NS, K}, Acc, Default).
289
290 -spec delete(Namespace :: any(), K :: any(), Acc :: t()) -> t().
291 delete(NS, K, #{ mongoose_acc := true, non_strippable := NonStrippable } = Acc0) ->
292 7096 Key = {NS, K},
293 7096 Acc1 = maps:remove(Key, Acc0),
294 7096 Acc1#{ non_strippable := lists:delete(Key, NonStrippable) }.
295
296 -spec delete_many(Namespace :: any(), [K :: any()], Acc :: t()) -> t().
297 delete_many(NS, Keys, #{ mongoose_acc := true, non_strippable := NonStrippable } = Acc0) ->
298 137 KVs = [{NS, K} || K <- Keys],
299 137 Acc1 = maps:without(KVs, Acc0),
300 137 Acc1#{ non_strippable := lists:subtract(NonStrippable, KVs) }.
301
302 -spec delete(Namespace :: any(), Acc :: t()) -> t().
303 delete(NS, Acc) ->
304
:-(
KeyList = [K || {K, _} <- get(NS, Acc)],
305
:-(
delete_many(NS, KeyList, Acc).
306
307 -spec strip(Acc :: t()) -> t().
308 strip(#{ mongoose_acc := true, non_strippable := NonStrippable } = Acc) ->
309 57706 Stripped = maps:with(NonStrippable ++ default_non_strippable(), Acc),
310 57706 Stripped#{statem_acc := mongoose_c2s_acc:new()}.
311
312 -spec strip(ParamsToOverwrite :: strip_params(), Acc :: t()) -> t().
313 strip(#{ lserver := NewLServer } = Params, Acc) ->
314 57662 StrippedAcc = strip(Acc),
315 57662 StrippedAcc#{ lserver := NewLServer, host_type := get_host_type(Params),
316 stanza := stanza_from_params(Params) }.
317
318 %% --------------------------------------------------------
319 %% Internal functions
320 %% --------------------------------------------------------
321 -spec get_host_type(new_acc_params() | strip_params()) -> binary() | undefined.
322 get_host_type(#{host_type := HostType}) ->
323 145173 HostType;
324 get_host_type(_) ->
325 8084 undefined.
326
327 -spec get_mongoose_c2s_acc(new_acc_params() | strip_params()) -> mongoose_c2s_acc:t() | undefined.
328 get_mongoose_c2s_acc(#{statem_acc := C2SAcc}) ->
329
:-(
C2SAcc;
330 get_mongoose_c2s_acc(_) ->
331 95595 mongoose_c2s_acc:new().
332
333 -spec stanza_from_params(Params :: stanza_params() | strip_params()) ->
334 stanza_metadata().
335 stanza_from_params(#{ element := El } = Params) ->
336 332631 #{
337 element => El,
338 from_jid => jid_from_params(from_jid, <<"from">>, Params),
339 to_jid => jid_from_params(to_jid, <<"to">>, Params),
340 name => El#xmlel.name,
341 type => exml_query:attr(El, <<"type">>),
342 ref => make_ref()
343 }.
344
345 -spec jid_from_params(MapKey :: to_jid | from_jid,
346 StanzaAttrName :: binary(),
347 Params :: stanza_params()) -> jid:jid().
348 jid_from_params(to_jid, _, #{to_jid := Jid}) ->
349 325208 Jid;
350 jid_from_params(from_jid, _, #{from_jid := Jid}) ->
351 332619 Jid;
352 jid_from_params(_, StanzaAttrName, #{element := El}) ->
353 7435 #jid{} = jid:from_binary(exml_query:attr(El, StanzaAttrName)).
354
355 -spec default_non_strippable() -> [atom()].
356 default_non_strippable() ->
357 57706 [
358 mongoose_acc,
359 ref,
360 timestamp,
361 origin_pid,
362 origin_location,
363 stanza,
364 lserver,
365 host_type,
366 statem_acc,
367 non_strippable
368 ].
369
370 -spec append(OldVal :: list(), Val :: list() | any()) -> list().
371 1791 append(OldVal, Val) when is_list(OldVal), is_list(Val) -> OldVal ++ Val;
372 58164 append(OldVal, Val) when is_list(OldVal) -> [Val | OldVal].
Line Hits Source