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 |
66463 |
Stanza = case maps:get(element, Params, undefined) of |
133 |
20215 |
undefined -> undefined; |
134 |
46248 |
_Element -> stanza_from_params(Params) |
135 |
|
end, |
136 |
66463 |
HostType = get_host_type(Params), |
137 |
66463 |
#{ |
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 |
8946 |
TS. |
160 |
|
|
161 |
|
-spec lserver(Acc :: t()) -> jid:lserver(). |
162 |
|
lserver(#{ mongoose_acc := true, lserver := LServer }) -> |
163 |
43903 |
LServer. |
164 |
|
|
165 |
|
-spec host_type(Acc :: t()) -> binary() | undefined. |
166 |
|
host_type(#{ mongoose_acc := true, host_type := HostType }) -> |
167 |
190116 |
HostType. |
168 |
|
|
169 |
|
-spec element(Acc :: t()) -> exml:element() | undefined. |
170 |
|
element(#{ mongoose_acc := true, stanza := #{ element := El } }) -> |
171 |
185472 |
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 |
13030 |
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 |
3480 |
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 |
88077 |
{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 |
61548 |
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 |
93417 |
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 |
28401 |
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 |
165768 |
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 |
87726 |
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 |
34459 |
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 |
120827 |
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 |
72226 |
NSKVs = [ {{NS, K}, V} || {K, V} <- KVs ], |
233 |
72226 |
Input = maps:from_list(NSKVs), |
234 |
72226 |
maps:merge(Acc, Input). |
235 |
|
|
236 |
|
-spec set([ns_key_value()], Acc :: t()) -> t(). |
237 |
|
set(NSKVs, #{ mongoose_acc := true } = Acc) -> |
238 |
2612 |
PropList = [ {{NS, K}, V} || {NS, K, V} <- NSKVs ], |
239 |
2612 |
Input = maps:from_list(PropList), |
240 |
2612 |
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 |
5993 |
Key = {NS, K}, |
246 |
5993 |
NewNonStrippable = [Key | lists:delete(Key, NonStrippable)], |
247 |
5993 |
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 |
39756 |
NewKeys = [{NS, K} || {K, _V} <- KVs, not lists:member({NS, K}, NonStrippable)], |
252 |
39756 |
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 |
2612 |
NewKeys = [{NS, K} || {NS, K, _V} <- NSKVs, not lists:member({NS, K}, NonStrippable)], |
257 |
2612 |
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 |
44796 |
OldVal = get(NS, Key, [], Acc), |
262 |
44796 |
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 |
2636 |
NonStrippable. |
267 |
|
|
268 |
|
-spec get_permanent_fields(Acc :: t()) -> [ns_key_value()]. |
269 |
|
get_permanent_fields(Acc) -> |
270 |
2636 |
[{NS, Key, mongoose_acc:get(NS, Key, Acc)} || |
271 |
2636 |
{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 |
20 |
Fn = fun({Namespace, K}, V, List) when Namespace =:= NS -> |
276 |
40 |
[{K, V} | List]; |
277 |
|
(_, _, List) -> |
278 |
280 |
List |
279 |
|
end, |
280 |
20 |
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 |
28531 |
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 |
337200 |
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 |
5229 |
Key = {NS, K}, |
293 |
5229 |
Acc1 = maps:remove(Key, Acc0), |
294 |
5229 |
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 |
121 |
KVs = [{NS, K} || K <- Keys], |
299 |
121 |
Acc1 = maps:without(KVs, Acc0), |
300 |
121 |
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 |
44046 |
Stripped = maps:with(NonStrippable ++ default_non_strippable(), Acc), |
310 |
44046 |
Stripped#{statem_acc := mongoose_c2s_acc:new()}. |
311 |
|
|
312 |
|
-spec strip(ParamsToOverwrite :: strip_params(), Acc :: t()) -> t(). |
313 |
|
strip(#{ lserver := NewLServer } = Params, Acc) -> |
314 |
44015 |
StrippedAcc = strip(Acc), |
315 |
44015 |
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 |
103099 |
HostType; |
324 |
|
get_host_type(_) -> |
325 |
7379 |
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 |
66463 |
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 |
256031 |
#{ |
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 |
250175 |
Jid; |
350 |
|
jid_from_params(from_jid, _, #{from_jid := Jid}) -> |
351 |
256019 |
Jid; |
352 |
|
jid_from_params(_, StanzaAttrName, #{element := El}) -> |
353 |
5868 |
#jid{} = jid:from_binary(exml_query:attr(El, StanzaAttrName)). |
354 |
|
|
355 |
|
-spec default_non_strippable() -> [atom()]. |
356 |
|
default_non_strippable() -> |
357 |
44046 |
[ |
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 |
755 |
append(OldVal, Val) when is_list(OldVal), is_list(Val) -> OldVal ++ Val; |
372 |
44041 |
append(OldVal, Val) when is_list(OldVal) -> [Val | OldVal]. |