./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 %% nksip specs do not fully cover case when they return a parsing error
51 -dialyzer({nowarn_function, [assert_sdp_record/2]}).
52
53 sip_invite(Req, Call) ->
54
:-(
try
55
:-(
sip_invite_unsafe(Req, Call)
56 catch Class:Reason:StackTrace ->
57
:-(
?LOG_WARNING(#{what => sip_invite_failed,
58 text => <<"Error parsing sip invite">>, sip_req => Req,
59
:-(
class => Class, reason => Reason, stacktrace => StackTrace}),
60
:-(
{error, request_not_parsable}
61 end.
62
63 sip_reinvite(Req, Call) ->
64
:-(
try
65
:-(
sip_reinvite_unsafe(Req, Call)
66 catch Class:Reason:StackTrace ->
67
:-(
?LOG_WARNING(#{what => sip_reinvite_failed,
68 text => <<"Error parsing sip reinvite">>, sip_req => Req,
69
:-(
class => Class, reason => Reason, stacktrace => StackTrace}),
70
:-(
{error, request_not_parsable}
71 end.
72
73
74 sip_invite_unsafe(Req, _Call) ->
75
:-(
{FromJID, FromBinary} = get_user_from_sip_msg(from, Req),
76
:-(
{ToJID, ToBinary} = get_user_from_sip_msg(to, Req),
77
78
:-(
case ejabberd_sm:is_offline(ToJID) of
79 false ->
80
:-(
translate_and_deliver_invite(Req, FromJID, FromBinary, ToJID, ToBinary);
81 _ ->
82
:-(
CallID = nksip_sipmsg:header(<<"call-id">>, Req),
83
:-(
?LOG_INFO(#{what => sip_invite, reply => temporarily_unavailable,
84 text => <<"Got SIP INVITE from NkSIP, but destination user is offline">>,
85 from_jid => FromBinary, to_jid => ToBinary,
86
:-(
call_id => CallID, sip_req => Req}),
87
:-(
{reply, temporarily_unavailable}
88 end.
89
90 translate_and_deliver_invite(Req, FromJID, FromBinary, ToJID, ToBinary) ->
91
:-(
CallID = nksip_sipmsg:header(<<"call-id">>, Req),
92
:-(
Body = nksip_sipmsg:meta(body, Req),
93
94
:-(
{ok, ReqID} = nksip_request:get_handle(Req),
95
96
:-(
{CodecMap, SDP} = nksip_sdp_util:extract_codec_map(Body),
97
:-(
assert_sdp_record(Body, SDP),
98
99
:-(
OtherEls = sip_to_jingle:parse_sdp_attributes(SDP#sdp.attributes),
100
101
:-(
ContentEls = [sip_to_jingle:sdp_media_to_content_el(Media, CodecMap) || Media <- SDP#sdp.medias],
102
103
:-(
JingleEl = jingle_sip_helper:jingle_element(CallID, <<"session-initiate">>, ContentEls ++ OtherEls),
104
105
:-(
ok = mod_jingle_sip_session:set_incoming_request(CallID, ReqID, FromJID, ToJID, JingleEl),
106
107
:-(
?LOG_INFO(#{what => sip_invite, text => <<"Got SIP INVITE from NkSIP">>,
108 from_jid => FromBinary, to_jid => ToBinary,
109
:-(
call_id => CallID, sip_req => Req}),
110
111
:-(
IQEl = jingle_sip_helper:jingle_iq(ToBinary, FromBinary, JingleEl),
112
:-(
Acc = mongoose_acc:new(#{ location => ?LOCATION,
113 lserver => FromJID#jid.lserver,
114 element => IQEl,
115 from_jid => FromJID,
116 to_jid => ToJID }),
117
:-(
maybe_route_to_all_sessions(FromJID, ToJID, Acc, IQEl),
118
119
:-(
{reply, ringing}.
120
121 sip_reinvite_unsafe(Req, _Call) ->
122
:-(
?LOG_INFO(#{what => sip_reinvite, sip_req => Req}),
123
:-(
{FromJID, FromBinary} = get_user_from_sip_msg(from, Req),
124
:-(
{ToJID, ToBinary} = get_user_from_sip_msg(to, Req),
125
126
:-(
CallID = nksip_sipmsg:header(<<"call-id">>, Req),
127
:-(
Body = nksip_sipmsg:meta(body, Req),
128
129
:-(
{CodecMap, SDP} = nksip_sdp_util:extract_codec_map(Body),
130
:-(
assert_sdp_record(Body, SDP),
131
:-(
RemainingAttrs = SDP#sdp.attributes,
132
:-(
OtherEls = sip_to_jingle:parse_sdp_attributes(RemainingAttrs),
133
134
:-(
ContentEls = [sip_to_jingle:sdp_media_to_content_el(Media, CodecMap) || Media <- SDP#sdp.medias],
135
:-(
Name = get_action_name_from_sdp(RemainingAttrs, <<"transport-info">>),
136
:-(
JingleEl = jingle_sip_helper:jingle_element(CallID, Name, ContentEls ++ OtherEls),
137
138
:-(
?LOG_INFO(#{what => sip_reinvite, text => <<"Got SIP re-INVITE from NkSIP">>,
139 from_jid => FromBinary, to_jid => ToBinary,
140
:-(
call_id => CallID, sip_req => Req}),
141
142
:-(
IQEl = jingle_sip_helper:jingle_iq(ToBinary, FromBinary, JingleEl),
143
:-(
Acc = mongoose_acc:new(#{ location => ?LOCATION,
144 lserver => FromJID#jid.lserver,
145 element => IQEl,
146 from_jid => FromJID,
147 to_jid => ToJID }),
148
:-(
maybe_route_to_all_sessions(FromJID, ToJID, Acc, IQEl),
149
:-(
{reply, ok}.
150
151 get_action_name_from_sdp(Attrs, Default) ->
152
:-(
case lists:keyfind(<<"jingle-action">>, 1, Attrs) of
153 {_, [Name]} ->
154
:-(
Name;
155 _ ->
156
:-(
Default
157 end.
158
159
160 sip_info(Req, _Call) ->
161
:-(
?LOG_INFO(#{what => sip_info, sip_req => Req}),
162
:-(
noreply.
163
164 sip_bye(Req, _Call) ->
165
:-(
{FromJID, FromBinary} = get_user_from_sip_msg(from, Req),
166
:-(
{ToJID, ToBinary} = get_user_from_sip_msg(to, Req),
167
168
:-(
CallID = nksip_sipmsg:header(<<"call-id">>, Req),
169
:-(
ReasonEl = #xmlel{name = <<"reason">>,
170 children = [#xmlel{name = <<"success">>}]},
171
:-(
JingleEl = jingle_sip_helper:jingle_element(CallID, <<"session-terminate">>, [ReasonEl]),
172
:-(
IQEl = jingle_sip_helper:jingle_iq(ToBinary, FromBinary, JingleEl),
173
:-(
Acc = mongoose_acc:new(#{ location => ?LOCATION,
174 lserver => FromJID#jid.lserver,
175 element => IQEl,
176 from_jid => FromJID,
177 to_jid => ToJID }),
178
:-(
maybe_route_to_all_sessions(FromJID, ToJID, Acc, IQEl),
179
:-(
ok = mod_jingle_sip_session:remove_session(CallID),
180
:-(
{reply, ok}.
181
182 sip_cancel(_InviteReq, Req, _Call) ->
183
:-(
{FromJID, FromBinary} = get_user_from_sip_msg(from, Req),
184
:-(
{ToJID, ToBinary} = get_user_from_sip_msg(to, Req),
185
186
:-(
CallID = nksip_sipmsg:header(<<"call-id">>, Req),
187
:-(
ReasonEl = #xmlel{name = <<"reason">>,
188 children = [#xmlel{name = <<"decline">>}]},
189
:-(
JingleEl = jingle_sip_helper:jingle_element(CallID, <<"session-terminate">>, [ReasonEl]),
190
:-(
IQEl = jingle_sip_helper:jingle_iq(ToBinary, FromBinary, JingleEl),
191
:-(
Acc = mongoose_acc:new(#{ location => ?LOCATION,
192 lserver => FromJID#jid.lserver,
193 element => IQEl,
194 from_jid => FromJID,
195 to_jid => ToJID }),
196
:-(
maybe_route_to_all_sessions(FromJID, ToJID, Acc, IQEl),
197
:-(
ok = mod_jingle_sip_session:remove_session(CallID),
198
:-(
{reply, ok}.
199
200 sip_dialog_update(start, Dialog, Call) ->
201
:-(
{ok, DialogHandle} = nksip_dialog:get_handle(Dialog),
202
:-(
[Transaction | _] = Call#call.trans,
203
:-(
case Transaction#trans.class of
204 uas ->
205
:-(
{ok, CallID} = nksip_dialog:call_id(Dialog),
206
:-(
mod_jingle_sip_session:set_incoming_handle(CallID, DialogHandle);
207
208 _ ->
209
:-(
ok
210 end,
211
:-(
noreply;
212 sip_dialog_update(_, _, _) ->
213
:-(
noreply.
214
215 %% @doc
216 %% This function is called for every response to the SIP INVITE
217 %% SIP response contains the same headers as request
218 %% That's why we need to switch `from' and `to' when preparing and routing Jingle
219 %% to the request originator
220 %% interpreted status codes:
221 %% * 180 and 183 - provisional respons - we can send `ringing' session-info
222 %% * 200 - the invite was accepted we can sent `session-accepted' stanza
223 %% * 487 - this is to confirm INVITE cancelation from the other side (no action in this case)
224 %% * 603 - used to decline the INVITE by the reciving side
225 %% * all error responses between 400 and 700 result in genering session-terminate reason
226 invite_resp_callback({resp, 180, SIPMsg, _Call}) ->
227
:-(
send_ringing_session_info(SIPMsg, 180);
228 invite_resp_callback({resp, 183, SIPMsg, _Call}) ->
229
:-(
send_ringing_session_info(SIPMsg, 183);
230 invite_resp_callback({resp, 200, SIPMsg, _Call}) ->
231
:-(
{ToJID, ToBinary} = get_user_from_sip_msg(from, SIPMsg),
232
:-(
{FromJID, FromBinary} = get_user_from_sip_msg(to, SIPMsg),
233
234
:-(
Body = nksip_sipmsg:meta(body, SIPMsg),
235
:-(
CallID = nksip_sipmsg:header(<<"call-id">>, SIPMsg),
236
:-(
{CodecMap, SDP} = nksip_sdp_util:extract_codec_map(Body),
237
:-(
assert_sdp_record(Body, SDP),
238
:-(
OtherEls = sip_to_jingle:parse_sdp_attributes(SDP#sdp.attributes),
239
240
241
:-(
ContentEls = [sip_to_jingle:sdp_media_to_content_el(Media, CodecMap) || Media <- SDP#sdp.medias],
242
243
:-(
JingleEl = jingle_sip_helper:jingle_element(CallID, <<"session-accept">>, ContentEls ++ OtherEls),
244
:-(
IQEl = jingle_sip_helper:jingle_iq(ToBinary, FromBinary, JingleEl),
245
:-(
Acc = mongoose_acc:new(#{ location => ?LOCATION,
246 lserver => FromJID#jid.lserver,
247 element => IQEl,
248 from_jid => FromJID,
249 to_jid => ToJID }),
250
:-(
ok = mod_jingle_sip_session:set_outgoing_accepted(CallID),
251
:-(
maybe_route_to_all_sessions(FromJID, ToJID, Acc, IQEl),
252
:-(
ok;
253 invite_resp_callback({resp, 487, _SIPMsg, _Call}) ->
254 %% this error response only confirms that that the transaction was canceled
255 %% the real `session-terminate` stanza is sent by `sip_cancel/3` callback
256
:-(
ok;
257 invite_resp_callback({resp, 486, SIPMsg, _Call}) ->
258
:-(
{ToJID, ToBinary} = get_user_from_sip_msg(from, SIPMsg),
259
:-(
{FromJID, FromBinary} = get_user_from_sip_msg(to, SIPMsg),
260
:-(
CallID = nksip_sipmsg:header(<<"call-id">>, SIPMsg),
261
262
:-(
ReasonEl = #xmlel{name = <<"reason">>,
263 children = [#xmlel{name = <<"decline">>}]},
264
:-(
JingleEl = jingle_sip_helper:jingle_element(CallID, <<"session-terminate">>, [ReasonEl]),
265
:-(
IQEl = jingle_sip_helper:jingle_iq(ToBinary, FromBinary, JingleEl),
266
:-(
Acc = mongoose_acc:new(#{ location => ?LOCATION,
267 lserver => FromJID#jid.lserver,
268 element => IQEl,
269 from_jid => FromJID,
270 to_jid => ToJID }),
271
:-(
maybe_route_to_all_sessions(FromJID, ToJID, Acc, IQEl),
272
:-(
ok;
273 invite_resp_callback({resp, ErrorCode, SIPMsg, _Call})
274 when ErrorCode >= 400, ErrorCode =< 700 ->
275
:-(
{ToJID, ToBinary} = get_user_from_sip_msg(from, SIPMsg),
276
:-(
{FromJID, FromBinary} = get_user_from_sip_msg(to, SIPMsg),
277
:-(
CallID = nksip_sipmsg:header(<<"call-id">>, SIPMsg),
278
279
:-(
ReasonEl = make_session_terminate_reason_el(ErrorCode, SIPMsg),
280
281
:-(
JingleEl = jingle_sip_helper:jingle_element(CallID, <<"session-terminate">>, [ReasonEl]),
282
:-(
IQEl = jingle_sip_helper:jingle_iq(ToBinary, FromBinary, JingleEl),
283
:-(
Acc = mongoose_acc:new(#{ location => ?LOCATION,
284 lserver => FromJID#jid.lserver,
285 element => IQEl,
286 from_jid => FromJID,
287 to_jid => ToJID }),
288
:-(
maybe_route_to_all_sessions(FromJID, ToJID, Acc, IQEl),
289
:-(
ok = mod_jingle_sip_session:remove_session(CallID),
290
:-(
ok;
291 invite_resp_callback(Data) ->
292
:-(
?LOG_ERROR(#{what => sip_unknown_response, sip_data => Data}).
293
294 send_ringing_session_info(SIPMsg, ErrorCode) ->
295 %% SIP response contains the same headers as request
296 %% That's why we need to switch `from` and `to` when preparing Jingle packet
297
:-(
{ToJID, ToBinary} = get_user_from_sip_msg(from, SIPMsg),
298
:-(
{FromJID, FromBinary} = get_user_from_sip_msg(to, SIPMsg),
299
300
:-(
DialogHandle = nksip_sipmsg:meta(dialog_handle, SIPMsg),
301
:-(
{SrvId, DialogId, CallID} = nksip_dialog_lib:parse_handle(DialogHandle),
302
:-(
?LOG_INFO(#{what => sip_invite_resp_callback, error_code => ErrorCode,
303 call_id => CallID, sip_req => SIPMsg,
304 dialog_id => DialogId, server_id => SrvId,
305
:-(
from_jid => FromBinary, to_binary => ToBinary}),
306
307
:-(
mod_jingle_sip_session:set_outgoing_handle(CallID, DialogHandle, FromJID, ToJID),
308
309
:-(
RingingEl = #xmlel{name = <<"ringing">>,
310 attrs = [{<<"xmlns">>, <<"urn:xmpp:jingle:apps:rtp:info:1">>}]},
311
:-(
JingleEl = jingle_sip_helper:jingle_element(CallID, <<"session-info">>, [RingingEl]),
312
:-(
IQEl = jingle_sip_helper:jingle_iq(ToBinary, FromBinary, JingleEl),
313
:-(
Acc = mongoose_acc:new(#{ location => ?LOCATION,
314 lserver => FromJID#jid.lserver,
315 element => IQEl,
316 from_jid => FromJID,
317 to_jid => ToJID }),
318
:-(
maybe_route_to_all_sessions(FromJID, ToJID, Acc, IQEl),
319
:-(
ok.
320
321 get_user_from_sip_msg(Field, SIPMsg) ->
322
:-(
URI = nksip_sipmsg:meta(Field, SIPMsg),
323
:-(
#uri{user = ToUserIn, domain = ToDomain, path = ToPath} = URI,
324
325
:-(
Resource = path_to_res(ToPath),
326
327
:-(
ToUser = jingle_sip_helper:maybe_rewrite_from_phone(ToDomain, ToUserIn),
328
329
:-(
ToJID = jid:make(ToUser, ToDomain, Resource),
330
:-(
{ToJID, jid:to_binary({ToUser, ToDomain, Resource})}.
331
332 path_to_res(<<"/", Rest/binary>>) ->
333
:-(
Rest;
334 path_to_res(Other) ->
335
:-(
Other.
336
337 make_session_terminate_reason_el(ErrorCode, #sipmsg{class = {resp, ErrorCode, Binary}}) ->
338
:-(
Reason = #xmlel{name = <<"general-error">>},
339
:-(
Details = #xmlel{name = <<"sip-error">>,
340 attrs = [{<<"code">>, integer_to_binary(ErrorCode)}],
341 children = [#xmlcdata{content = Binary}]},
342
:-(
#xmlel{name = <<"reason">>,
343 children = [Reason, Details]}.
344
345 maybe_route_to_all_sessions(From, To, Acc, Packet) ->
346
:-(
PResources = ejabberd_sm:get_user_present_resources(To),
347
:-(
lists:foreach(
348 fun({_, R}) ->
349
:-(
ejabberd_router:route(From, jid:replace_resource(To, R), Acc, Packet)
350 end, PResources).
351
352
353 assert_sdp_record(_Body, #sdp{}) ->
354
:-(
ok;
355 assert_sdp_record(Body, SDP) ->
356
:-(
error({assert_sdp_record, Body, SDP}).
Line Hits Source