./ct_report/coverage/mod_presence.COVER.html

1 -module(mod_presence).
2
3 -include("jlib.hrl").
4 -include("mongoose_logger.hrl").
5
6 -behavior(gen_mod).
7
8 -type presence() ::
9 available
10 | unavailable
11 | error
12 | probe
13 | subscribe
14 | subscribed
15 | unsubscribe
16 | unsubscribed.
17 -type maybe_presence() :: presence() | {error, invalid_presence}.
18
19 -type priority() :: -128..127.
20 -type maybe_priority() :: priority() | undefined.
21
22 -type available() :: sets:set(jid:jid()).
23 -type subscription() :: from | to | both.
24 -type subscriptions() :: #{jid:jid() := subscription()}.
25
26 -record(presences_state, {
27 subscriptions = #{} :: subscriptions(),
28 available = sets:new([{version, 2}]) :: available(),
29 pres_pri = 0 :: priority(),
30 pres_last :: undefined | exml:element(),
31 pres_timestamp :: undefined | integer() % unix time in microseconds
32 }).
33 -type state() :: #presences_state{}.
34
35 -export([start/2, stop/1, hooks/1, supported_features/0]).
36 -export([
37 user_send_presence/3,
38 user_receive_presence/3,
39 user_terminate/3,
40 foreign_event/3
41 ]).
42 -export([
43 get/2,
44 is_subscribed_to_my_presence/3,
45 am_i_subscribed_to_presence/3,
46 presence_unavailable_stanza/0,
47 get_presence/1,
48 get_subscribed/1,
49 set_presence/2,
50 maybe_get_handler/1,
51 get_old_priority/1
52 ]).
53
54 -spec start(mongooseim:host_type(), gen_mod:module_opts()) -> ok.
55 start(_HostType, _Opts) ->
56 284 ok.
57
58 -spec stop(mongooseim:host_type()) -> ok.
59 stop(_HostType) ->
60 284 ok.
61
62 -spec hooks(mongooseim:host_type()) -> gen_hook:hook_list(mongoose_c2s_hooks:fn()).
63 hooks(HostType) ->
64 568 [
65 {user_send_presence, HostType, fun ?MODULE:user_send_presence/3, #{}, 50},
66 {user_receive_presence, HostType, fun ?MODULE:user_receive_presence/3, #{}, 50},
67 {user_terminate, HostType, fun ?MODULE:user_terminate/3, #{}, 90},
68 {foreign_event, HostType, fun ?MODULE:foreign_event/3, #{}, 50}
69 ].
70
71 -spec supported_features() -> [atom()].
72 supported_features() ->
73 177 [dynamic_domains].
74
75 -spec get_presence(pid()) -> {jid:luser(), jid:lresource(), binary(), binary()}.
76 get_presence(Pid) ->
77 30 mongoose_c2s:call(Pid, ?MODULE, get_presence).
78
79 -spec get_subscribed(pid()) -> [jid:jid()].
80 get_subscribed(Pid) ->
81 9 mongoose_c2s:call(Pid, ?MODULE, get_subscribed).
82
83 -spec set_presence(pid(), exml:element()) -> ok.
84 set_presence(Pid, Message) ->
85 9 mongoose_c2s:cast(Pid, ?MODULE, {set_presence, Message}).
86
87 -spec user_send_presence(mongoose_acc:t(), mongoose_c2s_hooks:params(), gen_hook:extra()) ->
88 mongoose_c2s_hooks:result().
89 user_send_presence(Acc, #{c2s_data := StateData}, _Extra) ->
90 8137 {FromJid, ToJid, Packet} = mongoose_acc:packet(Acc),
91 8137 case {get_presence_type(Acc), jid:are_bare_equal(FromJid, ToJid)} of
92 {{error, invalid_presence}, _} ->
93 1 handle_invalid_presence_type(Acc, FromJid, ToJid, Packet, StateData);
94 {Type, true} ->
95 7154 Acc1 = presence_update(Acc, FromJid, ToJid, Packet, StateData, Type),
96 7154 {ok, Acc1};
97 {Type, false} ->
98 982 Acc1 = presence_track(Acc, FromJid, ToJid, Packet, StateData, Type),
99 982 {ok, Acc1}
100 end.
101
102 -spec user_receive_presence(mongoose_acc:t(), mongoose_c2s_hooks:params(), gen_hook:extra()) ->
103 mongoose_c2s_hooks:result().
104 user_receive_presence(Acc, #{c2s_data := StateData}, _Extra) ->
105 16134 case {get_presence_type(Acc), get_mod_state(StateData)} of
106 {{error, invalid_presence}, _} ->
107
:-(
{FromJid, ToJid, Packet} = mongoose_acc:packet(Acc),
108
:-(
handle_invalid_presence_type(Acc, FromJid, ToJid, Packet, StateData);
109 {_, {error, not_found}} ->
110 4 {stop, Acc};
111 {Type, Presences} ->
112 16130 handle_user_received_presence(Acc, Presences, Type)
113 end.
114
115 -spec handle_invalid_presence_type(mongoose_acc:t(), jid:jid(), jid:jid(), exml:element(), mongoose_c2s:data()) ->
116 mongoose_c2s_hooks:result().
117 handle_invalid_presence_type(Acc, FromJid, ToJid, Packet, StateData) ->
118 1 Lang = mongoose_c2s:get_lang(StateData),
119 1 Error = mongoose_xmpp_errors:bad_request(Lang, <<"Invalid presence type">>),
120 1 Reply = jlib:make_error_reply(Packet, Error),
121 1 ejabberd_router:route(ToJid, FromJid, Acc, Reply),
122 1 {stop, Acc}.
123
124 -spec user_terminate(mongoose_acc:t(), mongoose_c2s_hooks:params(), gen_hook:extra()) ->
125 gen_hook:hook_fn_ret(mongoose_acc:t()).
126 user_terminate(Acc, #{c2s_data := StateData, reason := Reason}, _Extra) ->
127 7512 case get_mod_state(StateData) of
128 1191 {error, not_found} -> {ok, Acc};
129 210 #presences_state{pres_last = undefined} -> {ok, Acc};
130 6111 Presences -> handle_user_terminate(Acc, StateData, Presences, Reason)
131 end.
132
133 -spec handle_user_terminate(mongoose_acc:t(), mongoose_c2s:data(), state(), term()) ->
134 gen_hook:hook_fn_ret(mongoose_acc:t()).
135 handle_user_terminate(Acc, StateData, Presences, Reason) ->
136 6111 Jid = mongoose_c2s:get_jid(StateData),
137 6111 Status = close_session_status(Reason),
138 6111 PresenceUnavailable = presence_unavailable_stanza(Status),
139 6111 ParamsAcc = #{from_jid => Jid, to_jid => jid:to_bare(Jid), element => PresenceUnavailable},
140 6111 Acc1 = mongoose_acc:update_stanza(ParamsAcc, Acc),
141 6111 presence_broadcast(Acc1, Presences),
142 6111 mongoose_hooks:unset_presence_hook(Acc1, Jid, Status),
143 6111 {ok, Acc}.
144
145 -spec foreign_event(Acc, Params, Extra) -> Result when
146 Acc :: mongoose_acc:t(),
147 Params :: mongoose_c2s_hooks:params(),
148 Extra :: gen_hook:extra(),
149 Result :: mongoose_c2s_hooks:result().
150 foreign_event(Acc, #{c2s_data := StateData,
151 event_type := cast,
152 event_tag := mod_roster,
153 event_content := {item, IJID, ISubscription}}, _Extra) ->
154 839 case get_mod_state(StateData) of
155 {error, not_found} ->
156
:-(
{ok, Acc};
157 Presences ->
158 839 {stop, handle_subscription_change(Acc, StateData, IJID, ISubscription, Presences)}
159 end;
160 foreign_event(Acc, #{c2s_data := StateData,
161 event_type := {call, From},
162 event_tag := ?MODULE,
163 event_content := get_presence}, _Extra) ->
164 30 PresLast = case get_mod_state(StateData) of
165 {error, not_found} ->
166
:-(
undefined;
167 #presences_state{pres_last = Value} ->
168 30 Value
169 end,
170 30 #jid{luser = User, lresource = Resource} = mongoose_c2s:get_jid(StateData),
171 30 Reply = {User, Resource, get_showtag(PresLast), get_statustag(PresLast)},
172 30 Acc1 = mongoose_c2s_acc:to_acc(Acc, actions, [{reply, From, Reply}]),
173 30 {stop, Acc1};
174 foreign_event(Acc, #{c2s_data := StateData,
175 event_type := {call, From},
176 event_tag := ?MODULE,
177 event_content := get_subscribed}, _Extra) ->
178 8 Subscribed = case get_mod_state(StateData) of
179 {error, not_found} ->
180
:-(
[];
181 Presences ->
182 8 PresF = get_by_sub(Presences, from),
183 8 maps:keys(PresF)
184 end,
185 8 Acc1 = mongoose_c2s_acc:to_acc(Acc, actions, [{reply, From, Subscribed}]),
186 8 {stop, Acc1};
187 foreign_event(Acc, #{c2s_data := StateData,
188 event_type := cast,
189 event_tag := ?MODULE,
190 event_content := {set_presence, Message}}, _Extra) ->
191 9 Acc1 = mongoose_acc:update_stanza(#{element => Message}, Acc),
192 9 {FromJid, ToJid, Packet} = mongoose_acc:packet(Acc1),
193 9 case get_presence_type(Acc) of
194 {error, invalid_presence} ->
195
:-(
{stop, Acc};
196 Type ->
197 9 {stop, presence_update(Acc1, FromJid, ToJid, Packet, StateData, Type)}
198 end;
199 foreign_event(Acc, _Params, _Extra) ->
200 132 {ok, Acc}.
201
202 -spec get_showtag(undefined | exml:element()) -> binary().
203 get_showtag(undefined) ->
204
:-(
<<"unavailable">>;
205 get_showtag(Presence) ->
206 30 case xml:get_path_s(Presence, [{elem, <<"show">>}, cdata]) of
207
:-(
<<>> -> <<"available">>;
208 30 ShowTag -> ShowTag
209 end.
210
211 -spec get_statustag(undefined | exml:element()) -> binary().
212
213 get_statustag(undefined) ->
214
:-(
<<>>;
215 get_statustag(Presence) ->
216 30 xml:get_path_s(Presence, [{elem, <<"status">>}, cdata]).
217
218 -spec handle_subscription_change(
219 mongoose_acc:t(), mongoose_c2s:data(), mod_roster:contact(), term(), state()) ->
220 mongoose_acc:t().
221 handle_subscription_change(Acc, StateData, IJID, ISubscription, Presences) ->
222 839 To = jid:make_noprep(IJID),
223 839 IsSubscribedToMe = (ISubscription =:= both) or (ISubscription =:= from),
224 839 AmISubscribedTo = (ISubscription =:= both) or (ISubscription =:= to),
225 839 WasSubscribedToMe = is_subscribed(Presences, To, from),
226 839 Subs = case {IsSubscribedToMe, AmISubscribedTo} of
227 {true, true} ->
228 164 maps:put(To, both, Presences#presences_state.subscriptions);
229 {true, _} ->
230 124 maps:put(To, from, Presences#presences_state.subscriptions);
231 {_, true} ->
232 130 maps:put(To, to, Presences#presences_state.subscriptions);
233 {false, false} ->
234 421 maps:remove(To, Presences#presences_state.subscriptions)
235 end,
236 839 case Presences#presences_state.pres_last of
237 undefined ->
238
:-(
NewPresences = Presences#presences_state{subscriptions = Subs},
239
:-(
mongoose_c2s_acc:to_acc(Acc, state_mod, {?MODULE, NewPresences});
240 P ->
241 839 ?LOG_DEBUG(#{what => roster_changed, roster_jid => To,
242 839 acc => Acc, c2s_state => StateData}),
243 839 From = mongoose_c2s:get_jid(StateData),
244 839 ImAvailableTo = is_available(Presences, To),
245 839 BecomeAvailable = IsSubscribedToMe and not WasSubscribedToMe,
246 839 BecomeUnavailable = not IsSubscribedToMe and WasSubscribedToMe and ImAvailableTo,
247 839 case {BecomeAvailable, BecomeUnavailable} of
248 {true, _} ->
249 181 ?LOG_DEBUG(#{what => become_available_to, roster_jid => To,
250 181 acc => Acc, c2s_state => StateData}),
251 181 ejabberd_router:route(From, To, Acc, P),
252 181 Available = sets:add_element(To, Presences#presences_state.available),
253 181 NewPresences = Presences#presences_state{subscriptions = Subs,
254 available = Available},
255 181 mongoose_c2s_acc:to_acc(Acc, state_mod, {?MODULE, NewPresences});
256 {_, true} ->
257 19 ?LOG_DEBUG(#{what => become_unavailable_to, roster_jid => To,
258 19 acc => Acc, c2s_state => StateData}),
259 19 PU = presence_unavailable_stanza(),
260 19 ejabberd_router:route(From, To, Acc, PU),
261 19 Available = sets:del_element(To, Presences#presences_state.available),
262 19 NewPresences = Presences#presences_state{subscriptions = Subs,
263 available = Available},
264 19 mongoose_c2s_acc:to_acc(Acc, state_mod, {?MODULE, NewPresences});
265 _ ->
266 639 NewPresences = Presences#presences_state{subscriptions = Subs},
267 639 mongoose_c2s_acc:to_acc(Acc, state_mod, {?MODULE, NewPresences})
268 end
269 end.
270
271 -spec handle_user_received_presence(mongoose_acc:t(), state(), presence()) ->
272 mongoose_c2s_hooks:result().
273 handle_user_received_presence(Acc, Presences, available) ->
274 8686 {ok, handle_received_available(Acc, Presences)};
275 handle_user_received_presence(Acc, Presences, unavailable) ->
276 457 {ok, handle_received_unavailable(Acc, Presences)};
277 handle_user_received_presence(Acc, Presences, error) ->
278 57 {ok, handle_received_unavailable(Acc, Presences)};
279 handle_user_received_presence(Acc, Presences, probe) ->
280 6534 {stop, handle_received_probe(Acc, Presences)};
281 handle_user_received_presence(Acc, _, _) ->
282 396 {ok, Acc}.
283
284 -spec handle_received_probe(mongoose_acc:t(), state()) -> mongoose_acc:t().
285 handle_received_probe(Acc, Presences) ->
286 6534 {FromJid, ToJid, _Packet} = mongoose_acc:packet(Acc),
287 6534 BareFromJid = jid:to_bare(FromJid),
288 6534 NewPresences = case am_i_available_to(FromJid, BareFromJid, Presences) of
289 6534 true -> Presences;
290
:-(
false -> make_available_to(FromJid, BareFromJid, Presences)
291 end,
292 6534 Acc1 = mongoose_c2s_acc:to_acc(Acc, state_mod, {?MODULE, NewPresences}),
293 6534 process_presence_probe(Acc1, NewPresences, FromJid, BareFromJid, ToJid).
294
295 -spec process_presence_probe(mongoose_acc:t(), state(), jid:jid(), jid:jid(), jid:jid()) -> mongoose_acc:t().
296 process_presence_probe(Acc, #presences_state{pres_last = undefined}, _, _, _) ->
297
:-(
Acc;
298 process_presence_probe(Acc, Presences, FromJid, BareFromJid, ToJid) ->
299 6534 case {is_subscribed_to_my_presence(FromJid, BareFromJid, Presences),
300 specifically_visible_to(FromJid, Presences)} of
301 {true, _} ->
302 6534 route_probe(Acc, Presences, FromJid, ToJid);
303 {false, true} ->
304
:-(
ejabberd_router:route(ToJid, FromJid, Acc, #xmlel{name = <<"presence">>}),
305
:-(
Acc;
306 _ ->
307
:-(
Acc
308 end.
309
310 -spec route_probe(mongoose_acc:t(), state(), jid:jid(), jid:jid()) -> mongoose_acc:t().
311 route_probe(Acc, Presences, FromJid, ToJid) ->
312 6534 Packet0 = Presences#presences_state.pres_last,
313 6534 TS = Presences#presences_state.pres_timestamp,
314 %% To is the one sending the presence (the target of the probe)
315 6534 Packet1 = jlib:maybe_append_delay(Packet0, ToJid, TS, <<>>),
316 6534 HostType = mongoose_acc:host_type(Acc),
317 6534 Acc2 = mongoose_hooks:presence_probe_hook(HostType, Acc, FromJid, ToJid, self()),
318 %% Don't route a presence probe to oneself
319 6534 case jid:are_equal(FromJid, ToJid) of
320 false ->
321 194 ejabberd_router:route(ToJid, FromJid, Acc2, Packet1),
322 194 Acc;
323 true ->
324 6340 Acc2
325 end.
326
327 -spec handle_received_unavailable(mongoose_acc:t(), state()) -> mongoose_acc:t().
328 handle_received_unavailable(Acc, Presences) ->
329 514 FromJid = mongoose_acc:from_jid(Acc),
330 514 NewS = sets:del_element(FromJid, Presences#presences_state.available),
331 514 NewPresences = Presences#presences_state{available = NewS},
332 514 mongoose_c2s_acc:to_acc(Acc, state_mod, {?MODULE, NewPresences}).
333
334 -spec handle_received_available(mongoose_acc:t(), state()) -> mongoose_acc:t().
335 handle_received_available(Acc, Presences) ->
336 8686 FromJid = mongoose_acc:from_jid(Acc),
337 8686 BareJid = jid:to_bare(FromJid),
338 8686 case am_i_available_to(FromJid, BareJid, Presences) of
339 true ->
340 7627 Acc;
341 false ->
342 1059 NewPresences = make_available_to(FromJid, BareJid, Presences),
343 1059 mongoose_c2s_acc:to_acc(Acc, state_mod, {?MODULE, NewPresences})
344 end.
345
346 %% @doc User updates his presence (non-directed presence packet)
347 -spec presence_update(
348 mongoose_acc:t(), jid:jid(), jid:jid(), exml:element(), mongoose_c2s:data(), presence()) ->
349 mongoose_acc:t().
350 presence_update(Acc, FromJid, ToJid, Packet, StateData, available) ->
351 6943 Presences = maybe_get_handler(StateData),
352 6943 presence_update_to_available(Acc, FromJid, ToJid, Packet, StateData, Presences);
353 presence_update(Acc, FromJid, ToJid, Packet, StateData, unavailable) ->
354 220 Presences = maybe_get_handler(StateData),
355 220 presence_update_to_unavailable(Acc, FromJid, ToJid, Packet, StateData, Presences);
356
:-(
presence_update(Acc, _, _, _, _, _) -> Acc.
357
358 -spec presence_update_to_available(
359 mongoose_acc:t(), jid:jid(), jid:jid(), exml:element(), mongoose_c2s:data(), state()) ->
360 mongoose_acc:t().
361 presence_update_to_available(Acc0, FromJid, ToJid, Packet, StateData, Presences) ->
362 6943 Jid = mongoose_c2s:get_jid(StateData),
363 6943 HostType = mongoose_c2s:get_host_type(StateData),
364 6943 {Acc1, Subs, Pending} = build_subs_and_pendings(HostType, Acc0, Jid),
365 6943 OldPriority = get_old_priority(Presences),
366 6943 NewPriority = get_priority_from_presence(Packet),
367 6943 Acc2 = update_priority(Acc1, NewPriority, Packet, StateData),
368 6943 FromUnavail = (Presences#presences_state.pres_last =:= undefined),
369 6943 ?LOG_DEBUG(#{what => presence_update_to_available,
370 text => <<"Presence changes from unavailable to available">>,
371 6943 from_unavail => FromUnavail, acc => Acc2, c2s_state => StateData}),
372 6943 NewPresences = Presences#presences_state{subscriptions = Subs,
373 pres_pri = NewPriority,
374 pres_last = Packet,
375 pres_timestamp = erlang:system_time(microsecond)},
376 6943 presence_update_to_available(
377 Acc2, FromJid, ToJid, Packet, StateData, NewPresences, Pending,
378 OldPriority, NewPriority, FromUnavail).
379
380 -spec presence_update_to_unavailable(
381 mongoose_acc:t(), jid:jid(), jid:jid(), exml:element(), mongoose_c2s:data(), state()) ->
382 mongoose_acc:t().
383 presence_update_to_unavailable(Acc, _FromJid, _ToJid, Packet, StateData, Presences) ->
384 220 Status = exml_query:path(Packet, [{element, <<"status">>}, cdata], <<>>),
385 220 Sid = mongoose_c2s:get_sid(StateData),
386 220 Jid = mongoose_c2s:get_jid(StateData),
387 220 Info = mongoose_c2s:get_info(StateData),
388 220 Acc1 = ejabberd_sm:unset_presence(Acc, Sid, Jid, Status, Info),
389 220 presence_broadcast(Acc1, Presences),
390 220 NewPresences = Presences#presences_state{pres_last = undefined,
391 pres_timestamp = undefined},
392 220 mongoose_c2s_acc:to_acc(Acc1, state_mod, {?MODULE, NewPresences}).
393
394 %% @doc User sends a directed presence packet
395 -spec presence_track(
396 mongoose_acc:t(), jid:jid(), jid:jid(), exml:element(), mongoose_c2s:data(), presence()) ->
397 mongoose_acc:t().
398 presence_track(Acc, FromJid, ToJid, Packet, StateData, available) ->
399 755 Presences = maybe_get_handler(StateData),
400 755 process_presence_track_available(Acc, FromJid, ToJid, Packet, Presences);
401 presence_track(Acc, FromJid, ToJid, Packet, StateData, unavailable) ->
402 17 Presences = maybe_get_handler(StateData),
403 17 process_presence_track_unavailable(Acc, FromJid, ToJid, Packet, Presences);
404 presence_track(Acc, FromJid, ToJid, Packet, _, Type) when error =:= Type; probe =:= Type ->
405 2 ejabberd_router:route(FromJid, ToJid, Acc, Packet),
406 2 Acc;
407 presence_track(Acc, FromJid, ToJid, Packet, _, Type) ->
408 208 process_presence_track_subscription_and_route(Acc, FromJid, ToJid, Packet, Type).
409
410 process_presence_track_available(Acc, FromJid, ToJid, Packet, Presences) ->
411 755 ejabberd_router:route(FromJid, ToJid, Acc, Packet),
412 755 Available = sets:add_element(ToJid, Presences#presences_state.available),
413 755 NewPresences = Presences#presences_state{available = Available},
414 755 mongoose_c2s_acc:to_acc(Acc, state_mod, {?MODULE, NewPresences}).
415
416 process_presence_track_unavailable(Acc, FromJid, ToJid, Packet, Presences) ->
417 17 ejabberd_router:route(FromJid, ToJid, Acc, Packet),
418 17 Available = sets:del_element(ToJid, Presences#presences_state.available),
419 17 NewPresences = Presences#presences_state{available = Available},
420 17 mongoose_c2s_acc:to_acc(Acc, state_mod, {?MODULE, NewPresences}).
421
422 process_presence_track_subscription_and_route(Acc, FromJid, ToJid, Packet, Type) ->
423 208 Acc1 = mongoose_hooks:roster_out_subscription(Acc, FromJid, ToJid, Type),
424 208 ejabberd_router:route(jid:to_bare(FromJid), ToJid, Acc1, Packet),
425 208 Acc1.
426
427 -spec presence_broadcast(mongoose_acc:t(), state()) -> ok.
428 presence_broadcast(Acc, #presences_state{available = Available}) ->
429 6331 {FromJID, _, Packet} = mongoose_acc:packet(Acc),
430 6331 Route = fun(ToJID, _) -> ejabberd_router:route(FromJID, ToJID, Acc, Packet) end,
431 6331 sets:fold(Route, undefined, Available).
432
433 -spec presence_update_to_available(
434 mongoose_acc:t(), jid:jid(), jid:jid(), exml:element(), mongoose_c2s:data(),
435 state(), [exml:element()], maybe_priority(), priority(), boolean()) ->
436 mongoose_acc:t().
437 presence_update_to_available(Acc, FromJid, _ToJid, Packet, StateData, Presences, Pending,
438 _OldPriority, NewPriority, true) ->
439 6908 Acc1 = mongoose_hooks:user_available_hook(Acc, FromJid),
440 6908 Acc2 = case NewPriority >= 0 of
441 true ->
442 6908 resend_offline_messages(Acc1, StateData);
443 false ->
444
:-(
Acc1
445 end,
446 6908 presence_broadcast_first(Acc2, FromJid, Packet, Presences, Pending);
447 presence_update_to_available(Acc, FromJid, _ToJid, Packet, StateData, Presences, Pending,
448 OldPriority, NewPriority, false) ->
449 35 presence_broadcast_to_trusted(
450 Acc, FromJid, Presences, Packet),
451 35 Acc1 = case (OldPriority < 0 orelse OldPriority =:= undefined) andalso NewPriority >= 0 of
452 true ->
453
:-(
resend_offline_messages(Acc, StateData);
454 false ->
455 35 Acc
456 end,
457 35 Accs = create_route_accs(Acc1, FromJid, Pending),
458 35 ToAcc = [{route, Accs}, {state_mod, {?MODULE, Presences}}],
459 35 mongoose_c2s_acc:to_acc_many(Acc1, ToAcc).
460
461 -spec presence_broadcast_to_trusted(mongoose_acc:t(), jid:jid(), state(), exml:element()) -> ok.
462 presence_broadcast_to_trusted(Acc, FromJid, Presences, Packet) ->
463 35 sets:fold(
464 fun(JID, _) ->
465 38 case is_subscribed(Presences, JID, from) of
466 true ->
467 38 ejabberd_router:route(FromJid, JID, Acc, Packet);
468 _ ->
469
:-(
Acc
470 end
471 end, undefined, Presences#presences_state.available).
472
473 -spec resend_offline_messages(mongoose_acc:t(), mongoose_c2s:data()) -> mongoose_acc:t().
474 resend_offline_messages(Acc, StateData) ->
475 6908 ?LOG_DEBUG(#{what => resend_offline_messages, acc => Acc, c2s_state => StateData}),
476 6908 Jid = mongoose_c2s:get_jid(StateData),
477 6908 Acc1 = mongoose_hooks:resend_offline_messages_hook(Acc, Jid),
478 6908 Rs = mongoose_acc:get(offline, messages, [], Acc1),
479 6908 Acc2 = lists:foldl(fun({route, FromJid, ToJid, MsgAcc}, A) ->
480 138 resend_offline_message(A, FromJid, ToJid, MsgAcc, in);
481 ({route, MsgAcc}, A) ->
482
:-(
{FromJid, ToJid, _} = mongoose_acc:packet(Acc),
483
:-(
resend_offline_message(A, FromJid, ToJid, MsgAcc, in)
484 end, Acc1, Rs),
485 6908 mongoose_acc:delete(offline, messages, Acc2). % they are gone from db backend and sent
486
487 resend_offline_message(Acc0, FromJid, To, Acc, in) ->
488 138 Packet = mongoose_acc:element(Acc),
489 138 NewAcc = strip_c2s_fields(Acc),
490 138 ejabberd_router:route(FromJid, To, NewAcc, Packet),
491 138 Acc0.
492
493 -spec strip_c2s_fields(mongoose_acc:t()) -> mongoose_acc:t().
494 strip_c2s_fields(Acc) ->
495 %% TODO: verify if we really need to strip down these 2 fields
496 138 mongoose_acc:delete_many(c2s, [origin_jid, origin_sid], Acc).
497
498 -spec presence_broadcast_first(
499 mongoose_acc:t(), jid:jid(), exml:element(), state(), [exml:element()]) ->
500 mongoose_acc:t().
501 presence_broadcast_first(Acc0, FromJid, Packet, Presences, Pending) ->
502 6908 broadcast_probe(Acc0, FromJid, Presences),
503 6908 Ss = maps:fold(fun(JID, Sub, S) ->
504 6913 notify_available_to_subscribers(Acc0, FromJid, Packet, JID, Sub, S)
505 end, Presences#presences_state.available, Presences#presences_state.subscriptions),
506 6908 NewPresences = Presences#presences_state{available = Ss},
507 6908 Accs = create_route_accs(Acc0, FromJid, Pending),
508 6908 ToAcc = [{route, Accs}, {state_mod, {?MODULE, NewPresences}}],
509 6908 mongoose_c2s_acc:to_acc_many(Acc0, ToAcc).
510
511 -spec notify_available_to_subscribers(
512 mongoose_acc:t(), jid:jid(), exml:element(), jid:jid(), subscriptions(), available()) ->
513 available().
514 notify_available_to_subscribers(Acc0, FromJid, Packet, JID, both, S) ->
515 6913 ejabberd_router:route(FromJid, JID, Acc0, Packet),
516 6913 sets:add_element(JID, S);
517 notify_available_to_subscribers(Acc0, FromJid, Packet, JID, from, S) ->
518
:-(
ejabberd_router:route(FromJid, JID, Acc0, Packet),
519
:-(
sets:add_element(JID, S);
520 notify_available_to_subscribers(_, _, _, _, _, S) ->
521
:-(
S.
522
523 -spec broadcast_probe(mongoose_acc:t(), jid:jid(), state()) -> ok.
524 broadcast_probe(Acc, FromJid, Presences) ->
525 6908 Probe = presence_probe(),
526 6908 Fun = fun(ToJid, Sub) when both =:= Sub; to =:= Sub ->
527 6913 ejabberd_router:route(FromJid, ToJid, Acc, Probe);
528 (_, _) ->
529
:-(
ok
530 end,
531 6908 maps:foreach(Fun, Presences#presences_state.subscriptions).
532
533 -spec create_route_accs(mongoose_acc:t(), jid:jid(), [exml:element()]) -> [mongoose_acc:t()].
534 create_route_accs(Acc0, To, List) when is_list(List) ->
535 6943 [ mongoose_acc:update_stanza(#{to_jid => To, element => P}, Acc0) || P <- List ].
536
537 -spec presence_probe() -> exml:element().
538 presence_probe() ->
539 6908 #xmlel{name = <<"presence">>,
540 attrs = [{<<"type">>, <<"probe">>}]}.
541
542 -spec presence_unavailable_stanza() -> exml:element().
543 presence_unavailable_stanza() ->
544 45 presence_unavailable_stanza(<<>>).
545
546 -spec presence_unavailable_stanza(binary()) -> exml:element().
547 presence_unavailable_stanza(<<>>) ->
548 45 #xmlel{name = <<"presence">>,
549 attrs = [{<<"type">>, <<"unavailable">>}]};
550 presence_unavailable_stanza(Status) ->
551 6111 StatusEl = #xmlel{name = <<"status">>,
552 children = [#xmlcdata{content = Status}]},
553 6111 #xmlel{name = <<"presence">>,
554 attrs = [{<<"type">>, <<"unavailable">>}],
555 children = [StatusEl]}.
556
557 close_session_status(normal) ->
558
:-(
<<>>;
559 close_session_status({shutdown, retries}) ->
560
:-(
<<"Too many attempts">>;
561 close_session_status({shutdown, replaced}) ->
562
:-(
<<"Replaced by new connection">>;
563 close_session_status({shutdown, Reason}) when is_atom(Reason) ->
564 6070 <<"Shutdown by reason: ", (atom_to_binary(Reason))/binary>>;
565 close_session_status({shutdown, Reason}) when is_binary(Reason) ->
566 38 <<"Shutdown by reason: ", Reason/binary>>;
567 close_session_status(_) ->
568 3 <<"Unknown condition">>.
569
570 -spec get_presence_type(mongoose_acc:t()) -> maybe_presence().
571 get_presence_type(Acc) ->
572 24280 case mongoose_acc:stanza_type(Acc) of
573 %% Note that according to https://www.rfc-editor.org/rfc/rfc6121.html#section-4.7.1
574 %% there is no default value nor "available" is considered a valid type.
575 %% However, we keep accepting this for compatibility reasons.
576 16384 undefined -> available;
577 2 <<"available">> -> available;
578 59 <<"error">> -> error;
579 6536 <<"probe">> -> probe;
580 290 <<"subscribe">> -> subscribe;
581 280 <<"subscribed">> -> subscribed;
582 15 <<"unsubscribe">> -> unsubscribe;
583 19 <<"unsubscribed">> -> unsubscribed;
584 694 <<"unavailable">> -> unavailable;
585 1 _ -> {error, invalid_presence}
586 end.
587
588 -spec maybe_get_handler(mongoose_c2s:data()) -> state().
589 maybe_get_handler(StateData) ->
590 8407 case mongoose_c2s:get_mod_state(StateData, ?MODULE) of
591 1502 {ok, #presences_state{} = Presences} -> Presences;
592 6905 {error, not_found} -> #presences_state{}
593 end.
594
595 -spec get_mod_state(mongoose_c2s:data()) -> state() | {error, not_found}.
596 get_mod_state(StateData) ->
597 24523 case mongoose_c2s:get_mod_state(StateData, ?MODULE) of
598 23328 {ok, Presence} -> Presence;
599 1195 Error -> Error
600 end.
601
602 -spec get_priority_from_presence(exml:element()) -> priority().
603 get_priority_from_presence(PresencePacket) ->
604 7185 MaybePriority = exml_query:path(PresencePacket, [{element, <<"priority">>}, cdata], undefined),
605 7185 case catch binary_to_integer(MaybePriority) of
606 6 P when is_integer(P), -128 =< P, P =< 127 -> P;
607 7179 _ -> 0
608 end.
609
610 -spec get_old_priority(state()) -> maybe_priority().
611 get_old_priority(Presences) ->
612 7259 case Presences#presences_state.pres_last of
613 7017 undefined -> undefined;
614 242 OldPresence -> get_priority_from_presence(OldPresence)
615 end.
616
617 -spec build_subs_and_pendings(mongooseim:host_type(), mongoose_acc:t(), jid:jid()) ->
618 {mongoose_acc:t(), subscriptions(), [exml:element()]}.
619 build_subs_and_pendings(HostType, Acc0, Jid) ->
620 6943 Acc1 = mongoose_hooks:roster_get_subscription_lists(HostType, Acc0, Jid),
621 6943 {Fs0, Ts0, Pending} = mongoose_acc:get(roster, subscription_lists, {[], [], []}, Acc1),
622 6943 BareJid = jid:to_bare(Jid),
623 6943 Fs = maps:from_list([{jid:make(BJ), from} || BJ <- Fs0]),
624 6943 Ts = maps:from_list([{jid:make(BJ), to} || BJ <- Ts0]),
625 6943 Subs = maps:merge_with(fun(_, _, _) -> both end, Fs, Ts),
626 6943 {Acc1, Subs#{BareJid => both}, Pending}.
627
628 -spec update_priority(Acc :: mongoose_acc:t(),
629 Priority :: integer(),
630 Packet :: exml:element(),
631 StateData :: mongoose_c2s:data()) -> mongoose_acc:t().
632 update_priority(Acc, Priority, Packet, StateData) ->
633 6943 Sid = mongoose_c2s:get_sid(StateData),
634 6943 Jid = mongoose_c2s:get_jid(StateData),
635 6943 Info = mongoose_c2s:get_info(StateData),
636 6943 ejabberd_sm:set_presence(Acc, Sid, Jid, Priority, Packet, Info).
637
638 -spec am_i_subscribed_to_presence(jid:jid(), jid:jid(), state()) -> boolean().
639 am_i_subscribed_to_presence(LJID, LBareJID, S) ->
640 272 is_subscribed(S, LJID, to)
641 242 orelse (LJID /= LBareJID)
642 210 andalso is_subscribed(S, LBareJID, to).
643
644 -spec am_i_available_to(jid:jid(), jid:jid(), state()) -> boolean().
645 am_i_available_to(FromJid, BareJid, Presences) ->
646 15220 is_available(Presences, FromJid)
647 14440 orelse (FromJid /= BareJid)
648 14440 andalso is_available(Presences, BareJid).
649
650 -spec make_available_to(jid:jid(), jid:jid(), state()) -> state().
651 make_available_to(FromJid, BareJid, Presences) ->
652 1059 case is_subscribed(Presences, FromJid, from) of
653 true ->
654
:-(
S = sets:add_element(FromJid, Presences#presences_state.available),
655
:-(
Presences#presences_state{available = S};
656 false ->
657 1059 case is_subscribed(Presences, BareJid, from) of
658 true ->
659
:-(
S = sets:add_element(BareJid, Presences#presences_state.available),
660
:-(
Presences#presences_state{available = S};
661 false ->
662 1059 Presences
663 end
664 end.
665
666 -spec is_subscribed_to_my_presence(jid:jid(), jid:jid(), state()) -> boolean().
667 is_subscribed_to_my_presence(FromJid, BareFromJid, Presences) ->
668 6839 is_subscribed(Presences, FromJid, from)
669 6824 orelse (FromJid /= BareFromJid)
670 6744 andalso is_subscribed(Presences, BareFromJid, from).
671
672 -spec specifically_visible_to(jid:jid(), state()) -> boolean().
673 specifically_visible_to(FromJid, Presences) ->
674 6534 is_subscribed(Presences, FromJid, from)
675
:-(
andalso is_available(Presences, FromJid).
676
677 -spec is_available(state(), jid:jid()) -> boolean().
678 is_available(#presences_state{available = Available}, Jid) ->
679 30499 sets:is_element(Jid, Available).
680
681 -spec is_subscribed(state(), jid:jid(), subscription()) -> boolean().
682 is_subscribed(#presences_state{subscriptions = Subs}, Jid, DesiredSub) ->
683 23594 Sub = maps:get(Jid, Subs, '$imposible_status'),
684 23594 both =:= Sub orelse DesiredSub =:= Sub.
685
686 -spec get_by_sub(state(), subscription()) -> subscriptions().
687 get_by_sub(#presences_state{subscriptions = Subs}, DesiredStatus) ->
688 131 Filter = fun(_, Status) -> both =:= Status orelse DesiredStatus =:= Status end,
689 131 maps:filter(Filter, Subs).
690
691 -spec get(state(), s_to) -> subscriptions();
692 (state(), s_from) -> subscriptions();
693 (state(), s_available) -> available();
694 (state(), priority) -> priority();
695 (state(), last) -> undefined | exml:element();
696 (state(), timestamp) -> undefined | integer().
697
:-(
get(P, s_to) -> get_by_sub(P, to);
698 123 get(P, s_from) -> get_by_sub(P, from);
699
:-(
get(#presences_state{available = Value}, s_available) -> Value;
700
:-(
get(#presences_state{pres_pri = Value}, priority) -> Value;
701 7 get(#presences_state{pres_last = Value}, last) -> Value;
702
:-(
get(#presences_state{pres_timestamp = Value}, timestamp) -> Value.
Line Hits Source