./ct_report/coverage/mod_presence.COVER.html

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