./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 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 66 #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 66 #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.
Line Hits Source