./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 16 ok.
57
58 -spec stop(mongooseim:host_type()) -> ok.
59 stop(_HostType) ->
60 16 ok.
61
62 -spec hooks(mongooseim:host_type()) -> gen_hook:hook_list(mongoose_c2s_hooks:fn()).
63 hooks(HostType) ->
64 32 [
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 7 [dynamic_domains].
74
75 -spec get_presence(pid()) -> {jid:luser(), jid:lresource(), binary(), binary()}.
76 get_presence(Pid) ->
77
:-(
mongoose_c2s:call(Pid, ?MODULE, get_presence).
78
79 -spec get_subscribed(pid()) -> [jid:jid()].
80 get_subscribed(Pid) ->
81
:-(
mongoose_c2s:call(Pid, ?MODULE, get_subscribed).
82
83 -spec set_presence(pid(), exml:element()) -> ok.
84 set_presence(Pid, Message) ->
85
:-(
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 503 {FromJid, ToJid, Packet} = mongoose_acc:packet(Acc),
91 503 case {get_presence_type(Acc), jid:are_bare_equal(FromJid, ToJid)} of
92 {{error, invalid_presence}, _} ->
93
:-(
handle_invalid_presence_type(Acc, FromJid, ToJid, Packet, StateData);
94 {Type, true} ->
95 377 Acc1 = presence_update(Acc, FromJid, ToJid, Packet, StateData, Type),
96 377 {ok, Acc1};
97 {Type, false} ->
98 126 Acc1 = presence_track(Acc, FromJid, ToJid, Packet, StateData, Type),
99 126 {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 1103 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
:-(
{stop, Acc};
111 {Type, Presences} ->
112 1103 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
:-(
Lang = mongoose_c2s:get_lang(StateData),
119
:-(
Error = mongoose_xmpp_errors:bad_request(Lang, <<"Invalid presence type">>),
120
:-(
Reply = jlib:make_error_reply(Packet, Error),
121
:-(
ejabberd_router:route(ToJid, FromJid, Acc, Reply),
122
:-(
{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 541 case get_mod_state(StateData) of
128 164 {error, not_found} -> {ok, Acc};
129
:-(
#presences_state{pres_last = undefined} -> {ok, Acc};
130 377 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 377 Jid = mongoose_c2s:get_jid(StateData),
137 377 Status = close_session_status(Reason),
138 377 PresenceUnavailable = presence_unavailable_stanza(Status),
139 377 ParamsAcc = #{from_jid => Jid, to_jid => jid:to_bare(Jid), element => PresenceUnavailable},
140 377 Acc1 = mongoose_acc:update_stanza(ParamsAcc, Acc),
141 377 presence_broadcast(Acc1, Presences),
142 377 mongoose_hooks:unset_presence_hook(Acc1, Jid, Status),
143 377 {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 58 case get_mod_state(StateData) of
155 {error, not_found} ->
156
:-(
{ok, Acc};
157 Presences ->
158 58 {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
:-(
PresLast = case get_mod_state(StateData) of
165 {error, not_found} ->
166
:-(
undefined;
167 #presences_state{pres_last = Value} ->
168
:-(
Value
169 end,
170
:-(
#jid{luser = User, lresource = Resource} = mongoose_c2s:get_jid(StateData),
171
:-(
Reply = {User, Resource, get_showtag(PresLast), get_statustag(PresLast)},
172
:-(
Acc1 = mongoose_c2s_acc:to_acc(Acc, actions, [{reply, From, Reply}]),
173
:-(
{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
:-(
Subscribed = case get_mod_state(StateData) of
179 {error, not_found} ->
180
:-(
[];
181 Presences ->
182
:-(
PresF = get_by_sub(Presences, from),
183
:-(
maps:keys(PresF)
184 end,
185
:-(
Acc1 = mongoose_c2s_acc:to_acc(Acc, actions, [{reply, From, Subscribed}]),
186
:-(
{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
:-(
Acc1 = mongoose_acc:update_stanza(#{element => Message}, Acc),
192
:-(
{FromJid, ToJid, Packet} = mongoose_acc:packet(Acc1),
193
:-(
case get_presence_type(Acc) of
194 {error, invalid_presence} ->
195
:-(
{stop, Acc};
196 Type ->
197
:-(
{stop, presence_update(Acc1, FromJid, ToJid, Packet, StateData, Type)}
198 end;
199 foreign_event(Acc, _Params, _Extra) ->
200 18 {ok, Acc}.
201
202 -spec get_showtag(undefined | exml:element()) -> binary().
203 get_showtag(undefined) ->
204
:-(
<<"unavailable">>;
205 get_showtag(Presence) ->
206
:-(
case xml:get_path_s(Presence, [{elem, <<"show">>}, cdata]) of
207
:-(
<<>> -> <<"available">>;
208
:-(
ShowTag -> ShowTag
209 end.
210
211 -spec get_statustag(undefined | exml:element()) -> binary().
212
213 get_statustag(undefined) ->
214
:-(
<<>>;
215 get_statustag(Presence) ->
216
:-(
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 58 To = jid:make(IJID),
222 58 IsSubscribedToMe = (ISubscription =:= both) or (ISubscription =:= from),
223 58 AmISubscribedTo = (ISubscription =:= both) or (ISubscription =:= to),
224 58 WasSubscribedToMe = is_subscribed(Presences, To, from),
225 58 Subs = case {IsSubscribedToMe, AmISubscribedTo} of
226 {true, true} ->
227 16 maps:put(To, both, Presences#presences_state.subscriptions);
228 {true, _} ->
229 14 maps:put(To, from, Presences#presences_state.subscriptions);
230 {_, true} ->
231 15 maps:put(To, to, Presences#presences_state.subscriptions);
232 {false, false} ->
233 13 maps:remove(To, Presences#presences_state.subscriptions)
234 end,
235 58 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 58 ?LOG_DEBUG(#{what => roster_changed, roster_jid => To,
241 58 acc => Acc, c2s_state => StateData}),
242 58 From = mongoose_c2s:get_jid(StateData),
243 58 ImAvailableTo = is_available(Presences, To),
244 58 BecomeAvailable = IsSubscribedToMe and not WasSubscribedToMe,
245 58 BecomeUnavailable = not IsSubscribedToMe and WasSubscribedToMe and ImAvailableTo,
246 58 case {BecomeAvailable, BecomeUnavailable} of
247 {true, _} ->
248 16 ?LOG_DEBUG(#{what => become_available_to, roster_jid => To,
249 16 acc => Acc, c2s_state => StateData}),
250 16 ejabberd_router:route(From, To, Acc, P),
251 16 Available = sets:add_element(To, Presences#presences_state.available),
252 16 NewPresences = Presences#presences_state{subscriptions = Subs,
253 available = Available},
254 16 mongoose_c2s_acc:to_acc(Acc, state_mod, {?MODULE, NewPresences});
255 {_, true} ->
256 1 ?LOG_DEBUG(#{what => become_unavailable_to, roster_jid => To,
257 1 acc => Acc, c2s_state => StateData}),
258 1 PU = presence_unavailable_stanza(),
259 1 ejabberd_router:route(From, To, Acc, PU),
260 1 Available = sets:del_element(To, Presences#presences_state.available),
261 1 NewPresences = Presences#presences_state{subscriptions = Subs,
262 available = Available},
263 1 mongoose_c2s_acc:to_acc(Acc, state_mod, {?MODULE, NewPresences});
264 _ ->
265 41 NewPresences = Presences#presences_state{subscriptions = Subs},
266 41 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 605 {ok, handle_received_available(Acc, Presences)};
274 handle_user_received_presence(Acc, Presences, unavailable) ->
275 71 {ok, handle_received_unavailable(Acc, Presences)};
276 handle_user_received_presence(Acc, Presences, error) ->
277 8 {ok, handle_received_unavailable(Acc, Presences)};
278 handle_user_received_presence(Acc, Presences, probe) ->
279 385 {stop, handle_received_probe(Acc, Presences)};
280 handle_user_received_presence(Acc, _, _) ->
281 34 {ok, Acc}.
282
283 -spec handle_received_probe(mongoose_acc:t(), state()) -> mongoose_acc:t().
284 handle_received_probe(Acc, Presences) ->
285 385 {FromJid, ToJid, _Packet} = mongoose_acc:packet(Acc),
286 385 BareFromJid = jid:to_bare(FromJid),
287 385 NewPresences = case am_i_available_to(FromJid, BareFromJid, Presences) of
288 385 true -> Presences;
289
:-(
false -> make_available_to(FromJid, BareFromJid, Presences)
290 end,
291 385 Acc1 = mongoose_c2s_acc:to_acc(Acc, state_mod, {?MODULE, NewPresences}),
292 385 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 385 case {is_subscribed_to_my_presence(FromJid, BareFromJid, Presences),
299 specifically_visible_to(FromJid, Presences)} of
300 {true, _} ->
301 385 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 385 Packet0 = Presences#presences_state.pres_last,
312 385 TS = Presences#presences_state.pres_timestamp,
313 %% To is the one sending the presence (the target of the probe)
314 385 Packet1 = jlib:maybe_append_delay(Packet0, ToJid, TS, <<>>),
315 385 HostType = mongoose_acc:host_type(Acc),
316 385 Acc2 = mongoose_hooks:presence_probe_hook(HostType, Acc, FromJid, ToJid, self()),
317 %% Don't route a presence probe to oneself
318 385 case jid:are_equal(FromJid, ToJid) of
319 false ->
320 8 ejabberd_router:route(ToJid, FromJid, Acc2, Packet1),
321 8 Acc;
322 true ->
323 377 Acc2
324 end.
325
326 -spec handle_received_unavailable(mongoose_acc:t(), state()) -> mongoose_acc:t().
327 handle_received_unavailable(Acc, Presences) ->
328 79 FromJid = mongoose_acc:from_jid(Acc),
329 79 NewS = sets:del_element(FromJid, Presences#presences_state.available),
330 79 NewPresences = Presences#presences_state{available = NewS},
331 79 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 605 FromJid = mongoose_acc:from_jid(Acc),
336 605 BareJid = jid:to_bare(FromJid),
337 605 case am_i_available_to(FromJid, BareJid, Presences) of
338 true ->
339 487 Acc;
340 false ->
341 118 NewPresences = make_available_to(FromJid, BareJid, Presences),
342 118 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 377 Presences = maybe_get_handler(StateData),
351 377 presence_update_to_available(Acc, FromJid, ToJid, Packet, StateData, Presences);
352 presence_update(Acc, FromJid, ToJid, Packet, StateData, unavailable) ->
353
:-(
Presences = maybe_get_handler(StateData),
354
:-(
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 377 Jid = mongoose_c2s:get_jid(StateData),
362 377 HostType = mongoose_c2s:get_host_type(StateData),
363 377 {Acc1, Subs, Pending} = build_subs_and_pendings(HostType, Acc0, Jid),
364 377 OldPriority = get_old_priority(Presences),
365 377 NewPriority = get_priority_from_presence(Packet),
366 377 Acc2 = update_priority(Acc1, NewPriority, Packet, StateData),
367 377 FromUnavail = (Presences#presences_state.pres_last =:= undefined),
368 377 ?LOG_DEBUG(#{what => presence_update_to_available,
369 text => <<"Presence changes from unavailable to available">>,
370 377 from_unavail => FromUnavail, acc => Acc2, c2s_state => StateData}),
371 377 NewPresences = Presences#presences_state{subscriptions = Subs,
372 pres_pri = NewPriority,
373 pres_last = Packet,
374 pres_timestamp = erlang:system_time(microsecond)},
375 377 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
:-(
Status = exml_query:path(Packet, [{element, <<"status">>}, cdata], <<>>),
384
:-(
Sid = mongoose_c2s:get_sid(StateData),
385
:-(
Jid = mongoose_c2s:get_jid(StateData),
386
:-(
Info = mongoose_c2s:get_info(StateData),
387
:-(
Acc1 = ejabberd_sm:unset_presence(Acc, Sid, Jid, Status, Info),
388
:-(
presence_broadcast(Acc1, Presences),
389
:-(
NewPresences = Presences#presences_state{pres_last = undefined,
390 pres_timestamp = undefined},
391
:-(
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 94 Presences = maybe_get_handler(StateData),
399 94 process_presence_track_available(Acc, FromJid, ToJid, Packet, Presences);
400 presence_track(Acc, FromJid, ToJid, Packet, StateData, unavailable) ->
401
:-(
Presences = maybe_get_handler(StateData),
402
:-(
process_presence_track_unavailable(Acc, FromJid, ToJid, Packet, Presences);
403 presence_track(Acc, FromJid, ToJid, Packet, _, Type) when error =:= Type; probe =:= Type ->
404
:-(
ejabberd_router:route(FromJid, ToJid, Acc, Packet),
405
:-(
Acc;
406 presence_track(Acc, FromJid, ToJid, Packet, _, Type) ->
407 32 process_presence_track_subscription_and_route(Acc, FromJid, ToJid, Packet, Type).
408
409 process_presence_track_available(Acc, FromJid, ToJid, Packet, Presences) ->
410 94 ejabberd_router:route(FromJid, ToJid, Acc, Packet),
411 94 Available = sets:add_element(ToJid, Presences#presences_state.available),
412 94 NewPresences = Presences#presences_state{available = Available},
413 94 mongoose_c2s_acc:to_acc(Acc, state_mod, {?MODULE, NewPresences}).
414
415 process_presence_track_unavailable(Acc, FromJid, ToJid, Packet, Presences) ->
416
:-(
ejabberd_router:route(FromJid, ToJid, Acc, Packet),
417
:-(
Available = sets:del_element(ToJid, Presences#presences_state.available),
418
:-(
NewPresences = Presences#presences_state{available = Available},
419
:-(
mongoose_c2s_acc:to_acc(Acc, state_mod, {?MODULE, NewPresences}).
420
421 process_presence_track_subscription_and_route(Acc, FromJid, ToJid, Packet, Type) ->
422 32 Acc1 = mongoose_hooks:roster_out_subscription(Acc, FromJid, ToJid, Type),
423 32 ejabberd_router:route(jid:to_bare(FromJid), ToJid, Acc1, Packet),
424 32 Acc1.
425
426 -spec presence_broadcast(mongoose_acc:t(), state()) -> ok.
427 presence_broadcast(Acc, #presences_state{available = Available}) ->
428 377 {FromJID, _, Packet} = mongoose_acc:packet(Acc),
429 377 Route = fun(ToJID, _) -> ejabberd_router:route(FromJID, ToJID, Acc, Packet) end,
430 377 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 377 Acc1 = mongoose_hooks:user_available_hook(Acc, FromJid),
439 377 Acc2 = case NewPriority >= 0 of
440 true ->
441 377 resend_offline_messages(Acc1, StateData);
442 false ->
443
:-(
Acc1
444 end,
445 377 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
:-(
presence_broadcast_to_trusted(
449 Acc, FromJid, Presences, Packet),
450
:-(
Acc1 = case (OldPriority < 0 orelse OldPriority =:= undefined) andalso NewPriority >= 0 of
451 true ->
452
:-(
resend_offline_messages(Acc, StateData);
453 false ->
454
:-(
Acc
455 end,
456
:-(
Accs = create_route_accs(Acc1, FromJid, Pending),
457
:-(
ToAcc = [{route, Accs}, {state_mod, {?MODULE, Presences}}],
458
:-(
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
:-(
sets:fold(
463 fun(JID, _) ->
464
:-(
case is_subscribed(Presences, JID, from) of
465 true ->
466
:-(
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 377 ?LOG_DEBUG(#{what => resend_offline_messages, acc => Acc, c2s_state => StateData}),
475 377 Jid = mongoose_c2s:get_jid(StateData),
476 377 Acc1 = mongoose_hooks:resend_offline_messages_hook(Acc, Jid),
477 377 Rs = mongoose_acc:get(offline, messages, [], Acc1),
478 377 Acc2 = lists:foldl(fun({route, FromJid, ToJid, MsgAcc}, A) ->
479
:-(
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 377 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
:-(
Packet = mongoose_acc:element(Acc),
488
:-(
NewAcc = strip_c2s_fields(Acc),
489
:-(
ejabberd_router:route(FromJid, To, NewAcc, Packet),
490
:-(
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
:-(
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 377 broadcast_probe(Acc0, FromJid, Presences),
502 377 Ss = maps:fold(fun(JID, Sub, S) ->
503 377 notify_available_to_subscribers(Acc0, FromJid, Packet, JID, Sub, S)
504 end, Presences#presences_state.available, Presences#presences_state.subscriptions),
505 377 NewPresences = Presences#presences_state{available = Ss},
506 377 Accs = create_route_accs(Acc0, FromJid, Pending),
507 377 ToAcc = [{route, Accs}, {state_mod, {?MODULE, NewPresences}}],
508 377 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 377 ejabberd_router:route(FromJid, JID, Acc0, Packet),
515 377 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 377 Probe = presence_probe(),
525 377 Fun = fun(ToJid, Sub) when both =:= Sub; to =:= Sub ->
526 377 ejabberd_router:route(FromJid, ToJid, Acc, Probe);
527 (_, _) ->
528
:-(
ok
529 end,
530 377 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 377 [ mongoose_acc:update_stanza(#{to_jid => To, element => P}, Acc0) || P <- List ].
535
536 -spec presence_probe() -> exml:element().
537 presence_probe() ->
538 377 #xmlel{name = <<"presence">>,
539 attrs = [{<<"type">>, <<"probe">>}]}.
540
541 -spec presence_unavailable_stanza() -> exml:element().
542 presence_unavailable_stanza() ->
543 1 presence_unavailable_stanza(<<>>).
544
545 -spec presence_unavailable_stanza(binary()) -> exml:element().
546 presence_unavailable_stanza(<<>>) ->
547 1 #xmlel{name = <<"presence">>,
548 attrs = [{<<"type">>, <<"unavailable">>}]};
549 presence_unavailable_stanza(Status) ->
550 377 StatusEl = #xmlel{name = <<"status">>,
551 children = [#xmlcdata{content = Status}]},
552 377 #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 364 <<"Shutdown by reason: ", (atom_to_binary(Reason))/binary>>;
564 close_session_status({shutdown, Reason}) when is_binary(Reason) ->
565 13 <<"Shutdown by reason: ", Reason/binary>>;
566 close_session_status(_) ->
567
:-(
<<"Unknown condition">>.
568
569 -spec get_presence_type(mongoose_acc:t()) -> maybe_presence().
570 get_presence_type(Acc) ->
571 1606 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 1076 undefined -> available;
576
:-(
<<"available">> -> available;
577 8 <<"error">> -> error;
578 385 <<"probe">> -> probe;
579 32 <<"subscribe">> -> subscribe;
580 32 <<"subscribed">> -> subscribed;
581 1 <<"unsubscribe">> -> unsubscribe;
582 1 <<"unsubscribed">> -> unsubscribed;
583 71 <<"unavailable">> -> unavailable;
584
:-(
_ -> {error, invalid_presence}
585 end.
586
587 -spec maybe_get_handler(mongoose_c2s:data()) -> state().
588 maybe_get_handler(StateData) ->
589 479 case mongoose_c2s:get_mod_state(StateData, ?MODULE) of
590 102 {ok, #presences_state{} = Presences} -> Presences;
591 377 {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 1702 case mongoose_c2s:get_mod_state(StateData, ?MODULE) of
597 1538 {ok, Presence} -> Presence;
598 164 Error -> Error
599 end.
600
601 -spec get_priority_from_presence(exml:element()) -> priority().
602 get_priority_from_presence(PresencePacket) ->
603 385 MaybePriority = exml_query:path(PresencePacket, [{element, <<"priority">>}, cdata], undefined),
604 385 case catch binary_to_integer(MaybePriority) of
605
:-(
P when is_integer(P), -128 =< P, P =< 127 -> P;
606 385 _ -> 0
607 end.
608
609 -spec get_old_priority(state()) -> maybe_priority().
610 get_old_priority(Presences) ->
611 385 case Presences#presences_state.pres_last of
612 377 undefined -> undefined;
613 8 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 377 Acc1 = mongoose_hooks:roster_get_subscription_lists(HostType, Acc0, Jid),
620 377 {Fs0, Ts0, Pending} = mongoose_acc:get(roster, subscription_lists, {[], [], []}, Acc1),
621 377 BareJid = jid:to_bare(Jid),
622 377 Fs = maps:from_list([{jid:make(BJ), from} || BJ <- Fs0]),
623 377 Ts = maps:from_list([{jid:make(BJ), to} || BJ <- Ts0]),
624 377 Subs = maps:merge_with(fun(_, _, _) -> both end, Fs, Ts),
625 377 {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 377 Sid = mongoose_c2s:get_sid(StateData),
633 377 Jid = mongoose_c2s:get_jid(StateData),
634 377 Info = mongoose_c2s:get_info(StateData),
635 377 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 46 is_subscribed(S, LJID, to)
640 46 orelse (LJID /= LBareJID)
641 46 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 990 is_available(Presences, FromJid)
646 904 orelse (FromJid /= BareJid)
647 904 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 118 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 118 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 118 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 431 is_subscribed(Presences, FromJid, from)
668 431 orelse (FromJid /= BareFromJid)
669 431 andalso is_subscribed(Presences, BareFromJid, from).
670
671 -spec specifically_visible_to(jid:jid(), state()) -> boolean().
672 specifically_visible_to(FromJid, Presences) ->
673 385 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 1952 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 1633 Sub = maps:get(Jid, Subs, '$imposible_status'),
683 1633 both =:= Sub orelse DesiredSub =:= Sub.
684
685 -spec get_by_sub(state(), subscription()) -> subscriptions().
686 get_by_sub(#presences_state{subscriptions = Subs}, DesiredStatus) ->
687
:-(
Filter = fun(_, Status) -> both =:= Status orelse DesiredStatus =:= Status end,
688
:-(
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
:-(
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
:-(
get(#presences_state{pres_last = Value}, last) -> Value;
701
:-(
get(#presences_state{pres_timestamp = Value}, timestamp) -> Value.
Line Hits Source