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