./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 % Access to namespaced fields
38 -export([
39 set/3,
40 set/4,
41 set_permanent/2,
42 set_permanent/3,
43 set_permanent/4,
44 append/4,
45 get_permanent_keys/1,
46 get_permanent_fields/1,
47 get/2,
48 get/3,
49 get/4,
50 delete/3,
51 delete/2,
52 delete_many/3
53 ]).
54 % Strip with or without stanza replacement
55 -export([strip/1, strip/2]).
56
57 -ignore_xref([delete/2, ref/1, set/3]).
58
59 %% Note about 'undefined' to_jid and from_jid: these are the special cases when JID may be
60 %% truly unknown: before a client is authorized.
61
62 -type line_number() :: non_neg_integer().
63 -type location() :: #{mfa := mfa(),
64 line := line_number(),
65 file := string()}.
66
67 -type stanza_metadata() :: #{
68 element := exml:element(),
69 from_jid := jid:jid() | undefined,
70 to_jid := jid:jid() | undefined,
71 name := binary(),
72 type := binary(),
73 ref := reference()
74 }.
75
76 %% If it is defined as -opaque then dialyzer fails
77 %% It's still valid in acc 2.0 and gain is probably not worth the effort
78 -type t() :: #{
79 mongoose_acc := true,
80 ref := reference(),
81 timestamp := integer(), %microsecond
82 origin_pid := pid(),
83 origin_location := location(),
84 origin_stanza := binary() | undefined,
85 stanza := stanza_metadata() | undefined,
86 lserver := jid:lserver(),
87 host_type := binary() | undefined,
88 non_strippable := [ns_key()],
89 ns_key() => Value :: any()
90 }.
91
92 -export_type([t/0]).
93
94 -type new_acc_params() :: #{
95 location := location(),
96 lserver := jid:lserver(),
97 element => exml:element() | undefined,
98 host_type => binary() | undefined, % optional
99 from_jid => jid:jid() | undefined, % optional
100 to_jid => jid:jid() | undefined % optional
101 }.
102
103 -type strip_params() :: #{
104 lserver := jid:lserver(),
105 element := exml:element(),
106 host_type => binary() | undefined, % optional
107 from_jid => jid:jid() | undefined, % optional
108 to_jid => jid:jid() | undefined % optional
109 }.
110
111 -type stanza_params() :: #{
112 element := exml:element(),
113 from_jid => jid:jid() | undefined, % optional
114 to_jid => jid:jid() | undefined, % optional
115 _ => _
116 }.
117
118 -type ns_key() :: {NS :: any(), Key :: any()}.
119 -type ns_key_value() :: {Namespace :: any(), K :: any(), V :: any()}.
120
121 %% --------------------------------------------------------
122 %% API
123 %% --------------------------------------------------------
124
125 -spec new(Params :: new_acc_params()) -> t().
126 new(#{ location := Location, lserver := LServer } = Params) ->
127 33995 {ElementBin, Stanza} =
128 case maps:get(element, Params, undefined) of
129 7307 undefined -> {undefined, undefined};
130 26688 Element -> {exml:to_binary(Element), stanza_from_params(Params)}
131 end,
132 33995 HostType = get_host_type(Params),
133 33995 #{
134 mongoose_acc => true,
135 ref => make_ref(),
136 timestamp => os:system_time(microsecond),
137 origin_pid => self(),
138 origin_location => Location,
139 origin_stanza => ElementBin,
140 stanza => Stanza,
141 lserver => LServer,
142 host_type => HostType,
143 %% The non_strippable elements must be unique.
144 %% This used to be represented with the sets module, but as the number of elements inserted
145 %% was too small, sets were themselves an overhead, and also annoying when printing
146 non_strippable => []
147 }.
148
149 -spec ref(Acc :: t()) -> reference().
150 ref(#{ mongoose_acc := true, ref := Ref }) ->
151 6 Ref.
152
153 -spec timestamp(Acc :: t()) -> integer().
154 timestamp(#{ mongoose_acc := true, timestamp := TS }) ->
155 218 TS.
156
157 -spec lserver(Acc :: t()) -> jid:lserver().
158 lserver(#{ mongoose_acc := true, lserver := LServer }) ->
159 17496 LServer.
160
161 -spec host_type(Acc :: t()) -> binary() | undefined.
162 host_type(#{ mongoose_acc := true, host_type := HostType }) ->
163 169421 HostType.
164
165 -spec element(Acc :: t()) -> exml:element() | undefined.
166 element(#{ mongoose_acc := true, stanza := #{ element := El } }) ->
167 52810 El;
168 element(#{ mongoose_acc := true }) ->
169
:-(
undefined.
170
171 -spec from_jid(Acc :: t()) -> jid:jid() | undefined.
172 from_jid(#{ mongoose_acc := true, stanza := #{ from_jid := FromJID } }) ->
173 37057 FromJID;
174 from_jid(#{ mongoose_acc := true }) ->
175
:-(
undefined.
176
177 -spec to_jid(Acc :: t()) -> jid:jid() | undefined.
178 to_jid(#{ mongoose_acc := true, stanza := #{ to_jid := ToJID } }) ->
179 12869 ToJID;
180 to_jid(#{ mongoose_acc := true }) ->
181
:-(
undefined.
182
183 -spec packet(Acc :: t()) -> ejabberd_c2s:packet() | undefined.
184 packet(#{ mongoose_acc := true, stanza := #{ to_jid := ToJID,
185 from_jid := FromJID,
186 element := El } }) ->
187 350 {FromJID, ToJID, El};
188 packet(#{ mongoose_acc := true }) ->
189
:-(
undefined.
190
191 -spec stanza_name(Acc :: t()) -> binary() | undefined.
192 stanza_name(#{ mongoose_acc := true, stanza := #{ name := Name } }) ->
193 23012 Name;
194 stanza_name(#{ mongoose_acc := true }) ->
195
:-(
undefined.
196
197 -spec stanza_type(Acc :: t()) -> binary() | undefined.
198 stanza_type(#{ mongoose_acc := true, stanza := #{ type := Type } }) ->
199 60225 Type;
200 stanza_type(#{ mongoose_acc := true }) ->
201 78 undefined.
202
203 -spec stanza_ref(Acc :: t()) -> reference() | undefined.
204 stanza_ref(#{ mongoose_acc := true, stanza := #{ ref := StanzaRef } }) ->
205 5645 StanzaRef;
206 stanza_ref(#{ mongoose_acc := true }) ->
207
:-(
undefined.
208
209 -spec update_stanza(NewStanzaParams :: stanza_params(), Acc :: t()) -> t().
210 update_stanza(NewStanzaParams, #{ mongoose_acc := true } = Acc) ->
211 29531 Acc#{ stanza := stanza_from_params(NewStanzaParams) }.
212
213 %% Values set with this function are discarded during 'strip' operation...
214 -spec set(Namespace :: any(), K :: any(), V :: any(), Acc :: t()) -> t().
215 set(NS, K, V, #{ mongoose_acc := true } = Acc) ->
216 119390 Acc#{ {NS, K} => V }.
217
218 -spec set(Namespace :: any(), [{K :: any(), V :: any()}], Acc :: t()) -> t().
219 set(_, [], Acc) ->
220
:-(
Acc;
221 set(NS, [{K, V} | T], Acc) ->
222
:-(
NewAcc = set(NS, K, V, Acc),
223
:-(
set(NS, T, NewAcc).
224
225 %% .. while these are not.
226 -spec set_permanent(Namespace :: any(), K :: any(), V :: any(), Acc :: t()) -> t().
227 set_permanent(NS, K, V, #{ mongoose_acc := true, non_strippable := NonStrippable } = Acc) ->
228 26407 Key = {NS, K},
229 26407 NewNonStrippable = [Key | lists:delete(Key, NonStrippable)],
230 26407 Acc#{ Key => V, non_strippable => NewNonStrippable }.
231
232 -spec set_permanent(Namespace :: any(), [{K :: any(), V :: any()}], Acc :: t()) -> t().
233 set_permanent(_, [], #{mongoose_acc := true} = Acc) ->
234 986 Acc;
235 set_permanent(NS, [{K, V} | T], Acc) ->
236 80 NewAcc = set_permanent(NS, K, V, Acc),
237 80 set_permanent(NS, T, NewAcc).
238
239 -spec set_permanent([ns_key_value()], Acc :: t()) -> t().
240 set_permanent([], #{mongoose_acc := true} = Acc) ->
241 549 Acc;
242 set_permanent([{NS, K, V} | T], Acc) ->
243 878 NewAcc = set_permanent(NS, K, V, Acc),
244 878 set_permanent(T, NewAcc).
245
246 -spec append(NS :: any(), Key :: any(), Val :: any() | [any()], Acc :: t()) -> t().
247 append(NS, Key, Val, Acc) ->
248 20419 OldVal = get(NS, Key, [], Acc),
249 20419 set(NS, Key, append(OldVal, Val), Acc).
250
251 -spec get_permanent_keys(Acc :: t()) -> [ns_key()].
252 get_permanent_keys(#{mongoose_acc := true, non_strippable := NonStrippable}) ->
253 599 NonStrippable.
254
255 -spec get_permanent_fields(Acc :: t()) -> [ns_key_value()].
256 get_permanent_fields(Acc) ->
257 599 [{NS, Key, mongoose_acc:get(NS, Key, Acc)} ||
258 599 {NS, Key} <- mongoose_acc:get_permanent_keys(Acc)].
259
260 -spec get(Namespace :: any(), Acc :: t()) -> [{K :: any(), V :: any()}].
261 get(NS, #{mongoose_acc := true} = Acc) ->
262 986 Fn = fun({Namespace, K}, V, List) when Namespace =:= NS ->
263 80 [{K, V} | List];
264 (_, _, List) ->
265 11628 List
266 end,
267 986 maps:fold(Fn, [], Acc).
268
269 -spec get(Namespace :: any(), K :: any(), Acc :: t()) -> V :: any().
270 get(NS, K, #{ mongoose_acc := true } = Acc) ->
271 53548 maps:get({NS, K}, Acc).
272
273 -spec get(Namespace :: any(), K :: any(), Default :: any(), Acc :: t()) -> V :: any().
274 get(NS, K, Default, #{ mongoose_acc := true } = Acc) ->
275 138783 maps:get({NS, K}, Acc, Default).
276
277 -spec delete(Namespace :: any(), K :: any(), Acc :: t()) -> t().
278 delete(NS, K, #{ mongoose_acc := true, non_strippable := NonStrippable } = Acc0) ->
279 3595 Key = {NS, K},
280 3595 Acc1 = maps:remove(Key, Acc0),
281 3595 Acc1#{ non_strippable => lists:delete(Key, NonStrippable) }.
282
283 -spec delete_many(Namespace :: any(), [K :: any()], Acc :: t()) -> t().
284 delete_many(_, [], Acc) ->
285 362 Acc;
286 delete_many(NS, [K | T], Acc) ->
287 724 NewAcc = delete(NS, K, Acc),
288 724 delete_many(NS, T, NewAcc).
289
290 -spec delete(Namespace :: any(), Acc :: t()) -> t().
291 delete(NS, Acc) ->
292
:-(
KeyList = [K || {K, _} <- get(NS, Acc)],
293
:-(
delete_many(NS, KeyList, Acc).
294
295 -spec strip(Acc :: t()) -> t().
296 strip(#{ mongoose_acc := true, non_strippable := NonStrippable } = Acc) ->
297 17404 maps:with(NonStrippable ++ default_non_strippable(), Acc).
298
299 -spec strip(ParamsToOverwrite :: strip_params(), Acc :: t()) -> t().
300 strip(#{ lserver := NewLServer } = Params, Acc) ->
301 17343 StrippedAcc = strip(Acc),
302 17343 StrippedAcc#{ lserver := NewLServer, host_type := get_host_type(Params),
303 stanza := stanza_from_params(Params) }.
304
305 %% --------------------------------------------------------
306 %% Internal functions
307 %% --------------------------------------------------------
308 -spec get_host_type(new_acc_params() | strip_params()) -> binary() | undefined.
309 get_host_type(#{host_type := HostType}) ->
310 48383 HostType;
311 get_host_type(_) ->
312 2955 undefined.
313
314 -spec stanza_from_params(Params :: stanza_params() | strip_params()) ->
315 stanza_metadata().
316 stanza_from_params(#{ element := El } = Params) ->
317 73562 #{
318 element => El,
319 from_jid => jid_from_params(from_jid, <<"from">>, Params),
320 to_jid => jid_from_params(to_jid, <<"to">>, Params),
321 name => El#xmlel.name,
322 type => exml_query:attr(El, <<"type">>),
323 ref => make_ref()
324 }.
325
326 -spec jid_from_params(MapKey :: to_jid | from_jid,
327 StanzaAttrName :: binary(),
328 Params :: stanza_params()) -> jid:jid().
329 jid_from_params(MapKey, StanzaAttrName, #{ element := El } = Params) ->
330 147124 case maps:find(MapKey, Params) of
331 143766 {ok, JID0} -> JID0;
332 3358 error -> #jid{} = jid:from_binary(exml_query:attr(El, StanzaAttrName))
333 end.
334
335 -spec default_non_strippable() -> [atom()].
336 default_non_strippable() ->
337 17404 [
338 mongoose_acc,
339 ref,
340 timestamp,
341 origin_pid,
342 origin_location,
343 origin_stanza,
344 stanza,
345 lserver,
346 host_type,
347 non_strippable
348 ].
349
350 -spec append(OldVal :: list(), Val :: list() | any()) -> list().
351 2818 append(OldVal, Val) when is_list(OldVal), is_list(Val) -> OldVal ++ Val;
352 17601 append(OldVal, Val) when is_list(OldVal) -> [Val | OldVal].
Line Hits Source