./ct_report/coverage/jingle_sip_callbacks.COVER.html

1 %% @doc
2 %% This module defines callbacks for nksip application
3 %% Exported functions whill be called when there is new message to MongooseIM
4 %% or when there is an response to a SIP INVITE sent from MongooseIM to a SIP Proxy
5 %% @author Michal Piotrowski <michal.piotrowski@erlang-solutions.com>
6 %%
7 %%==============================================================================
8 %% Copyright 2018 Erlang Solutions Ltd.
9 %%
10 %% Licensed under the Apache License, Version 2.0 (the "License");
11 %% you may not use this file except in compliance with the License.
12 %% You may obtain a copy of the License at
13 %%
14 %% http://www.apache.org/licenses/LICENSE-2.0
15 %%
16 %% Unless required by applicable law or agreed to in writing, software
17 %% distributed under the License is distributed on an "AS IS" BASIS,
18 %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19 %% See the License for the specific language governing permissions and
20 %% limitations under the License.
21 %%==============================================================================
22 -module(jingle_sip_callbacks).
23
24 -include("mongoose.hrl").
25 -include("jlib.hrl").
26 -include_lib("nksip/include/nksip.hrl").
27 -include_lib("nksip/include/nksip_call.hrl").
28
29 %% this is because nksip has wrong type specs
30 -dialyzer({nowarn_function, [sip_invite_unsafe/2,
31 sip_reinvite_unsafe/2,
32 sip_bye/2,
33 sip_cancel/3,
34 send_ringing_session_info/2,
35 invite_resp_callback/1
36 ]}).
37
38 %% SIP callbacks
39 -export([sip_invite/2]).
40 -export([sip_reinvite/2]).
41 -export([sip_info/2]).
42 -export([sip_bye/2]).
43 -export([sip_cancel/3]).
44 -export([sip_dialog_update/3]).
45 -export([invite_resp_callback/1]).
46
47 -ignore_xref([sip_bye/2, sip_cancel/3, sip_dialog_update/3, sip_info/2,
48 sip_invite/2, sip_reinvite/2]).
49
50 sip_invite(Req, Call) ->
51
:-(
try
52
:-(
sip_invite_unsafe(Req, Call)
53 catch Class:Reason:StackTrace ->
54
:-(
?LOG_WARNING(#{what => sip_invite_failed,
55 text => <<"Error parsing sip invite">>, sip_req => Req,
56
:-(
class => Class, reason => Reason, stacktrace => StackTrace}),
57
:-(
{error, request_not_parsable}
58 end.
59
60 sip_reinvite(Req, Call) ->
61
:-(
try
62
:-(
sip_reinvite_unsafe(Req, Call)
63 catch Class:Reason:StackTrace ->
64
:-(
?LOG_WARNING(#{what => sip_reinvite_failed,
65 text => <<"Error parsing sip reinvite">>, sip_req => Req,
66
:-(
class => Class, reason => Reason, stacktrace => StackTrace}),
67
:-(
{error, request_not_parsable}
68 end.
69
70
71 sip_invite_unsafe(Req, _Call) ->
72
:-(
{FromJID, FromBinary} = get_user_from_sip_msg(from, Req),
73
:-(
{ToJID, ToBinary} = get_user_from_sip_msg(to, Req),
74
75
:-(
case ejabberd_sm:is_offline(ToJID) of
76 false ->
77
:-(
translate_and_deliver_invite(Req, FromJID, FromBinary, ToJID, ToBinary);
78 _ ->
79
:-(
CallID = nksip_sipmsg:header(<<"call-id">>, Req),
80
:-(
?LOG_INFO(#{what => sip_invite, reply => temporarily_unavailable,
81 text => <<"Got SIP INVITE from NkSIP, but destination user is offline">>,
82 from_jid => FromBinary, to_jid => ToBinary,
83
:-(
call_id => CallID, sip_req => Req}),
84
:-(
{reply, temporarily_unavailable}
85 end.
86
87 translate_and_deliver_invite(Req, FromJID, FromBinary, ToJID, ToBinary) ->
88
:-(
CallID = nksip_sipmsg:header(<<"call-id">>, Req),
89
:-(
Body = nksip_sipmsg:meta(body, Req),
90
91
:-(
{ok, ReqID} = nksip_request:get_handle(Req),
92
93
:-(
{CodecMap, SDP} = nksip_sdp_util:extract_codec_map(Body),
94
95
:-(
OtherEls = sip_to_jingle:parse_sdp_attributes(SDP#sdp.attributes),
96
97
:-(
ContentEls = [sip_to_jingle:sdp_media_to_content_el(Media, CodecMap) || Media <- SDP#sdp.medias],
98
99
:-(
JingleEl = jingle_sip_helper:jingle_element(CallID, <<"session-initiate">>, ContentEls ++ OtherEls),
100
101
:-(
ok = mod_jingle_sip_backend:set_incoming_request(CallID, ReqID, FromJID, ToJID, JingleEl),
102
103
:-(
?LOG_INFO(#{what => sip_invite, text => <<"Got SIP INVITE from NkSIP">>,
104 from_jid => FromBinary, to_jid => ToBinary,
105
:-(
call_id => CallID, sip_req => Req}),
106
107
:-(
IQEl = jingle_sip_helper:jingle_iq(ToBinary, FromBinary, JingleEl),
108
:-(
Acc = mongoose_acc:new(#{ location => ?LOCATION,
109 lserver => FromJID#jid.lserver,
110 element => IQEl,
111 from_jid => FromJID,
112 to_jid => ToJID }),
113
:-(
maybe_route_to_all_sessions(FromJID, ToJID, Acc, IQEl),
114
115
:-(
{reply, ringing}.
116
117 sip_reinvite_unsafe(Req, _Call) ->
118
:-(
?LOG_INFO(#{what => sip_reinvite, sip_req => Req}),
119
:-(
{FromJID, FromBinary} = get_user_from_sip_msg(from, Req),
120
:-(
{ToJID, ToBinary} = get_user_from_sip_msg(to, Req),
121
122
:-(
CallID = nksip_sipmsg:header(<<"call-id">>, Req),
123
:-(
Body = nksip_sipmsg:meta(body, Req),
124
125
:-(
{CodecMap, SDP} = nksip_sdp_util:extract_codec_map(Body),
126
:-(
RemainingAttrs = SDP#sdp.attributes,
127
:-(
OtherEls = sip_to_jingle:parse_sdp_attributes(RemainingAttrs),
128
129
:-(
ContentEls = [sip_to_jingle:sdp_media_to_content_el(Media, CodecMap) || Media <- SDP#sdp.medias],
130
:-(
Name = get_action_name_from_sdp(RemainingAttrs, <<"transport-info">>),
131
:-(
JingleEl = jingle_sip_helper:jingle_element(CallID, Name, ContentEls ++ OtherEls),
132
133
:-(
?LOG_INFO(#{what => sip_reinvite, text => <<"Got SIP re-INVITE from NkSIP">>,
134 from_jid => FromBinary, to_jid => ToBinary,
135
:-(
call_id => CallID, sip_req => Req}),
136
137
:-(
IQEl = jingle_sip_helper:jingle_iq(ToBinary, FromBinary, JingleEl),
138
:-(
Acc = mongoose_acc:new(#{ location => ?LOCATION,
139 lserver => FromJID#jid.lserver,
140 element => IQEl,
141 from_jid => FromJID,
142 to_jid => ToJID }),
143
:-(
maybe_route_to_all_sessions(FromJID, ToJID, Acc, IQEl),
144
:-(
{reply, ok}.
145
146 get_action_name_from_sdp(Attrs, Default) ->
147
:-(
case lists:keyfind(<<"jingle-action">>, 1, Attrs) of
148 {_, [Name]} ->
149
:-(
Name;
150 _ ->
151
:-(
Default
152 end.
153
154
155 sip_info(Req, _Call) ->
156
:-(
?LOG_INFO(#{what => sip_info, sip_req => Req}),
157
:-(
noreply.
158
159 sip_bye(Req, _Call) ->
160
:-(
{FromJID, FromBinary} = get_user_from_sip_msg(from, Req),
161
:-(
{ToJID, ToBinary} = get_user_from_sip_msg(to, Req),
162
163
:-(
CallID = nksip_sipmsg:header(<<"call-id">>, Req),
164
:-(
ReasonEl = #xmlel{name = <<"reason">>,
165 children = [#xmlel{name = <<"success">>}]},
166
:-(
JingleEl = jingle_sip_helper:jingle_element(CallID, <<"session-terminate">>, [ReasonEl]),
167
:-(
IQEl = jingle_sip_helper:jingle_iq(ToBinary, FromBinary, JingleEl),
168
:-(
Acc = mongoose_acc:new(#{ location => ?LOCATION,
169 lserver => FromJID#jid.lserver,
170 element => IQEl,
171 from_jid => FromJID,
172 to_jid => ToJID }),
173
:-(
maybe_route_to_all_sessions(FromJID, ToJID, Acc, IQEl),
174
175
:-(
{reply, ok}.
176
177 sip_cancel(_InviteReq, Req, _Call) ->
178
:-(
{FromJID, FromBinary} = get_user_from_sip_msg(from, Req),
179
:-(
{ToJID, ToBinary} = get_user_from_sip_msg(to, Req),
180
181
:-(
CallID = nksip_sipmsg:header(<<"call-id">>, Req),
182
:-(
ReasonEl = #xmlel{name = <<"reason">>,
183 children = [#xmlel{name = <<"decline">>}]},
184
:-(
JingleEl = jingle_sip_helper:jingle_element(CallID, <<"session-terminate">>, [ReasonEl]),
185
:-(
IQEl = jingle_sip_helper:jingle_iq(ToBinary, FromBinary, JingleEl),
186
:-(
Acc = mongoose_acc:new(#{ location => ?LOCATION,
187 lserver => FromJID#jid.lserver,
188 element => IQEl,
189 from_jid => FromJID,
190 to_jid => ToJID }),
191
:-(
maybe_route_to_all_sessions(FromJID, ToJID, Acc, IQEl),
192
193
:-(
{reply, ok}.
194
195 sip_dialog_update(start, Dialog, Call) ->
196
:-(
{ok, DialogHandle} = nksip_dialog:get_handle(Dialog),
197
:-(
[Transaction | _] = Call#call.trans,
198
:-(
case Transaction#trans.class of
199 uas ->
200
:-(
{ok, CallID} = nksip_dialog:call_id(Dialog),
201
:-(
mod_jingle_sip_backend:set_incoming_handle(CallID, DialogHandle);
202
203 _ ->
204
:-(
ok
205 end,
206
:-(
noreply;
207 sip_dialog_update(_, _, _) ->
208
:-(
noreply.
209
210 %% @doc
211 %% This function is called for every response to the SIP INVITE
212 %% SIP response contains the same headers as request
213 %% That's why we need to switch `from' and `to' when preparing and routing Jingle
214 %% to the request originator
215 %% interpreted status codes:
216 %% * 180 and 183 - provisional respons - we can send `ringing' session-info
217 %% * 200 - the invite was accepted we can sent `session-accepted' stanza
218 %% * 487 - this is to confirm INVITE cancelation from the other side (no action in this case)
219 %% * 603 - used to decline the INVITE by the reciving side
220 %% * all error responses between 400 and 700 result in genering session-terminate reason
221 invite_resp_callback({resp, 180, SIPMsg, _Call}) ->
222
:-(
send_ringing_session_info(SIPMsg, 180);
223 invite_resp_callback({resp, 183, SIPMsg, _Call}) ->
224
:-(
send_ringing_session_info(SIPMsg, 183);
225 invite_resp_callback({resp, 200, SIPMsg, _Call}) ->
226
:-(
{ToJID, ToBinary} = get_user_from_sip_msg(from, SIPMsg),
227
:-(
{FromJID, FromBinary} = get_user_from_sip_msg(to, SIPMsg),
228
229
:-(
Body = nksip_sipmsg:meta(body, SIPMsg),
230
:-(
CallID = nksip_sipmsg:header(<<"call-id">>, SIPMsg),
231
:-(
{CodecMap, SDP} = nksip_sdp_util:extract_codec_map(Body),
232
:-(
OtherEls = sip_to_jingle:parse_sdp_attributes(SDP#sdp.attributes),
233
234
235
:-(
ContentEls = [sip_to_jingle:sdp_media_to_content_el(Media, CodecMap) || Media <- SDP#sdp.medias],
236
237
:-(
JingleEl = jingle_sip_helper:jingle_element(CallID, <<"session-accept">>, ContentEls ++ OtherEls),
238
:-(
IQEl = jingle_sip_helper:jingle_iq(ToBinary, FromBinary, JingleEl),
239
:-(
Acc = mongoose_acc:new(#{ location => ?LOCATION,
240 lserver => FromJID#jid.lserver,
241 element => IQEl,
242 from_jid => FromJID,
243 to_jid => ToJID }),
244
:-(
ok = mod_jingle_sip_backend:set_outgoing_accepted(CallID),
245
:-(
maybe_route_to_all_sessions(FromJID, ToJID, Acc, IQEl),
246
:-(
ok;
247 invite_resp_callback({resp, 487, _SIPMsg, _Call}) ->
248 %% this error response only confirms that that the transaction was canceled
249 %% the real `session-terminate` stanza is sent by `sip_cancel/3` callback
250
:-(
ok;
251 invite_resp_callback({resp, 486, SIPMsg, _Call}) ->
252
:-(
{ToJID, ToBinary} = get_user_from_sip_msg(from, SIPMsg),
253
:-(
{FromJID, FromBinary} = get_user_from_sip_msg(to, SIPMsg),
254
:-(
CallID = nksip_sipmsg:header(<<"call-id">>, SIPMsg),
255
256
:-(
ReasonEl = #xmlel{name = <<"reason">>,
257 children = [#xmlel{name = <<"decline">>}]},
258
:-(
JingleEl = jingle_sip_helper:jingle_element(CallID, <<"session-terminate">>, [ReasonEl]),
259
:-(
IQEl = jingle_sip_helper:jingle_iq(ToBinary, FromBinary, JingleEl),
260
:-(
Acc = mongoose_acc:new(#{ location => ?LOCATION,
261 lserver => FromJID#jid.lserver,
262 element => IQEl,
263 from_jid => FromJID,
264 to_jid => ToJID }),
265
:-(
maybe_route_to_all_sessions(FromJID, ToJID, Acc, IQEl),
266
:-(
ok;
267 invite_resp_callback({resp, ErrorCode, SIPMsg, _Call})
268 when ErrorCode >= 400, ErrorCode =< 700 ->
269
:-(
{ToJID, ToBinary} = get_user_from_sip_msg(from, SIPMsg),
270
:-(
{FromJID, FromBinary} = get_user_from_sip_msg(to, SIPMsg),
271
:-(
CallID = nksip_sipmsg:header(<<"call-id">>, SIPMsg),
272
273
:-(
ReasonEl = make_session_terminate_reason_el(ErrorCode, SIPMsg),
274
275
:-(
JingleEl = jingle_sip_helper:jingle_element(CallID, <<"session-terminate">>, [ReasonEl]),
276
:-(
IQEl = jingle_sip_helper:jingle_iq(ToBinary, FromBinary, JingleEl),
277
:-(
Acc = mongoose_acc:new(#{ location => ?LOCATION,
278 lserver => FromJID#jid.lserver,
279 element => IQEl,
280 from_jid => FromJID,
281 to_jid => ToJID }),
282
:-(
maybe_route_to_all_sessions(FromJID, ToJID, Acc, IQEl),
283
:-(
ok;
284 invite_resp_callback(Data) ->
285
:-(
?LOG_ERROR(#{what => sip_unknown_response, sip_data => Data}).
286
287 send_ringing_session_info(SIPMsg, ErrorCode) ->
288 %% SIP response contains the same headers as request
289 %% That's why we need to switch `from` and `to` when preparing Jingle packet
290
:-(
{ToJID, ToBinary} = get_user_from_sip_msg(from, SIPMsg),
291
:-(
{FromJID, FromBinary} = get_user_from_sip_msg(to, SIPMsg),
292
293
:-(
DialogHandle = nksip_sipmsg:meta(dialog_handle, SIPMsg),
294
:-(
{SrvId, DialogId, CallID} = nksip_dialog_lib:parse_handle(DialogHandle),
295
:-(
?LOG_INFO(#{what => sip_invite_resp_callback, error_code => ErrorCode,
296 call_id => CallID, sip_req => SIPMsg,
297 dialog_id => DialogId, server_id => SrvId,
298
:-(
from_jid => FromBinary, to_binary => ToBinary}),
299
300
:-(
mod_jingle_sip_backend:set_outgoing_handle(CallID, DialogHandle, FromJID, ToJID),
301
302
:-(
RingingEl = #xmlel{name = <<"ringing">>,
303 attrs = [{<<"xmlns">>, <<"urn:xmpp:jingle:apps:rtp:info:1">>}]},
304
:-(
JingleEl = jingle_sip_helper:jingle_element(CallID, <<"session-info">>, [RingingEl]),
305
:-(
IQEl = jingle_sip_helper:jingle_iq(ToBinary, FromBinary, JingleEl),
306
:-(
Acc = mongoose_acc:new(#{ location => ?LOCATION,
307 lserver => FromJID#jid.lserver,
308 element => IQEl,
309 from_jid => FromJID,
310 to_jid => ToJID }),
311
:-(
maybe_route_to_all_sessions(FromJID, ToJID, Acc, IQEl),
312
:-(
ok.
313
314 get_user_from_sip_msg(Field, SIPMsg) ->
315
:-(
URI = nksip_sipmsg:meta(Field, SIPMsg),
316
:-(
#uri{user = ToUserIn, domain = ToDomain, path = ToPath} = URI,
317
318
:-(
Resource = path_to_res(ToPath),
319
320
:-(
ToUser = jingle_sip_helper:maybe_rewrite_from_phone(ToDomain, ToUserIn),
321
322
:-(
ToJID = jid:make(ToUser, ToDomain, Resource),
323
:-(
{ToJID, jid:to_binary({ToUser, ToDomain, Resource})}.
324
325 path_to_res(<<"/", Rest/binary>>) ->
326
:-(
Rest;
327 path_to_res(Other) ->
328
:-(
Other.
329
330 make_session_terminate_reason_el(ErrorCode, #sipmsg{class = {resp, ErrorCode, Binary}}) ->
331
:-(
Reason = #xmlel{name = <<"general-error">>},
332
:-(
Details = #xmlel{name = <<"sip-error">>,
333 attrs = [{<<"code">>, integer_to_binary(ErrorCode)}],
334 children = [#xmlcdata{content = Binary}]},
335
:-(
#xmlel{name = <<"reason">>,
336 children = [Reason, Details]}.
337
338 maybe_route_to_all_sessions(From, To, Acc, Packet) ->
339
:-(
PResources = ejabberd_sm:get_user_present_resources(To),
340
:-(
lists:foreach(
341 fun({_, R}) ->
342
:-(
ejabberd_router:route(From, jid:replace_resource(To, R), Acc, Packet)
343 end, PResources).
344
Line Hits Source