./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("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
Line Hits Source