./ct_report/coverage/mod_jingle_sip.COVER.html

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
:-(
start_nksip_service_or_error(Opts),
52
:-(
mod_jingle_sip_backend:init(Host, Opts),
53
:-(
ok.
54
55 start_nksip_service_or_error(Opts = #{listen_port := ListenPort}) ->
56
:-(
{ok, _} = application:ensure_all_started(nksip),
57
:-(
NkSipBasicOpts = #{sip_listen => "sip:all:" ++ integer_to_list(ListenPort),
58 callback => jingle_sip_callbacks,
59 plugins => [nksip_outbound, nksip_100rel]},
60
:-(
NkSipOpts = maybe_add_udp_max_size(NkSipBasicOpts, Opts),
61
:-(
Res = nksip:start(?SERVICE, NkSipOpts),
62
:-(
check_start_result(Res).
63
64 -dialyzer({no_match, check_start_result/1}).
65 check_start_result({ok, _SrvID}) ->
66
:-(
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
:-(
case gen_mod:get_opt(udp_max_size, Opts, undefined) of
74 undefined ->
75
:-(
NkSipOpts;
76 Size ->
77
:-(
NkSipOpts#{sip_udp_max_size => Size}
78 end.
79
80 -spec stop(jid:server()) -> ok.
81 stop(_Host) ->
82
:-(
ok.
83
84 -spec config_spec() -> mongoose_config_spec:config_section().
85 config_spec() ->
86 186 #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 186 #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
:-(
[{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
:-(
{From, To, Packet} = mongoose_acc:packet(Acc),
131
:-(
#jid{luser = StanzaTo} = To,
132
:-(
#jid{luser = LUser} = mongoose_acc:get(c2s, origin_jid, Acc),
133
:-(
case LUser of
134 StanzaTo ->
135
:-(
QueryInfo = jlib:iq_query_info(Packet),
136
:-(
maybe_jingle_get_stanza_to_self(QueryInfo, Acc);
137 _ ->
138
:-(
QueryInfo = jlib:iq_query_info(Packet),
139
:-(
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
:-(
JingleAction = exml_query:attr(Jingle, <<"action">>),
144
:-(
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
:-(
JingleAction = exml_query:attr(Jingle, <<"action">>),
150
:-(
case JingleAction of
151 <<"existing-session-initiate">> ->
152
:-(
resend_session_initiate(IQ, Acc),
153
:-(
{stop, Acc};
154 _ ->
155
:-(
{ok, Acc}
156 end;
157 maybe_jingle_get_stanza_to_self(_, Acc) ->
158
:-(
{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
:-(
#iq{sub_el = Jingle} = IQ,
169
:-(
try
170
:-(
Result = translate_to_sip(JingleAction, Jingle, Acc),
171
:-(
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
:-(
{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
:-(
route_ok_result(From, To, IQ);
186 route_result({ok, _}, From, To, IQ) ->
187
:-(
route_ok_result(From, To, IQ);
188 route_result({ok, _, _}, From, To, IQ) ->
189
:-(
route_ok_result(From, To, IQ);
190 route_result({error, item_not_found}, From, To, IQ) ->
191
:-(
Error = mongoose_xmpp_errors:item_not_found(),
192
:-(
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
:-(
IQResult = IQ#iq{type = error, sub_el = [Error]},
200
:-(
Packet = jlib:replace_from_to(From, To, jlib:iq_to_xml(IQResult)),
201
:-(
ejabberd_router:route(To, From, Packet).
202
203 route_ok_result(From, To, IQ) ->
204
:-(
IQResult = IQ#iq{type = result, sub_el = []},
205
:-(
Packet = jlib:replace_from_to(From, To, jlib:iq_to_xml(IQResult)),
206
:-(
ejabberd_router:route(To, From, Packet).
207
208 resend_session_initiate(#iq{sub_el = Jingle} = IQ, Acc) ->
209
:-(
From = mongoose_acc:from_jid(Acc),
210
:-(
To = mongoose_acc:to_jid(Acc),
211
:-(
SID = exml_query:attr(Jingle, <<"sid">>),
212
:-(
case mod_jingle_sip_session:get_session_info(SID, From) of
213 {ok, Session} ->
214
:-(
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
:-(
case nksip_uac:invite(Service, Uri, Opts) of
223 {error, Reason} ->
224
:-(
error(Reason);
225 {async, _} = Async ->
226
:-(
Async
227 end.
228
229 translate_to_sip(<<"session-initiate">>, Jingle, Acc) ->
230
:-(
SID = exml_query:attr(Jingle, <<"sid">>),
231
:-(
#jid{luser = ToUser} = ToJID = jingle_sip_helper:maybe_rewrite_to_phone(Acc),
232
:-(
#jid{luser = FromUser} = FromJID = mongoose_acc:from_jid(Acc),
233
:-(
From = jid:to_bare_binary(FromJID),
234
:-(
To = jid:to_bare_binary(ToJID),
235
:-(
LServer = mongoose_acc:lserver(Acc),
236
:-(
SDP = prepare_initial_sdp(LServer, Jingle),
237
:-(
ProxyURI = get_proxy_uri(LServer),
238
:-(
RequestURI = list_to_binary(["sip:", ToUser, "@", ProxyURI]),
239
:-(
ToHeader = <<ToUser/binary, " <sip:",To/binary, ">">>,
240
:-(
LocalHost = gen_mod:get_module_opt(LServer, ?MODULE, local_host),
241
242
:-(
{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
:-(
Result = mod_jingle_sip_session:set_outgoing_request(SID, Handle, FromJID, ToJID),
254
:-(
{_, SrvId, DialogId, _CallId} = nksip_sipmsg:parse_handle(Handle),
255
:-(
?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
:-(
call_id => SID, server_id => SrvId, dialog_id => DialogId}),
260
:-(
{ok, Handle};
261 translate_to_sip(<<"session-accept">>, Jingle, Acc) ->
262
:-(
LServer = mongoose_acc:lserver(Acc),
263
:-(
SID = exml_query:attr(Jingle, <<"sid">>),
264
:-(
case mod_jingle_sip_session:get_incoming_request(SID, mongoose_acc:get(c2s, origin_jid, Acc)) of
265 {ok, ReqID} ->
266
:-(
try_to_accept_session(ReqID, Jingle, Acc, LServer, SID);
267 _ ->
268
:-(
{error, item_not_found}
269 end;
270 translate_to_sip(<<"source-remove">> = Name, Jingle, Acc) ->
271
:-(
translate_source_change_to_sip(Name, Jingle, Acc);
272 translate_to_sip(<<"source-add">> = Name, Jingle, Acc) ->
273
:-(
translate_source_change_to_sip(Name, Jingle, Acc);
274 translate_to_sip(<<"source-update">> = Name, Jingle, Acc) ->
275
:-(
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
:-(
SID = exml_query:attr(Jingle, <<"sid">>),
292
:-(
ToJID = jingle_sip_helper:maybe_rewrite_to_phone(Acc),
293
:-(
From = mongoose_acc:get(c2s, origin_jid, Acc),
294
:-(
FromLUS = jid:to_lus(From),
295
:-(
ToLUS = jid:to_lus(ToJID),
296
:-(
case mod_jingle_sip_session:get_session_info(SID, From) of
297 {ok, Session} ->
298
:-(
try_to_terminate_the_session(FromLUS, ToLUS, Session);
299 _ ->
300
:-(
{error, item_not_found}
301 end.
302
303 translate_source_change_to_sip(ActionName, Jingle, Acc) ->
304
:-(
SID = exml_query:attr(Jingle, <<"sid">>),
305
:-(
SDP = get_spd(ActionName, Jingle, Acc),
306
:-(
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
:-(
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
:-(
LServer = mongoose_acc:lserver(Acc),
319
:-(
#sdp{attributes = SDPAttrs} = RawSDP = prepare_initial_sdp(LServer, Jingle),
320
:-(
SDPAttrsWithActionName = [{<<"jingle-action">>, [ActionName]} | SDPAttrs],
321
:-(
RawSDP#sdp{attributes = SDPAttrsWithActionName}.
322
323 try_to_terminate_the_session(FromLUS, ToLUS, Session) ->
324
:-(
case maps:get(state, Session) of
325 accepted ->
326
:-(
DialogHandle = maps:get(dialog, Session),
327
:-(
Node = maps:get(node, Session),
328
:-(
nksip_uac_bye(Node, DialogHandle, [{to, make_user_header(ToLUS)},
329 {from, make_user_header(FromLUS)}]);
330 _ ->
331
:-(
RequestHandle = maps:get(request, Session),
332
:-(
case maps:get(direction, Session) of
333 out ->
334
:-(
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
:-(
Node = maps:get(node, Session),
339
:-(
nksip_request_reply(busy, {Node, RequestHandle})
340 end
341 end.
342
343 try_to_accept_session(ReqID, Jingle, Acc, Server, SID) ->
344
:-(
SDP = prepare_initial_sdp(Server, Jingle),
345
:-(
LocalHost = gen_mod:get_module_opt(Server, ?MODULE, local_host),
346
:-(
case nksip_request_reply({ok, [{body, SDP}, {local_host, LocalHost}]}, ReqID) of
347 ok ->
348
:-(
ok = mod_jingle_sip_session:set_incoming_accepted(SID),
349
:-(
terminate_session_on_other_devices(SID, Acc),
350
:-(
ok;
351 Other ->
352
:-(
Other
353 end.
354
355 terminate_session_on_other_devices(SID, Acc) ->
356
:-(
#jid{lresource = Res} = From = mongoose_acc:from_jid(Acc),
357
:-(
FromBin = jid:to_binary(From),
358
:-(
ReasonEl = #xmlel{name = <<"reason">>,
359 children = [#xmlel{name = <<"cancel">>}]},
360
:-(
JingleEl = jingle_sip_helper:jingle_element(SID, <<"session-terminate">>, [ReasonEl]),
361
:-(
PResources = ejabberd_sm:get_user_present_resources(From),
362
:-(
lists:foreach(
363 fun({_, R}) when R /= Res ->
364
:-(
ToJID = jid:replace_resource(From, R),
365
:-(
IQEl = jingle_sip_helper:jingle_iq(jid:to_binary(ToJID), FromBin, JingleEl),
366
:-(
ejabberd_router:route(From, ToJID, Acc, IQEl);
367 (_) ->
368
:-(
skip
369 end, PResources).
370
371 make_user_header({User, _} = US) ->
372
:-(
JIDBin = jid:to_binary(US),
373
:-(
<<User/binary, " <sip:", JIDBin/binary, ">">>.
374
375
376 get_proxy_uri(Server) ->
377
:-(
Opts = gen_mod:get_module_opts(Server, ?MODULE),
378
:-(
#{proxy_host := ProxyHost, proxy_port := ProxyPort, transport := Transport} = Opts,
379
:-(
PortStr = integer_to_list(ProxyPort),
380
:-(
[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
:-(
Medias = [jingle_content_to_media(Content) ||
427
:-(
Content <- exml_query:subelements(Jingle, <<"content">>)],
428
429
:-(
GroupingAttrs = add_group_attr_from_jingle(Jingle, []),
430
431
:-(
OriginAddress = gen_mod:get_module_opt(Server, ?MODULE, sdp_origin),
432
:-(
SDP = nksip_sdp:new(OriginAddress, []),
433
:-(
SDP#sdp{medias = Medias,
434 attributes = GroupingAttrs}.
435
436 jingle_content_to_media(ContentEl) ->
437
:-(
Content = jingle_to_sdp:from_media(ContentEl),
438
:-(
content_to_nksip_media(Content).
439
440 content_to_nksip_media(Content) ->
441
:-(
{FMTS, DescAttrs} = description_to_nksip_attrs(maps:get(description, Content)),
442
:-(
TransportAttrs = transport_to_nksip_attrs(maps:get(transport, Content), DescAttrs),
443
:-(
AttrsWithMediaName = add_media_name_attr(maps:get(name, Content), TransportAttrs),
444
:-(
AttrsWithSender = add_sender_attr(maps:get(senders, Content), AttrsWithMediaName),
445
446
:-(
#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
:-(
AttrsWithSSRC = sources_to_nksip_attrs(maps:get(sources, Desc)),
454
:-(
{FMTS, CodecAttrs} = codecs_to_nksip_attrs(maps:get(codecs, Desc), AttrsWithSSRC),
455
:-(
AttrsWithRTPHeaderExt = add_rtp_header_ext(maps:get(rtphdr_ext, Desc), CodecAttrs),
456
:-(
AttrsWithRTCPMUX = add_rtcp_mux(Desc, AttrsWithRTPHeaderExt),
457
458
:-(
{FMTS, AttrsWithRTCPMUX}.
459
460 sources_to_nksip_attrs(Sources) ->
461
:-(
lists:flatmap(fun source_to_nksip_attr/1, Sources).
462
463 source_to_nksip_attr({ID, KeyValues}) ->
464
:-(
[{<<"ssrc">>, [ID, source_to_nksip_value(Name, Value)]} || {Name, Value} <- KeyValues].
465
466 source_to_nksip_value(Name, Value) ->
467
:-(
<<Name/binary, $:, Value/binary>>.
468
469 codecs_to_nksip_attrs(Codecs, Attrs) ->
470
:-(
codecs_to_nksip_attrs2(Codecs, {[], Attrs}).
471
472 codecs_to_nksip_attrs2([], Acc) ->
473
:-(
Acc;
474 codecs_to_nksip_attrs2([Codec | Rest], {FMTS, Attrs}) ->
475
:-(
ID = maps:get(id, Codec),
476
:-(
NewFMTS = [ID | FMTS],
477
:-(
RTCPFBAttrs = [rtcp_fb_param_to_attr(ID, Param) || Param <- maps:get(rtcp_fb_params, Codec)],
478
:-(
AttrsWithFMTPParams = maybe_add_fmtp_attr(ID, maps:get(params, Codec), RTCPFBAttrs),
479
:-(
RTPMapValue = prepare_rtp_map(maps:get(name, Codec), maps:get(clock_rate, Codec), maps:get(channels, Codec)),
480
:-(
RTPAttr = {<<"rtpmap">>, [ID, RTPMapValue]},
481
:-(
CodecAttrs = [RTPAttr | AttrsWithFMTPParams],
482
483
:-(
codecs_to_nksip_attrs2(Rest, {NewFMTS, lists:append(CodecAttrs, Attrs)}).
484
485 rtcp_fb_param_to_attr(ID, RTCPParam) ->
486
:-(
{<<"rtcp-fb">>, [ID | RTCPParam]}.
487
488 maybe_add_fmtp_attr(_ID, [], RTCPParams) ->
489
:-(
RTCPParams;
490 maybe_add_fmtp_attr(ID, Params, RTCPParams) ->
491
:-(
KV = [basic_parameter_to_fmtp_value(Param) || Param <- Params],
492
:-(
Joined = lists:join($;, KV),
493
:-(
FMTPValue = list_to_binary(Joined),
494
:-(
[{<<"fmtp">>, [ID, FMTPValue]} | RTCPParams].
495
496 basic_parameter_to_fmtp_value({undefined, Value}) ->
497
:-(
Value;
498 basic_parameter_to_fmtp_value({Name, Value}) ->
499
:-(
<<Name/binary, $=, Value/binary>>.
500
501 prepare_rtp_map(Name, ClockRate, <<"1">>) ->
502
:-(
<<Name/binary, $/, ClockRate/binary>>;
503 prepare_rtp_map(Name, ClockRate, Channels) ->
504
:-(
<<Name/binary, $/, ClockRate/binary, $/, Channels/binary>>.
505
506 add_rtp_header_ext(RTPHdrExts, Attrs) ->
507
:-(
RTPHdrextAttrs = [rtp_hdrext_to_sdp_attr(RTPHdrExt) || RTPHdrExt <- RTPHdrExts],
508
:-(
lists:append(RTPHdrextAttrs, Attrs).
509
510 rtp_hdrext_to_sdp_attr({ID, URI, Senders}) ->
511
:-(
{<<"extmap">>, rtp_hdrext_value(ID, URI, Senders)}.
512
513 rtp_hdrext_value(ID, URI, <<"sendrecv">>) ->
514
:-(
[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
:-(
[{<<"rtcp-mux">>, []} | Attrs];
520 add_rtcp_mux(_, Attrs) ->
521
:-(
Attrs.
522
523 transport_to_nksip_attrs(Transport, Attrs) ->
524
:-(
AttrsWithUfrag = maybe_add_ice_ufrag_attr(maps:get(ufrag, Transport), Attrs),
525
:-(
AttrsWithPwd = maybe_add_ice_pwd_attr(maps:get(pwd, Transport), AttrsWithUfrag),
526
:-(
maybe_add_fingerprint(maps:get(fingerprint, Transport), AttrsWithPwd).
527
528 maybe_add_fingerprint({Hash, Setup, Fingerprint}, Attrs) ->
529
:-(
AttrsWithFingerprint = [{<<"fingerprint">>, [Hash, Fingerprint]} | Attrs],
530
:-(
case Setup of
531 undefined ->
532
:-(
AttrsWithFingerprint;
533 _ ->
534
:-(
[{<<"setup">>, [Setup]} | AttrsWithFingerprint]
535 end;
536 maybe_add_fingerprint(_, Attrs) ->
537
:-(
Attrs.
538
539 maybe_add_ice_ufrag_attr(undefined, Attrs) ->
540
:-(
Attrs;
541 maybe_add_ice_ufrag_attr(Ufrag, Attrs) ->
542
:-(
[{<<"ice-ufrag">>, [Ufrag]} | Attrs].
543
544 maybe_add_ice_pwd_attr(undefined, Attrs) ->
545
:-(
Attrs;
546 maybe_add_ice_pwd_attr(Pwd, Attrs) ->
547
:-(
[{<<"ice-pwd">>, [Pwd]} | Attrs].
548
549 add_media_name_attr(Name, Attrs) ->
550
:-(
[{<<"mid">>, [Name]} | Attrs].
551
552 add_sender_attr(Sender, Attrs) ->
553
:-(
[{Sender, []} | Attrs].
554
555 add_group_attr_from_jingle(Jingle, Attrs) ->
556
:-(
case exml_query:subelement(Jingle, <<"group">>) of
557 #xmlel{} = El ->
558
:-(
Attr = make_group_attr(El),
559
:-(
[Attr | Attrs];
560 _ ->
561
:-(
Attrs
562 end.
563
564 make_group_attr(#xmlel{children = Children} = Group) ->
565
:-(
Semantic = exml_query:attr(Group, <<"semantics">>),
566
:-(
Contents = [exml_query:attr(Content, <<"name">>) || Content <- Children],
567
568
:-(
{<<"group">>, [Semantic | Contents]}.
569
570 maybe_resend_session_initiate(From, To, IQ, Acc,
571 #{meta := Meta, from := OriginFrom}) ->
572
:-(
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
:-(
IQResult = IQ#iq{type = result, sub_el = []},
579
:-(
Packet = jlib:replace_from_to(From, To, jlib:iq_to_xml(IQResult)),
580
:-(
ejabberd_router:route(To, From, Acc, Packet),
581
:-(
OriginFromBin = jid:to_binary(OriginFrom),
582
:-(
IQSet = jingle_sip_helper:jingle_iq(jid:to_binary(From), OriginFromBin,
583 Stanza),
584
:-(
{U, S} = OriginFrom,
585
:-(
OriginJID = jid:make_noprep(U, S, <<>>),
586
:-(
ejabberd_router:route(OriginJID, From, Acc, IQSet)
587 end.
588
589 nksip_request_reply(Reply, {Node, ReqID}) ->
590
:-(
case node() of
591 Node ->
592
:-(
nksip_request:reply(Reply, ReqID);
593 _ ->
594
:-(
rpc:call(Node, nksip_request, reply, [Reply, ReqID], timer:seconds(5))
595 end.
596
597 nksip_uac_bye(Node, DialogHandle, Args) ->
598
:-(
case node() of
599 Node ->
600
:-(
nksip_uac:bye(DialogHandle, Args);
601 _ ->
602
:-(
rpc:call(Node, nksip_uac, bye, [DialogHandle, Args], timer:seconds(5))
603 end.
Line Hits Source