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