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