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