1 |
|
%% @doc Enables Jingle to SIP and SIP to Jingle translator. |
2 |
|
%% @author Michal Piotrowski <michal.piotrowski@erlang-solutions.com> |
3 |
|
%% |
4 |
|
%%============================================================================== |
5 |
|
%% Copyright 2018 Erlang Solutions Ltd. |
6 |
|
%% |
7 |
|
%% Licensed under the Apache License, Version 2.0 (the "License"); |
8 |
|
%% you may not use this file except in compliance with the License. |
9 |
|
%% You may obtain a copy of the License at |
10 |
|
%% |
11 |
|
%% http://www.apache.org/licenses/LICENSE-2.0 |
12 |
|
%% |
13 |
|
%% Unless required by applicable law or agreed to in writing, software |
14 |
|
%% distributed under the License is distributed on an "AS IS" BASIS, |
15 |
|
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
16 |
|
%% See the License for the specific language governing permissions and |
17 |
|
%% limitations under the License. |
18 |
|
%%============================================================================== |
19 |
|
-module(mod_jingle_sip). |
20 |
|
|
21 |
|
-behaviour(gen_mod). |
22 |
|
-behaviour(mongoose_module_metrics). |
23 |
|
|
24 |
|
-include("jlib.hrl"). |
25 |
|
-include("mongoose.hrl"). |
26 |
|
-include("mongoose_config_spec.hrl"). |
27 |
|
-include_lib("nklib/include/nklib.hrl"). |
28 |
|
-include_lib("nksip/include/nksip.hrl"). |
29 |
|
-include_lib("nksip/include/nksip_call.hrl"). |
30 |
|
|
31 |
|
-define(SERVICE, "mim_sip"). |
32 |
|
|
33 |
|
%% gen_mod callbacks |
34 |
|
-export([start/2, stop/1, config_spec/0]). |
35 |
|
|
36 |
|
-export([intercept_jingle_stanza/2]). |
37 |
|
|
38 |
|
-export([content_to_nksip_media/1]). |
39 |
|
|
40 |
|
-ignore_xref([content_to_nksip_media/1, intercept_jingle_stanza/2]). |
41 |
|
|
42 |
|
%% this is because nksip has wrong type specs |
43 |
|
-dialyzer({nowarn_function, [jingle_content_to_media/1, |
44 |
|
content_to_nksip_media/1]}). |
45 |
|
|
46 |
|
%%-------------------------------------------------------------------- |
47 |
|
%% gen_mod callbacks |
48 |
|
%%-------------------------------------------------------------------- |
49 |
|
%% |
50 |
|
|
51 |
|
-spec start(jid:server(), gen_mod:module_opts()) -> ok. |
52 |
|
start(Host, Opts) -> |
53 |
2 |
start_nksip_service_or_error(Opts), |
54 |
2 |
mod_jingle_sip_backend:init(Host, Opts), |
55 |
2 |
ejabberd_hooks:add(hooks(Host)), |
56 |
2 |
ok. |
57 |
|
|
58 |
|
start_nksip_service_or_error(Opts = #{listen_port := ListenPort}) -> |
59 |
2 |
{ok, _} = application:ensure_all_started(nksip), |
60 |
2 |
NkSipBasicOpts = #{sip_listen => "sip:all:" ++ integer_to_list(ListenPort), |
61 |
|
callback => jingle_sip_callbacks, |
62 |
|
plugins => [nksip_outbound, nksip_100rel]}, |
63 |
2 |
NkSipOpts = maybe_add_udp_max_size(NkSipBasicOpts, Opts), |
64 |
2 |
case nksip:start(?SERVICE, NkSipOpts) of |
65 |
|
{ok, _SrvID} -> |
66 |
2 |
ok; |
67 |
|
{error, already_started} -> |
68 |
:-( |
ok; |
69 |
|
Other -> |
70 |
:-( |
erlang:error(Other) |
71 |
|
end. |
72 |
|
|
73 |
|
maybe_add_udp_max_size(NkSipOpts, Opts) -> |
74 |
2 |
case gen_mod:get_opt(udp_max_size, Opts, undefined) of |
75 |
|
undefined -> |
76 |
2 |
NkSipOpts; |
77 |
|
Size -> |
78 |
:-( |
NkSipOpts#{sip_udp_max_size => Size} |
79 |
|
end. |
80 |
|
|
81 |
|
-spec stop(jid:server()) -> ok. |
82 |
|
stop(Host) -> |
83 |
2 |
ejabberd_hooks:delete(hooks(Host)), |
84 |
2 |
ok. |
85 |
|
|
86 |
|
-spec config_spec() -> mongoose_config_spec:config_section(). |
87 |
|
config_spec() -> |
88 |
166 |
#section{ |
89 |
|
items = #{<<"proxy_host">> => #option{type = string, |
90 |
|
validate = network_address}, |
91 |
|
<<"proxy_port">> => #option{type = integer, |
92 |
|
validate = port}, |
93 |
|
<<"listen_port">> => #option{type = integer, |
94 |
|
validate = port}, |
95 |
|
<<"local_host">> => #option{type = string, |
96 |
|
validate = network_address}, |
97 |
|
<<"sdp_origin">> => #option{type = string, |
98 |
|
validate = ip_address}, |
99 |
|
<<"transport">> => #option{type = string, |
100 |
|
validate = {enum, ["udp", "tcp"]}}, |
101 |
|
<<"username_to_phone">> => #list{items = username_to_phone_spec()} |
102 |
|
}, |
103 |
|
defaults = #{<<"proxy_host">> => "localhost", |
104 |
|
<<"proxy_port">> => 5060, |
105 |
|
<<"listen_port">> => 5600, |
106 |
|
<<"local_host">> => "localhost", |
107 |
|
<<"sdp_origin">> => "127.0.0.1", |
108 |
|
<<"transport">> => "udp", |
109 |
|
<<"username_to_phone">> => []} |
110 |
|
}. |
111 |
|
|
112 |
|
username_to_phone_spec() -> |
113 |
166 |
#section{ |
114 |
|
items = #{<<"username">> => #option{type = binary}, |
115 |
|
<<"phone">> => #option{type = binary}}, |
116 |
|
required = all, |
117 |
|
process = fun process_u2p/1 |
118 |
|
}. |
119 |
|
|
120 |
|
process_u2p(#{username := U, phone := P}) -> |
121 |
:-( |
{U, P}. |
122 |
|
|
123 |
|
hooks(Host) -> |
124 |
4 |
[{c2s_preprocessing_hook, Host, ?MODULE, intercept_jingle_stanza, 75}]. |
125 |
|
|
126 |
|
intercept_jingle_stanza(Acc, _C2SState) -> |
127 |
81 |
case mongoose_acc:get(hook, result, undefined, Acc) of |
128 |
|
drop -> |
129 |
:-( |
Acc; |
130 |
|
_ -> |
131 |
81 |
maybe_iq_stanza(Acc) |
132 |
|
end. |
133 |
|
|
134 |
|
maybe_iq_stanza(Acc) -> |
135 |
81 |
case mongoose_acc:stanza_name(Acc) of |
136 |
|
<<"iq">> -> |
137 |
39 |
maybe_iq_to_other_user(Acc); |
138 |
|
_ -> |
139 |
42 |
Acc |
140 |
|
end. |
141 |
|
|
142 |
|
maybe_iq_to_other_user(Acc) -> |
143 |
39 |
#jid{luser = StanzaTo} = mongoose_acc:to_jid(Acc), |
144 |
39 |
#jid{luser = LUser} = mongoose_acc:get(c2s, origin_jid, Acc), |
145 |
39 |
case LUser of |
146 |
|
StanzaTo -> |
147 |
1 |
QueryInfo = jlib:iq_query_info(mongoose_acc:element(Acc)), |
148 |
1 |
maybe_jingle_get_stanza_to_self(QueryInfo, Acc); |
149 |
|
_ -> |
150 |
38 |
QueryInfo = jlib:iq_query_info(mongoose_acc:element(Acc)), |
151 |
38 |
maybe_jingle_stanza(QueryInfo, Acc) |
152 |
|
end. |
153 |
|
|
154 |
|
maybe_jingle_stanza(#iq{xmlns = ?JINGLE_NS, sub_el = Jingle, type = set} = IQ, Acc) -> |
155 |
38 |
JingleAction = exml_query:attr(Jingle, <<"action">>), |
156 |
38 |
From = mongoose_acc:from_jid(Acc), |
157 |
38 |
To = mongoose_acc:to_jid(Acc), |
158 |
38 |
maybe_translate_to_sip(JingleAction, From, To, IQ, Acc); |
159 |
|
maybe_jingle_stanza(_, Acc) -> |
160 |
:-( |
Acc. |
161 |
|
|
162 |
|
maybe_jingle_get_stanza_to_self(#iq{xmlns = ?JINGLE_NS, sub_el = Jingle, type = get} = IQ, Acc) -> |
163 |
1 |
JingleAction = exml_query:attr(Jingle, <<"action">>), |
164 |
1 |
case JingleAction of |
165 |
|
<<"existing-session-initiate">> -> |
166 |
1 |
resend_session_initiate(IQ, Acc), |
167 |
1 |
mongoose_acc:set(hook, result, drop, Acc); |
168 |
|
_ -> |
169 |
:-( |
Acc |
170 |
|
end; |
171 |
|
maybe_jingle_get_stanza_to_self(_, Acc) -> |
172 |
:-( |
Acc. |
173 |
|
|
174 |
|
maybe_translate_to_sip(JingleAction, From, To, IQ, Acc) |
175 |
|
when JingleAction =:= <<"session-initiate">>; |
176 |
|
JingleAction =:= <<"session-accept">>; |
177 |
|
JingleAction =:= <<"session-terminate">>; |
178 |
|
JingleAction =:= <<"source-remove">>; |
179 |
|
JingleAction =:= <<"source-add">>; |
180 |
|
JingleAction =:= <<"source-update">>; |
181 |
|
JingleAction =:= <<"transport-info">> -> |
182 |
38 |
#iq{sub_el = Jingle} = IQ, |
183 |
38 |
try |
184 |
38 |
Result = translate_to_sip(JingleAction, Jingle, Acc), |
185 |
38 |
route_result(Result, From, To, IQ) |
186 |
|
catch Class:Error:StackTrace -> |
187 |
:-( |
ejabberd_router:route_error_reply(To, From, Acc, mongoose_xmpp_errors:internal_server_error()), |
188 |
:-( |
?LOG_ERROR(#{what => sip_translate_failed, acc => Acc, |
189 |
:-( |
class => Class, reason => Error, stacktrace => StackTrace}) |
190 |
|
end, |
191 |
38 |
mongoose_acc:set(hook, result, drop, Acc); |
192 |
|
maybe_translate_to_sip(JingleAction, _, _, _, Acc) -> |
193 |
:-( |
?LOG_WARNING(#{what => sip_unknown_action, |
194 |
|
text => <<"Forwarding unknown action to SIP">>, |
195 |
:-( |
jingle_action => JingleAction, acc => Acc}), |
196 |
:-( |
Acc. |
197 |
|
|
198 |
|
route_result(ok, From, To, IQ) -> |
199 |
12 |
route_ok_result(From, To, IQ); |
200 |
|
route_result({ok, _}, From, To, IQ) -> |
201 |
18 |
route_ok_result(From, To, IQ); |
202 |
|
route_result({ok, _, _}, From, To, IQ) -> |
203 |
6 |
route_ok_result(From, To, IQ); |
204 |
|
route_result({error, item_not_found}, From, To, IQ) -> |
205 |
2 |
Error = mongoose_xmpp_errors:item_not_found(), |
206 |
2 |
route_error_reply(From, To, IQ, Error); |
207 |
|
route_result(Other, From, To, IQ) -> |
208 |
:-( |
?LOG_WARNING(#{what => sip_unknown_result, reason => Other, iq => IQ}), |
209 |
:-( |
Error = mongoose_xmpp_errors:internal_server_error(), |
210 |
:-( |
route_error_reply(From, To, IQ, Error). |
211 |
|
|
212 |
|
route_error_reply(From, To, IQ, Error) -> |
213 |
2 |
IQResult = IQ#iq{type = error, sub_el = [Error]}, |
214 |
2 |
Packet = jlib:replace_from_to(From, To, jlib:iq_to_xml(IQResult)), |
215 |
2 |
ejabberd_router:route(To, From, Packet). |
216 |
|
|
217 |
|
route_ok_result(From, To, IQ) -> |
218 |
36 |
IQResult = IQ#iq{type = result, sub_el = []}, |
219 |
36 |
Packet = jlib:replace_from_to(From, To, jlib:iq_to_xml(IQResult)), |
220 |
36 |
ejabberd_router:route(To, From, Packet). |
221 |
|
|
222 |
|
resend_session_initiate(#iq{sub_el = Jingle} = IQ, Acc) -> |
223 |
1 |
From = mongoose_acc:from_jid(Acc), |
224 |
1 |
To = mongoose_acc:to_jid(Acc), |
225 |
1 |
SID = exml_query:attr(Jingle, <<"sid">>), |
226 |
1 |
case mod_jingle_sip_backend:get_session_info(SID, From) of |
227 |
|
{ok, Session} -> |
228 |
1 |
maybe_resend_session_initiate(From, To, IQ, Acc, Session); |
229 |
|
_ -> |
230 |
:-( |
ejabberd_router:route_error_reply(To, From, Acc, mongoose_xmpp_errors:item_not_found()) |
231 |
|
end. |
232 |
|
|
233 |
|
translate_to_sip(<<"session-initiate">>, Jingle, Acc) -> |
234 |
18 |
SID = exml_query:attr(Jingle, <<"sid">>), |
235 |
18 |
#jid{luser = ToUser} = ToJID = jingle_sip_helper:maybe_rewrite_to_phone(Acc), |
236 |
18 |
#jid{luser = FromUser} = FromJID = mongoose_acc:from_jid(Acc), |
237 |
18 |
From = jid:to_bare_binary(FromJID), |
238 |
18 |
To = jid:to_bare_binary(ToJID), |
239 |
18 |
LServer = mongoose_acc:lserver(Acc), |
240 |
18 |
SDP = prepare_initial_sdp(LServer, Jingle), |
241 |
18 |
ProxyURI = get_proxy_uri(LServer), |
242 |
18 |
RequestURI = list_to_binary(["sip:", ToUser, "@", ProxyURI]), |
243 |
18 |
ToHeader = <<ToUser/binary, " <sip:",To/binary, ">">>, |
244 |
18 |
LocalHost = gen_mod:get_module_opt(LServer, ?MODULE, local_host), |
245 |
|
|
246 |
18 |
{async, Handle} = nksip_uac:invite(?SERVICE, RequestURI, |
247 |
|
[%% Request options |
248 |
|
{to, ToHeader}, |
249 |
|
{from, <<FromUser/binary, " <sip:", From/binary, ">">>}, |
250 |
|
{call_id, SID}, |
251 |
|
{body, SDP}, |
252 |
|
{local_host, LocalHost}, |
253 |
|
auto_2xx_ack, |
254 |
|
%% Internal options |
255 |
|
async, |
256 |
|
{callback, fun jingle_sip_callbacks:invite_resp_callback/1}]), |
257 |
18 |
Result = mod_jingle_sip_backend:set_outgoing_request(SID, Handle, FromJID, ToJID), |
258 |
18 |
{_, SrvId, DialogId, _CallId} = nksip_sipmsg:parse_handle(Handle), |
259 |
18 |
?LOG_INFO(#{what => sip_session_start, |
260 |
|
text => <<"Start SIP session with set_outgoing_request call">>, |
261 |
|
jingle_action => 'session-initiate', result => Result, |
262 |
|
from_jid => From, to_jid => To, |
263 |
18 |
call_id => SID, server_id => SrvId, dialog_id => DialogId}), |
264 |
18 |
{ok, Handle}; |
265 |
|
translate_to_sip(<<"session-accept">>, Jingle, Acc) -> |
266 |
10 |
LServer = mongoose_acc:lserver(Acc), |
267 |
10 |
SID = exml_query:attr(Jingle, <<"sid">>), |
268 |
10 |
case mod_jingle_sip_backend:get_incoming_request(SID, mongoose_acc:get(c2s, origin_jid, Acc)) of |
269 |
|
{ok, ReqID} -> |
270 |
9 |
try_to_accept_session(ReqID, Jingle, Acc, LServer, SID); |
271 |
|
_ -> |
272 |
1 |
{error, item_not_found} |
273 |
|
end; |
274 |
|
translate_to_sip(<<"source-remove">> = Name, Jingle, Acc) -> |
275 |
1 |
translate_source_change_to_sip(Name, Jingle, Acc); |
276 |
|
translate_to_sip(<<"source-add">> = Name, Jingle, Acc) -> |
277 |
1 |
translate_source_change_to_sip(Name, Jingle, Acc); |
278 |
|
translate_to_sip(<<"source-update">> = Name, Jingle, Acc) -> |
279 |
1 |
translate_source_change_to_sip(Name, Jingle, Acc); |
280 |
|
translate_to_sip(<<"transport-info">>, Jingle, Acc) -> |
281 |
:-( |
SID = exml_query:attr(Jingle, <<"sid">>), |
282 |
:-( |
SDP = make_sdp_for_ice_candidate(Jingle), |
283 |
:-( |
case mod_jingle_sip_backend:get_outgoing_handle(SID, mongoose_acc:get(c2s, origin_jid, Acc)) of |
284 |
|
{ok, undefined} -> |
285 |
:-( |
?LOG_ERROR(#{what => sip_missing_dialog, sid => SID, acc => Acc}), |
286 |
:-( |
{error, item_not_found}; |
287 |
|
{ok, Handle} -> |
288 |
:-( |
nksip_uac:info(Handle, [{content_type, <<"application/sdp">>}, |
289 |
|
{body, SDP}]); |
290 |
|
_ -> |
291 |
:-( |
?LOG_ERROR(#{what => missing_sip_session, sid => SID, acc => Acc}), |
292 |
:-( |
{error, item_not_found} |
293 |
|
end; |
294 |
|
translate_to_sip(<<"session-terminate">>, Jingle, Acc) -> |
295 |
7 |
SID = exml_query:attr(Jingle, <<"sid">>), |
296 |
7 |
ToJID = jingle_sip_helper:maybe_rewrite_to_phone(Acc), |
297 |
7 |
From = mongoose_acc:get(c2s, origin_jid, Acc), |
298 |
7 |
FromLUS = jid:to_lus(From), |
299 |
7 |
ToLUS = jid:to_lus(ToJID), |
300 |
7 |
case mod_jingle_sip_backend:get_session_info(SID, From) of |
301 |
|
{ok, Session} -> |
302 |
6 |
try_to_terminate_the_session(FromLUS, ToLUS, Session); |
303 |
|
_ -> |
304 |
1 |
{error, item_not_found} |
305 |
|
end. |
306 |
|
|
307 |
|
translate_source_change_to_sip(ActionName, Jingle, Acc) -> |
308 |
3 |
SID = exml_query:attr(Jingle, <<"sid">>), |
309 |
3 |
LServer = mongoose_acc:lserver(Acc), |
310 |
3 |
#sdp{attributes = SDPAttrs} = RawSDP = prepare_initial_sdp(LServer, Jingle), |
311 |
|
|
312 |
3 |
SDPAttrsWithActionName = [{<<"jingle-action">>, [ActionName]} |
313 |
|
| SDPAttrs], |
314 |
3 |
SDP = RawSDP#sdp{attributes = SDPAttrsWithActionName}, |
315 |
|
|
316 |
3 |
case mod_jingle_sip_backend:get_outgoing_handle(SID, mongoose_acc:get(c2s, origin_jid, Acc)) of |
317 |
|
{ok, undefined} -> |
318 |
:-( |
?LOG_ERROR(#{what => sip_missing_dialod, sid => SID, acc => Acc}), |
319 |
:-( |
{error, item_not_found}; |
320 |
|
{ok, Handle} -> |
321 |
3 |
nksip_uac:invite(Handle, [auto_2xx_ack, {body, SDP}]); |
322 |
|
_ -> |
323 |
:-( |
?LOG_ERROR(#{what => sip_missing_session, sid => SID, acc => Acc}), |
324 |
:-( |
{error, item_not_found} |
325 |
|
end. |
326 |
|
|
327 |
|
try_to_terminate_the_session(FromLUS, ToLUS, Session) -> |
328 |
6 |
case maps:get(state, Session) of |
329 |
|
accepted -> |
330 |
3 |
DialogHandle = maps:get(dialog, Session), |
331 |
3 |
Node = maps:get(node, Session), |
332 |
3 |
nksip_uac_bye(Node, DialogHandle, [{to, make_user_header(ToLUS)}, |
333 |
|
{from, make_user_header(FromLUS)}]); |
334 |
|
_ -> |
335 |
3 |
RequestHandle = maps:get(request, Session), |
336 |
3 |
case maps:get(direction, Session) of |
337 |
|
out -> |
338 |
1 |
nksip_uac:cancel(RequestHandle, [{to, make_user_header(ToLUS)}, |
339 |
|
{from, make_user_header(FromLUS)}]); |
340 |
|
in -> |
341 |
|
%% When reject incoming invite we need to reply with an error code |
342 |
2 |
Node = maps:get(node, Session), |
343 |
2 |
nksip_request_reply(busy, {Node, RequestHandle}) |
344 |
|
end |
345 |
|
end. |
346 |
|
|
347 |
|
try_to_accept_session(ReqID, Jingle, Acc, Server, SID) -> |
348 |
9 |
SDP = prepare_initial_sdp(Server, Jingle), |
349 |
9 |
LocalHost = gen_mod:get_module_opt(Server, ?MODULE, local_host), |
350 |
9 |
case nksip_request_reply({ok, [{body, SDP}, {local_host, LocalHost}]}, ReqID) of |
351 |
|
ok -> |
352 |
9 |
ok = mod_jingle_sip_backend:set_incoming_accepted(SID), |
353 |
9 |
terminate_session_on_other_devices(SID, Acc), |
354 |
9 |
ok; |
355 |
|
Other -> |
356 |
:-( |
Other |
357 |
|
end. |
358 |
|
|
359 |
|
terminate_session_on_other_devices(SID, Acc) -> |
360 |
9 |
#jid{lresource = Res} = From = mongoose_acc:from_jid(Acc), |
361 |
9 |
FromBin = jid:to_binary(From), |
362 |
9 |
ReasonEl = #xmlel{name = <<"reason">>, |
363 |
|
children = [#xmlel{name = <<"cancel">>}]}, |
364 |
9 |
JingleEl = jingle_sip_helper:jingle_element(SID, <<"session-terminate">>, [ReasonEl]), |
365 |
9 |
PResources = ejabberd_sm:get_user_present_resources(From), |
366 |
9 |
lists:foreach( |
367 |
|
fun({_, R}) when R /= Res -> |
368 |
1 |
ToJID = jid:replace_resource(From, R), |
369 |
1 |
IQEl = jingle_sip_helper:jingle_iq(jid:to_binary(ToJID), FromBin, JingleEl), |
370 |
1 |
ejabberd_router:route(From, ToJID, Acc, IQEl); |
371 |
|
(_) -> |
372 |
9 |
skip |
373 |
|
end, PResources). |
374 |
|
|
375 |
|
make_user_header({User, _} = US) -> |
376 |
8 |
JIDBin = jid:to_binary(US), |
377 |
8 |
<<User/binary, " <sip:", JIDBin/binary, ">">>. |
378 |
|
|
379 |
|
|
380 |
|
get_proxy_uri(Server) -> |
381 |
18 |
Opts = gen_mod:get_module_opts(Server, ?MODULE), |
382 |
18 |
#{proxy_host := ProxyHost, proxy_port := ProxyPort, transport := Transport} = Opts, |
383 |
18 |
PortStr = integer_to_list(ProxyPort), |
384 |
18 |
[ProxyHost, ":", PortStr, ";transport=", Transport]. |
385 |
|
|
386 |
|
make_sdp_for_ice_candidate(#xmlel{children = Children}) -> |
387 |
:-( |
Content = parse_content(Children, []), |
388 |
:-( |
list_to_binary(Content). |
389 |
|
|
390 |
|
parse_content([], Acc) -> |
391 |
:-( |
Acc; |
392 |
|
parse_content([#xmlel{name = <<"content">>} = Content | Rest], Acc) -> |
393 |
:-( |
SDPCandidate = parse_content(Content), |
394 |
:-( |
NewAcc = [SDPCandidate | Acc], |
395 |
:-( |
parse_content(Rest, NewAcc). |
396 |
|
|
397 |
|
parse_content(Content) -> |
398 |
:-( |
Name = exml_query:attr(Content, <<"name">>), |
399 |
:-( |
MediaLine = [<<"m=">>, Name, <<"9 RTP/AVP 0">>, <<"\r\n">>, |
400 |
|
<<"a=mid:1\r\n">>], |
401 |
:-( |
Transport = jingle_to_sdp:parse_transport_element(exml_query:subelement(Content, <<"transport">>)), |
402 |
:-( |
IceParamsLine = [<<"a=ice-pwd:">>, maps:get(pwd, Transport), <<"\r\n">>, |
403 |
|
<<"a=ice-ufrag:">>, maps:get(ufrag, Transport), <<"\r\n">>], |
404 |
:-( |
Candidates = [candidate_to_sdp_line(Candidate) || Candidate <- maps:get(candidates, Transport)], |
405 |
:-( |
[IceParamsLine, MediaLine, Candidates]. |
406 |
|
|
407 |
|
candidate_to_sdp_line(Candidate) -> |
408 |
:-( |
OptionalArgs = [generation, tcptype, raddr, rport], |
409 |
:-( |
ExtraArgs = [map_field_to_candidate_arg(Field, Candidate) || Field <- OptionalArgs], |
410 |
:-( |
[<<"a=candidate: ">>, |
411 |
|
maps:get(foundation, Candidate), " ", |
412 |
|
maps:get(component, Candidate), " ", |
413 |
|
maps:get(protocol, Candidate), " ", |
414 |
|
maps:get(priority, Candidate), " ", |
415 |
|
maps:get(ip, Candidate), " ", |
416 |
|
maps:get(port, Candidate), " ", |
417 |
|
"typ ", maps:get(type, Candidate), |
418 |
|
ExtraArgs, |
419 |
|
<<"\r\n">>]. |
420 |
|
|
421 |
|
map_field_to_candidate_arg(Field, Map) -> |
422 |
:-( |
case maps:find(Field, Map) of |
423 |
|
{ok, Value} -> |
424 |
:-( |
[" ", atom_to_binary(Field, utf8), " ", Value]; |
425 |
|
_ -> |
426 |
:-( |
[] |
427 |
|
end. |
428 |
|
|
429 |
|
prepare_initial_sdp(Server, Jingle) -> |
430 |
30 |
Medias = [jingle_content_to_media(Content) || |
431 |
30 |
Content <- exml_query:subelements(Jingle, <<"content">>)], |
432 |
|
|
433 |
30 |
GroupingAttrs = add_group_attr_from_jingle(Jingle, []), |
434 |
|
|
435 |
30 |
OriginAddress = gen_mod:get_module_opt(Server, ?MODULE, sdp_origin), |
436 |
30 |
SDP = nksip_sdp:new(OriginAddress, []), |
437 |
30 |
SDP#sdp{medias = Medias, |
438 |
|
attributes = GroupingAttrs}. |
439 |
|
|
440 |
|
jingle_content_to_media(ContentEl) -> |
441 |
60 |
Content = jingle_to_sdp:from_media(ContentEl), |
442 |
60 |
content_to_nksip_media(Content). |
443 |
|
|
444 |
|
content_to_nksip_media(Content) -> |
445 |
60 |
{FMTS, DescAttrs} = description_to_nksip_attrs(maps:get(description, Content)), |
446 |
60 |
TransportAttrs = transport_to_nksip_attrs(maps:get(transport, Content), DescAttrs), |
447 |
60 |
AttrsWithMediaName = add_media_name_attr(maps:get(name, Content), TransportAttrs), |
448 |
60 |
AttrsWithSender = add_sender_attr(maps:get(senders, Content), AttrsWithMediaName), |
449 |
|
|
450 |
60 |
#sdp_m{media = maps:get(media, Content), |
451 |
|
port = 1024 + rand:uniform(1024), |
452 |
|
proto = maps:get(protocol, Content), |
453 |
|
fmt = FMTS, |
454 |
|
attributes = AttrsWithSender}. |
455 |
|
|
456 |
|
description_to_nksip_attrs(Desc) -> |
457 |
60 |
AttrsWithSSRC = sources_to_nksip_attrs(maps:get(sources, Desc)), |
458 |
60 |
{FMTS, CodecAttrs} = codecs_to_nksip_attrs(maps:get(codecs, Desc), AttrsWithSSRC), |
459 |
60 |
AttrsWithRTPHeaderExt = add_rtp_header_ext(maps:get(rtphdr_ext, Desc), CodecAttrs), |
460 |
60 |
AttrsWithRTCPMUX = add_rtcp_mux(Desc, AttrsWithRTPHeaderExt), |
461 |
|
|
462 |
60 |
{FMTS, AttrsWithRTCPMUX}. |
463 |
|
|
464 |
|
sources_to_nksip_attrs(Sources) -> |
465 |
60 |
lists:flatmap(fun source_to_nksip_attr/1, Sources). |
466 |
|
|
467 |
|
source_to_nksip_attr({ID, KeyValues}) -> |
468 |
33 |
[{<<"ssrc">>, [ID, source_to_nksip_value(Name, Value)]} || {Name, Value} <- KeyValues]. |
469 |
|
|
470 |
|
source_to_nksip_value(Name, Value) -> |
471 |
63 |
<<Name/binary, $:, Value/binary>>. |
472 |
|
|
473 |
|
codecs_to_nksip_attrs(Codecs, Attrs) -> |
474 |
60 |
codecs_to_nksip_attrs2(Codecs, {[], Attrs}). |
475 |
|
|
476 |
|
codecs_to_nksip_attrs2([], Acc) -> |
477 |
60 |
Acc; |
478 |
|
codecs_to_nksip_attrs2([Codec | Rest], {FMTS, Attrs}) -> |
479 |
540 |
ID = maps:get(id, Codec), |
480 |
540 |
NewFMTS = [ID | FMTS], |
481 |
540 |
RTCPFBAttrs = [rtcp_fb_param_to_attr(ID, Param) || Param <- maps:get(rtcp_fb_params, Codec)], |
482 |
540 |
AttrsWithFMTPParams = maybe_add_fmtp_attr(ID, maps:get(params, Codec), RTCPFBAttrs), |
483 |
540 |
RTPMapValue = prepare_rtp_map(maps:get(name, Codec), maps:get(clock_rate, Codec), maps:get(channels, Codec)), |
484 |
540 |
RTPAttr = {<<"rtpmap">>, [ID, RTPMapValue]}, |
485 |
540 |
CodecAttrs = [RTPAttr | AttrsWithFMTPParams], |
486 |
|
|
487 |
540 |
codecs_to_nksip_attrs2(Rest, {NewFMTS, lists:append(CodecAttrs, Attrs)}). |
488 |
|
|
489 |
|
rtcp_fb_param_to_attr(ID, RTCPParam) -> |
490 |
255 |
{<<"rtcp-fb">>, [ID | RTCPParam]}. |
491 |
|
|
492 |
|
maybe_add_fmtp_attr(_ID, [], RTCPParams) -> |
493 |
441 |
RTCPParams; |
494 |
|
maybe_add_fmtp_attr(ID, Params, RTCPParams) -> |
495 |
99 |
KV = [basic_parameter_to_fmtp_value(Param) || Param <- Params], |
496 |
99 |
Joined = lists:join($;, KV), |
497 |
99 |
FMTPValue = list_to_binary(Joined), |
498 |
99 |
[{<<"fmtp">>, [ID, FMTPValue]} | RTCPParams]. |
499 |
|
|
500 |
|
basic_parameter_to_fmtp_value({undefined, Value}) -> |
501 |
3 |
Value; |
502 |
|
basic_parameter_to_fmtp_value({Name, Value}) -> |
503 |
144 |
<<Name/binary, $=, Value/binary>>. |
504 |
|
|
505 |
|
prepare_rtp_map(Name, ClockRate, <<"1">>) -> |
506 |
510 |
<<Name/binary, $/, ClockRate/binary>>; |
507 |
|
prepare_rtp_map(Name, ClockRate, Channels) -> |
508 |
30 |
<<Name/binary, $/, ClockRate/binary, $/, Channels/binary>>. |
509 |
|
|
510 |
|
add_rtp_header_ext(RTPHdrExts, Attrs) -> |
511 |
60 |
RTPHdrextAttrs = [rtp_hdrext_to_sdp_attr(RTPHdrExt) || RTPHdrExt <- RTPHdrExts], |
512 |
60 |
lists:append(RTPHdrextAttrs, Attrs). |
513 |
|
|
514 |
|
rtp_hdrext_to_sdp_attr({ID, URI, Senders}) -> |
515 |
132 |
{<<"extmap">>, rtp_hdrext_value(ID, URI, Senders)}. |
516 |
|
|
517 |
|
rtp_hdrext_value(ID, URI, <<"sendrecv">>) -> |
518 |
132 |
[ID, URI]; |
519 |
|
rtp_hdrext_value(ID, URI, Sender) -> |
520 |
:-( |
[<<ID/binary, $/, Sender/binary>>, URI]. |
521 |
|
|
522 |
|
add_rtcp_mux(#{rtcp_mux := true}, Attrs) -> |
523 |
51 |
[{<<"rtcp-mux">>, []} | Attrs]; |
524 |
|
add_rtcp_mux(_, Attrs) -> |
525 |
9 |
Attrs. |
526 |
|
|
527 |
|
transport_to_nksip_attrs(Transport, Attrs) -> |
528 |
60 |
AttrsWithUfrag = maybe_add_ice_ufrag_attr(maps:get(ufrag, Transport), Attrs), |
529 |
60 |
AttrsWithPwd = maybe_add_ice_pwd_attr(maps:get(pwd, Transport), AttrsWithUfrag), |
530 |
60 |
maybe_add_fingerprint(maps:get(fingerprint, Transport), AttrsWithPwd). |
531 |
|
|
532 |
|
maybe_add_fingerprint({Hash, Setup, Fingerprint}, Attrs) -> |
533 |
60 |
AttrsWithFingerprint = [{<<"fingerprint">>, [Hash, Fingerprint]} | Attrs], |
534 |
60 |
case Setup of |
535 |
|
undefined -> |
536 |
9 |
AttrsWithFingerprint; |
537 |
|
_ -> |
538 |
51 |
[{<<"setup">>, [Setup]} | AttrsWithFingerprint] |
539 |
|
end; |
540 |
|
maybe_add_fingerprint(_, Attrs) -> |
541 |
:-( |
Attrs. |
542 |
|
|
543 |
|
maybe_add_ice_ufrag_attr(undefined, Attrs) -> |
544 |
9 |
Attrs; |
545 |
|
maybe_add_ice_ufrag_attr(Ufrag, Attrs) -> |
546 |
51 |
[{<<"ice-ufrag">>, [Ufrag]} | Attrs]. |
547 |
|
|
548 |
|
maybe_add_ice_pwd_attr(undefined, Attrs) -> |
549 |
9 |
Attrs; |
550 |
|
maybe_add_ice_pwd_attr(Pwd, Attrs) -> |
551 |
51 |
[{<<"ice-pwd">>, [Pwd]} | Attrs]. |
552 |
|
|
553 |
|
add_media_name_attr(Name, Attrs) -> |
554 |
60 |
[{<<"mid">>, [Name]} | Attrs]. |
555 |
|
|
556 |
|
add_sender_attr(Sender, Attrs) -> |
557 |
60 |
[{Sender, []} | Attrs]. |
558 |
|
|
559 |
|
add_group_attr_from_jingle(Jingle, Attrs) -> |
560 |
30 |
case exml_query:subelement(Jingle, <<"group">>) of |
561 |
|
#xmlel{} = El -> |
562 |
30 |
Attr = make_group_attr(El), |
563 |
30 |
[Attr | Attrs]; |
564 |
|
_ -> |
565 |
:-( |
Attrs |
566 |
|
end. |
567 |
|
|
568 |
|
make_group_attr(#xmlel{children = Children} = Group) -> |
569 |
30 |
Semantic = exml_query:attr(Group, <<"semantics">>), |
570 |
30 |
Contents = [exml_query:attr(Content, <<"name">>) || Content <- Children], |
571 |
|
|
572 |
30 |
{<<"group">>, [Semantic | Contents]}. |
573 |
|
|
574 |
|
maybe_resend_session_initiate(From, To, IQ, Acc, |
575 |
|
#{meta := Meta, from := OriginFrom}) -> |
576 |
1 |
case maps:get(init_stanza, Meta, undefined) of |
577 |
|
undefined -> |
578 |
:-( |
Error = mongoose_xmpp_errors:item_not_found(<<"en">>, |
579 |
|
<<"no session-initiate for this SID">>), |
580 |
:-( |
ejabberd_router:route_error_reply(To, From, Acc, Error); |
581 |
|
Stanza -> |
582 |
1 |
IQResult = IQ#iq{type = result, sub_el = []}, |
583 |
1 |
Packet = jlib:replace_from_to(From, To, jlib:iq_to_xml(IQResult)), |
584 |
1 |
ejabberd_router:route(To, From, Acc, Packet), |
585 |
1 |
OriginFromBin = jid:to_binary(OriginFrom), |
586 |
1 |
IQSet = jingle_sip_helper:jingle_iq(jid:to_binary(From), OriginFromBin, |
587 |
|
Stanza), |
588 |
1 |
{U, S} = OriginFrom, |
589 |
1 |
OriginJID = jid:make_noprep(U, S, <<>>), |
590 |
1 |
ejabberd_router:route(OriginJID, From, Acc, IQSet) |
591 |
|
end. |
592 |
|
|
593 |
|
nksip_request_reply(Reply, {Node, ReqID}) -> |
594 |
11 |
case node() of |
595 |
|
Node -> |
596 |
8 |
nksip_request:reply(Reply, ReqID); |
597 |
|
_ -> |
598 |
3 |
rpc:call(Node, nksip_request, reply, [Reply, ReqID], timer:seconds(5)) |
599 |
|
end. |
600 |
|
|
601 |
|
nksip_uac_bye(Node, DialogHandle, Args) -> |
602 |
3 |
case node() of |
603 |
|
Node -> |
604 |
2 |
nksip_uac:bye(DialogHandle, Args); |
605 |
|
_ -> |
606 |
1 |
rpc:call(Node, nksip_uac, bye, [DialogHandle, Args], timer:seconds(5)) |
607 |
|
end. |
608 |
|
|