./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 185 ok.
57
58 -spec stop(mongooseim:host_type()) -> ok.
59 stop(_HostType) ->
60 185 ok.
61
62 -spec hooks(mongooseim:host_type()) -> gen_hook:hook_list(mongoose_c2s_hooks:fn()).
63 hooks(HostType) ->
64 370 [
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 118 [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 6476 {FromJid, ToJid, Packet} = mongoose_acc:packet(Acc),
91 6476 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 5626 Acc1 = presence_update(Acc, FromJid, ToJid, Packet, StateData, Type),
96 5626 {ok, Acc1};
97 {Type, false} ->
98 849 Acc1 = presence_track(Acc, FromJid, ToJid, Packet, StateData, Type),
99 849 {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 14060 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 14056 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 6362 case get_mod_state(StateData) of
128 1024 {error, not_found} -> {ok, Acc};
129 211 #presences_state{pres_last = undefined} -> {ok, Acc};
130 5127 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 5127 Jid = mongoose_c2s:get_jid(StateData),
137 5127 Status = close_session_status(Reason),
138 5127 PresenceUnavailable = presence_unavailable_stanza(Status),
139 5127 ParamsAcc = #{from_jid => Jid, to_jid => jid:to_bare(Jid), element => PresenceUnavailable},
140 5127 Acc1 = mongoose_acc:update_stanza(ParamsAcc, Acc),
141 5127 presence_broadcast(Acc1, Presences),
142 5127 mongoose_hooks:unset_presence_hook(Acc1, Jid, Status),
143 5127 {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 798 case get_mod_state(StateData) of
155 {error, not_found} ->
156
:-(
{ok, Acc};
157 Presences ->
158 798 {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 140 {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(mongoose_acc:t(), mongoose_c2s:data(), term(), term(), state()) ->
219 mongoose_acc:t().
220 handle_subscription_change(Acc, StateData, IJID, ISubscription, Presences) ->
221 798 To = jid:make(IJID),
222 798 IsSubscribedToMe = (ISubscription =:= both) or (ISubscription =:= from),
223 798 AmISubscribedTo = (ISubscription =:= both) or (ISubscription =:= to),
224 798 WasSubscribedToMe = is_subscribed(Presences, To, from),
225 798 Subs = case {IsSubscribedToMe, AmISubscribedTo} of
226 {true, true} ->
227 152 maps:put(To, both, Presences#presences_state.subscriptions);
228 {true, _} ->
229 112 maps:put(To, from, Presences#presences_state.subscriptions);
230 {_, true} ->
231 118 maps:put(To, to, Presences#presences_state.subscriptions);
232 {false, false} ->
233 416 maps:remove(To, Presences#presences_state.subscriptions)
234 end,
235 798 case Presences#presences_state.pres_last of
236 undefined ->
237
:-(
NewPresences = Presences#presences_state{subscriptions = Subs},
238
:-(
mongoose_c2s_acc:to_acc(Acc, state_mod, {?MODULE, NewPresences});
239 P ->
240 798 ?LOG_DEBUG(#{what => roster_changed, roster_jid => To,
241 798 acc => Acc, c2s_state => StateData}),
242 798 From = mongoose_c2s:get_jid(StateData),
243 798 ImAvailableTo = is_available(Presences, To),
244 798 BecomeAvailable = IsSubscribedToMe and not WasSubscribedToMe,
245 798 BecomeUnavailable = not IsSubscribedToMe and WasSubscribedToMe and ImAvailableTo,
246 798 case {BecomeAvailable, BecomeUnavailable} of
247 {true, _} ->
248 169 ?LOG_DEBUG(#{what => become_available_to, roster_jid => To,
249 169 acc => Acc, c2s_state => StateData}),
250 169 ejabberd_router:route(From, To, Acc, P),
251 169 Available = sets:add_element(To, Presences#presences_state.available),
252 169 NewPresences = Presences#presences_state{subscriptions = Subs,
253 available = Available},
254 169 mongoose_c2s_acc:to_acc(Acc, state_mod, {?MODULE, NewPresences});
255 {_, true} ->
256 19 ?LOG_DEBUG(#{what => become_unavailable_to, roster_jid => To,
257 19 acc => Acc, c2s_state => StateData}),
258 19 PU = presence_unavailable_stanza(),
259 19 ejabberd_router:route(From, To, Acc, PU),
260 19 Available = sets:del_element(To, Presences#presences_state.available),
261 19 NewPresences = Presences#presences_state{subscriptions = Subs,
262 available = Available},
263 19 mongoose_c2s_acc:to_acc(Acc, state_mod, {?MODULE, NewPresences});
264 _ ->
265 610 NewPresences = Presences#presences_state{subscriptions = Subs},
266 610 mongoose_c2s_acc:to_acc(Acc, state_mod, {?MODULE, NewPresences})
267 end
268 end.
269
270 -spec handle_user_received_presence(mongoose_acc:t(), state(), presence()) ->
271 mongoose_c2s_hooks:result().
272 handle_user_received_presence(Acc, Presences, available) ->
273 7472 {ok, handle_received_available(Acc, Presences)};
274 handle_user_received_presence(Acc, Presences, unavailable) ->
275 608 {ok, handle_received_unavailable(Acc, Presences)};
276 handle_user_received_presence(Acc, Presences, error) ->
277 49 {ok, handle_received_unavailable(Acc, Presences)};
278 handle_user_received_presence(Acc, Presences, probe) ->
279 5553 {stop, handle_received_probe(Acc, Presences)};
280 handle_user_received_presence(Acc, _, _) ->
281 374 {ok, Acc}.
282
283 -spec handle_received_probe(mongoose_acc:t(), state()) -> mongoose_acc:t().
284 handle_received_probe(Acc, Presences) ->
285 5553 {FromJid, ToJid, _Packet} = mongoose_acc:packet(Acc),
286 5553 BareFromJid = jid:to_bare(FromJid),
287 5553 NewPresences = case am_i_available_to(FromJid, BareFromJid, Presences) of
288 5553 true -> Presences;
289
:-(
false -> make_available_to(FromJid, BareFromJid, Presences)
290 end,
291 5553 Acc1 = mongoose_c2s_acc:to_acc(Acc, state_mod, {?MODULE, NewPresences}),
292 5553 process_presence_probe(Acc1, NewPresences, FromJid, BareFromJid, ToJid).
293
294 -spec process_presence_probe(mongoose_acc:t(), state(), jid:jid(), jid:jid(), jid:jid()) -> mongoose_acc:t().
295 process_presence_probe(Acc, #presences_state{pres_last = undefined}, _, _, _) ->
296
:-(
Acc;
297 process_presence_probe(Acc, Presences, FromJid, BareFromJid, ToJid) ->
298 5553 case {is_subscribed_to_my_presence(FromJid, BareFromJid, Presences),
299 specifically_visible_to(FromJid, Presences)} of
300 {true, _} ->
301 5553 route_probe(Acc, Presences, FromJid, ToJid);
302 {false, true} ->
303
:-(
ejabberd_router:route(ToJid, FromJid, Acc, #xmlel{name = <<"presence">>}),
304
:-(
Acc;
305 _ ->
306
:-(
Acc
307 end.
308
309 -spec route_probe(mongoose_acc:t(), state(), jid:jid(), jid:jid()) -> mongoose_acc:t().
310 route_probe(Acc, Presences, FromJid, ToJid) ->
311 5553 Packet0 = Presences#presences_state.pres_last,
312 5553 TS = Presences#presences_state.pres_timestamp,
313 %% To is the one sending the presence (the target of the probe)
314 5553 Packet1 = jlib:maybe_append_delay(Packet0, ToJid, TS, <<>>),
315 5553 HostType = mongoose_acc:host_type(Acc),
316 5553 Acc2 = mongoose_hooks:presence_probe_hook(HostType, Acc, FromJid, ToJid, self()),
317 %% Don't route a presence probe to oneself
318 5553 case jid:are_equal(FromJid, ToJid) of
319 false ->
320 180 ejabberd_router:route(ToJid, FromJid, Acc2, Packet1),
321 180 Acc;
322 true ->
323 5373 Acc2
324 end.
325
326 -spec handle_received_unavailable(mongoose_acc:t(), state()) -> mongoose_acc:t().
327 handle_received_unavailable(Acc, Presences) ->
328 657 FromJid = mongoose_acc:from_jid(Acc),
329 657 NewS = sets:del_element(FromJid, Presences#presences_state.available),
330 657 NewPresences = Presences#presences_state{available = NewS},
331 657 mongoose_c2s_acc:to_acc(Acc, state_mod, {?MODULE, NewPresences}).
332
333 -spec handle_received_available(mongoose_acc:t(), state()) -> mongoose_acc:t().
334 handle_received_available(Acc, Presences) ->
335 7472 FromJid = mongoose_acc:from_jid(Acc),
336 7472 BareJid = jid:to_bare(FromJid),
337 7472 case am_i_available_to(FromJid, BareJid, Presences) of
338 true ->
339 6530 Acc;
340 false ->
341 942 NewPresences = make_available_to(FromJid, BareJid, Presences),
342 942 mongoose_c2s_acc:to_acc(Acc, state_mod, {?MODULE, NewPresences})
343 end.
344
345 %% @doc User updates his presence (non-directed presence packet)
346 -spec presence_update(
347 mongoose_acc:t(), jid:jid(), jid:jid(), exml:element(), mongoose_c2s:data(), presence()) ->
348 mongoose_acc:t().
349 presence_update(Acc, FromJid, ToJid, Packet, StateData, available) ->
350 5414 Presences = maybe_get_handler(StateData),
351 5414 presence_update_to_available(Acc, FromJid, ToJid, Packet, StateData, Presences);
352 presence_update(Acc, FromJid, ToJid, Packet, StateData, unavailable) ->
353 221 Presences = maybe_get_handler(StateData),
354 221 presence_update_to_unavailable(Acc, FromJid, ToJid, Packet, StateData, Presences);
355
:-(
presence_update(Acc, _, _, _, _, _) -> Acc.
356
357 -spec presence_update_to_available(
358 mongoose_acc:t(), jid:jid(), jid:jid(), exml:element(), mongoose_c2s:data(), state()) ->
359 mongoose_acc:t().
360 presence_update_to_available(Acc0, FromJid, ToJid, Packet, StateData, Presences) ->
361 5414 Jid = mongoose_c2s:get_jid(StateData),
362 5414 HostType = mongoose_c2s:get_host_type(StateData),
363 5414 {Acc1, Subs, Pending} = build_subs_and_pendings(HostType, Acc0, Jid),
364 5414 OldPriority = get_old_priority(Presences),
365 5414 NewPriority = get_priority_from_presence(Packet),
366 5414 Acc2 = update_priority(Acc1, NewPriority, Packet, StateData),
367 5414 FromUnavail = (Presences#presences_state.pres_last =:= undefined),
368 5414 ?LOG_DEBUG(#{what => presence_update_to_available,
369 text => <<"Presence changes from unavailable to available">>,
370 5414 from_unavail => FromUnavail, acc => Acc2, c2s_state => StateData}),
371 5414 NewPresences = Presences#presences_state{subscriptions = Subs,
372 pres_pri = NewPriority,
373 pres_last = Packet,
374 pres_timestamp = erlang:system_time(microsecond)},
375 5414 presence_update_to_available(
376 Acc2, FromJid, ToJid, Packet, StateData, NewPresences, Pending,
377 OldPriority, NewPriority, FromUnavail).
378
379 -spec presence_update_to_unavailable(
380 mongoose_acc:t(), jid:jid(), jid:jid(), exml:element(), mongoose_c2s:data(), state()) ->
381 mongoose_acc:t().
382 presence_update_to_unavailable(Acc, _FromJid, _ToJid, Packet, StateData, Presences) ->
383 221 Status = exml_query:path(Packet, [{element, <<"status">>}, cdata], <<>>),
384 221 Sid = mongoose_c2s:get_sid(StateData),
385 221 Jid = mongoose_c2s:get_jid(StateData),
386 221 Info = mongoose_c2s:get_info(StateData),
387 221 Acc1 = ejabberd_sm:unset_presence(Acc, Sid, Jid, Status, Info),
388 221 presence_broadcast(Acc1, Presences),
389 221 NewPresences = Presences#presences_state{pres_last = undefined,
390 pres_timestamp = undefined},
391 221 mongoose_c2s_acc:to_acc(Acc1, state_mod, {?MODULE, NewPresences}).
392
393 %% @doc User sends a directed presence packet
394 -spec presence_track(
395 mongoose_acc:t(), jid:jid(), jid:jid(), exml:element(), mongoose_c2s:data(), presence()) ->
396 mongoose_acc:t().
397 presence_track(Acc, FromJid, ToJid, Packet, StateData, available) ->
398 645 Presences = maybe_get_handler(StateData),
399 645 process_presence_track_available(Acc, FromJid, ToJid, Packet, Presences);
400 presence_track(Acc, FromJid, ToJid, Packet, StateData, unavailable) ->
401 17 Presences = maybe_get_handler(StateData),
402 17 process_presence_track_unavailable(Acc, FromJid, ToJid, Packet, Presences);
403 presence_track(Acc, FromJid, ToJid, Packet, _, Type) when error =:= Type; probe =:= Type ->
404 2 ejabberd_router:route(FromJid, ToJid, Acc, Packet),
405 2 Acc;
406 presence_track(Acc, FromJid, ToJid, Packet, _, Type) ->
407 185 process_presence_track_subscription_and_route(Acc, FromJid, ToJid, Packet, Type).
408
409 process_presence_track_available(Acc, FromJid, ToJid, Packet, Presences) ->
410 645 ejabberd_router:route(FromJid, ToJid, Acc, Packet),
411 645 Available = sets:add_element(ToJid, Presences#presences_state.available),
412 645 NewPresences = Presences#presences_state{available = Available},
413 645 mongoose_c2s_acc:to_acc(Acc, state_mod, {?MODULE, NewPresences}).
414
415 process_presence_track_unavailable(Acc, FromJid, ToJid, Packet, Presences) ->
416 17 ejabberd_router:route(FromJid, ToJid, Acc, Packet),
417 17 Available = sets:del_element(ToJid, Presences#presences_state.available),
418 17 NewPresences = Presences#presences_state{available = Available},
419 17 mongoose_c2s_acc:to_acc(Acc, state_mod, {?MODULE, NewPresences}).
420
421 process_presence_track_subscription_and_route(Acc, FromJid, ToJid, Packet, Type) ->
422 185 Acc1 = mongoose_hooks:roster_out_subscription(Acc, FromJid, ToJid, Type),
423 185 ejabberd_router:route(jid:to_bare(FromJid), ToJid, Acc1, Packet),
424 185 Acc1.
425
426 -spec presence_broadcast(mongoose_acc:t(), state()) -> ok.
427 presence_broadcast(Acc, #presences_state{available = Available}) ->
428 5348 {FromJID, _, Packet} = mongoose_acc:packet(Acc),
429 5348 Route = fun(ToJID, _) -> ejabberd_router:route(FromJID, ToJID, Acc, Packet) end,
430 5348 sets:fold(Route, undefined, Available).
431
432 -spec presence_update_to_available(
433 mongoose_acc:t(), jid:jid(), jid:jid(), exml:element(), mongoose_c2s:data(),
434 state(), [exml:element()], maybe_priority(), priority(), boolean()) ->
435 mongoose_acc:t().
436 presence_update_to_available(Acc, FromJid, _ToJid, Packet, StateData, Presences, Pending,
437 _OldPriority, NewPriority, true) ->
438 5375 Acc1 = mongoose_hooks:user_available_hook(Acc, FromJid),
439 5375 Acc2 = case NewPriority >= 0 of
440 true ->
441 5375 resend_offline_messages(Acc1, StateData);
442 false ->
443
:-(
Acc1
444 end,
445 5375 presence_broadcast_first(Acc2, FromJid, Packet, Presences, Pending);
446 presence_update_to_available(Acc, FromJid, _ToJid, Packet, StateData, Presences, Pending,
447 OldPriority, NewPriority, false) ->
448 39 presence_broadcast_to_trusted(
449 Acc, FromJid, Presences, Packet),
450 39 Acc1 = case (OldPriority < 0 orelse OldPriority =:= undefined) andalso NewPriority >= 0 of
451 true ->
452
:-(
resend_offline_messages(Acc, StateData);
453 false ->
454 39 Acc
455 end,
456 39 Accs = create_route_accs(Acc1, FromJid, Pending),
457 39 ToAcc = [{route, Accs}, {state_mod, {?MODULE, Presences}}],
458 39 mongoose_c2s_acc:to_acc_many(Acc1, ToAcc).
459
460 -spec presence_broadcast_to_trusted(mongoose_acc:t(), jid:jid(), state(), exml:element()) -> ok.
461 presence_broadcast_to_trusted(Acc, FromJid, Presences, Packet) ->
462 39 sets:fold(
463 fun(JID, _) ->
464 42 case is_subscribed(Presences, JID, from) of
465 true ->
466 42 ejabberd_router:route(FromJid, JID, Acc, Packet);
467 _ ->
468
:-(
Acc
469 end
470 end, undefined, Presences#presences_state.available).
471
472 -spec resend_offline_messages(mongoose_acc:t(), mongoose_c2s:data()) -> mongoose_acc:t().
473 resend_offline_messages(Acc, StateData) ->
474 5375 ?LOG_DEBUG(#{what => resend_offline_messages, acc => Acc, c2s_state => StateData}),
475 5375 Jid = mongoose_c2s:get_jid(StateData),
476 5375 Acc1 = mongoose_hooks:resend_offline_messages_hook(Acc, Jid),
477 5375 Rs = mongoose_acc:get(offline, messages, [], Acc1),
478 5375 Acc2 = lists:foldl(fun({route, FromJid, ToJid, MsgAcc}, A) ->
479 150 resend_offline_message(A, FromJid, ToJid, MsgAcc, in);
480 ({route, MsgAcc}, A) ->
481
:-(
{FromJid, ToJid, _} = mongoose_acc:packet(Acc),
482
:-(
resend_offline_message(A, FromJid, ToJid, MsgAcc, in)
483 end, Acc1, Rs),
484 5375 mongoose_acc:delete(offline, messages, Acc2). % they are gone from db backend and sent
485
486 resend_offline_message(Acc0, FromJid, To, Acc, in) ->
487 150 Packet = mongoose_acc:element(Acc),
488 150 NewAcc = strip_c2s_fields(Acc),
489 150 ejabberd_router:route(FromJid, To, NewAcc, Packet),
490 150 Acc0.
491
492 -spec strip_c2s_fields(mongoose_acc:t()) -> mongoose_acc:t().
493 strip_c2s_fields(Acc) ->
494 %% TODO: verify if we really need to strip down these 2 fields
495 150 mongoose_acc:delete_many(c2s, [origin_jid, origin_sid], Acc).
496
497 -spec presence_broadcast_first(
498 mongoose_acc:t(), jid:jid(), exml:element(), state(), [exml:element()]) ->
499 mongoose_acc:t().
500 presence_broadcast_first(Acc0, FromJid, Packet, Presences, Pending) ->
501 5375 broadcast_probe(Acc0, FromJid, Presences),
502 5375 Ss = maps:fold(fun(JID, Sub, S) ->
503 5380 notify_available_to_subscribers(Acc0, FromJid, Packet, JID, Sub, S)
504 end, Presences#presences_state.available, Presences#presences_state.subscriptions),
505 5375 NewPresences = Presences#presences_state{available = Ss},
506 5375 Accs = create_route_accs(Acc0, FromJid, Pending),
507 5375 ToAcc = [{route, Accs}, {state_mod, {?MODULE, NewPresences}}],
508 5375 mongoose_c2s_acc:to_acc_many(Acc0, ToAcc).
509
510 -spec notify_available_to_subscribers(
511 mongoose_acc:t(), jid:jid(), exml:element(), jid:jid(), subscriptions(), available()) ->
512 available().
513 notify_available_to_subscribers(Acc0, FromJid, Packet, JID, both, S) ->
514 5380 ejabberd_router:route(FromJid, JID, Acc0, Packet),
515 5380 sets:add_element(JID, S);
516 notify_available_to_subscribers(Acc0, FromJid, Packet, JID, from, S) ->
517
:-(
ejabberd_router:route(FromJid, JID, Acc0, Packet),
518
:-(
sets:add_element(JID, S);
519 notify_available_to_subscribers(_, _, _, _, _, S) ->
520
:-(
S.
521
522 -spec broadcast_probe(mongoose_acc:t(), jid:jid(), state()) -> ok.
523 broadcast_probe(Acc, FromJid, Presences) ->
524 5375 Probe = presence_probe(),
525 5375 Fun = fun(ToJid, Sub) when both =:= Sub; to =:= Sub ->
526 5380 ejabberd_router:route(FromJid, ToJid, Acc, Probe);
527 (_, _) ->
528
:-(
ok
529 end,
530 5375 maps:foreach(Fun, Presences#presences_state.subscriptions).
531
532 -spec create_route_accs(mongoose_acc:t(), jid:jid(), [exml:element()]) -> [mongoose_acc:t()].
533 create_route_accs(Acc0, To, List) when is_list(List) ->
534 5414 [ mongoose_acc:update_stanza(#{to_jid => To, element => P}, Acc0) || P <- List ].
535
536 -spec presence_probe() -> exml:element().
537 presence_probe() ->
538 5375 #xmlel{name = <<"presence">>,
539 attrs = [{<<"type">>, <<"probe">>}]}.
540
541 -spec presence_unavailable_stanza() -> exml:element().
542 presence_unavailable_stanza() ->
543 45 presence_unavailable_stanza(<<>>).
544
545 -spec presence_unavailable_stanza(binary()) -> exml:element().
546 presence_unavailable_stanza(<<>>) ->
547 45 #xmlel{name = <<"presence">>,
548 attrs = [{<<"type">>, <<"unavailable">>}]};
549 presence_unavailable_stanza(Status) ->
550 5127 StatusEl = #xmlel{name = <<"status">>,
551 children = [#xmlcdata{content = Status}]},
552 5127 #xmlel{name = <<"presence">>,
553 attrs = [{<<"type">>, <<"unavailable">>}],
554 children = [StatusEl]}.
555
556 close_session_status(normal) ->
557
:-(
<<>>;
558 close_session_status({shutdown, retries}) ->
559
:-(
<<"Too many attempts">>;
560 close_session_status({shutdown, replaced}) ->
561
:-(
<<"Replaced by new connection">>;
562 close_session_status({shutdown, Reason}) when is_atom(Reason) ->
563 5086 <<"Shutdown by reason: ", (atom_to_binary(Reason))/binary>>;
564 close_session_status({shutdown, Reason}) when is_binary(Reason) ->
565 38 <<"Shutdown by reason: ", Reason/binary>>;
566 close_session_status(_) ->
567 3 <<"Unknown condition">>.
568
569 -spec get_presence_type(mongoose_acc:t()) -> maybe_presence().
570 get_presence_type(Acc) ->
571 20545 case mongoose_acc:stanza_type(Acc) of
572 %% Note that according to https://www.rfc-editor.org/rfc/rfc6121.html#section-4.7.1
573 %% there is no default value nor "available" is considered a valid type.
574 %% However, we keep accepting this for compatibility reasons.
575 13531 undefined -> available;
576 2 <<"available">> -> available;
577 51 <<"error">> -> error;
578 5555 <<"probe">> -> probe;
579 269 <<"subscribe">> -> subscribe;
580 256 <<"subscribed">> -> subscribed;
581 15 <<"unsubscribe">> -> unsubscribe;
582 19 <<"unsubscribed">> -> unsubscribed;
583 846 <<"unavailable">> -> unavailable;
584 1 _ -> {error, invalid_presence}
585 end.
586
587 -spec maybe_get_handler(mongoose_c2s:data()) -> state().
588 maybe_get_handler(StateData) ->
589 6760 case mongoose_c2s:get_mod_state(StateData, ?MODULE) of
590 1388 {ok, #presences_state{} = Presences} -> Presences;
591 5372 {error, not_found} -> #presences_state{}
592 end.
593
594 -spec get_mod_state(mongoose_c2s:data()) -> state() | {error, not_found}.
595 get_mod_state(StateData) ->
596 21258 case mongoose_c2s:get_mod_state(StateData, ?MODULE) of
597 20230 {ok, Presence} -> Presence;
598 1028 Error -> Error
599 end.
600
601 -spec get_priority_from_presence(exml:element()) -> priority().
602 get_priority_from_presence(PresencePacket) ->
603 5653 MaybePriority = exml_query:path(PresencePacket, [{element, <<"priority">>}, cdata], undefined),
604 5653 case catch binary_to_integer(MaybePriority) of
605 6 P when is_integer(P), -128 =< P, P =< 127 -> P;
606 5647 _ -> 0
607 end.
608
609 -spec get_old_priority(state()) -> maybe_priority().
610 get_old_priority(Presences) ->
611 5721 case Presences#presences_state.pres_last of
612 5482 undefined -> undefined;
613 239 OldPresence -> get_priority_from_presence(OldPresence)
614 end.
615
616 -spec build_subs_and_pendings(mongooseim:host_type(), mongoose_acc:t(), jid:jid()) ->
617 {mongoose_acc:t(), subscriptions(), [exml:element()]}.
618 build_subs_and_pendings(HostType, Acc0, Jid) ->
619 5414 Acc1 = mongoose_hooks:roster_get_subscription_lists(HostType, Acc0, Jid),
620 5414 {Fs0, Ts0, Pending} = mongoose_acc:get(roster, subscription_lists, {[], [], []}, Acc1),
621 5414 BareJid = jid:to_bare(Jid),
622 5414 Fs = maps:from_list([{jid:make(BJ), from} || BJ <- Fs0]),
623 5414 Ts = maps:from_list([{jid:make(BJ), to} || BJ <- Ts0]),
624 5414 Subs = maps:merge_with(fun(_, _, _) -> both end, Fs, Ts),
625 5414 {Acc1, Subs#{BareJid => both}, Pending}.
626
627 -spec update_priority(Acc :: mongoose_acc:t(),
628 Priority :: integer(),
629 Packet :: exml:element(),
630 StateData :: mongoose_c2s:data()) -> mongoose_acc:t().
631 update_priority(Acc, Priority, Packet, StateData) ->
632 5414 Sid = mongoose_c2s:get_sid(StateData),
633 5414 Jid = mongoose_c2s:get_jid(StateData),
634 5414 Info = mongoose_c2s:get_info(StateData),
635 5414 ejabberd_sm:set_presence(Acc, Sid, Jid, Priority, Packet, Info).
636
637 -spec am_i_subscribed_to_presence(jid:jid(), jid:jid(), state()) -> boolean().
638 am_i_subscribed_to_presence(LJID, LBareJID, S) ->
639 274 is_subscribed(S, LJID, to)
640 244 orelse (LJID /= LBareJID)
641 212 andalso is_subscribed(S, LBareJID, to).
642
643 -spec am_i_available_to(jid:jid(), jid:jid(), state()) -> boolean().
644 am_i_available_to(FromJid, BareJid, Presences) ->
645 13025 is_available(Presences, FromJid)
646 12345 orelse (FromJid /= BareJid)
647 12345 andalso is_available(Presences, BareJid).
648
649 -spec make_available_to(jid:jid(), jid:jid(), state()) -> state().
650 make_available_to(FromJid, BareJid, Presences) ->
651 942 case is_subscribed(Presences, FromJid, from) of
652 true ->
653
:-(
S = sets:add_element(FromJid, Presences#presences_state.available),
654
:-(
Presences#presences_state{available = S};
655 false ->
656 942 case is_subscribed(Presences, BareJid, from) of
657 true ->
658
:-(
S = sets:add_element(BareJid, Presences#presences_state.available),
659
:-(
Presences#presences_state{available = S};
660 false ->
661 942 Presences
662 end
663 end.
664
665 -spec is_subscribed_to_my_presence(jid:jid(), jid:jid(), state()) -> boolean().
666 is_subscribed_to_my_presence(FromJid, BareFromJid, Presences) ->
667 5860 is_subscribed(Presences, FromJid, from)
668 5845 orelse (FromJid /= BareFromJid)
669 5765 andalso is_subscribed(Presences, BareFromJid, from).
670
671 -spec specifically_visible_to(jid:jid(), state()) -> boolean().
672 specifically_visible_to(FromJid, Presences) ->
673 5553 is_subscribed(Presences, FromJid, from)
674
:-(
andalso is_available(Presences, FromJid).
675
676 -spec is_available(state(), jid:jid()) -> boolean().
677 is_available(#presences_state{available = Available}, Jid) ->
678 26168 sets:is_element(Jid, Available).
679
680 -spec is_subscribed(state(), jid:jid(), subscription()) -> boolean().
681 is_subscribed(#presences_state{subscriptions = Subs}, Jid, DesiredSub) ->
682 20388 Sub = maps:get(Jid, Subs, '$imposible_status'),
683 20388 both =:= Sub orelse DesiredSub =:= Sub.
684
685 -spec get_by_sub(state(), subscription()) -> subscriptions().
686 get_by_sub(#presences_state{subscriptions = Subs}, DesiredStatus) ->
687 131 Filter = fun(_, Status) -> both =:= Status orelse DesiredStatus =:= Status end,
688 131 maps:filter(Filter, Subs).
689
690 -spec get(state(), s_to) -> subscriptions();
691 (state(), s_from) -> subscriptions();
692 (state(), s_available) -> available();
693 (state(), priority) -> priority();
694 (state(), last) -> undefined | exml:element();
695 (state(), timestamp) -> undefined | integer().
696
:-(
get(P, s_to) -> get_by_sub(P, to);
697 123 get(P, s_from) -> get_by_sub(P, from);
698
:-(
get(#presences_state{available = Value}, s_available) -> Value;
699
:-(
get(#presences_state{pres_pri = Value}, priority) -> Value;
700 7 get(#presences_state{pres_last = Value}, last) -> Value;
701
:-(
get(#presences_state{pres_timestamp = Value}, timestamp) -> Value.
Line Hits Source