Line data Source code
1 : /*
2 : * Copyright (C) 2004-2026 Savoir-faire Linux Inc.
3 : *
4 : * This program is free software: you can redistribute it and/or modify
5 : * it under the terms of the GNU General Public License as published by
6 : * the Free Software Foundation, either version 3 of the License, or
7 : * (at your option) any later version.
8 : *
9 : * This program is distributed in the hope that it will be useful,
10 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 : * GNU General Public License for more details.
13 : *
14 : * You should have received a copy of the GNU General Public License
15 : * along with this program. If not, see <https://www.gnu.org/licenses/>.
16 : */
17 :
18 : #include "call_factory.h"
19 : #include "sip/sipcall.h"
20 : #include "sip/sipaccount.h"
21 : #include "sip/sipaccountbase.h"
22 : #include "sip/sipvoiplink.h"
23 : #include "jamidht/jamiaccount.h"
24 : #include "logger.h"
25 : #include "sdp.h"
26 : #include "manager.h"
27 : #include "string_utils.h"
28 :
29 : #include "connectivity/sip_utils.h"
30 : #include "audio/audio_rtp_session.h"
31 : #include "im/instant_messaging.h"
32 : #include "jami/account_const.h"
33 : #include "jami/call_const.h"
34 : #include "jami/media_const.h"
35 : #include "client/jami_signal.h"
36 : #include "pjsip-ua/sip_inv.h"
37 : #include "video/video_mixer.h"
38 :
39 : #ifdef ENABLE_PLUGIN
40 : #include "plugin/jamipluginmanager.h"
41 : #endif
42 :
43 : #ifdef ENABLE_VIDEO
44 : #include "client/videomanager.h"
45 : #include "video/video_rtp_session.h"
46 : #include <chrono>
47 : #include <libavutil/display.h>
48 : #include <video/sinkclient.h>
49 : #endif
50 : #include "audio/ringbufferpool.h"
51 :
52 : #include <dhtnet/upnp/upnp_control.h>
53 : #include <dhtnet/ice_transport_factory.h>
54 :
55 : #include <opendht/crypto.h>
56 : #include <opendht/thread_pool.h>
57 : #include <fmt/ranges.h>
58 :
59 : #include <utility>
60 :
61 : #include "tracepoint.h"
62 :
63 : namespace jami {
64 :
65 : using sip_utils::CONST_PJ_STR;
66 : using namespace libjami::Call;
67 :
68 : #ifdef ENABLE_VIDEO
69 : static DeviceParams
70 339 : getVideoSettings()
71 : {
72 339 : if (auto* videomon = jami::getVideoDeviceMonitor())
73 678 : return videomon->getDeviceParams(videomon->getDefaultDevice());
74 0 : return DeviceParams {};
75 : }
76 : #endif
77 :
78 : static constexpr std::chrono::seconds DEFAULT_ICE_INIT_TIMEOUT {35}; // seconds
79 : static constexpr std::chrono::seconds CALL_RECOVERY_TIMEOUT {20};
80 : static constexpr std::chrono::milliseconds EXPECTED_ICE_INIT_MAX_TIME {5000};
81 : static constexpr std::chrono::milliseconds MS_BETWEEN_2_KEYFRAME_REQUEST {1000};
82 : static constexpr int ICE_COMP_ID_RTP {1};
83 : static constexpr int ICE_COMP_COUNT_PER_STREAM {2};
84 : static constexpr auto MULTISTREAM_REQUIRED_VERSION_STR = "10.0.2"sv;
85 : static const std::vector<unsigned> MULTISTREAM_REQUIRED_VERSION
86 : = split_string_to_unsigned(MULTISTREAM_REQUIRED_VERSION_STR, '.');
87 : static constexpr auto MULTIICE_REQUIRED_VERSION_STR = "13.3.0"sv;
88 : static const std::vector<unsigned> MULTIICE_REQUIRED_VERSION = split_string_to_unsigned(MULTIICE_REQUIRED_VERSION_STR,
89 : '.');
90 : static constexpr auto NEW_CONFPROTOCOL_VERSION_STR = "13.1.0"sv;
91 : static const std::vector<unsigned> NEW_CONFPROTOCOL_VERSION = split_string_to_unsigned(NEW_CONFPROTOCOL_VERSION_STR,
92 : '.');
93 : static constexpr auto REUSE_ICE_IN_REINVITE_REQUIRED_VERSION_STR = "11.0.2"sv;
94 : static const std::vector<unsigned> REUSE_ICE_IN_REINVITE_REQUIRED_VERSION
95 : = split_string_to_unsigned(REUSE_ICE_IN_REINVITE_REQUIRED_VERSION_STR, '.');
96 : static constexpr auto MULTIAUDIO_REQUIRED_VERSION_STR = "13.11.0"sv;
97 : static const std::vector<unsigned> MULTIAUDIO_REQUIRED_VERSION
98 : = split_string_to_unsigned(MULTIAUDIO_REQUIRED_VERSION_STR, '.');
99 :
100 419 : SIPCall::SIPCall(const std::shared_ptr<SIPAccountBase>& account,
101 : const std::string& callId,
102 : Call::CallType type,
103 419 : const std::vector<libjami::MediaMap>& mediaList)
104 : : Call(account, callId, type)
105 419 : , sdp_(new Sdp(callId))
106 419 : , enableIce_(account->isIceForMediaEnabled())
107 1257 : , srtpEnabled_(account->isSrtpEnabled())
108 : {
109 : jami_tracepoint(call_start, callId.c_str());
110 :
111 419 : if (account->getUPnPActive())
112 0 : upnp_ = std::make_shared<dhtnet::upnp::Controller>(Manager::instance().upnpContext());
113 :
114 419 : setCallMediaLocal();
115 :
116 : // Set the media caps.
117 419 : sdp_->setLocalMediaCapabilities(MediaType::MEDIA_AUDIO, account->getActiveAccountCodecInfoList(MEDIA_AUDIO));
118 : #ifdef ENABLE_VIDEO
119 419 : sdp_->setLocalMediaCapabilities(MediaType::MEDIA_VIDEO, account->getActiveAccountCodecInfoList(MEDIA_VIDEO));
120 : #endif
121 :
122 419 : auto mediaAttrList = MediaAttribute::buildMediaAttributesList(mediaList, isSrtpEnabled());
123 :
124 419 : if (mediaAttrList.size() == 0) {
125 0 : if (type_ == Call::CallType::INCOMING) {
126 : // Handle incoming call without media offer.
127 0 : JAMI_WARNING("[call:{}] No media offered in the incoming invite. An offer will be provided in "
128 : "the answer",
129 : getCallId());
130 0 : mediaAttrList = getSIPAccount()->createDefaultMediaList(false, getState() == CallState::HOLD);
131 : } else {
132 0 : JAMI_WARNING("[call:{}] Creating an outgoing call with empty offer", getCallId());
133 : }
134 : }
135 :
136 419 : JAMI_DEBUG("[call:{:s}] Create a new [{:s}] SIP call with {:d} media",
137 : getCallId(),
138 : type == Call::CallType::INCOMING ? "INCOMING"
139 : : (type == Call::CallType::OUTGOING ? "OUTGOING" : "MISSED"),
140 : mediaList.size());
141 :
142 419 : initMediaStreams(mediaAttrList);
143 419 : }
144 :
145 419 : SIPCall::~SIPCall()
146 : {
147 419 : std::lock_guard lk {callMutex_};
148 :
149 419 : stopCallRecovery();
150 419 : setSipTransport({});
151 419 : setInviteSession(); // prevents callback usage
152 :
153 : #ifdef ENABLE_VIDEO
154 419 : closeMediaPlayer(mediaPlayerId_);
155 : #endif
156 419 : }
157 :
158 : int
159 758 : SIPCall::findRtpStreamIndex(const std::string& label) const
160 : {
161 758 : const auto iter = std::find_if(rtpStreams_.begin(), rtpStreams_.end(), [&label](const RtpStream& rtp) {
162 1204 : return label == rtp.mediaAttribute_->label_;
163 : });
164 :
165 : // Return the index if there is a match.
166 758 : if (iter != rtpStreams_.end())
167 1496 : return static_cast<int>(std::distance(rtpStreams_.begin(), iter));
168 :
169 : // No match found.
170 10 : return -1;
171 : }
172 :
173 : void
174 760 : SIPCall::createRtpSession(RtpStream& stream)
175 : {
176 760 : if (not stream.mediaAttribute_)
177 0 : throw std::runtime_error("Missing media attribute");
178 :
179 : // To get audio_0 ; video_0
180 760 : auto streamId = sip_utils::streamId(id_, stream.mediaAttribute_->label_);
181 760 : if (stream.mediaAttribute_->type_ == MediaType::MEDIA_AUDIO) {
182 421 : stream.rtpSession_ = std::make_shared<AudioRtpSession>(id_, streamId, recorder_);
183 : }
184 : #ifdef ENABLE_VIDEO
185 339 : else if (stream.mediaAttribute_->type_ == MediaType::MEDIA_VIDEO) {
186 339 : stream.rtpSession_ = std::make_shared<video::VideoRtpSession>(id_, streamId, getVideoSettings(), recorder_);
187 339 : std::static_pointer_cast<video::VideoRtpSession>(stream.rtpSession_)->setRotation(rotation_);
188 : }
189 : #endif
190 : else {
191 0 : throw std::runtime_error("Unsupported media type");
192 : }
193 :
194 : // Must be valid at this point.
195 760 : if (not stream.rtpSession_)
196 0 : throw std::runtime_error("Failed to create RTP session");
197 : ;
198 760 : }
199 :
200 : void
201 400 : SIPCall::configureRtpSession(const std::shared_ptr<RtpSession>& rtpSession,
202 : const std::shared_ptr<MediaAttribute>& mediaAttr,
203 : const MediaDescription& localMedia,
204 : const MediaDescription& remoteMedia)
205 : {
206 400 : JAMI_DEBUG("[call:{}] Configuring [{}] RTP session",
207 : getCallId(),
208 : MediaAttribute::mediaTypeToString(mediaAttr->type_));
209 :
210 400 : if (not rtpSession)
211 0 : throw std::runtime_error("Must have a valid RTP session");
212 :
213 : // Configure the media stream
214 400 : auto new_mtu = sipTransport_->getTlsMtu();
215 400 : rtpSession->setMtu(new_mtu);
216 400 : rtpSession->updateMedia(remoteMedia, localMedia);
217 :
218 : // Mute/un-mute media
219 400 : rtpSession->setMuted(mediaAttr->muted_);
220 :
221 400 : rtpSession->setMediaSource(mediaAttr->sourceUri_);
222 :
223 400 : rtpSession->setSuccessfulSetupCb([w = weak()](MediaType, bool) {
224 : // This sends SIP messages on socket, so move to io
225 611 : dht::ThreadPool::io().run([w = std::move(w)] {
226 611 : if (auto thisPtr = w.lock())
227 611 : thisPtr->rtpSetupSuccess();
228 611 : });
229 611 : });
230 :
231 400 : if (localMedia.type == MediaType::MEDIA_AUDIO) {
232 221 : setupVoiceCallback(rtpSession);
233 : }
234 :
235 : #ifdef ENABLE_VIDEO
236 400 : if (localMedia.type == MediaType::MEDIA_VIDEO) {
237 179 : auto videoRtp = std::dynamic_pointer_cast<video::VideoRtpSession>(rtpSession);
238 179 : assert(videoRtp && mediaAttr);
239 179 : auto streamIdx = findRtpStreamIndex(mediaAttr->label_);
240 179 : videoRtp->setRequestKeyFrameCallback([w = weak(), streamIdx] {
241 : // This sends SIP messages on socket, so move to I/O
242 93 : dht::ThreadPool::io().run([w = std::move(w), streamIdx] {
243 93 : if (auto thisPtr = w.lock())
244 93 : thisPtr->requestKeyframe(streamIdx);
245 93 : });
246 93 : });
247 179 : videoRtp->setChangeOrientationCallback([w = weak(), streamIdx](int angle) {
248 : // This sends SIP messages on socket, so move to I/O
249 59 : dht::ThreadPool::io().run([w, angle, streamIdx] {
250 59 : if (auto thisPtr = w.lock())
251 59 : thisPtr->setVideoOrientation(streamIdx, angle);
252 59 : });
253 59 : });
254 179 : }
255 : #endif
256 400 : }
257 :
258 : void
259 221 : SIPCall::setupVoiceCallback(const std::shared_ptr<RtpSession>& rtpSession)
260 : {
261 : // need to downcast to access setVoiceCallback
262 221 : auto audioRtp = std::dynamic_pointer_cast<AudioRtpSession>(rtpSession);
263 :
264 221 : audioRtp->setVoiceCallback([w = weak()](bool voice) {
265 : // this is called whenever voice is detected on the local audio
266 :
267 0 : runOnMainThread([w, voice] {
268 0 : if (auto thisPtr = w.lock()) {
269 : // TODO: once we support multiple streams, change this to the right one
270 0 : std::string streamId = "";
271 :
272 : #ifdef ENABLE_VIDEO
273 0 : if (auto* videoManager = Manager::instance().getVideoManager()) {
274 0 : if (not videoManager->videoDeviceMonitor.getDeviceList().empty()) {
275 : // if we have a video device
276 0 : streamId = sip_utils::streamId("", sip_utils::DEFAULT_VIDEO_STREAMID);
277 : }
278 : }
279 : #endif
280 :
281 : // send our local voice activity
282 0 : if (auto conference = thisPtr->conf_.lock()) {
283 : // we are in a conference
284 :
285 : // updates conference info and sends it to others via ConfInfo
286 : // (only if there was a change)
287 : // also emits signal with updated conference info
288 0 : conference->setVoiceActivity(streamId, voice);
289 : } else {
290 : // we are in a one-to-one call
291 : // send voice activity over SIP
292 : // TODO: change the streamID once multiple streams are supported
293 0 : thisPtr->sendVoiceActivity("-1", voice);
294 :
295 : // TODO: maybe emit signal here for local voice activity
296 0 : }
297 0 : } else {
298 0 : JAMI_ERROR("Voice activity callback unable to lock weak ptr to SIPCall");
299 0 : }
300 0 : });
301 0 : });
302 221 : }
303 :
304 : std::shared_ptr<SIPAccountBase>
305 2159 : SIPCall::getSIPAccount() const
306 : {
307 2159 : return std::static_pointer_cast<SIPAccountBase>(getAccount().lock());
308 : }
309 :
310 : #ifdef ENABLE_PLUGIN
311 : void
312 477 : SIPCall::createCallAVStreams()
313 : {
314 : #ifdef ENABLE_VIDEO
315 828 : for (const auto& videoRtp : getRtpSessionList(MediaType::MEDIA_VIDEO)) {
316 432 : if (std::static_pointer_cast<video::VideoRtpSession>(videoRtp)->hasConference()) {
317 81 : clearCallAVStreams();
318 81 : return;
319 : }
320 477 : }
321 : #endif
322 :
323 396 : auto baseId = getCallId();
324 8557 : auto mediaMap = [](const std::shared_ptr<jami::MediaFrame>& m) -> AVFrame* {
325 8557 : return m->pointer();
326 : };
327 :
328 396 : std::lock_guard lk(avStreamsMtx_);
329 1151 : for (const auto& rtpSession : getRtpSessionList()) {
330 755 : auto isVideo = rtpSession->getMediaType() == MediaType::MEDIA_VIDEO;
331 755 : auto streamType = isVideo ? StreamType::video : StreamType::audio;
332 755 : StreamData previewStreamData {baseId, false, streamType, getPeerNumber(), getAccountId()};
333 755 : StreamData receiveStreamData {baseId, true, streamType, getPeerNumber(), getAccountId()};
334 : #ifdef ENABLE_VIDEO
335 755 : if (isVideo) {
336 : // Preview
337 351 : auto videoRtp = std::static_pointer_cast<video::VideoRtpSession>(rtpSession);
338 351 : if (auto& videoPreview = videoRtp->getVideoLocal())
339 247 : createCallAVStream(previewStreamData, *videoPreview, std::make_shared<MediaStreamSubject>(mediaMap));
340 : // Receive
341 351 : if (auto& videoReceive = videoRtp->getVideoReceive())
342 327 : createCallAVStream(receiveStreamData, *videoReceive, std::make_shared<MediaStreamSubject>(mediaMap));
343 351 : } else {
344 : #endif
345 404 : auto audioRtp = std::static_pointer_cast<AudioRtpSession>(rtpSession);
346 : // Preview
347 404 : if (auto& localAudio = audioRtp->getAudioLocal())
348 387 : createCallAVStream(previewStreamData, *localAudio, std::make_shared<MediaStreamSubject>(mediaMap));
349 : // Receive
350 404 : if (auto& audioReceive = audioRtp->getAudioReceive())
351 296 : createCallAVStream(receiveStreamData,
352 296 : (AVMediaStream&) *audioReceive,
353 592 : std::make_shared<MediaStreamSubject>(mediaMap));
354 : #ifdef ENABLE_VIDEO
355 404 : }
356 : #endif
357 1151 : }
358 396 : }
359 :
360 : void
361 1257 : SIPCall::createCallAVStream(const StreamData& streamData,
362 : AVMediaStream& streamSource,
363 : const std::shared_ptr<MediaStreamSubject>& mediaStreamSubject)
364 : {
365 2514 : const std::string AVStreamId = streamData.id + std::to_string(static_cast<int>(streamData.type))
366 3771 : + std::to_string(streamData.direction);
367 1257 : auto it = callAVStreams.find(AVStreamId);
368 1257 : if (it != callAVStreams.end())
369 484 : return;
370 773 : it = callAVStreams.insert(it, {AVStreamId, mediaStreamSubject});
371 773 : streamSource.attachPriorityObserver(it->second);
372 773 : jami::Manager::instance().getJamiPluginManager().getCallServicesManager().createAVSubject(streamData, it->second);
373 1257 : }
374 :
375 : void
376 643 : SIPCall::clearCallAVStreams()
377 : {
378 643 : std::lock_guard lk(avStreamsMtx_);
379 643 : callAVStreams.clear();
380 643 : }
381 : #endif // ENABLE_PLUGIN
382 :
383 : void
384 419 : SIPCall::setCallMediaLocal()
385 : {
386 419 : if (localAudioPort_ == 0
387 : #ifdef ENABLE_VIDEO
388 0 : || localVideoPort_ == 0
389 : #endif
390 : )
391 419 : generateMediaPorts();
392 419 : }
393 :
394 : void
395 456 : SIPCall::generateMediaPorts()
396 : {
397 456 : auto account = getSIPAccount();
398 456 : if (!account) {
399 0 : JAMI_ERROR("[call:{}] No account detected", getCallId());
400 0 : return;
401 : }
402 :
403 : // TODO. Setting specfic range for RTP ports is obsolete, in
404 : // particular in the context of ICE.
405 :
406 : // Reference: http://www.cs.columbia.edu/~hgs/rtp/faq.html#ports
407 : // We only want to set ports to new values if they haven't been set
408 456 : const unsigned callLocalAudioPort = account->generateAudioPort();
409 456 : if (localAudioPort_ != 0)
410 37 : account->releasePort(localAudioPort_);
411 456 : localAudioPort_ = callLocalAudioPort;
412 456 : sdp_->setLocalPublishedAudioPorts(callLocalAudioPort, rtcpMuxEnabled_ ? 0 : callLocalAudioPort + 1);
413 :
414 : #ifdef ENABLE_VIDEO
415 : // https://projects.savoirfairelinux.com/issues/17498
416 456 : const unsigned int callLocalVideoPort = account->generateVideoPort();
417 456 : if (localVideoPort_ != 0)
418 37 : account->releasePort(localVideoPort_);
419 : // this should already be guaranteed by SIPAccount
420 456 : assert(localAudioPort_ != callLocalVideoPort);
421 456 : localVideoPort_ = callLocalVideoPort;
422 456 : sdp_->setLocalPublishedVideoPorts(callLocalVideoPort, rtcpMuxEnabled_ ? 0 : callLocalVideoPort + 1);
423 : #endif
424 456 : }
425 :
426 : const std::string&
427 216 : SIPCall::getContactHeader() const
428 : {
429 216 : return contactHeader_;
430 : }
431 :
432 : std::string
433 7 : SIPCall::getPeerAccountId() const
434 : {
435 7 : std::lock_guard lk {callMutex_};
436 21 : return sipTransport_ ? std::string(sipTransport_->peerAccountId()) : std::string();
437 7 : }
438 :
439 : void
440 1176 : SIPCall::setSipTransport(const std::shared_ptr<SipTransport>& transport, const std::string& contactHdr)
441 : {
442 1176 : if (transport != sipTransport_) {
443 632 : JAMI_DEBUG("[call:{}] Setting transport to [{}]", getCallId(), fmt::ptr(transport.get()));
444 : }
445 :
446 1176 : const auto list_id = reinterpret_cast<uintptr_t>(this);
447 1176 : if (sipTransport_)
448 325 : sipTransport_->removeStateListener(list_id);
449 1176 : sipTransport_ = transport;
450 1176 : contactHeader_ = contactHdr;
451 :
452 1176 : if (not transport) {
453 : // Done.
454 851 : return;
455 : }
456 :
457 325 : if (contactHeader_.empty()) {
458 0 : JAMI_WARNING("[call:{}] Contact header is empty", getCallId());
459 : }
460 :
461 325 : if (isSrtpEnabled() and not sipTransport_->isSecure()) {
462 18 : JAMI_WARNING("[call:{}] Crypto (SRTP) is negotiated over an unencrypted signaling channel", getCallId());
463 : }
464 :
465 325 : if (not isSrtpEnabled() and sipTransport_->isSecure()) {
466 0 : JAMI_WARNING("[call:{}] The signaling channel is encrypted but the media is unencrypted", getCallId());
467 : }
468 :
469 : // Listen for transport destruction
470 325 : sipTransport_
471 325 : ->addStateListener(list_id, [wthis_ = weak(), wtransport = std::weak_ptr(transport)](auto state, const auto*) {
472 : // pjsip invokes this under the transport lock, which a dialog may be
473 : // waiting for while another thread holds the call mutex and waits for
474 : // that dialog: never take the call mutex from here.
475 22 : runOnMainThread([wthis_, wtransport, state] {
476 22 : auto this_ = wthis_.lock();
477 22 : if (not this_ or this_->sipTransport_ != wtransport.lock())
478 13 : return;
479 9 : JAMI_DEBUG("[call:{}] SIP transport state [{}] - connection state [{}]",
480 : this_->getCallId(),
481 : static_cast<int>(state),
482 : static_cast<unsigned>(this_->getConnectionState()));
483 :
484 : // End the call if the SIP transport was shut down
485 9 : auto isAlive = SipTransport::isAlive(state);
486 9 : if (not isAlive and this_->getConnectionState() != ConnectionState::DISCONNECTED) {
487 9 : if (this_->beginCallRecovery())
488 9 : return;
489 0 : JAMI_WARNING("[call:{}] Ending call because underlying SIP transport was closed",
490 : this_->getCallId());
491 0 : this_->stopAllMedia();
492 0 : this_->detachAudioFromConference();
493 0 : this_->onFailure(PJSIP_SC_SERVICE_UNAVAILABLE);
494 : }
495 22 : });
496 22 : });
497 : }
498 :
499 : bool
500 12 : SIPCall::beginCallRecovery(bool newNetwork)
501 : {
502 12 : auto account = std::dynamic_pointer_cast<JamiAccount>(getAccount().lock());
503 12 : if (not account)
504 0 : return false;
505 : {
506 12 : std::lock_guard lk {callMutex_};
507 12 : const bool firstRecovery = not recovering_;
508 12 : if (not inviteSession_)
509 0 : return false;
510 12 : if (not recovering_) {
511 18 : if (not peerSupportsHandover() or not isIceEnabled() or isSubcall()
512 9 : or getConnectionState() != ConnectionState::CONNECTED or not inviteSession_
513 18 : or inviteSession_->state != PJSIP_INV_STATE_CONFIRMED or not sipTransport_)
514 0 : return false;
515 9 : startRecoveryLocked();
516 : }
517 12 : if (firstRecovery or newNetwork or not waitingForRecoveryChannel_) {
518 9 : if (inviteSession_->role == PJSIP_ROLE_UAC)
519 5 : ++recoveryGeneration_;
520 : else
521 4 : ++recoveryRequest_;
522 9 : recoveryCandidate_.reset();
523 9 : recoveryRestartPending_ = false;
524 : }
525 12 : waitingForRecoveryChannel_ = true;
526 12 : }
527 :
528 12 : JAMI_WARNING("[call:{}] Reconnecting SIP and ICE after {}",
529 : getCallId(),
530 : newNetwork ? "a network change" : "losing the SIP channel");
531 12 : if (not account->requestCallRecovery(shared(), newNetwork)) {
532 0 : stopCallRecovery();
533 0 : return false;
534 : }
535 12 : return true;
536 12 : }
537 :
538 : void
539 12 : SIPCall::startRecoveryLocked()
540 : {
541 12 : recovering_ = true;
542 12 : recoveryNegotiation_.reset();
543 12 : recoveryTimer_ = std::make_unique<asio::steady_timer>(*Manager::instance().ioContext(), CALL_RECOVERY_TIMEOUT);
544 12 : auto episode = ++recoveryEpisode_;
545 12 : recoveryTimer_->async_wait([w = weak(), episode](const asio::error_code& ec) {
546 12 : if (ec)
547 12 : return;
548 0 : runOnMainThread([w, episode] {
549 0 : if (auto call = w.lock()) {
550 0 : std::lock_guard lk {call->callMutex_};
551 0 : if (not call->recovering_ or call->recoveryEpisode_ != episode)
552 0 : return;
553 0 : JAMI_WARNING("[call:{}] Network recovery timed out", call->getCallId());
554 0 : call->onFailure(PJSIP_SC_SERVICE_UNAVAILABLE);
555 0 : }
556 : });
557 : });
558 12 : }
559 :
560 : std::optional<std::uint64_t>
561 12 : SIPCall::startRecoveryAttempt(bool force)
562 : {
563 12 : std::lock_guard lk {callMutex_};
564 12 : if (not recovering_ or not waitingForRecoveryChannel_ or (recoveryAttemptPending_ and not force))
565 3 : return std::nullopt;
566 9 : recoveryAttemptPending_ = true;
567 9 : return ++recoveryAttempt_;
568 12 : }
569 :
570 : bool
571 2 : SIPCall::finishRecoveryAttempt(std::uint64_t attempt)
572 : {
573 2 : std::lock_guard lk {callMutex_};
574 2 : if (not recovering_ or not waitingForRecoveryChannel_ or recoveryAttempt_ != attempt)
575 2 : return false;
576 0 : recoveryAttemptPending_ = false;
577 0 : return true;
578 2 : }
579 :
580 : bool
581 13 : SIPCall::useRecoveredTransport(const std::shared_ptr<SipTransport>& transport,
582 : const std::string& contact,
583 : std::optional<std::uint64_t> attempt)
584 : {
585 : {
586 13 : std::lock_guard lk {callMutex_};
587 26 : if (not recovering_ or not waitingForRecoveryChannel_ or (attempt and recoveryAttempt_ != *attempt)
588 11 : or not inviteSession_ or inviteSession_->state != PJSIP_INV_STATE_CONFIRMED
589 11 : or not sipTransport_ or not transport or not transport->isConnected()
590 11 : or isRetiredSipTransport(transport->get())
591 11 : or sipTransport_->deviceId() != transport->deviceId()
592 26 : or sipTransport_->peerAccountId() != transport->peerAccountId())
593 2 : return false;
594 11 : if (inviteSession_->role != PJSIP_ROLE_UAC) {
595 6 : recoveryCandidate_ = transport;
596 6 : recoveryAttemptPending_ = false;
597 6 : sendRecoveryProbeLocked(transport);
598 6 : scheduleRecoveryProbeLocked();
599 6 : return true;
600 : }
601 5 : if (not rebindSipDialogLocked(transport, contact))
602 0 : return false;
603 5 : waitingForRecoveryChannel_ = false;
604 5 : recoveryAttemptPending_ = false;
605 5 : recoveryRestartPending_ = false;
606 5 : recoveryNegotiation_.reset();
607 13 : }
608 5 : if (isRecovering())
609 5 : requestIceRestart(transport);
610 5 : return true;
611 : }
612 :
613 : void
614 6 : SIPCall::followPeerTransport(const std::shared_ptr<SipTransport>& transport,
615 : const std::string& contact,
616 : std::uint64_t generation,
617 : std::uint64_t request)
618 : {
619 : {
620 6 : std::lock_guard lk {callMutex_};
621 12 : if (not peerSupportsHandover() or isSubcall() or not inviteSession_
622 6 : or inviteSession_->state != PJSIP_INV_STATE_CONFIRMED or inviteSession_->role != PJSIP_ROLE_UAC
623 6 : or not sipTransport_ or not transport or not request or request <= lastPeerRecoveryRequest_
624 12 : or isRetiredSipTransport(transport->get()))
625 2 : return;
626 4 : lastPeerRecoveryRequest_ = request;
627 : // A delayed probe for a recovery that already completed.
628 4 : if (generation < recoveryGeneration_ and not recovering_)
629 0 : return;
630 4 : if (not rebindSipDialogLocked(transport, contact))
631 0 : return;
632 4 : if (not recovering_)
633 1 : startRecoveryLocked();
634 : // Our own recovery already issued a generation for this channel.
635 4 : if (not waitingForRecoveryChannel_)
636 4 : ++recoveryGeneration_;
637 4 : waitingForRecoveryChannel_ = false;
638 4 : recoveryAttemptPending_ = false;
639 4 : recoveryNegotiation_.reset();
640 6 : }
641 4 : requestIceRestart(transport);
642 : }
643 :
644 : bool
645 6 : SIPCall::adoptRecoveryOfferLocked(const std::shared_ptr<SipTransport>& transport,
646 : const std::string& contact,
647 : std::uint64_t generation)
648 : {
649 12 : if (not inviteSession_ or inviteSession_->role != PJSIP_ROLE_UAS
650 6 : or inviteSession_->state != PJSIP_INV_STATE_CONFIRMED or not sipTransport_ or not transport
651 12 : or generation <= recoveryGeneration_ or not rebindSipDialogLocked(transport, contact))
652 0 : return false;
653 6 : JAMI_WARNING("[call:{}] Following the caller to its recovery channel", getCallId());
654 6 : if (not recovering_)
655 2 : startRecoveryLocked();
656 6 : recoveryGeneration_ = generation;
657 6 : waitingForRecoveryChannel_ = false;
658 6 : recoveryAttemptPending_ = false;
659 6 : recoveryCandidate_.reset();
660 6 : recoveryNegotiation_.reset();
661 6 : return true;
662 : }
663 :
664 : bool
665 15 : SIPCall::rebindSipDialogLocked(const std::shared_ptr<SipTransport>& transport, const std::string& contact)
666 : {
667 30 : if (not transport or contact.empty() or sipTransport_->deviceId() != transport->deviceId()
668 30 : or sipTransport_->peerAccountId().empty() or sipTransport_->peerAccountId() != transport->peerAccountId()) {
669 0 : JAMI_ERROR("[call:{}] Rejecting a SIP channel from a different peer device", getCallId());
670 0 : return false;
671 : }
672 :
673 15 : pjsip_tpselector selector {};
674 15 : selector.type = PJSIP_TPSELECTOR_TRANSPORT;
675 15 : selector.u.transport = transport->get();
676 15 : auto* dialog = inviteSession_->dlg;
677 15 : auto status = pjsip_dlg_set_transport(dialog, &selector);
678 15 : if (status != PJ_SUCCESS) {
679 0 : JAMI_ERROR("[call:{}] Could not rebind SIP dialog: {}", getCallId(), sip_utils::sip_strerror(status));
680 0 : return false;
681 : }
682 :
683 : // The peer's Contact may use another address family than the new channel,
684 : // which pjsip refuses for in-dialog requests: target the peer at the other
685 : // end of the channel, and advertise our end of it.
686 15 : pjsip_dlg_inc_lock(dialog);
687 15 : auto* target = pjsip_uri_get_uri(dialog->target);
688 15 : if (PJSIP_URI_SCHEME_IS_SIP(target) or PJSIP_URI_SCHEME_IS_SIPS(target)) {
689 15 : auto* uri = static_cast<pjsip_sip_uri*>(pjsip_uri_clone(dialog->pool, target));
690 15 : pj_strdup(dialog->pool, &uri->host, &transport->get()->remote_name.host);
691 15 : uri->port = transport->get()->remote_name.port;
692 15 : dialog->target = reinterpret_cast<pjsip_uri*>(uri);
693 : }
694 : static constexpr pj_str_t STR_CONTACT = sip_utils::CONST_PJ_STR("Contact");
695 : pj_str_t localContact;
696 15 : pj_strdup2_with_null(dialog->pool, &localContact, contact.c_str());
697 15 : if (auto* header = static_cast<pjsip_contact_hdr*>(
698 15 : pjsip_parse_hdr(dialog->pool, &STR_CONTACT, localContact.ptr, localContact.slen, nullptr)))
699 15 : dialog->local.contact = header;
700 15 : pjsip_dlg_dec_lock(dialog);
701 :
702 : // Requests sent by the peer before following us must not bring us back.
703 18 : std::erase_if(abandonedTransports_, [](const auto& abandoned) { return abandoned.expired(); });
704 15 : if (sipTransport_->get() != transport->get())
705 12 : abandonedTransports_.emplace_back(sipTransport_);
706 15 : setSipTransport(transport, contact);
707 15 : return true;
708 : }
709 :
710 : bool
711 15 : SIPCall::isRetiredSipTransport(const pjsip_transport* transport) const
712 : {
713 15 : std::lock_guard lk {callMutex_};
714 15 : return std::any_of(abandonedTransports_.begin(), abandonedTransports_.end(), [transport](const auto& previous) {
715 3 : auto channel = previous.lock();
716 3 : return channel and channel->get() == transport;
717 33 : });
718 15 : }
719 :
720 : SIPCall::HandoverRequest
721 40 : SIPCall::classifyHandoverRequest(const pjsip_rx_data* rdata, bool caller, std::uint64_t generation)
722 : {
723 40 : if (not rdata or not rdata->msg_info.msg)
724 0 : return HandoverRequest::Pass;
725 40 : const auto* msg = rdata->msg_info.msg;
726 40 : switch (msg->line.req.method.id) {
727 6 : case PJSIP_OPTIONS_METHOD: {
728 6 : auto request = sip_utils::getHandoverRequest(rdata);
729 6 : if (not caller or not request or not *request)
730 0 : return HandoverRequest::Pass;
731 6 : return sip_utils::getHandoverGeneration(rdata) ? HandoverRequest::Probe : HandoverRequest::Invalid;
732 : }
733 34 : case PJSIP_INVITE_METHOD: {
734 34 : if (not sip_utils::hasHandoverGeneration(rdata))
735 0 : return HandoverRequest::Pass;
736 34 : auto offered = sip_utils::getHandoverGeneration(rdata);
737 34 : if (not offered)
738 0 : return HandoverRequest::Invalid;
739 34 : if (*offered < generation)
740 0 : return HandoverRequest::Stale;
741 : // Only the caller issues generations, always with a new offer.
742 34 : if (*offered > generation and (caller or not msg->body))
743 0 : return HandoverRequest::Invalid;
744 34 : return HandoverRequest::Pass;
745 : }
746 0 : default:
747 : // Other requests are ordered by the dialog's CSeq on any channel.
748 0 : return HandoverRequest::Pass;
749 : }
750 : }
751 :
752 : bool
753 1755 : SIPCall::isCurrentMediaNegotiation() const
754 : {
755 5265 : return mediaNegotiationGeneration_.load() == recoveryGeneration_.load();
756 : }
757 :
758 : bool
759 72 : SIPCall::isPendingReinviteLocked(pj_int32_t cseq) const
760 : {
761 72 : auto* tsx = inviteSession_ ? inviteSession_->invite_tsx : nullptr;
762 72 : return tsx and tsx->role == PJSIP_ROLE_UAS and tsx->cseq == cseq and tsx->status_code < PJSIP_SC_OK
763 144 : and tsx->state < PJSIP_TSX_STATE_COMPLETED;
764 : }
765 :
766 : void
767 0 : SIPCall::rejectPendingReinviteLocked(std::optional<pj_int32_t> cseq)
768 : {
769 0 : if (not inviteSession_)
770 0 : return;
771 0 : sip_utils::PJDialogLock dialogLock {inviteSession_->dlg};
772 0 : auto* tsx = inviteSession_->invite_tsx;
773 0 : if (not tsx or not isPendingReinviteLocked(cseq.value_or(tsx->cseq)))
774 0 : return;
775 0 : mediaNegotiationGeneration_ = recoveryGeneration_.load();
776 0 : JAMI_WARNING("[call:{}] Rejecting a re-INVITE superseded by a newer negotiation", getCallId());
777 0 : pjsip_tx_data* tdata = nullptr;
778 0 : auto status = pjsip_inv_answer(inviteSession_.get(), PJSIP_SC_REQUEST_PENDING, nullptr, nullptr, &tdata);
779 0 : if (status != PJ_SUCCESS) {
780 0 : JAMI_ERROR("[call:{}] Unable to create re-INVITE rejection: {}", getCallId(), sip_utils::sip_strerror(status));
781 0 : return;
782 : }
783 0 : status = pjsip_inv_send_msg(inviteSession_.get(), tdata);
784 0 : if (status != PJ_SUCCESS)
785 0 : JAMI_ERROR("[call:{}] Unable to send re-INVITE rejection: {}", getCallId(), sip_utils::sip_strerror(status));
786 0 : }
787 :
788 : void
789 9 : SIPCall::requestIceRestart(const std::shared_ptr<SipTransport>& transport)
790 : {
791 9 : std::lock_guard lk {callMutex_};
792 9 : auto account = std::dynamic_pointer_cast<JamiAccount>(getAccount().lock());
793 9 : if (not account) {
794 0 : JAMI_ERROR("[call:{}] Missing account during SIP recovery", getCallId());
795 0 : return;
796 : }
797 18 : account->getIceOptions(
798 27 : [w = weak(), wtransport = std::weak_ptr(transport), generation = recoveryGeneration_.load()]
799 : (dhtnet::IceTransportOptions&& options) mutable {
800 9 : runOnMainThread([w, wtransport, generation, options = std::move(options)]() mutable {
801 9 : if (auto call = w.lock()) {
802 9 : std::lock_guard lk {call->callMutex_};
803 9 : if (auto transport = wtransport.lock(); transport and call->sipTransport_ == transport)
804 9 : call->restartIceAfterRecovery(std::move(options), generation);
805 18 : }
806 9 : });
807 9 : });
808 9 : }
809 :
810 : void
811 6 : SIPCall::sendRecoveryProbeLocked(const std::shared_ptr<SipTransport>& transport)
812 : {
813 6 : sip_utils::PJDialogLock dialogLock {inviteSession_->dlg};
814 6 : auto* dialog = inviteSession_->dlg;
815 6 : pjsip_tx_data* tdata = nullptr;
816 6 : auto status = pjsip_dlg_create_request(dialog, &pjsip_options_method, dialog->local.cseq++, &tdata);
817 6 : if (status == PJ_SUCCESS) {
818 6 : if (auto account = getSIPAccount())
819 6 : sip_utils::addUserAgentHeader(account->getUserAgentName(), tdata);
820 6 : sip_utils::addHandoverRequest(tdata, recoveryRequest_);
821 12 : sip_utils::addHandoverGeneration(tdata, recoveryGeneration_.load());
822 6 : auto* uri = static_cast<pjsip_sip_uri*>(pjsip_uri_get_uri(tdata->msg->line.req.uri));
823 6 : pj_strdup(tdata->pool, &uri->host, &transport->get()->remote_name.host);
824 6 : uri->port = transport->get()->remote_name.port;
825 6 : pj_strdup(tdata->pool, &tdata->dest_info.name, &transport->get()->remote_name.host);
826 6 : tdata->mod_data[dialog->ua->id] = nullptr;
827 6 : pjsip_tpselector selector {};
828 6 : selector.type = PJSIP_TPSELECTOR_TRANSPORT;
829 6 : selector.u.transport = transport->get();
830 6 : pjsip_tx_data_set_transport(tdata, &selector);
831 6 : status = pjsip_endpt_send_request(Manager::instance().sipVoIPLink().getEndpoint(), tdata, -1, nullptr, nullptr);
832 : }
833 6 : if (status != PJ_SUCCESS)
834 0 : JAMI_ERROR("[call:{}] Unable to announce the new SIP channel: {}", getCallId(), sip_utils::sip_strerror(status));
835 6 : }
836 :
837 : void
838 6 : SIPCall::scheduleRecoveryProbeLocked()
839 : {
840 6 : auto timer = std::make_shared<asio::steady_timer>(*Manager::instance().ioContext(), std::chrono::milliseconds(500));
841 6 : timer->async_wait([w = weak(), timer, candidate = std::weak_ptr(recoveryCandidate_), request = recoveryRequest_]
842 : (const asio::error_code& ec) {
843 6 : if (ec)
844 0 : return;
845 6 : runOnMainThread([w, candidate, request] {
846 6 : if (auto call = w.lock()) {
847 6 : std::lock_guard lk {call->callMutex_};
848 6 : auto transport = candidate.lock();
849 12 : if (not call->recovering_ or not call->waitingForRecoveryChannel_ or not transport
850 12 : or call->recoveryRequest_ != request or call->recoveryCandidate_ != transport)
851 6 : return;
852 0 : if (transport->isConnected()) {
853 0 : call->sendRecoveryProbeLocked(transport);
854 0 : call->scheduleRecoveryProbeLocked();
855 : } else {
856 0 : call->recoveryCandidate_.reset();
857 0 : if (auto account = std::dynamic_pointer_cast<JamiAccount>(call->getAccount().lock()))
858 0 : account->requestCallRecovery(call);
859 : }
860 18 : }
861 : });
862 : });
863 6 : }
864 :
865 : void
866 9 : SIPCall::restartIceAfterRecovery(dhtnet::IceTransportOptions&& options, std::optional<std::uint64_t> generation)
867 : {
868 9 : std::lock_guard lk {callMutex_};
869 18 : if (not recovering_ or waitingForRecoveryChannel_ or not inviteSession_
870 9 : or inviteSession_->state != PJSIP_INV_STATE_CONFIRMED
871 18 : or (generation and *generation != recoveryGeneration_))
872 3 : return;
873 : // Only use the UPnP port mappings already open: waiting for new ones
874 : // would delay the offer by seconds on networks refusing them.
875 6 : options.upnpMappingTimeout = {};
876 6 : recoveryRestartPending_ = false;
877 6 : auto status = SIPSessionReinvite(getMediaAttributeList(), true, std::move(options), true);
878 6 : if (status == PJ_EBUSY)
879 0 : recoveryRestartPending_ = true;
880 6 : else if (status != PJ_SUCCESS)
881 0 : JAMI_ERROR("[call:{}] Could not restart ICE on the recovered SIP channel", getCallId());
882 9 : }
883 :
884 : void
885 0 : SIPCall::retryRecoveryOffer(std::uint64_t generation)
886 : {
887 0 : auto timer = std::make_shared<asio::steady_timer>(*Manager::instance().ioContext(), std::chrono::milliseconds(500));
888 0 : timer->async_wait([w = weak(), timer, generation](const asio::error_code& ec) {
889 0 : if (ec)
890 0 : return;
891 0 : runOnMainThread([w, generation] {
892 0 : if (auto call = w.lock()) {
893 0 : std::lock_guard lk {call->callMutex_};
894 0 : if (call->recovering_ and not call->waitingForRecoveryChannel_
895 0 : and call->recoveryGeneration_ == generation and call->sipTransport_)
896 0 : call->requestIceRestart(call->sipTransport_);
897 0 : }
898 0 : });
899 : });
900 0 : }
901 :
902 : void
903 261 : SIPCall::onInviteTransactionEnded()
904 : {
905 261 : std::shared_ptr<SipTransport> transport;
906 : {
907 261 : std::lock_guard lk {callMutex_};
908 261 : std::optional<sip_utils::PJDialogLock> dialogLock;
909 261 : if (inviteSession_)
910 261 : dialogLock.emplace(inviteSession_->dlg);
911 261 : if (inviteSession_ and not inviteSession_->invite_tsx)
912 522 : mediaNegotiationGeneration_ = recoveryGeneration_.load();
913 273 : if (recovering_ and not waitingForRecoveryChannel_ and inviteSession_
914 12 : and inviteSession_->state == PJSIP_INV_STATE_CONFIRMED and not inviteSession_->invite_tsx
915 273 : and recoveryRestartPending_) {
916 0 : recoveryRestartPending_ = false;
917 0 : transport = sipTransport_;
918 : }
919 261 : }
920 261 : if (transport)
921 0 : requestIceRestart(transport);
922 : else
923 261 : drainPendingRequests();
924 261 : }
925 :
926 : void
927 12 : SIPCall::completeRecoveryIfReadyLocked()
928 : {
929 12 : if (not recovering_ or waitingForRecoveryChannel_ or not recoveryNegotiation_
930 12 : or recoveryNegotiation_->generation != recoveryGeneration_
931 12 : or recoveryNegotiation_->phase != RecoveryNegotiation::Phase::MediaReady
932 24 : or getIceMedia() != recoveryNegotiation_->ice or sipTransport_ != recoveryNegotiation_->transport.lock())
933 0 : return;
934 12 : stopCallRecovery();
935 12 : runOnMainThread([w = weak()] {
936 12 : if (auto call = w.lock())
937 12 : call->drainPendingRequests();
938 12 : });
939 : }
940 :
941 : void
942 965 : SIPCall::stopCallRecovery()
943 : {
944 965 : std::lock_guard lk {callMutex_};
945 965 : recovering_ = false;
946 965 : waitingForRecoveryChannel_ = false;
947 965 : recoveryAttemptPending_ = false;
948 965 : recoveryCandidate_.reset();
949 965 : recoveryRestartPending_ = false;
950 965 : if (recoveryTimer_) {
951 12 : recoveryTimer_->cancel();
952 12 : recoveryTimer_.reset();
953 : }
954 965 : recoveryNegotiation_.reset();
955 965 : }
956 :
957 : bool
958 1067 : SIPCall::isReinviteBusyLocked() const
959 : {
960 1067 : if (recovering_ or isWaitingForIceAndMedia_ or (peerSupportsHandover() and not isCurrentMediaNegotiation()))
961 30 : return true;
962 1037 : if (not inviteSession_)
963 1 : return false;
964 1036 : sip_utils::PJDialogLock dialogLock {inviteSession_->dlg};
965 1036 : return inviteSession_->invite_tsx != nullptr;
966 1036 : }
967 :
968 : void
969 489 : SIPCall::drainPendingRequests()
970 : {
971 945 : auto ready = [this] {
972 1888 : return getConnectionState() == ConnectionState::CONNECTED and inviteSession_
973 1888 : and inviteSession_->state == PJSIP_INV_STATE_CONFIRMED and not isReinviteBusyLocked();
974 489 : };
975 489 : std::function<void()> reply;
976 : {
977 489 : std::lock_guard lk {callMutex_};
978 489 : if (not ready())
979 33 : return;
980 456 : reply = processRemainingRequest();
981 489 : }
982 456 : if (reply)
983 0 : reply();
984 : {
985 456 : std::lock_guard lk {callMutex_};
986 456 : if (not ready() or remainingRequest_ != Request::NoRequest or not pendingMediaChange_)
987 456 : return;
988 0 : auto mediaChange = MediaAttribute::buildMediaAttributesList(*pendingMediaChange_, isSrtpEnabled());
989 0 : for (auto& media : mediaChange)
990 0 : media.hold_ = getState() == CallState::HOLD;
991 0 : pendingMediaChange_.reset();
992 0 : requestMediaChange(MediaAttribute::mediaAttributesToMediaMaps(mediaChange));
993 456 : }
994 489 : }
995 :
996 : void
997 15 : SIPCall::requestReinvite(const std::vector<MediaAttribute>& mediaAttrList, bool needNewIce)
998 : {
999 15 : JAMI_DEBUG("[call:{}] Sending a SIP re-invite to request media change", getCallId());
1000 :
1001 : // No re-INVITE before the call is confirmed: the change stays local.
1002 15 : if (not inviteSession_ or inviteSession_->state != PJSIP_INV_STATE_CONFIRMED)
1003 0 : return;
1004 15 : if (isReinviteBusyLocked()) {
1005 0 : pendingMediaChange_ = MediaAttribute::mediaAttributesToMediaMaps(mediaAttrList);
1006 0 : return;
1007 : }
1008 15 : auto status = SIPSessionReinvite(mediaAttrList, needNewIce);
1009 15 : if (status == PJ_EBUSY)
1010 0 : pendingMediaChange_ = MediaAttribute::mediaAttributesToMediaMaps(mediaAttrList);
1011 15 : else if (status == PJ_SUCCESS and reinvIceMedia_)
1012 15 : isWaitingForIceAndMedia_ = true;
1013 : }
1014 :
1015 : /**
1016 : * Send a reINVITE inside an active dialog to modify its state
1017 : * Local SDP session should be modified before calling this method
1018 : */
1019 : int
1020 38 : SIPCall::SIPSessionReinvite(const std::vector<MediaAttribute>& mediaAttrList,
1021 : bool needNewIce,
1022 : std::optional<dhtnet::IceTransportOptions> iceOptions,
1023 : bool recoveryOffer)
1024 : {
1025 38 : assert(not mediaAttrList.empty());
1026 :
1027 38 : std::lock_guard lk {callMutex_};
1028 :
1029 38 : if (not inviteSession_)
1030 1 : return PJ_EINVALIDOP;
1031 37 : sip_utils::PJDialogLock dialogLock {inviteSession_->dlg};
1032 37 : if (inviteSession_->invite_tsx)
1033 0 : return PJ_EBUSY;
1034 :
1035 37 : JAMI_DEBUG("[call:{}] Preparing and sending a re-invite (state={})",
1036 : getCallId(),
1037 : pjsip_inv_state_name(inviteSession_->state));
1038 37 : JAMI_DEBUG("[call:{}] New ICE required for this re-invite: [{}]", getCallId(), needNewIce ? "Yes" : "No");
1039 :
1040 : // Generate new ports to receive the new media stream
1041 : // LibAV doesn't discriminate SSRCs and will be confused about Seq changes on a given port
1042 37 : generateMediaPorts();
1043 :
1044 37 : sdp_->clearIce();
1045 37 : sdp_->setActiveRemoteSdpSession(nullptr);
1046 37 : sdp_->setActiveLocalSdpSession(nullptr);
1047 :
1048 37 : auto acc = getSIPAccount();
1049 37 : if (not acc) {
1050 0 : JAMI_ERROR("[call:{}] No account detected", getCallId());
1051 0 : return !PJ_SUCCESS;
1052 : }
1053 :
1054 37 : if (recovering_) {
1055 6 : auto newAddress = iceOptions ? iceOptions->accountPublicAddr : dhtnet::IpAddr {};
1056 6 : if (not newAddress and iceOptions)
1057 0 : newAddress = iceOptions->accountLocalAddr;
1058 6 : if (not newAddress)
1059 0 : newAddress = dhtnet::ip_utils::getInterfaceAddr(acc->getLocalInterface(), AF_INET);
1060 6 : if (not newAddress)
1061 0 : newAddress = dhtnet::ip_utils::getInterfaceAddr(acc->getLocalInterface(), AF_INET6);
1062 6 : if (not newAddress) {
1063 0 : JAMI_ERROR("[call:{}] No new local address for ICE recovery", getCallId());
1064 0 : return !PJ_SUCCESS;
1065 : }
1066 6 : sdp_->setPublishedIP(newAddress);
1067 : }
1068 :
1069 74 : mediaNegotiationGeneration_ = recoveryGeneration_.load();
1070 37 : if (not sdp_->createOffer(mediaAttrList))
1071 0 : return !PJ_SUCCESS;
1072 :
1073 37 : if (isIceEnabled() and needNewIce) {
1074 29 : if (not createIceMediaTransport(true) or not initIceMediaTransport(true, std::move(iceOptions))) {
1075 0 : return !PJ_SUCCESS;
1076 : }
1077 29 : addLocalIceAttributes();
1078 : // Media transport changed, must restart the media.
1079 29 : mediaRestartRequired_ = true;
1080 : }
1081 :
1082 : pjsip_tx_data* tdata;
1083 37 : auto* local_sdp = sdp_->getLocalSdpSession();
1084 37 : auto contact = sip_utils::CONST_PJ_STR(contactHeader_);
1085 37 : auto result = pjsip_inv_reinvite(inviteSession_.get(), recovering_ ? &contact : nullptr, local_sdp, &tdata);
1086 37 : if (result == PJ_SUCCESS) {
1087 36 : if (!tdata)
1088 0 : return PJ_SUCCESS;
1089 :
1090 : // Add user-agent header
1091 36 : sip_utils::addUserAgentHeader(acc->getUserAgentName(), tdata);
1092 36 : if (peerSupportsHandover())
1093 34 : sip_utils::addHandoverGeneration(tdata, recoveryGeneration_);
1094 :
1095 36 : result = pjsip_inv_send_msg(inviteSession_.get(), tdata);
1096 36 : if (result == PJ_SUCCESS) {
1097 36 : if (recoveryOffer and recovering_ and needNewIce and not waitingForRecoveryChannel_) {
1098 6 : recoveryNegotiation_ = RecoveryNegotiation {recoveryGeneration_, sipTransport_, getIceMedia()};
1099 : }
1100 36 : return PJ_SUCCESS;
1101 : }
1102 0 : JAMI_ERROR("[call:{}] Failed to send REINVITE msg (pjsip: {})", getCallId(), sip_utils::sip_strerror(result));
1103 : // Canceling internals without sending (anyways the send has just failed!)
1104 0 : pjsip_inv_cancel_reinvite(inviteSession_.get(), &tdata);
1105 : } else
1106 1 : JAMI_ERROR("[call:{}] Failed to create REINVITE msg (pjsip: {})", getCallId(), sip_utils::sip_strerror(result));
1107 :
1108 1 : return !PJ_SUCCESS;
1109 38 : }
1110 :
1111 : int
1112 8 : SIPCall::SIPSessionReinvite()
1113 : {
1114 8 : auto mediaList = getMediaAttributeList();
1115 16 : return SIPSessionReinvite(mediaList, isNewIceMediaRequired(mediaList));
1116 8 : }
1117 :
1118 : void
1119 352 : SIPCall::sendSIPInfo(std::string_view body, std::string_view subtype)
1120 : {
1121 352 : std::lock_guard lk {callMutex_};
1122 352 : if (not inviteSession_ or not inviteSession_->dlg)
1123 0 : throw VoipLinkException("Unable to get invite dialog");
1124 :
1125 352 : constexpr pj_str_t methodName = CONST_PJ_STR("INFO");
1126 352 : constexpr pj_str_t type = CONST_PJ_STR("application");
1127 :
1128 : pjsip_method method;
1129 352 : pjsip_method_init_np(&method, (pj_str_t*) &methodName);
1130 :
1131 : /* Create request message. */
1132 : pjsip_tx_data* tdata;
1133 352 : if (pjsip_dlg_create_request(inviteSession_->dlg, &method, -1, &tdata) != PJ_SUCCESS) {
1134 0 : JAMI_ERROR("[call:{}] Unable to create dialog", getCallId());
1135 0 : return;
1136 : }
1137 :
1138 : /* Create "application/<subtype>" message body. */
1139 352 : pj_str_t content = CONST_PJ_STR(body);
1140 352 : pj_str_t pj_subtype = CONST_PJ_STR(subtype);
1141 352 : tdata->msg->body = pjsip_msg_body_create(tdata->pool, &type, &pj_subtype, &content);
1142 352 : if (tdata->msg->body == NULL)
1143 0 : pjsip_tx_data_dec_ref(tdata);
1144 : else
1145 352 : pjsip_dlg_send_request(inviteSession_->dlg, tdata, Manager::instance().sipVoIPLink().getModId(), NULL);
1146 352 : }
1147 :
1148 : void
1149 19 : SIPCall::updateRecState(bool state)
1150 : {
1151 : std::string BODY = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>"
1152 : "<media_control><vc_primitive><to_encoder>"
1153 : "<recording_state="
1154 38 : + std::to_string(state)
1155 : + "/>"
1156 19 : "</to_encoder></vc_primitive></media_control>";
1157 : // see https://tools.ietf.org/html/rfc5168 for XML Schema for Media Control details
1158 :
1159 19 : JAMI_DEBUG("[call:{}] Sending recording state via SIP INFO", getCallId());
1160 :
1161 : try {
1162 19 : sendSIPInfo(BODY, "media_control+xml");
1163 0 : } catch (const std::exception& e) {
1164 0 : JAMI_ERROR("[call:{}] Error sending recording state: {}", getCallId(), e.what());
1165 0 : }
1166 19 : }
1167 :
1168 : void
1169 274 : SIPCall::requestKeyframe(int streamIdx)
1170 : {
1171 274 : auto now = clock::now();
1172 274 : if ((now - lastKeyFrameReq_) < MS_BETWEEN_2_KEYFRAME_REQUEST and lastKeyFrameReq_ != time_point::min())
1173 5 : return;
1174 :
1175 269 : std::string streamIdPart;
1176 269 : if (streamIdx != -1)
1177 538 : streamIdPart = fmt::format("<stream_id>{}</stream_id>", streamIdx);
1178 : std::string BODY = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>"
1179 : "<media_control><vc_primitive> "
1180 538 : + streamIdPart + "<to_encoder>"
1181 : + "<picture_fast_update/>"
1182 538 : "</to_encoder></vc_primitive></media_control>";
1183 269 : JAMI_DEBUG("[call:{}] Sending video keyframe request via SIP INFO", getCallId());
1184 : try {
1185 269 : sendSIPInfo(BODY, "media_control+xml");
1186 0 : } catch (const std::exception& e) {
1187 0 : JAMI_ERROR("[call:{}] Error sending video keyframe request: {}", getCallId(), e.what());
1188 0 : }
1189 269 : lastKeyFrameReq_ = now;
1190 269 : }
1191 :
1192 : void
1193 5 : SIPCall::sendMuteState(bool state)
1194 : {
1195 : std::string BODY = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>"
1196 : "<media_control><vc_primitive><to_encoder>"
1197 : "<mute_state="
1198 10 : + std::to_string(state)
1199 : + "/>"
1200 5 : "</to_encoder></vc_primitive></media_control>";
1201 : // see https://tools.ietf.org/html/rfc5168 for XML Schema for Media Control details
1202 :
1203 5 : JAMI_DEBUG("[call:{}] Sending mute state via SIP INFO", getCallId());
1204 :
1205 : try {
1206 5 : sendSIPInfo(BODY, "media_control+xml");
1207 0 : } catch (const std::exception& e) {
1208 0 : JAMI_ERROR("[call:{}] Error sending mute state: {}", getCallId(), e.what());
1209 0 : }
1210 5 : }
1211 :
1212 : void
1213 0 : SIPCall::sendVoiceActivity(std::string_view streamId, bool state)
1214 : {
1215 : // dont send streamId if it's -1
1216 0 : std::string streamIdPart = "";
1217 0 : if (streamId != "-1" && !streamId.empty()) {
1218 0 : streamIdPart = fmt::format("<stream_id>{}</stream_id>", streamId);
1219 : }
1220 :
1221 : std::string BODY = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>"
1222 : "<media_control><vc_primitive>"
1223 0 : + streamIdPart
1224 0 : + "<to_encoder>"
1225 0 : "<voice_activity="
1226 0 : + std::to_string(state)
1227 : + "/>"
1228 0 : "</to_encoder></vc_primitive></media_control>";
1229 :
1230 : try {
1231 0 : sendSIPInfo(BODY, "media_control+xml");
1232 0 : } catch (const std::exception& e) {
1233 0 : JAMI_ERROR("[call:{}] Error sending voice activity state: {}", getCallId(), e.what());
1234 0 : }
1235 0 : }
1236 :
1237 : void
1238 1301 : SIPCall::setInviteSession(pjsip_inv_session* inviteSession)
1239 : {
1240 1301 : std::lock_guard lk {callMutex_};
1241 :
1242 1301 : if (inviteSession == nullptr and inviteSession_) {
1243 226 : JAMI_DEBUG("[call:{}] Delete current invite session", getCallId());
1244 1075 : } else if (inviteSession != nullptr) {
1245 : // NOTE: The first reference of the invite session is owned by pjsip. If
1246 : // that counter goes down to zero the invite will be destroyed, and the
1247 : // unique_ptr will point freed datas. To avoid this, we increment the
1248 : // ref counter and let our unique_ptr share the ownership of the session
1249 : // with pjsip.
1250 226 : if (PJ_SUCCESS != pjsip_inv_add_ref(inviteSession)) {
1251 0 : JAMI_WARNING("[call:{}] Attempting to set invalid invite session [{}]",
1252 : getCallId(),
1253 : fmt::ptr(inviteSession));
1254 0 : inviteSession_.reset(nullptr);
1255 0 : return;
1256 : }
1257 226 : JAMI_DEBUG("[call:{}] Set new invite session [{}]", getCallId(), fmt::ptr(inviteSession));
1258 : } else {
1259 : // Nothing to do.
1260 849 : return;
1261 : }
1262 :
1263 452 : inviteSession_.reset(inviteSession);
1264 1301 : }
1265 :
1266 : void
1267 224 : SIPCall::terminateSipSession(int status)
1268 : {
1269 224 : JAMI_DEBUG("[call:{}] Terminate SIP session", getCallId());
1270 224 : std::lock_guard lk {callMutex_};
1271 224 : if (inviteSession_ and inviteSession_->state != PJSIP_INV_STATE_DISCONNECTED) {
1272 113 : pjsip_tx_data* tdata = nullptr;
1273 113 : auto ret = pjsip_inv_end_session(inviteSession_.get(), status, nullptr, &tdata);
1274 113 : if (ret == PJ_SUCCESS) {
1275 113 : if (tdata) {
1276 113 : auto account = getSIPAccount();
1277 113 : if (account) {
1278 113 : sip_utils::addContactHeader(contactHeader_, tdata);
1279 : // Add user-agent header
1280 113 : sip_utils::addUserAgentHeader(account->getUserAgentName(), tdata);
1281 : } else {
1282 0 : JAMI_ERROR("[call:{}] No account detected", getCallId());
1283 : throw std::runtime_error(
1284 0 : fmt::format("[call:{}] The account owning this call is invalid", getCallId()));
1285 : }
1286 :
1287 113 : ret = pjsip_inv_send_msg(inviteSession_.get(), tdata);
1288 113 : if (ret != PJ_SUCCESS)
1289 0 : JAMI_ERROR("[call:{}] Failed to send terminate msg, SIP error ({})",
1290 : getCallId(),
1291 : sip_utils::sip_strerror(ret));
1292 113 : }
1293 : } else
1294 0 : JAMI_ERROR("[call:{}] Failed to terminate INVITE@{}, SIP error ({})",
1295 : getCallId(),
1296 : fmt::ptr(inviteSession_.get()),
1297 : sip_utils::sip_strerror(ret));
1298 : }
1299 224 : setInviteSession();
1300 224 : }
1301 :
1302 : void
1303 103 : SIPCall::answer(const std::vector<libjami::MediaMap>& mediaList)
1304 : {
1305 103 : std::lock_guard lk {callMutex_};
1306 103 : auto account = getSIPAccount();
1307 103 : if (not account) {
1308 0 : JAMI_ERROR("[call:{}] No account detected", getCallId());
1309 0 : return;
1310 : }
1311 :
1312 103 : if (not inviteSession_) {
1313 0 : JAMI_ERROR("[call:{}] No invite session for this call", getCallId());
1314 0 : return;
1315 : }
1316 :
1317 103 : if (not sdp_) {
1318 0 : JAMI_ERROR("[call:{}] No SDP session for this call", getCallId());
1319 0 : return;
1320 : }
1321 :
1322 103 : auto newMediaAttrList = MediaAttribute::buildMediaAttributesList(mediaList, isSrtpEnabled());
1323 :
1324 103 : if (newMediaAttrList.empty() and rtpStreams_.empty()) {
1325 0 : JAMI_ERROR("[call:{}] Media list must not be empty!", getCallId());
1326 0 : return;
1327 : }
1328 :
1329 : // If the media list is empty, use the current media (this could happen
1330 : // with auto-answer for instance), otherwise update the current media.
1331 103 : if (newMediaAttrList.empty()) {
1332 74 : JAMI_DEBUG("[call:{}] Media list is empty, using current media", getCallId());
1333 29 : } else if (newMediaAttrList.size() != rtpStreams_.size()) {
1334 : // This should never happen, as we make sure that the sizes match earlier
1335 : // in handleIncomingConversationCall.
1336 0 : JAMI_ERROR("[call:{:s}] Media list size {:d} in answer does not match. Expected {:d}",
1337 : getCallId(),
1338 : newMediaAttrList.size(),
1339 : rtpStreams_.size());
1340 0 : return;
1341 : }
1342 :
1343 103 : auto const& mediaAttrList = newMediaAttrList.empty() ? getMediaAttributeList() : newMediaAttrList;
1344 :
1345 103 : JAMI_DEBUG("[call:{}] Answering incoming call with following media:", getCallId());
1346 288 : for (size_t idx = 0; idx < mediaAttrList.size(); idx++) {
1347 185 : auto const& mediaAttr = mediaAttrList.at(idx);
1348 185 : JAMI_DEBUG("[call:{:s}] Media @{:d} - {:s}", getCallId(), idx, mediaAttr.toString(true));
1349 : }
1350 :
1351 : // Apply the media attributes.
1352 288 : for (size_t idx = 0; idx < mediaAttrList.size(); idx++) {
1353 185 : updateMediaStream(mediaAttrList[idx], idx);
1354 : }
1355 :
1356 : // Create the SDP answer
1357 103 : sdp_->processIncomingOffer(mediaAttrList);
1358 :
1359 103 : if (isIceEnabled() and remoteHasValidIceAttributes()) {
1360 101 : setupIceResponse();
1361 : }
1362 :
1363 103 : if (not inviteSession_->neg) {
1364 : // We are answering to an INVITE that did not include a media offer (SDP).
1365 : // The SIP specification (RFCs 3261/6337) requires that if a UA wants to
1366 : // proceed with the call, it must provide a media offer (SDP) if the initial
1367 : // INVITE did not offer one. In this case, the SDP offer will be included in
1368 : // the SIP OK (200) answer. The peer UA will then include its SDP answer in
1369 : // the SIP ACK message.
1370 :
1371 : // TODO. This code should be unified with the code used by accounts to create
1372 : // SDP offers.
1373 :
1374 0 : JAMI_WARNING("[call:{}] No negotiator session, peer sent an empty INVITE (without SDP)", getCallId());
1375 :
1376 0 : Manager::instance().sipVoIPLink().createSDPOffer(inviteSession_.get());
1377 :
1378 0 : generateMediaPorts();
1379 :
1380 : // Setup and create ICE offer
1381 0 : if (isIceEnabled()) {
1382 0 : sdp_->clearIce();
1383 0 : sdp_->setActiveRemoteSdpSession(nullptr);
1384 0 : sdp_->setActiveLocalSdpSession(nullptr);
1385 :
1386 0 : auto opts = account->getIceOptions();
1387 :
1388 0 : auto publicAddr = account->getPublishedIpAddress();
1389 :
1390 0 : if (publicAddr) {
1391 0 : opts.accountPublicAddr = publicAddr;
1392 0 : if (auto interfaceAddr = dhtnet::ip_utils::getInterfaceAddr(account->getLocalInterface(),
1393 0 : publicAddr.getFamily())) {
1394 0 : opts.accountLocalAddr = interfaceAddr;
1395 0 : if (createIceMediaTransport(false) and initIceMediaTransport(true, std::move(opts))) {
1396 0 : addLocalIceAttributes();
1397 : }
1398 : } else {
1399 0 : JAMI_WARNING("[call:{}] Unable to init ICE transport, missing local address", getCallId());
1400 : }
1401 : } else {
1402 0 : JAMI_WARNING("[call:{}] Unable to init ICE transport, missing public address", getCallId());
1403 : }
1404 0 : }
1405 : }
1406 :
1407 103 : if (!inviteSession_->last_answer)
1408 0 : throw std::runtime_error("Should only be called for initial answer");
1409 :
1410 : // Set the SIP final answer (200 OK).
1411 : pjsip_tx_data* tdata;
1412 103 : if (pjsip_inv_answer(inviteSession_.get(), PJSIP_SC_OK, NULL, sdp_->getLocalSdpSession(), &tdata) != PJ_SUCCESS)
1413 0 : throw std::runtime_error("Unable to init invite request answer (200 OK)");
1414 :
1415 103 : if (contactHeader_.empty()) {
1416 0 : throw std::runtime_error("Unable to answer with an invalid contact header");
1417 : }
1418 :
1419 103 : JAMI_DEBUG("[call:{}] Answering with contact header: {}", getCallId(), contactHeader_);
1420 :
1421 103 : sip_utils::addContactHeader(contactHeader_, tdata);
1422 :
1423 : // Add user-agent header
1424 103 : sip_utils::addUserAgentHeader(account->getUserAgentName(), tdata);
1425 103 : if (std::dynamic_pointer_cast<JamiAccount>(account))
1426 94 : sip_utils::addCallHandoverHeader(tdata);
1427 :
1428 103 : if (pjsip_inv_send_msg(inviteSession_.get(), tdata) != PJ_SUCCESS) {
1429 0 : setInviteSession();
1430 0 : throw std::runtime_error("Unable to send invite request answer (200 OK)");
1431 : }
1432 :
1433 103 : setState(CallState::ACTIVE, ConnectionState::CONNECTED);
1434 103 : }
1435 :
1436 : void
1437 35 : SIPCall::answerMediaChangeRequest(const std::vector<libjami::MediaMap>& mediaList, bool isRemote)
1438 : {
1439 35 : std::lock_guard lk {callMutex_};
1440 35 : if (peerSupportsHandover() and not isCurrentMediaNegotiation()) {
1441 0 : rejectPendingReinviteLocked();
1442 0 : return;
1443 : }
1444 :
1445 35 : auto account = getSIPAccount();
1446 35 : if (not account) {
1447 0 : JAMI_ERROR("[call:{}] No account detected", getCallId());
1448 0 : return;
1449 : }
1450 :
1451 35 : auto mediaAttrList = MediaAttribute::buildMediaAttributesList(mediaList, isSrtpEnabled());
1452 :
1453 : // TODO. is the right place?
1454 : // Disable video if disabled in the account.
1455 35 : if (not account->isVideoEnabled()) {
1456 0 : for (auto& mediaAttr : mediaAttrList) {
1457 0 : if (mediaAttr.type_ == MediaType::MEDIA_VIDEO) {
1458 0 : mediaAttr.enabled_ = false;
1459 : }
1460 : }
1461 : }
1462 :
1463 35 : if (mediaAttrList.empty()) {
1464 0 : JAMI_WARNING("[call:{}] Media list is empty. Ignoring the media change request", getCallId());
1465 0 : return;
1466 : }
1467 :
1468 35 : if (not sdp_) {
1469 0 : JAMI_ERROR("[call:{}] No valid SDP session", getCallId());
1470 0 : return;
1471 : }
1472 :
1473 35 : JAMI_DEBUG("[call:{}] Current media", getCallId());
1474 35 : unsigned idx = 0;
1475 97 : for (auto const& rtp : rtpStreams_) {
1476 62 : JAMI_DEBUG("[call:{}] Media @{}: {}", getCallId(), idx++, rtp.mediaAttribute_->toString(true));
1477 : }
1478 :
1479 35 : JAMI_DEBUG("[call:{}] Answering to media change request with new media", getCallId());
1480 35 : idx = 0;
1481 102 : for (auto const& newMediaAttr : mediaAttrList) {
1482 67 : JAMI_DEBUG("[call:{}] Media @{}: {}", getCallId(), idx++, newMediaAttr.toString(true));
1483 : }
1484 :
1485 35 : if (!updateAllMediaStreams(mediaAttrList, isRemote))
1486 0 : return;
1487 :
1488 35 : if (not sdp_->processIncomingOffer(mediaAttrList)) {
1489 0 : JAMI_WARNING("[call:{}] Unable to process the new offer, ignoring", getCallId());
1490 0 : return;
1491 : }
1492 :
1493 35 : if (not sdp_->getRemoteSdpSession()) {
1494 0 : JAMI_ERROR("[call:{}] No valid remote SDP session", getCallId());
1495 0 : return;
1496 : }
1497 :
1498 35 : if (isIceEnabled() and remoteHasValidIceAttributes()) {
1499 27 : JAMI_WARNING("[call:{}] Requesting a new ICE media", getCallId());
1500 : // Answering a network change must not wait for new UPnP port mappings.
1501 27 : setupIceResponse(true, recovering_ or peerMovedInReinvite_.exchange(false));
1502 : }
1503 :
1504 35 : if (not sdp_->startNegotiation()) {
1505 0 : JAMI_ERROR("[call:{}] Unable to start media negotiation for a re-invite request", getCallId());
1506 0 : return;
1507 : }
1508 :
1509 35 : if (pjsip_inv_set_sdp_answer(inviteSession_.get(), sdp_->getLocalSdpSession()) != PJ_SUCCESS) {
1510 0 : JAMI_ERROR("[call:{}] Unable to start media negotiation for a re-invite request", getCallId());
1511 0 : return;
1512 : }
1513 :
1514 : pjsip_tx_data* tdata;
1515 35 : if (pjsip_inv_answer(inviteSession_.get(), PJSIP_SC_OK, NULL, NULL, &tdata) != PJ_SUCCESS) {
1516 6 : JAMI_ERROR("[call:{}] Unable to init answer to a re-invite request", getCallId());
1517 6 : return;
1518 : }
1519 :
1520 29 : if (not contactHeader_.empty()) {
1521 29 : sip_utils::addContactHeader(contactHeader_, tdata);
1522 : }
1523 :
1524 : // Add user-agent header
1525 29 : sip_utils::addUserAgentHeader(account->getUserAgentName(), tdata);
1526 :
1527 29 : if (pjsip_inv_send_msg(inviteSession_.get(), tdata) != PJ_SUCCESS) {
1528 0 : JAMI_ERROR("[call:{}] Unable to send answer to a re-invite request", getCallId());
1529 0 : setInviteSession();
1530 0 : return;
1531 : }
1532 :
1533 29 : JAMI_DEBUG("[call:{}] Successfully answered the media change request", getCallId());
1534 47 : }
1535 :
1536 : void
1537 146 : SIPCall::hangup(int code)
1538 : {
1539 146 : std::lock_guard lk {callMutex_};
1540 146 : pendingRecord_ = false;
1541 146 : if (inviteSession_ and inviteSession_->dlg) {
1542 117 : pjsip_route_hdr* route = inviteSession_->dlg->route_set.next;
1543 117 : while (route and route != &inviteSession_->dlg->route_set) {
1544 : char buf[1024];
1545 0 : int printed = pjsip_hdr_print_on(route, buf, sizeof(buf));
1546 0 : if (printed >= 0) {
1547 0 : buf[printed] = '\0';
1548 0 : JAMI_DEBUG("[call:{}] Route header {}", getCallId(), buf);
1549 : }
1550 0 : route = route->next;
1551 : }
1552 :
1553 117 : int status = PJSIP_SC_OK;
1554 117 : if (code)
1555 2 : status = code;
1556 115 : else if (inviteSession_->state <= PJSIP_INV_STATE_EARLY and inviteSession_->role != PJSIP_ROLE_UAC)
1557 1 : status = PJSIP_SC_CALL_TSX_DOES_NOT_EXIST;
1558 114 : else if (inviteSession_->state >= PJSIP_INV_STATE_DISCONNECTED)
1559 6 : status = PJSIP_SC_DECLINE;
1560 :
1561 : // Notify the peer
1562 117 : terminateSipSession(status);
1563 : }
1564 :
1565 : // Stop all RTP streams
1566 146 : stopAllMedia();
1567 146 : detachAudioFromConference();
1568 146 : setState(Call::ConnectionState::DISCONNECTED, code);
1569 146 : dht::ThreadPool::io().run([w = weak(), code] {
1570 146 : if (auto shared = w.lock())
1571 146 : shared->removeCall(code);
1572 146 : });
1573 146 : }
1574 :
1575 : void
1576 259 : SIPCall::detachAudioFromConference()
1577 : {
1578 : #ifdef ENABLE_VIDEO
1579 259 : if (auto conf = getConference()) {
1580 0 : if (auto mixer = conf->getVideoMixer()) {
1581 0 : for (auto& stream : getRtpSessionList(MediaType::MEDIA_AUDIO)) {
1582 0 : mixer->removeAudioOnlySource(getCallId(), stream->streamId());
1583 0 : }
1584 0 : }
1585 259 : }
1586 : #endif
1587 259 : }
1588 :
1589 : void
1590 2 : SIPCall::refuse()
1591 : {
1592 2 : if (!isIncoming() or getConnectionState() == ConnectionState::CONNECTED or !inviteSession_)
1593 0 : return;
1594 :
1595 2 : stopAllMedia();
1596 :
1597 : // Notify the peer
1598 2 : terminateSipSession(PJSIP_SC_DECLINE);
1599 :
1600 2 : setState(Call::ConnectionState::DISCONNECTED, PJSIP_SC_DECLINE);
1601 2 : removeCall();
1602 : }
1603 :
1604 : static void
1605 5 : transfer_client_cb(pjsip_evsub* sub, pjsip_event* event)
1606 : {
1607 5 : auto mod_ua_id = Manager::instance().sipVoIPLink().getModId();
1608 :
1609 5 : switch (pjsip_evsub_get_state(sub)) {
1610 2 : case PJSIP_EVSUB_STATE_ACCEPTED:
1611 2 : if (!event)
1612 0 : return;
1613 :
1614 2 : pj_assert(event->type == PJSIP_EVENT_TSX_STATE && event->body.tsx_state.type == PJSIP_EVENT_RX_MSG);
1615 2 : break;
1616 :
1617 1 : case PJSIP_EVSUB_STATE_TERMINATED:
1618 1 : pjsip_evsub_set_mod_data(sub, mod_ua_id, NULL);
1619 1 : break;
1620 :
1621 0 : case PJSIP_EVSUB_STATE_ACTIVE: {
1622 0 : if (!event)
1623 0 : return;
1624 :
1625 0 : pjsip_rx_data* r_data = event->body.rx_msg.rdata;
1626 :
1627 0 : if (!r_data)
1628 0 : return;
1629 :
1630 0 : std::string request(pjsip_rx_data_get_info(r_data));
1631 :
1632 0 : pjsip_status_line status_line = {500, *pjsip_get_status_text(500)};
1633 :
1634 0 : if (!r_data->msg_info.msg)
1635 0 : return;
1636 :
1637 0 : if (r_data->msg_info.msg->line.req.method.id == PJSIP_OTHER_METHOD
1638 0 : and request.find("NOTIFY") != std::string::npos) {
1639 0 : pjsip_msg_body* body = r_data->msg_info.msg->body;
1640 :
1641 0 : if (!body)
1642 0 : return;
1643 :
1644 0 : if (pj_stricmp2(&body->content_type.type, "message") or pj_stricmp2(&body->content_type.subtype, "sipfrag"))
1645 0 : return;
1646 :
1647 0 : if (pjsip_parse_status_line((char*) body->data, body->len, &status_line) != PJ_SUCCESS)
1648 0 : return;
1649 : }
1650 :
1651 0 : if (!r_data->msg_info.cid)
1652 0 : return;
1653 :
1654 0 : auto* call = static_cast<SIPCall*>(pjsip_evsub_get_mod_data(sub, mod_ua_id));
1655 0 : if (!call)
1656 0 : return;
1657 :
1658 0 : if (status_line.code / 100 == 2) {
1659 0 : if (call->inviteSession_)
1660 0 : call->terminateSipSession(PJSIP_SC_GONE);
1661 0 : Manager::instance().hangupCall(call->getAccountId(), call->getCallId());
1662 0 : pjsip_evsub_set_mod_data(sub, mod_ua_id, NULL);
1663 : }
1664 :
1665 0 : break;
1666 0 : }
1667 :
1668 2 : case PJSIP_EVSUB_STATE_NULL:
1669 : case PJSIP_EVSUB_STATE_SENT:
1670 : case PJSIP_EVSUB_STATE_PENDING:
1671 : case PJSIP_EVSUB_STATE_UNKNOWN:
1672 : default:
1673 2 : break;
1674 : }
1675 : }
1676 :
1677 : bool
1678 2 : SIPCall::transferCommon(const pj_str_t* dst)
1679 : {
1680 2 : if (not inviteSession_ or not inviteSession_->dlg)
1681 0 : return false;
1682 :
1683 : pjsip_evsub_user xfer_cb;
1684 2 : pj_bzero(&xfer_cb, sizeof(xfer_cb));
1685 2 : xfer_cb.on_evsub_state = &transfer_client_cb;
1686 :
1687 : pjsip_evsub* sub;
1688 :
1689 2 : if (pjsip_xfer_create_uac(inviteSession_->dlg, &xfer_cb, &sub) != PJ_SUCCESS)
1690 0 : return false;
1691 :
1692 : /* Associate this VoIPLink of call with the client subscription
1693 : * We are unable to just associate call with the client subscription
1694 : * because after this function, we are unable to find the corresponding
1695 : * VoIPLink from the call any more. But the VoIPLink is useful!
1696 : */
1697 2 : pjsip_evsub_set_mod_data(sub, Manager::instance().sipVoIPLink().getModId(), this);
1698 :
1699 : /*
1700 : * Create REFER request.
1701 : */
1702 : pjsip_tx_data* tdata;
1703 :
1704 2 : if (pjsip_xfer_initiate(sub, dst, &tdata) != PJ_SUCCESS)
1705 0 : return false;
1706 :
1707 : /* Send. */
1708 2 : if (pjsip_xfer_send_request(sub, tdata) != PJ_SUCCESS)
1709 0 : return false;
1710 :
1711 2 : return true;
1712 : }
1713 :
1714 : void
1715 2 : SIPCall::transfer(const std::string& to)
1716 : {
1717 2 : auto account = getSIPAccount();
1718 2 : if (!account) {
1719 0 : JAMI_ERROR("[call:{}] No account detected", getCallId());
1720 0 : return;
1721 : }
1722 :
1723 2 : deinitRecorder();
1724 2 : if (Call::isRecording())
1725 0 : stopRecording();
1726 :
1727 2 : std::string toUri = account->getToUri(to);
1728 2 : const pj_str_t dst(CONST_PJ_STR(toUri));
1729 :
1730 2 : JAMI_DEBUG("[call:{}] Transferring to {}", getCallId(), std::string_view(dst.ptr, dst.slen));
1731 :
1732 2 : if (!transferCommon(&dst))
1733 0 : throw VoipLinkException("Unable to transfer");
1734 2 : }
1735 :
1736 : bool
1737 0 : SIPCall::attendedTransfer(const std::string& to)
1738 : {
1739 0 : auto toCall = Manager::instance().callFactory.getCall<SIPCall>(to);
1740 0 : if (!toCall)
1741 0 : return false;
1742 :
1743 0 : if (not toCall->inviteSession_ or not toCall->inviteSession_->dlg)
1744 0 : return false;
1745 :
1746 0 : pjsip_dialog* target_dlg = toCall->inviteSession_->dlg;
1747 0 : pjsip_uri* uri = (pjsip_uri*) pjsip_uri_get_uri(target_dlg->remote.info->uri);
1748 :
1749 0 : char str_dest_buf[PJSIP_MAX_URL_SIZE * 2] = {'<'};
1750 0 : pj_str_t dst = {str_dest_buf, 1};
1751 :
1752 0 : dst.slen += pjsip_uri_print(PJSIP_URI_IN_REQ_URI, uri, str_dest_buf + 1, sizeof(str_dest_buf) - 1);
1753 0 : dst.slen += pj_ansi_snprintf(str_dest_buf + dst.slen,
1754 0 : sizeof(str_dest_buf) - dst.slen,
1755 : "?"
1756 : "Replaces=%.*s"
1757 : "%%3Bto-tag%%3D%.*s"
1758 : "%%3Bfrom-tag%%3D%.*s>",
1759 0 : (int) target_dlg->call_id->id.slen,
1760 0 : target_dlg->call_id->id.ptr,
1761 0 : (int) target_dlg->remote.info->tag.slen,
1762 0 : target_dlg->remote.info->tag.ptr,
1763 0 : (int) target_dlg->local.info->tag.slen,
1764 0 : target_dlg->local.info->tag.ptr);
1765 :
1766 0 : return transferCommon(&dst);
1767 0 : }
1768 :
1769 : bool
1770 8 : SIPCall::hold(OnReadyCb&& cb)
1771 : {
1772 8 : std::unique_lock lk {callMutex_};
1773 8 : if (isReinviteBusyLocked()) {
1774 0 : holdCb_ = std::move(cb);
1775 0 : remainingRequest_ = Request::Hold;
1776 0 : return false;
1777 : }
1778 :
1779 8 : auto result = hold();
1780 :
1781 8 : lk.unlock();
1782 8 : if (cb)
1783 8 : cb(result);
1784 :
1785 8 : return result;
1786 8 : }
1787 :
1788 : bool
1789 8 : SIPCall::hold()
1790 : {
1791 8 : if (getConnectionState() != ConnectionState::CONNECTED) {
1792 0 : JAMI_WARNING("[call:{}] Not connected, ignoring hold request", getCallId());
1793 0 : return false;
1794 : }
1795 :
1796 8 : if (not setState(CallState::HOLD)) {
1797 0 : JAMI_WARNING("[call:{}] Failed to set state to HOLD", getCallId());
1798 0 : return false;
1799 : }
1800 :
1801 8 : stopAllMedia();
1802 :
1803 23 : for (auto& stream : rtpStreams_) {
1804 15 : stream.mediaAttribute_->hold_ = true;
1805 : }
1806 :
1807 8 : auto status = SIPSessionReinvite();
1808 8 : if (status == PJ_EBUSY) {
1809 : // An INVITE transaction started meanwhile: tell the peer once it ends.
1810 0 : remainingRequest_ = Request::Hold;
1811 0 : return true;
1812 : }
1813 8 : if (status != PJ_SUCCESS) {
1814 0 : JAMI_WARNING("[call:{}] Reinvite failed", getCallId());
1815 0 : return false;
1816 : }
1817 :
1818 : // TODO. Do we need to check for reinvIceMedia_ ?
1819 8 : isWaitingForIceAndMedia_ = (reinvIceMedia_ != nullptr);
1820 :
1821 8 : JAMI_DEBUG("[call:{}] Set state to HOLD", getCallId());
1822 8 : return true;
1823 : }
1824 :
1825 : bool
1826 3 : SIPCall::resume(OnReadyCb&& cb)
1827 : {
1828 3 : std::unique_lock lk {callMutex_};
1829 3 : if (isReinviteBusyLocked()) {
1830 0 : JAMI_DEBUG("[call:{}] ICE negotiation in progress. Resume request will be once ICE "
1831 : "negotiation completes",
1832 : getCallId());
1833 0 : resumeCb_ = std::move(cb);
1834 0 : remainingRequest_ = Request::Resume;
1835 0 : return false;
1836 : }
1837 3 : JAMI_DEBUG("[call:{}] Resuming the call", getCallId());
1838 3 : auto result = resume();
1839 :
1840 3 : lk.unlock();
1841 3 : if (cb)
1842 3 : cb(result);
1843 :
1844 3 : return result;
1845 3 : }
1846 :
1847 : bool
1848 3 : SIPCall::resume()
1849 : {
1850 3 : auto account = getSIPAccount();
1851 3 : if (!account) {
1852 0 : JAMI_ERROR("[call:{}] No account detected", getCallId());
1853 0 : return false;
1854 : }
1855 :
1856 3 : bool success = false;
1857 : try {
1858 3 : success = internalResume([] {});
1859 0 : } catch (const SdpException& e) {
1860 0 : JAMI_ERROR("[call:{}] {}", getCallId(), e.what());
1861 0 : throw VoipLinkException("SDP issue in resume");
1862 0 : }
1863 :
1864 : // Only wait for ICE if we have an ICE re-invite in progress
1865 3 : isWaitingForIceAndMedia_ = success and remainingRequest_ != Request::Resume and (reinvIceMedia_ != nullptr);
1866 :
1867 3 : return success;
1868 3 : }
1869 :
1870 : bool
1871 3 : SIPCall::internalResume(const std::function<void()>& sdp_cb)
1872 : {
1873 3 : if (getConnectionState() != ConnectionState::CONNECTED) {
1874 0 : JAMI_WARNING("[call:{}] Not connected, ignoring resume request", getCallId());
1875 : }
1876 :
1877 3 : if (not setState(CallState::ACTIVE))
1878 0 : return false;
1879 :
1880 3 : sdp_cb();
1881 :
1882 : {
1883 8 : for (auto& stream : rtpStreams_) {
1884 5 : stream.mediaAttribute_->hold_ = false;
1885 : }
1886 : // For now, call resume will always require new ICE negotiation.
1887 3 : auto status = SIPSessionReinvite(getMediaAttributeList(), true);
1888 3 : if (status == PJ_EBUSY) {
1889 : // An INVITE transaction started meanwhile: tell the peer once it ends.
1890 0 : remainingRequest_ = Request::Resume;
1891 0 : return true;
1892 : }
1893 3 : if (status != PJ_SUCCESS) {
1894 0 : JAMI_WARNING("[call:{}] Resuming hold", getCallId());
1895 0 : if (isWaitingForIceAndMedia_) {
1896 0 : remainingRequest_ = Request::Hold;
1897 : } else {
1898 0 : hold();
1899 : }
1900 0 : return false;
1901 : }
1902 : }
1903 :
1904 3 : return true;
1905 : }
1906 :
1907 : void
1908 6 : SIPCall::switchInput(const std::string& source)
1909 : {
1910 6 : std::lock_guard lk {callMutex_};
1911 6 : JAMI_DEBUG("[call:{}] Set selected source to {}", getCallId(), source);
1912 :
1913 16 : for (auto const& stream : rtpStreams_) {
1914 10 : auto mediaAttr = stream.mediaAttribute_;
1915 10 : mediaAttr->sourceUri_ = source;
1916 10 : }
1917 :
1918 : // Check if the call is being recorded in order to continue
1919 : // … the recording after the switch
1920 6 : bool isRec = Call::isRecording();
1921 :
1922 6 : if (isReinviteBusyLocked()) {
1923 0 : remainingRequest_ = Request::SwitchInput;
1924 : } else {
1925 : // For now, switchInput will always trigger a re-invite
1926 : // with new ICE session.
1927 6 : auto status = SIPSessionReinvite(getMediaAttributeList(), true);
1928 6 : if (status == PJ_EBUSY)
1929 0 : remainingRequest_ = Request::SwitchInput;
1930 6 : else if (status == PJ_SUCCESS and reinvIceMedia_)
1931 4 : isWaitingForIceAndMedia_ = true;
1932 : }
1933 6 : if (isRec) {
1934 0 : readyToRecord_ = false;
1935 0 : pendingRecord_ = true;
1936 : }
1937 6 : }
1938 :
1939 : void
1940 113 : SIPCall::peerHungup()
1941 : {
1942 113 : pendingRecord_ = false;
1943 : // Stop all RTP streams
1944 113 : stopAllMedia();
1945 :
1946 113 : if (inviteSession_)
1947 105 : terminateSipSession(PJSIP_SC_NOT_FOUND);
1948 113 : detachAudioFromConference();
1949 113 : Call::peerHungup();
1950 113 : }
1951 :
1952 : void
1953 0 : SIPCall::carryingDTMFdigits(char code)
1954 : {
1955 0 : int duration = Manager::instance().voipPreferences.getPulseLength();
1956 : char dtmf_body[1000];
1957 : int ret;
1958 :
1959 : // handle flash code
1960 0 : if (code == '!') {
1961 0 : ret = snprintf(dtmf_body, sizeof dtmf_body - 1, "Signal=16\r\nDuration=%d\r\n", duration);
1962 : } else {
1963 0 : ret = snprintf(dtmf_body, sizeof dtmf_body - 1, "Signal=%c\r\nDuration=%d\r\n", code, duration);
1964 : }
1965 :
1966 : try {
1967 0 : sendSIPInfo({dtmf_body, (size_t) ret}, "dtmf-relay");
1968 0 : } catch (const std::exception& e) {
1969 0 : JAMI_ERROR("[call:{}] Error sending DTMF: {}", getCallId(), e.what());
1970 0 : }
1971 0 : }
1972 :
1973 : void
1974 59 : SIPCall::setVideoOrientation(int streamIdx, int rotation)
1975 : {
1976 59 : std::string streamIdPart;
1977 59 : if (streamIdx != -1)
1978 118 : streamIdPart = fmt::format("<stream_id>{}</stream_id>", streamIdx);
1979 : std::string sip_body = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>"
1980 : "<media_control><vc_primitive><to_encoder>"
1981 : "<device_orientation="
1982 118 : + std::to_string(-rotation) + "/>" + "</to_encoder>" + streamIdPart
1983 59 : + "</vc_primitive></media_control>";
1984 :
1985 59 : JAMI_DEBUG("[call:{}] Sending device orientation via SIP INFO {} for stream {}", getCallId(), rotation, streamIdx);
1986 :
1987 59 : sendSIPInfo(sip_body, "media_control+xml");
1988 59 : }
1989 :
1990 : void
1991 269 : SIPCall::sendTextMessage(const std::map<std::string, std::string>& messages, const std::string& from)
1992 : {
1993 : // TODO: for now we ignore the "from" (the previous implementation for sending this info was
1994 : // buggy and verbose), another way to send the original message sender will be implemented
1995 : // in the future
1996 269 : if (not subcalls_.empty()) {
1997 0 : pendingOutMessages_.emplace_back(messages, from);
1998 0 : for (auto& c : subcalls_)
1999 0 : c->sendTextMessage(messages, from);
2000 : } else {
2001 269 : if (inviteSession_) {
2002 : try {
2003 : // Ignore if the peer does not allow "MESSAGE" SIP method
2004 : // NOTE:
2005 : // The SIP "Allow" header is not mandatory as per RFC-3261. If it's
2006 : // not present and since "MESSAGE" method is an extention method,
2007 : // we choose to assume that the peer does not support the "MESSAGE"
2008 : // method to prevent unexpected behavior when interoperating with
2009 : // some SIP implementations.
2010 269 : if (not isSipMethodAllowedByPeer(sip_utils::SIP_METHODS::MESSAGE)) {
2011 0 : JAMI_WARNING("[call:{}] Peer does not allow \"{}\" method",
2012 : getCallId(),
2013 : sip_utils::SIP_METHODS::MESSAGE);
2014 :
2015 : // Print peer's allowed methods
2016 0 : std::lock_guard methodsLock {peerAllowedMethodsMutex_};
2017 0 : JAMI_LOG("[call:{}] Peer's allowed methods: {}", getCallId(), peerAllowedMethods_);
2018 0 : return;
2019 0 : }
2020 :
2021 269 : im::sendSipMessage(inviteSession_.get(), messages);
2022 :
2023 0 : } catch (...) {
2024 0 : JAMI_ERROR("[call:{}] Failed to send SIP text message", getCallId());
2025 0 : }
2026 : } else {
2027 0 : pendingOutMessages_.emplace_back(messages, from);
2028 0 : JAMI_ERROR("[call:{}] sendTextMessage: no invite session for this call", getCallId());
2029 : }
2030 : }
2031 : }
2032 :
2033 : void
2034 432 : SIPCall::removeCall(int code)
2035 : {
2036 : #ifdef ENABLE_PLUGIN
2037 432 : jami::Manager::instance().getJamiPluginManager().getCallServicesManager().clearCallHandlerMaps(getCallId());
2038 : #endif
2039 432 : std::lock_guard lk {callMutex_};
2040 432 : stopCallRecovery();
2041 432 : JAMI_DEBUG("[call:{}] removeCall()", getCallId());
2042 432 : if (sdp_) {
2043 348 : sdp_->setActiveLocalSdpSession(nullptr);
2044 348 : sdp_->setActiveRemoteSdpSession(nullptr);
2045 : }
2046 432 : Call::removeCall(code);
2047 :
2048 : {
2049 432 : std::lock_guard lk(transportMtx_);
2050 432 : resetTransport(std::move(iceMedia_));
2051 432 : resetTransport(std::move(reinvIceMedia_));
2052 432 : }
2053 :
2054 432 : setInviteSession();
2055 432 : setSipTransport({});
2056 432 : }
2057 :
2058 : void
2059 102 : SIPCall::onFailure(int code)
2060 : {
2061 102 : stopCallRecovery();
2062 102 : if (setState(CallState::MERROR, ConnectionState::DISCONNECTED, code)) {
2063 101 : runOnMainThread([w = weak(), code] {
2064 101 : if (auto shared = w.lock()) {
2065 98 : auto& call = *shared;
2066 98 : Manager::instance().callFailure(call);
2067 98 : call.removeCall(code);
2068 101 : }
2069 101 : });
2070 : }
2071 102 : }
2072 :
2073 : void
2074 2 : SIPCall::onBusyHere()
2075 : {
2076 2 : if (getCallType() == CallType::OUTGOING)
2077 1 : setState(CallState::PEER_BUSY, ConnectionState::DISCONNECTED);
2078 : else
2079 1 : setState(CallState::BUSY, ConnectionState::DISCONNECTED);
2080 :
2081 2 : runOnMainThread([w = weak()] {
2082 2 : if (auto shared = w.lock()) {
2083 0 : auto& call = *shared;
2084 0 : Manager::instance().callBusy(call);
2085 0 : call.removeCall();
2086 2 : }
2087 2 : });
2088 2 : }
2089 :
2090 : void
2091 111 : SIPCall::onClosed()
2092 : {
2093 111 : runOnMainThread([w = weak()] {
2094 111 : if (auto shared = w.lock()) {
2095 109 : auto& call = *shared;
2096 109 : Manager::instance().peerHungupCall(call);
2097 109 : call.removeCall();
2098 111 : }
2099 111 : });
2100 111 : }
2101 :
2102 : void
2103 204 : SIPCall::onAnswered()
2104 : {
2105 204 : JAMI_WARNING("[call:{}] onAnswered()", getCallId());
2106 204 : runOnMainThread([w = weak()] {
2107 204 : if (auto shared = w.lock()) {
2108 204 : if (shared->getConnectionState() != ConnectionState::CONNECTED) {
2109 103 : shared->setState(CallState::ACTIVE, ConnectionState::CONNECTED);
2110 103 : if (not shared->isSubcall()) {
2111 19 : Manager::instance().peerAnsweredCall(*shared);
2112 : }
2113 : }
2114 204 : }
2115 204 : });
2116 204 : }
2117 :
2118 : void
2119 268 : SIPCall::sendKeyframe(int streamIdx)
2120 : {
2121 : #ifdef ENABLE_VIDEO
2122 268 : dht::ThreadPool::io().run([w = weak(), streamIdx] {
2123 268 : if (auto sthis = w.lock()) {
2124 268 : JAMI_DEBUG("[call:{}] Handling picture fast update request", sthis->getCallId());
2125 268 : std::vector<std::shared_ptr<RtpSession>> sessions;
2126 : {
2127 268 : std::lock_guard lk {sthis->callMutex_};
2128 268 : if (streamIdx == -1)
2129 0 : sessions = sthis->getRtpSessionList(MediaType::MEDIA_VIDEO);
2130 268 : else if (streamIdx > -1 && streamIdx < static_cast<int>(sthis->rtpStreams_.size()))
2131 268 : sessions.emplace_back(sthis->rtpStreams_[streamIdx].rtpSession_);
2132 268 : }
2133 : // Without the call mutex: a video session starting its sender holds
2134 : // its own mutex while the client may call back into the call.
2135 536 : for (const auto& session : sessions) {
2136 268 : if (session && session->getMediaType() == MediaType::MEDIA_VIDEO)
2137 268 : std::static_pointer_cast<video::VideoRtpSession>(session)->forceKeyFrame();
2138 : }
2139 536 : }
2140 268 : });
2141 : #endif
2142 268 : }
2143 :
2144 : bool
2145 1231 : SIPCall::isIceEnabled() const
2146 : {
2147 1231 : return enableIce_;
2148 : }
2149 :
2150 : void
2151 537 : SIPCall::setPeerUaVersion(std::string_view ua)
2152 : {
2153 537 : if (peerUserAgent_ == ua or ua.empty()) {
2154 : // Silently ignore if it did not change or empty.
2155 313 : return;
2156 : }
2157 :
2158 225 : if (peerUserAgent_.empty()) {
2159 225 : JAMI_DEBUG("[call:{}] Set peer's User-Agent to [{}]", getCallId(), ua);
2160 0 : } else if (not peerUserAgent_.empty()) {
2161 : // Unlikely, but should be handled since we don't have control over the peer.
2162 : // Even if it's unexpected, we still attempt to parse the UA version.
2163 0 : JAMI_WARNING("[call:{}] Peer's User-Agent unexpectedly changed from [{}] to [{}]",
2164 : getCallId(),
2165 : peerUserAgent_,
2166 : ua);
2167 : }
2168 :
2169 225 : peerUserAgent_ = ua;
2170 :
2171 : // User-agent parsing
2172 225 : constexpr std::string_view PACK_NAME(PACKAGE_NAME " ");
2173 225 : auto pos = ua.find(PACK_NAME);
2174 225 : if (pos == std::string_view::npos) {
2175 : // Must have the expected package name.
2176 1 : JAMI_WARNING("[call:{}] Unable to find the expected package name in peer's User-Agent", getCallId());
2177 1 : return;
2178 : }
2179 :
2180 224 : ua = ua.substr(pos + PACK_NAME.length());
2181 :
2182 224 : std::string_view version;
2183 : // Unstable (un-released) versions has a hyphen + commit ID after
2184 : // the version number. Find the commit ID if any, and ignore it.
2185 224 : pos = ua.find('-');
2186 224 : if (pos != std::string_view::npos) {
2187 : // Get the version and ignore the commit ID.
2188 0 : version = ua.substr(0, pos);
2189 : } else {
2190 : // Extract the version number.
2191 224 : pos = ua.find(' ');
2192 224 : if (pos != std::string_view::npos) {
2193 224 : version = ua.substr(0, pos);
2194 : }
2195 : }
2196 :
2197 224 : if (version.empty()) {
2198 0 : JAMI_DEBUG("[call:{}] Unable to parse peer's version", getCallId());
2199 0 : return;
2200 : }
2201 :
2202 224 : auto peerVersion = split_string_to_unsigned(version, '.');
2203 224 : if (peerVersion.size() > 4u) {
2204 0 : JAMI_WARNING("[call:{}] Unable to parse peer's version", getCallId());
2205 0 : return;
2206 : }
2207 :
2208 : // Check if peer's version is at least 10.0.2 to enable multi-stream.
2209 224 : peerSupportMultiStream_ = Account::meetMinimumRequiredVersion(peerVersion, MULTISTREAM_REQUIRED_VERSION);
2210 224 : if (not peerSupportMultiStream_) {
2211 0 : JAMI_DEBUG("Peer's version [{}] does not support multi-stream. "
2212 : "Min required version: [{}]",
2213 : version,
2214 : MULTISTREAM_REQUIRED_VERSION_STR);
2215 : }
2216 :
2217 : // Check if peer's version is at least 13.11.0 to enable multi-audio-stream.
2218 224 : peerSupportMultiAudioStream_ = Account::meetMinimumRequiredVersion(peerVersion, MULTIAUDIO_REQUIRED_VERSION);
2219 224 : if (not peerSupportMultiAudioStream_) {
2220 0 : JAMI_DEBUG("Peer's version [{}] does not support multi-audio-stream. "
2221 : "Min required version: [{}]",
2222 : version,
2223 : MULTIAUDIO_REQUIRED_VERSION_STR);
2224 : }
2225 :
2226 : // Check if peer's version is at least 13.3.0 to enable multi-ICE.
2227 224 : peerSupportMultiIce_ = Account::meetMinimumRequiredVersion(peerVersion, MULTIICE_REQUIRED_VERSION);
2228 224 : if (not peerSupportMultiIce_) {
2229 0 : JAMI_DEBUG("Peer's version [{}] does not support more than 2 ICE media streams. "
2230 : "Min required version: [{}]",
2231 : version,
2232 : MULTIICE_REQUIRED_VERSION_STR);
2233 : }
2234 :
2235 : // Check if peer's version supports re-invite without ICE renegotiation.
2236 224 : peerSupportReuseIceInReinv_ = Account::meetMinimumRequiredVersion(peerVersion,
2237 : REUSE_ICE_IN_REINVITE_REQUIRED_VERSION);
2238 224 : if (not peerSupportReuseIceInReinv_) {
2239 0 : JAMI_LOG("Peer's version [{:s}] does not support re-invite without ICE renegotiation. "
2240 : "Min required version: [{:s}]",
2241 : version,
2242 : REUSE_ICE_IN_REINVITE_REQUIRED_VERSION_STR);
2243 : }
2244 224 : }
2245 :
2246 : void
2247 332 : SIPCall::setPeerAllowMethods(std::vector<std::string> methods)
2248 : {
2249 332 : std::lock_guard lock {peerAllowedMethodsMutex_};
2250 332 : peerAllowedMethods_ = std::move(methods);
2251 332 : }
2252 :
2253 : bool
2254 269 : SIPCall::isSipMethodAllowedByPeer(const std::string_view method) const
2255 : {
2256 269 : std::lock_guard lock {peerAllowedMethodsMutex_};
2257 :
2258 538 : return std::find(peerAllowedMethods_.begin(), peerAllowedMethods_.end(), method) != peerAllowedMethods_.end();
2259 269 : }
2260 :
2261 : void
2262 225 : SIPCall::onPeerRinging()
2263 : {
2264 225 : JAMI_DEBUG("[call:{}] Peer ringing", getCallId());
2265 225 : setState(ConnectionState::RINGING);
2266 225 : }
2267 :
2268 : void
2269 270 : SIPCall::addLocalIceAttributes()
2270 : {
2271 270 : if (not isIceEnabled())
2272 2 : return;
2273 :
2274 268 : auto iceMedia = getIceMedia();
2275 :
2276 268 : if (not iceMedia) {
2277 0 : JAMI_ERROR("[call:{}] Invalid ICE instance", getCallId());
2278 0 : return;
2279 : }
2280 :
2281 268 : auto start = std::chrono::steady_clock::now();
2282 :
2283 268 : if (not iceMedia->isInitialized()) {
2284 162 : JAMI_DEBUG("[call:{}] Waiting for ICE initialization", getCallId());
2285 : // we need an initialized ICE to progress further
2286 162 : if (iceMedia->waitForInitialization(DEFAULT_ICE_INIT_TIMEOUT) <= 0) {
2287 0 : JAMI_ERROR("[call:{}] ICE initialization timed out", getCallId());
2288 0 : return;
2289 : }
2290 : // ICE initialization may take longer than usual in some cases,
2291 : // for instance when TURN servers do not respond in time (DNS
2292 : // resolution or other issues).
2293 162 : auto duration = std::chrono::steady_clock::now() - start;
2294 162 : if (duration > EXPECTED_ICE_INIT_MAX_TIME) {
2295 0 : JAMI_WARNING("[call:{:s}] ICE initialization time was unexpectedly high ({})",
2296 : getCallId(),
2297 : std::chrono::duration_cast<std::chrono::milliseconds>(duration));
2298 : }
2299 : }
2300 :
2301 : // Check the state of ICE instance, the initialization may have failed.
2302 268 : if (not iceMedia->isInitialized()) {
2303 0 : JAMI_ERROR("[call:{}] ICE session is uninitialized", getCallId());
2304 0 : return;
2305 : }
2306 :
2307 : // Check the state, the call might have been canceled while waiting.
2308 : // for initialization.
2309 268 : if (getState() == Call::CallState::OVER) {
2310 0 : JAMI_WARNING("[call:{}] The call was terminated while waiting for ICE initialization", getCallId());
2311 0 : return;
2312 : }
2313 :
2314 268 : auto account = getSIPAccount();
2315 268 : if (not account) {
2316 0 : JAMI_ERROR("[call:{}] No account detected", getCallId());
2317 0 : return;
2318 : }
2319 268 : if (not sdp_) {
2320 0 : JAMI_ERROR("[call:{}] No sdp detected", getCallId());
2321 0 : return;
2322 : }
2323 :
2324 268 : JAMI_DEBUG("[call:{}] Add local attributes for ICE instance [{}]", getCallId(), fmt::ptr(iceMedia.get()));
2325 :
2326 268 : sdp_->addIceAttributes(iceMedia->getLocalAttributes());
2327 :
2328 268 : if (account->isIceCompIdRfc5245Compliant()) {
2329 2 : unsigned streamIdx = 0;
2330 6 : for (auto const& stream : rtpStreams_) {
2331 4 : if (not stream.mediaAttribute_->enabled_) {
2332 : // Dont add ICE candidates if the media is disabled
2333 0 : JAMI_DEBUG("[call:{}] Media [{}] @ {} is disabled, don't add local candidates",
2334 : getCallId(),
2335 : stream.mediaAttribute_->toString(),
2336 : streamIdx);
2337 0 : continue;
2338 : }
2339 4 : JAMI_DEBUG("[call:{}] Add ICE local candidates for media [{}] @ {}",
2340 : getCallId(),
2341 : stream.mediaAttribute_->toString(),
2342 : streamIdx);
2343 : // RTP
2344 4 : sdp_->addIceCandidates(streamIdx, iceMedia->getLocalCandidates(streamIdx, ICE_COMP_ID_RTP));
2345 : // RTCP if it has its own port
2346 4 : if (not rtcpMuxEnabled_) {
2347 4 : sdp_->addIceCandidates(streamIdx, iceMedia->getLocalCandidates(streamIdx, ICE_COMP_ID_RTP + 1));
2348 : }
2349 :
2350 4 : streamIdx++;
2351 : }
2352 : } else {
2353 266 : unsigned idx = 0;
2354 266 : unsigned compId = 1;
2355 753 : for (auto const& stream : rtpStreams_) {
2356 487 : if (not stream.mediaAttribute_->enabled_) {
2357 : // Skipping local ICE candidates if the media is disabled
2358 3 : continue;
2359 : }
2360 484 : JAMI_DEBUG("[call:{}] Add ICE local candidates for media [{}] @ {}",
2361 : getCallId(),
2362 : stream.mediaAttribute_->toString(),
2363 : idx);
2364 : // RTP
2365 484 : sdp_->addIceCandidates(idx, iceMedia->getLocalCandidates(compId));
2366 484 : compId++;
2367 :
2368 : // RTCP if it has its own port
2369 484 : if (not rtcpMuxEnabled_) {
2370 484 : sdp_->addIceCandidates(idx, iceMedia->getLocalCandidates(compId));
2371 484 : compId++;
2372 : }
2373 :
2374 484 : idx++;
2375 : }
2376 : }
2377 268 : }
2378 :
2379 : std::vector<IceCandidate>
2380 249 : SIPCall::getAllRemoteCandidates(dhtnet::IceTransport& transport) const
2381 : {
2382 249 : std::vector<IceCandidate> rem_candidates;
2383 710 : for (unsigned mediaIdx = 0; mediaIdx < static_cast<unsigned>(rtpStreams_.size()); mediaIdx++) {
2384 : IceCandidate cand;
2385 6373 : for (auto& line : sdp_->getIceCandidates(mediaIdx)) {
2386 5912 : if (transport.parseIceAttributeLine(mediaIdx, line, cand)) {
2387 5912 : JAMI_DEBUG("[call:{}] Add remote ICE candidate: {}", getCallId(), line);
2388 5912 : rem_candidates.emplace_back(std::move(cand));
2389 : }
2390 461 : }
2391 : }
2392 249 : return rem_candidates;
2393 0 : }
2394 :
2395 : std::shared_ptr<SystemCodecInfo>
2396 209 : SIPCall::getVideoCodec() const
2397 : {
2398 : #ifdef ENABLE_VIDEO
2399 : // Return first video codec as we negotiate only one codec for the call
2400 : // Note: with multistream we can negotiate codecs/stream, but it's not the case
2401 : // in practice (same for audio), so just return the first video codec.
2402 209 : for (const auto& videoRtp : getRtpSessionList(MediaType::MEDIA_VIDEO))
2403 209 : return videoRtp->getCodec();
2404 : #endif
2405 35 : return {};
2406 : }
2407 :
2408 : std::shared_ptr<SystemCodecInfo>
2409 0 : SIPCall::getAudioCodec() const
2410 : {
2411 : // Return first video codec as we negotiate only one codec for the call
2412 0 : for (const auto& audioRtp : getRtpSessionList(MediaType::MEDIA_AUDIO))
2413 0 : return audioRtp->getCodec();
2414 0 : return {};
2415 : }
2416 :
2417 : void
2418 760 : SIPCall::addMediaStream(const MediaAttribute& mediaAttr)
2419 : {
2420 : // Create and add the media stream with the provided attribute.
2421 : // Do not create the RTP sessions yet.
2422 760 : RtpStream stream;
2423 760 : stream.mediaAttribute_ = std::make_shared<MediaAttribute>(mediaAttr);
2424 :
2425 : // Set default media source if empty. Kept for backward compatibility.
2426 : #ifdef ENABLE_VIDEO
2427 760 : if (stream.mediaAttribute_->type_ == MediaType::MEDIA_VIDEO && stream.mediaAttribute_->sourceUri_.empty()) {
2428 325 : if (auto* videoManager = Manager::instance().getVideoManager()) {
2429 325 : stream.mediaAttribute_->sourceUri_ = videoManager->videoDeviceMonitor.getMRLForDefaultDevice();
2430 : }
2431 : }
2432 : #endif
2433 :
2434 760 : rtpStreams_.emplace_back(std::move(stream));
2435 760 : }
2436 :
2437 : size_t
2438 419 : SIPCall::initMediaStreams(const std::vector<MediaAttribute>& mediaAttrList)
2439 : {
2440 1169 : for (size_t idx = 0; idx < mediaAttrList.size(); idx++) {
2441 750 : auto const& mediaAttr = mediaAttrList.at(idx);
2442 750 : if (mediaAttr.type_ != MEDIA_AUDIO && mediaAttr.type_ != MEDIA_VIDEO) {
2443 0 : JAMI_ERROR("[call:{}] Unexpected media type {}", getCallId(), static_cast<int>(mediaAttr.type_));
2444 0 : assert(false);
2445 : }
2446 :
2447 750 : addMediaStream(mediaAttr);
2448 750 : auto& stream = rtpStreams_.back();
2449 : try {
2450 750 : createRtpSession(stream);
2451 750 : JAMI_DEBUG("[call:{:s}] Added media @{:d}: {:s}", getCallId(), idx, stream.mediaAttribute_->toString(true));
2452 0 : } catch (const std::exception& e) {
2453 0 : JAMI_ERROR("[call:{:s}] Failed to create RTP session for media @{:d}: {:s}. Ignoring the media",
2454 : getCallId(),
2455 : idx,
2456 : e.what());
2457 0 : rtpStreams_.pop_back();
2458 0 : }
2459 : }
2460 :
2461 419 : JAMI_DEBUG("[call:{:s}] Created {:d} media stream(s)", getCallId(), rtpStreams_.size());
2462 :
2463 419 : return rtpStreams_.size();
2464 : }
2465 :
2466 : bool
2467 1672 : SIPCall::hasVideo() const
2468 : {
2469 : #ifdef ENABLE_VIDEO
2470 4782 : std::function<bool(const RtpStream& stream)> videoCheck = [](auto const& stream) {
2471 3110 : bool validVideo = stream.mediaAttribute_ && stream.mediaAttribute_->hasValidVideo();
2472 3110 : bool validRemoteVideo = stream.remoteMediaAttribute_ && stream.remoteMediaAttribute_->hasValidVideo();
2473 3110 : return validVideo || validRemoteVideo;
2474 1672 : };
2475 :
2476 1672 : const auto iter = std::find_if(rtpStreams_.begin(), rtpStreams_.end(), videoCheck);
2477 :
2478 3344 : return iter != rtpStreams_.end();
2479 : #else
2480 : return false;
2481 : #endif
2482 1672 : }
2483 :
2484 : bool
2485 792 : SIPCall::isCaptureDeviceMuted(const MediaType& mediaType) const
2486 : {
2487 : // Return true only if all media of type 'mediaType' that use capture devices
2488 : // source, are muted.
2489 2702 : std::function<bool(const RtpStream& stream)> mutedCheck = [&mediaType](auto const& stream) {
2490 1118 : return (stream.mediaAttribute_->type_ == mediaType and not stream.mediaAttribute_->muted_);
2491 792 : };
2492 792 : const auto iter = std::find_if(rtpStreams_.begin(), rtpStreams_.end(), mutedCheck);
2493 1584 : return iter == rtpStreams_.end();
2494 792 : }
2495 :
2496 : void
2497 217 : SIPCall::setupNegotiatedMedia()
2498 : {
2499 217 : JAMI_DEBUG("[call:{}] Updating negotiated media", getCallId());
2500 :
2501 217 : if (not sipTransport_ or not sdp_) {
2502 0 : JAMI_ERROR("[call:{}] Call is in an invalid state", getCallId());
2503 0 : return;
2504 : }
2505 :
2506 217 : auto slots = sdp_->getMediaSlots();
2507 217 : bool peer_hold {true};
2508 217 : int streamIdx = -1;
2509 :
2510 621 : for (const auto& slot : slots) {
2511 404 : streamIdx++;
2512 404 : const auto& local = slot.first;
2513 404 : const auto& remote = slot.second;
2514 :
2515 : // Skip disabled media
2516 404 : if (not local.enabled) {
2517 4 : JAMI_DEBUG("[call:{}] [SDP:slot#{}] The media is disabled, skipping", getCallId(), streamIdx);
2518 4 : continue;
2519 : }
2520 :
2521 400 : if (static_cast<size_t>(streamIdx) >= rtpStreams_.size()) {
2522 0 : throw std::runtime_error("Stream index is out-of-range");
2523 : }
2524 :
2525 400 : auto const& rtpStream = rtpStreams_[streamIdx];
2526 :
2527 400 : if (not rtpStream.mediaAttribute_) {
2528 0 : throw std::runtime_error("Missing media attribute");
2529 : }
2530 :
2531 : // To enable a media, it must be enabled on both sides.
2532 400 : rtpStream.mediaAttribute_->enabled_ = local.enabled and remote.enabled;
2533 :
2534 400 : if (not rtpStream.rtpSession_)
2535 0 : throw std::runtime_error("Must have a valid RTP session");
2536 :
2537 400 : if (local.type != MEDIA_AUDIO && local.type != MEDIA_VIDEO) {
2538 0 : JAMI_ERROR("[call:{}] Unexpected media type {}", getCallId(), static_cast<int>(local.type));
2539 0 : throw std::runtime_error("Invalid media attribute");
2540 : }
2541 :
2542 400 : if (local.type != remote.type) {
2543 0 : JAMI_ERROR("[call:{}] [SDP:slot#{}] Inconsistent media type between local and remote",
2544 : getCallId(),
2545 : streamIdx);
2546 0 : continue;
2547 : }
2548 :
2549 400 : if (local.enabled and not local.codec) {
2550 0 : JAMI_WARNING("[call:{}] [SDP:slot#{}] Missing local codec", getCallId(), streamIdx);
2551 0 : continue;
2552 : }
2553 :
2554 400 : if (remote.enabled and not remote.codec) {
2555 0 : JAMI_WARNING("[call:{}] [SDP:slot#{}] Missing remote codec", getCallId(), streamIdx);
2556 0 : continue;
2557 : }
2558 :
2559 400 : if (isSrtpEnabled() and local.enabled and not local.crypto) {
2560 0 : JAMI_WARNING("[call:{}] [SDP:slot#{}] Secure mode but no local crypto attributes. "
2561 : "Ignoring the media",
2562 : getCallId(),
2563 : streamIdx);
2564 0 : continue;
2565 : }
2566 :
2567 400 : if (isSrtpEnabled() and remote.enabled and not remote.crypto) {
2568 0 : JAMI_WARNING("[call:{}] [SDP:slot#{}] Secure mode but no crypto remote attributes. "
2569 : "Ignoring the media",
2570 : getCallId(),
2571 : streamIdx);
2572 0 : continue;
2573 : }
2574 :
2575 : // Aggregate hold info over all remote streams
2576 400 : peer_hold &= remote.hold;
2577 :
2578 400 : configureRtpSession(rtpStream.rtpSession_, rtpStream.mediaAttribute_, local, remote);
2579 : }
2580 :
2581 : // TODO. Do we really use this?
2582 217 : if (not isSubcall() and peerHold_ != peer_hold) {
2583 0 : peerHold_ = peer_hold;
2584 0 : emitSignal<libjami::CallSignal::PeerHold>(getCallId(), peerHold_);
2585 : }
2586 217 : }
2587 :
2588 : void
2589 217 : SIPCall::startAllMedia()
2590 : {
2591 217 : JAMI_DEBUG("[call:{}] Starting all media", getCallId());
2592 :
2593 217 : if (not sipTransport_ or not sdp_) {
2594 0 : JAMI_ERROR("[call:{}] The call is in invalid state", getCallId());
2595 0 : return;
2596 : }
2597 :
2598 217 : if (isSrtpEnabled() && not sipTransport_->isSecure()) {
2599 18 : JAMI_WARNING("[call:{}] Crypto (SRTP) is negotiated over an insecure signaling transport", getCallId());
2600 : }
2601 :
2602 : // reset
2603 217 : readyToRecord_ = false;
2604 :
2605 : // Not restarting media loop on hold as it's a huge waste of CPU resources
2606 217 : if (getState() != CallState::HOLD) {
2607 217 : bool iceRunning = isIceRunning();
2608 217 : auto remoteMediaList = Sdp::getMediaAttributeListFromSdp(sdp_->getActiveRemoteSdpSession());
2609 217 : size_t idx = 0;
2610 621 : for (auto& rtpStream : rtpStreams_) {
2611 404 : if (not rtpStream.mediaAttribute_) {
2612 0 : throw std::runtime_error("Missing media attribute");
2613 : }
2614 404 : if (idx >= remoteMediaList.size()) {
2615 0 : JAMI_ERROR("[call:{}] Remote media list smaller than streams (idx={}, size={})",
2616 : getCallId(),
2617 : idx,
2618 : remoteMediaList.size());
2619 0 : break;
2620 : }
2621 404 : rtpStream.remoteMediaAttribute_ = std::make_shared<MediaAttribute>(remoteMediaList[idx]);
2622 404 : if (not rtpStream.mediaAttribute_->enabled_) {
2623 2 : JAMI_DEBUG("[call:{}] Skipping start for disabled stream @{}", getCallId(), idx);
2624 2 : idx++;
2625 2 : continue;
2626 : }
2627 402 : if (rtpStream.remoteMediaAttribute_->type_ == MediaType::MEDIA_VIDEO) {
2628 181 : rtpStream.rtpSession_->setMuted(rtpStream.remoteMediaAttribute_->muted_, RtpSession::Direction::RECV);
2629 : }
2630 804 : dht::ThreadPool::io().run(
2631 1206 : [w = weak(),
2632 : idx,
2633 402 : isVideo = rtpStream.remoteMediaAttribute_->type_ == MediaType::MEDIA_VIDEO,
2634 : iceRunning,
2635 402 : rtpSession = rtpStream.rtpSession_,
2636 : rtpSocketPair
2637 : = std::make_shared<std::pair<std::unique_ptr<dhtnet::IceSocket>, std::unique_ptr<dhtnet::IceSocket>>>(
2638 402 : std::move(rtpStream.rtpSocket_), std::move(rtpStream.rtcpSocket_))]() mutable {
2639 : try {
2640 402 : if (iceRunning) {
2641 396 : rtpSession->start(std::move(rtpSocketPair->first), std::move(rtpSocketPair->second));
2642 : } else {
2643 6 : rtpSession->start(nullptr, nullptr);
2644 : }
2645 402 : if (isVideo) {
2646 181 : if (auto call = w.lock())
2647 181 : call->requestKeyframe(static_cast<int>(idx));
2648 : }
2649 : #ifdef ENABLE_PLUGIN
2650 402 : if (auto call = w.lock()) {
2651 : // Create AVStreams associated with the call
2652 402 : call->createCallAVStreams();
2653 402 : }
2654 : #endif
2655 0 : } catch (const std::exception& e) {
2656 0 : JAMI_ERROR("[call:{}] Failed to start RTP session {}: {}",
2657 : w.lock() ? w.lock()->getCallId() : "unknown",
2658 : idx,
2659 : e.what());
2660 0 : }
2661 402 : });
2662 402 : idx++;
2663 : }
2664 217 : }
2665 :
2666 : // Media is restarted, we can process the last hold request.
2667 217 : isWaitingForIceAndMedia_ = false;
2668 217 : mediaRestartRequired_ = false;
2669 217 : runOnMainThread([w = weak()] {
2670 217 : if (auto call = w.lock())
2671 217 : call->drainPendingRequests();
2672 217 : });
2673 : }
2674 :
2675 : std::function<void()>
2676 456 : SIPCall::processRemainingRequest()
2677 : {
2678 456 : std::function<void()> reply;
2679 456 : switch (std::exchange(remainingRequest_, Request::NoRequest)) {
2680 0 : case Request::Hold: {
2681 0 : auto result = hold();
2682 0 : if (auto cb = std::exchange(holdCb_, nullptr))
2683 0 : reply = [cb = std::move(cb), result] {
2684 0 : cb(result);
2685 0 : };
2686 0 : break;
2687 : }
2688 0 : case Request::Resume: {
2689 0 : auto result = resume();
2690 0 : if (auto cb = std::exchange(resumeCb_, nullptr))
2691 0 : reply = [cb = std::move(cb), result] {
2692 0 : cb(result);
2693 0 : };
2694 0 : break;
2695 : }
2696 0 : case Request::SwitchInput: {
2697 0 : auto status = SIPSessionReinvite(getMediaAttributeList(), true);
2698 0 : if (status == PJ_EBUSY)
2699 0 : remainingRequest_ = Request::SwitchInput;
2700 0 : else if (status == PJ_SUCCESS and reinvIceMedia_)
2701 0 : isWaitingForIceAndMedia_ = true;
2702 0 : break;
2703 : }
2704 456 : default:
2705 456 : break;
2706 : }
2707 456 : return reply;
2708 0 : }
2709 :
2710 : void
2711 0 : SIPCall::restartMediaSender()
2712 : {
2713 0 : JAMI_DEBUG("[call:{}] Restarting TX media streams", getCallId());
2714 0 : for (const auto& rtpSession : getRtpSessionList())
2715 0 : rtpSession->restartSender();
2716 0 : }
2717 :
2718 : void
2719 486 : SIPCall::stopAllMedia()
2720 : {
2721 486 : JAMI_DEBUG("[call:{}] Stopping all media", getCallId());
2722 :
2723 : #ifdef ENABLE_VIDEO
2724 : {
2725 486 : std::lock_guard lk(sinksMtx_);
2726 486 : for (auto it = callSinksMap_.begin(); it != callSinksMap_.end();) {
2727 0 : for (const auto& videoRtp : getRtpSessionList(MediaType::MEDIA_VIDEO)) {
2728 0 : auto& videoReceive = std::static_pointer_cast<video::VideoRtpSession>(videoRtp)->getVideoReceive();
2729 0 : if (videoReceive) {
2730 0 : auto& sink = videoReceive->getSink();
2731 0 : sink->detach(it->second.get());
2732 : }
2733 0 : }
2734 0 : it->second->stop();
2735 0 : it = callSinksMap_.erase(it);
2736 : }
2737 486 : }
2738 : #endif
2739 : // Stop all RTP sessions in parallel
2740 486 : std::mutex mtx;
2741 486 : std::condition_variable cv;
2742 486 : unsigned int stoppedCount = 0;
2743 486 : unsigned int totalStreams = rtpStreams_.size();
2744 :
2745 1385 : for (const auto& rtpSession : getRtpSessionList()) {
2746 899 : dht::ThreadPool::io().run([&, rtpSession]() {
2747 : try {
2748 899 : rtpSession->stop();
2749 0 : } catch (const std::exception& e) {
2750 0 : JAMI_ERROR("Failed to stop RTP session: {}", e.what());
2751 0 : }
2752 :
2753 899 : std::lock_guard lk(mtx);
2754 899 : stoppedCount++;
2755 899 : cv.notify_one();
2756 899 : });
2757 486 : }
2758 :
2759 : // Wait for all streams to be stopped
2760 486 : std::unique_lock lk(mtx);
2761 1866 : cv.wait(lk, [&] { return stoppedCount == totalStreams; });
2762 :
2763 : #ifdef ENABLE_PLUGIN
2764 : {
2765 486 : clearCallAVStreams();
2766 486 : std::lock_guard lk(avStreamsMtx_);
2767 486 : Manager::instance().getJamiPluginManager().getCallServicesManager().clearAVSubject(getCallId());
2768 486 : }
2769 : #endif
2770 486 : }
2771 :
2772 : void
2773 4 : SIPCall::muteMedia(const std::string& mediaType, bool mute)
2774 : {
2775 4 : auto type = MediaAttribute::stringToMediaType(mediaType);
2776 :
2777 4 : if (type == MediaType::MEDIA_AUDIO) {
2778 2 : JAMI_WARNING("[call:{}] {} all audio media", getCallId(), mute ? "muting " : "unmuting ");
2779 :
2780 2 : } else if (type == MediaType::MEDIA_VIDEO) {
2781 2 : JAMI_WARNING("[call:{}] {} all video media", getCallId(), mute ? "muting" : "unmuting");
2782 : } else {
2783 0 : JAMI_ERROR("[call:{}] Invalid media type {}", getCallId(), mediaType);
2784 0 : assert(false);
2785 : }
2786 :
2787 : // Get the current media attributes.
2788 4 : auto mediaList = getMediaAttributeList();
2789 :
2790 : // Mute/Unmute all medias with matching type.
2791 12 : for (auto& mediaAttr : mediaList) {
2792 8 : if (mediaAttr.type_ == type) {
2793 4 : mediaAttr.muted_ = mute;
2794 : }
2795 : }
2796 :
2797 : // Apply
2798 4 : requestMediaChange(MediaAttribute::mediaAttributesToMediaMaps(mediaList));
2799 4 : }
2800 :
2801 : void
2802 417 : SIPCall::updateMediaStream(const MediaAttribute& newMediaAttr, size_t streamIdx)
2803 : {
2804 417 : assert(streamIdx < rtpStreams_.size());
2805 :
2806 417 : auto const& rtpStream = rtpStreams_[streamIdx];
2807 417 : assert(rtpStream.rtpSession_);
2808 :
2809 417 : auto const& mediaAttr = rtpStream.mediaAttribute_;
2810 417 : assert(mediaAttr);
2811 :
2812 417 : bool notifyMute = false;
2813 :
2814 417 : if (newMediaAttr.muted_ == mediaAttr->muted_) {
2815 : // Nothing to do. Already in the desired state.
2816 404 : JAMI_DEBUG("[call:{}] [{}] already {}",
2817 : getCallId(),
2818 : mediaAttr->label_,
2819 : mediaAttr->muted_ ? "muted " : "unmuted ");
2820 :
2821 : } else {
2822 : // Update
2823 13 : mediaAttr->muted_ = newMediaAttr.muted_;
2824 13 : notifyMute = true;
2825 13 : JAMI_DEBUG("[call:{}] {} [{}]", getCallId(), mediaAttr->muted_ ? "muting" : "unmuting", mediaAttr->label_);
2826 : }
2827 :
2828 : // Only update source and type if actually set.
2829 417 : if (not newMediaAttr.sourceUri_.empty())
2830 13 : mediaAttr->sourceUri_ = newMediaAttr.sourceUri_;
2831 :
2832 417 : if (notifyMute and mediaAttr->type_ == MediaType::MEDIA_AUDIO) {
2833 5 : rtpStream.rtpSession_->setMediaSource(mediaAttr->sourceUri_);
2834 5 : rtpStream.rtpSession_->setMuted(mediaAttr->muted_);
2835 5 : sendMuteState(mediaAttr->muted_);
2836 5 : if (not isSubcall())
2837 5 : emitSignal<libjami::CallSignal::AudioMuted>(getCallId(), mediaAttr->muted_);
2838 5 : return;
2839 : }
2840 :
2841 : #ifdef ENABLE_VIDEO
2842 412 : if (notifyMute and mediaAttr->type_ == MediaType::MEDIA_VIDEO) {
2843 8 : rtpStream.rtpSession_->setMediaSource(mediaAttr->sourceUri_);
2844 8 : rtpStream.rtpSession_->setMuted(mediaAttr->muted_);
2845 :
2846 8 : if (not isSubcall())
2847 8 : emitSignal<libjami::CallSignal::VideoMuted>(getCallId(), mediaAttr->muted_);
2848 : }
2849 : #endif
2850 : }
2851 :
2852 : bool
2853 128 : SIPCall::updateAllMediaStreams(const std::vector<MediaAttribute>& mediaAttrList, bool isRemote)
2854 : {
2855 128 : JAMI_DEBUG("[call:{}] New local media", getCallId());
2856 :
2857 128 : if (mediaAttrList.size() > PJ_ICE_MAX_COMP / 2) {
2858 0 : JAMI_DEBUG("[call:{:s}] Too many media streams, limit it ({:d} vs {:d})",
2859 : getCallId().c_str(),
2860 : mediaAttrList.size(),
2861 : PJ_ICE_MAX_COMP);
2862 0 : return false;
2863 : }
2864 :
2865 128 : unsigned idx = 0;
2866 370 : for (auto const& newMediaAttr : mediaAttrList) {
2867 242 : JAMI_DEBUG("[call:{}] Media @{}: {}", getCallId(), idx++, newMediaAttr.toString(true));
2868 : }
2869 :
2870 128 : JAMI_DEBUG("[call:{}] Updating local media streams", getCallId());
2871 :
2872 370 : for (auto const& newAttr : mediaAttrList) {
2873 242 : auto streamIdx = findRtpStreamIndex(newAttr.label_);
2874 :
2875 242 : if (streamIdx < 0) {
2876 : // Media does not exist, add a new one.
2877 10 : addMediaStream(newAttr);
2878 10 : auto& stream = rtpStreams_.back();
2879 : // If the remote asks for a new stream, our side sends nothing
2880 10 : stream.mediaAttribute_->muted_ = isRemote ? true : stream.mediaAttribute_->muted_;
2881 : try {
2882 10 : createRtpSession(stream);
2883 10 : JAMI_DEBUG("[call:{:s}] Added a new media stream @{:d}: {:s}",
2884 : getCallId(),
2885 : idx,
2886 : stream.mediaAttribute_->toString(true));
2887 0 : } catch (const std::exception& e) {
2888 0 : JAMI_ERROR("[call:{:s}] Failed to create RTP session for media @{:d}: {:s}. Ignoring the media",
2889 : getCallId(),
2890 : idx,
2891 : e.what());
2892 0 : rtpStreams_.pop_back();
2893 0 : }
2894 : } else {
2895 232 : updateMediaStream(newAttr, streamIdx);
2896 : }
2897 : }
2898 :
2899 : // If the new media list is smaller than the number of existing streams, that means some streams have been removed
2900 : // We need to clean them up
2901 128 : if (mediaAttrList.size() < rtpStreams_.size()) {
2902 : #ifdef ENABLE_VIDEO
2903 2 : for (auto i = mediaAttrList.size(); i < rtpStreams_.size(); ++i) {
2904 : // Clean up video streams that are absent from the new media list
2905 1 : auto& stream = rtpStreams_[i];
2906 1 : if (stream.rtpSession_->getMediaType() == MediaType::MEDIA_VIDEO)
2907 1 : std::static_pointer_cast<video::VideoRtpSession>(stream.rtpSession_)->exitConference();
2908 : }
2909 : #endif
2910 2 : for (auto i = mediaAttrList.size(); i < rtpStreams_.size(); ++i) {
2911 : // Clean up audio streams that are absent from the new media list
2912 1 : auto& stream = rtpStreams_[i];
2913 1 : if (stream.rtpSession_->getMediaType() == MediaType::MEDIA_AUDIO) {
2914 0 : JAMI_WARNING("[call:{}] Audio stream {} absent from new media list, stopping RTP session",
2915 : getCallId(),
2916 : stream.rtpSession_->streamId());
2917 0 : std::static_pointer_cast<AudioRtpSession>(stream.rtpSession_)->stop();
2918 : }
2919 : }
2920 1 : rtpStreams_.resize(mediaAttrList.size());
2921 : }
2922 128 : return true;
2923 : }
2924 :
2925 : bool
2926 93 : SIPCall::isReinviteRequired(const std::vector<MediaAttribute>& mediaAttrList)
2927 : {
2928 93 : if (mediaAttrList.size() != rtpStreams_.size())
2929 6 : return true;
2930 :
2931 239 : for (auto const& newAttr : mediaAttrList) {
2932 161 : auto streamIdx = findRtpStreamIndex(newAttr.label_);
2933 :
2934 161 : if (streamIdx < 0) {
2935 : // Always needs a re-invite when a new media is added.
2936 0 : return true;
2937 : }
2938 :
2939 : // Changing the source needs a re-invite
2940 161 : if (newAttr.sourceUri_ != rtpStreams_[streamIdx].mediaAttribute_->sourceUri_) {
2941 2 : return true;
2942 : }
2943 :
2944 : #ifdef ENABLE_VIDEO
2945 159 : if (newAttr.type_ == MediaType::MEDIA_VIDEO) {
2946 : // For now, only video mute triggers a re-invite.
2947 : // Might be done for audio as well if required.
2948 72 : if (newAttr.muted_ != rtpStreams_[streamIdx].mediaAttribute_->muted_) {
2949 7 : return true;
2950 : }
2951 : }
2952 : #endif
2953 : }
2954 :
2955 78 : return false;
2956 : }
2957 :
2958 : bool
2959 101 : SIPCall::isNewIceMediaRequired(const std::vector<MediaAttribute>& mediaAttrList)
2960 : {
2961 : // Always needs a new ICE media if the peer does not support
2962 : // re-invite without ICE renegotiation
2963 101 : if (not peerSupportReuseIceInReinv_)
2964 0 : return true;
2965 :
2966 : // Always needs a new ICE media when the number of media changes.
2967 101 : if (mediaAttrList.size() != rtpStreams_.size())
2968 6 : return true;
2969 :
2970 262 : for (auto const& newAttr : mediaAttrList) {
2971 176 : auto streamIdx = findRtpStreamIndex(newAttr.label_);
2972 176 : if (streamIdx < 0) {
2973 : // Always needs a new ICE media when a media is added or replaced.
2974 0 : return true;
2975 : }
2976 176 : auto const& currAttr = rtpStreams_[streamIdx].mediaAttribute_;
2977 176 : if (newAttr.sourceUri_ != currAttr->sourceUri_) {
2978 : // For now, media will be restarted if the source changes.
2979 : // TODO. This should not be needed if the decoder/receiver
2980 : // correctly handles dynamic media properties changes.
2981 2 : return true;
2982 : }
2983 :
2984 : #ifdef ENABLE_VIDEO
2985 174 : if (newAttr.type_ == MediaType::MEDIA_VIDEO) {
2986 : // Video mute/unmute changes trigger a reinvite, and reinvites always clear ICE.
2987 : // Therefore, we need recreate ICE transport.
2988 79 : if (newAttr.muted_ != currAttr->muted_) {
2989 7 : return true;
2990 : }
2991 : }
2992 : #endif
2993 : }
2994 :
2995 86 : return false;
2996 : }
2997 :
2998 : bool
2999 94 : SIPCall::requestMediaChange(const std::vector<libjami::MediaMap>& mediaList)
3000 : {
3001 94 : std::lock_guard lk {callMutex_};
3002 : // Before the call is confirmed, the change is applied locally only (see requestReinvite).
3003 94 : if (inviteSession_ and inviteSession_->state == PJSIP_INV_STATE_CONFIRMED and isReinviteBusyLocked()) {
3004 1 : JAMI_DEBUG("[call:{}] Media change deferred until negotiation completes", getCallId());
3005 1 : pendingMediaChange_ = mediaList;
3006 1 : return true;
3007 : }
3008 93 : auto mediaAttrList = MediaAttribute::buildMediaAttributesList(mediaList, isSrtpEnabled());
3009 93 : bool hasFileSharing {false};
3010 :
3011 268 : for (const auto& media : mediaAttrList) {
3012 175 : if (!media.enabled_ || media.sourceUri_.empty())
3013 172 : continue;
3014 :
3015 : // Supported MRL schemes
3016 10 : static const std::string sep = libjami::Media::VideoProtocolPrefix::SEPARATOR;
3017 :
3018 10 : const auto pos = media.sourceUri_.find(sep);
3019 10 : if (pos == std::string::npos)
3020 7 : continue;
3021 :
3022 3 : const auto prefix = media.sourceUri_.substr(0, pos);
3023 3 : if ((pos + sep.size()) >= media.sourceUri_.size())
3024 0 : continue;
3025 :
3026 3 : if (prefix == libjami::Media::VideoProtocolPrefix::FILE) {
3027 3 : hasFileSharing = true;
3028 3 : mediaPlayerId_ = media.sourceUri_;
3029 : #ifdef ENABLE_VIDEO
3030 3 : createMediaPlayer(mediaPlayerId_);
3031 : #endif
3032 : }
3033 3 : }
3034 :
3035 93 : if (!hasFileSharing) {
3036 : #ifdef ENABLE_VIDEO
3037 90 : closeMediaPlayer(mediaPlayerId_);
3038 : #endif
3039 90 : mediaPlayerId_ = "";
3040 : }
3041 :
3042 : // Disable video if disabled in the account.
3043 93 : auto account = getSIPAccount();
3044 93 : if (not account) {
3045 0 : JAMI_ERROR("[call:{}] No account detected", getCallId());
3046 0 : return false;
3047 : }
3048 93 : if (not account->isVideoEnabled()) {
3049 0 : for (auto& mediaAttr : mediaAttrList) {
3050 0 : if (mediaAttr.type_ == MediaType::MEDIA_VIDEO) {
3051 : // This an API misuse. The new medialist should not contain video
3052 : // if it was disabled in the account settings.
3053 0 : JAMI_ERROR("[call:{}] New media has video, but it's disabled in the account. "
3054 : "Ignoring the change request!",
3055 : getCallId());
3056 0 : return false;
3057 : }
3058 : }
3059 : }
3060 :
3061 : // If the peer does not support multi-stream and the size of the new
3062 : // media list is different from the current media list, the media
3063 : // change request will be ignored.
3064 93 : if (not peerSupportMultiStream_ and rtpStreams_.size() != mediaAttrList.size()) {
3065 0 : JAMI_WARNING("[call:{}] Peer does not support multi-stream. Media change request ignored", getCallId());
3066 0 : return false;
3067 : }
3068 :
3069 : // If the peer does not support multi-audio-stream and the new
3070 : // media list has more than one audio. Ignore the one that comes from a file.
3071 93 : if (not peerSupportMultiAudioStream_ and rtpStreams_.size() != mediaAttrList.size() and hasFileSharing) {
3072 0 : JAMI_WARNING("[call:{}] Peer does not support multi-audio-stream. New Audio will be ignored", getCallId());
3073 0 : for (auto it = mediaAttrList.begin(); it != mediaAttrList.end();) {
3074 0 : if (it->type_ == MediaType::MEDIA_AUDIO and !it->sourceUri_.empty() and mediaPlayerId_ == it->sourceUri_) {
3075 0 : it = mediaAttrList.erase(it);
3076 0 : continue;
3077 : }
3078 0 : ++it;
3079 : }
3080 : }
3081 :
3082 : // If peer doesn't support multiple ice, keep only the last audio/video
3083 : // This keep the old behaviour (if sharing both camera + sharing a file, will keep the shared file)
3084 93 : if (!peerSupportMultiIce_) {
3085 0 : if (mediaList.size() > 2)
3086 0 : JAMI_WARNING("[call:{}] Peer does not support more than 2 ICE medias. "
3087 : "Media change request modified",
3088 : getCallId());
3089 0 : MediaAttribute audioAttr(MediaType::MEDIA_AUDIO);
3090 0 : MediaAttribute videoAttr;
3091 0 : auto hasVideo = false, hasAudio = false;
3092 0 : for (auto it = mediaAttrList.rbegin(); it != mediaAttrList.rend(); ++it) {
3093 0 : if (it->type_ == MediaType::MEDIA_VIDEO && !hasVideo) {
3094 0 : videoAttr = *it;
3095 0 : videoAttr.label_ = sip_utils::DEFAULT_VIDEO_STREAMID;
3096 0 : hasVideo = true;
3097 0 : } else if (it->type_ == MediaType::MEDIA_AUDIO && !hasAudio) {
3098 0 : audioAttr = *it;
3099 0 : audioAttr.label_ = sip_utils::DEFAULT_AUDIO_STREAMID;
3100 0 : hasAudio = true;
3101 : }
3102 0 : if (hasVideo && hasAudio)
3103 0 : break;
3104 : }
3105 0 : mediaAttrList.clear();
3106 : // Note: use the order VIDEO/AUDIO to avoid reinvite.
3107 : // Note: always add at least one media for valid SDP (RFC4566)
3108 0 : mediaAttrList.emplace_back(audioAttr);
3109 0 : if (hasVideo)
3110 0 : mediaAttrList.emplace_back(videoAttr);
3111 0 : }
3112 :
3113 93 : if (mediaAttrList.empty()) {
3114 0 : JAMI_ERROR("[call:{}] Invalid media change request: new media list is empty", getCallId());
3115 0 : return false;
3116 : }
3117 93 : pendingMediaChange_.reset();
3118 93 : JAMI_DEBUG("[call:{}] Requesting media change. List of new media:", getCallId());
3119 :
3120 93 : unsigned idx = 0;
3121 268 : for (auto const& newMediaAttr : mediaAttrList) {
3122 175 : JAMI_DEBUG("[call:{}] Media @{:d}: {}", getCallId(), idx++, newMediaAttr.toString(true));
3123 : }
3124 :
3125 93 : auto needReinvite = isReinviteRequired(mediaAttrList);
3126 93 : auto needNewIce = isNewIceMediaRequired(mediaAttrList);
3127 :
3128 93 : if (!updateAllMediaStreams(mediaAttrList, false))
3129 0 : return false;
3130 :
3131 93 : if (needReinvite) {
3132 15 : JAMI_DEBUG("[call:{}] Media change requires a new negotiation (re-invite)", getCallId());
3133 15 : requestReinvite(mediaAttrList, needNewIce);
3134 : } else {
3135 78 : JAMI_DEBUG("[call:{}] Media change DOES NOT require a new negotiation (re-invite)", getCallId());
3136 78 : reportMediaNegotiationStatus();
3137 : }
3138 :
3139 93 : return true;
3140 94 : }
3141 :
3142 : std::vector<std::map<std::string, std::string>>
3143 303 : SIPCall::currentMediaList() const
3144 : {
3145 303 : return MediaAttribute::mediaAttributesToMediaMaps(getMediaAttributeList());
3146 : }
3147 :
3148 : std::vector<MediaAttribute>
3149 2008 : SIPCall::getMediaAttributeList() const
3150 : {
3151 2008 : std::lock_guard lk {callMutex_};
3152 2008 : std::vector<MediaAttribute> mediaList;
3153 2008 : mediaList.reserve(rtpStreams_.size());
3154 5671 : for (auto const& stream : rtpStreams_)
3155 3663 : mediaList.emplace_back(*stream.mediaAttribute_);
3156 4016 : return mediaList;
3157 2008 : }
3158 :
3159 : std::map<std::string, bool>
3160 945 : SIPCall::getAudioStreams() const
3161 : {
3162 945 : std::map<std::string, bool> audioMedias {};
3163 945 : auto medias = getMediaAttributeList();
3164 2671 : for (const auto& media : medias) {
3165 1726 : if (media.type_ == MEDIA_AUDIO) {
3166 951 : auto label = fmt::format("{}_{}", getCallId(), media.label_);
3167 951 : audioMedias.emplace(label, media.muted_);
3168 951 : }
3169 : }
3170 1890 : return audioMedias;
3171 945 : }
3172 :
3173 : std::map<std::string, bool>
3174 136 : SIPCall::getRemoteAudioStreams() const
3175 : {
3176 136 : std::lock_guard lk {callMutex_};
3177 136 : std::map<std::string, bool> audioMedias {};
3178 386 : for (const auto& stream : rtpStreams_) {
3179 250 : if (stream.mediaAttribute_ && stream.mediaAttribute_->type_ == MEDIA_AUDIO) {
3180 136 : auto label = fmt::format("{}_{}", getCallId(), stream.mediaAttribute_->label_);
3181 136 : bool muted = stream.remoteMediaAttribute_ ? stream.remoteMediaAttribute_->muted_
3182 60 : : stream.mediaAttribute_->muted_;
3183 136 : audioMedias.emplace(label, muted);
3184 136 : }
3185 : }
3186 272 : return audioMedias;
3187 136 : }
3188 :
3189 : void
3190 269 : SIPCall::onMediaNegotiationComplete()
3191 : {
3192 269 : auto generation = mediaNegotiationGeneration_.load();
3193 517 : if (peerSupportsHandover() and generation != recoveryGeneration_.load())
3194 0 : return;
3195 269 : runOnMainThread([w = weak(), generation] {
3196 269 : if (auto this_ = w.lock()) {
3197 262 : std::lock_guard lk {this_->callMutex_};
3198 262 : if (this_->peerSupportsHandover()
3199 503 : and (this_->mediaNegotiationGeneration_.load() != generation or not this_->isCurrentMediaNegotiation()))
3200 0 : return;
3201 262 : JAMI_DEBUG("[call:{}] Media negotiation complete", this_->getCallId());
3202 :
3203 : // If the call has already ended, we don't need to start the media.
3204 524 : if (not this_->inviteSession_ or this_->inviteSession_->state == PJSIP_INV_STATE_DISCONNECTED
3205 524 : or not this_->sdp_) {
3206 0 : return;
3207 : }
3208 :
3209 : // This method is called to report media negotiation (SDP) for initial
3210 : // invite or subsequent invites (re-invite).
3211 : // If ICE is negotiated, the media update will be handled in the
3212 : // ICE callback, otherwise, it will be handled here.
3213 : // Note that ICE can be negotiated in the first invite and not negotiated
3214 : // in the re-invite. In this case, the media transport is unchanged (reused).
3215 262 : if (this_->isIceEnabled() and this_->remoteHasValidIceAttributes()) {
3216 249 : if (not this_->isSubcall()) {
3217 : // Start ICE checks. Media will be started once ICE checks complete.
3218 165 : this_->startIceMedia();
3219 : }
3220 : } else {
3221 : // Update the negotiated media.
3222 13 : if (this_->mediaRestartRequired_) {
3223 5 : this_->setupNegotiatedMedia();
3224 : // No ICE, start media now.
3225 5 : JAMI_WARNING("[call:{}] ICE media disabled, using default media ports", this_->getCallId());
3226 : // Start the media.
3227 5 : this_->stopAllMedia();
3228 5 : this_->startAllMedia();
3229 : }
3230 :
3231 : // this_->updateRemoteMedia();
3232 13 : this_->reportMediaNegotiationStatus();
3233 : }
3234 531 : }
3235 : });
3236 : }
3237 :
3238 : void
3239 303 : SIPCall::reportMediaNegotiationStatus()
3240 : {
3241 : // Notify using the parent Id if it's a subcall.
3242 303 : auto callId = isSubcall() ? parent_->getCallId() : getCallId();
3243 303 : emitSignal<libjami::CallSignal::MediaNegotiationStatus>(
3244 606 : callId, libjami::Media::MediaNegotiationStatusEvents::NEGOTIATION_SUCCESS, currentMediaList());
3245 :
3246 303 : updateRecordingMediaState(false);
3247 303 : }
3248 :
3249 : void
3250 914 : SIPCall::updateRecordingMediaState(bool readyToRecord)
3251 : {
3252 914 : bool restartRecording = false;
3253 914 : bool startPendingRecording = false;
3254 :
3255 : {
3256 914 : std::lock_guard lk {mediaStateMutex_};
3257 :
3258 914 : if (readyToRecord)
3259 611 : readyToRecord_ = true;
3260 :
3261 914 : auto previousState = isAudioOnly_;
3262 914 : auto newState = !hasVideo();
3263 :
3264 914 : if (!readyToRecord_) {
3265 233 : return;
3266 : }
3267 :
3268 681 : if (previousState != newState && Call::isRecording()) {
3269 2 : restartRecording = true;
3270 2 : pendingRecord_ = true;
3271 : }
3272 681 : isAudioOnly_ = newState;
3273 :
3274 681 : startPendingRecording = pendingRecord_ && readyToRecord_;
3275 914 : }
3276 :
3277 681 : if (restartRecording) {
3278 2 : deinitRecorder();
3279 2 : toggleRecording();
3280 : }
3281 :
3282 681 : if (startPendingRecording)
3283 7 : toggleRecording();
3284 : }
3285 :
3286 : void
3287 249 : SIPCall::startIceMedia()
3288 : {
3289 249 : JAMI_DEBUG("[call:{}] Starting ICE", getCallId());
3290 249 : auto iceMedia = getIceMedia();
3291 249 : if (not iceMedia or iceMedia->isFailed()) {
3292 0 : JAMI_ERROR("[call:{}] Media ICE init failed", getCallId());
3293 0 : onFailure(PJSIP_SC_INTERNAL_SERVER_ERROR);
3294 0 : return;
3295 : }
3296 :
3297 249 : if (iceMedia->isStarted()) {
3298 : // NOTE: for incoming calls, the ICE is already there and running
3299 0 : if (iceMedia->isRunning())
3300 0 : onIceNegoSucceed(iceMedia);
3301 0 : return;
3302 : }
3303 :
3304 249 : if (not iceMedia->isInitialized()) {
3305 : // In this case, onInitDone will occurs after the startIceMedia
3306 0 : waitForIceInit_ = true;
3307 0 : return;
3308 : }
3309 :
3310 : // Start transport on SDP data and wait for negotiation
3311 249 : if (!sdp_)
3312 0 : return;
3313 249 : auto rem_ice_attrs = sdp_->getIceAttributes();
3314 249 : if (rem_ice_attrs.ufrag.empty() or rem_ice_attrs.pwd.empty()) {
3315 0 : JAMI_ERROR("[call:{}] Missing remote media ICE attributes", getCallId());
3316 0 : onFailure(PJSIP_SC_NOT_ACCEPTABLE_HERE);
3317 0 : return;
3318 : }
3319 249 : if (not iceMedia->startIce(rem_ice_attrs, getAllRemoteCandidates(*iceMedia))) {
3320 1 : JAMI_ERROR("[call:{}] ICE media failed to start", getCallId());
3321 1 : onFailure(PJSIP_SC_NOT_ACCEPTABLE_HERE);
3322 : }
3323 249 : }
3324 :
3325 : void
3326 267 : SIPCall::onIceInitDone(const std::shared_ptr<dhtnet::IceTransport>& initializedIce, bool ok,
3327 : std::optional<std::uint64_t> generation)
3328 : {
3329 267 : std::lock_guard lk {callMutex_};
3330 267 : if (generation and *generation != recoveryGeneration_)
3331 0 : return;
3332 534 : if (not ok or not initializedIce or initializedIce != getIceMedia() or not sdp_
3333 534 : or not waitForIceInit_.exchange(false))
3334 267 : return;
3335 0 : auto remoteIce = sdp_->getIceAttributes();
3336 0 : if (remoteIce.ufrag.empty() or remoteIce.pwd.empty())
3337 0 : return;
3338 0 : startIceMedia();
3339 267 : }
3340 :
3341 : void
3342 214 : SIPCall::onIceNegoDone(const std::shared_ptr<dhtnet::IceTransport>& negotiatedIce, bool ok,
3343 : std::optional<std::uint64_t> generation)
3344 : {
3345 214 : if (not negotiatedIce)
3346 0 : return;
3347 : bool fromSubcall;
3348 214 : std::shared_ptr<SIPCall> mediaCall;
3349 : {
3350 214 : std::lock_guard lk {callMutex_};
3351 214 : fromSubcall = isSubcall();
3352 214 : mediaCall = fromSubcall ? std::dynamic_pointer_cast<SIPCall>(parent_) : shared();
3353 214 : }
3354 214 : if (not mediaCall)
3355 0 : return;
3356 214 : std::lock_guard lk {mediaCall->callMutex_};
3357 214 : if (generation and *generation != mediaCall->recoveryGeneration_)
3358 2 : return;
3359 212 : if (negotiatedIce != mediaCall->getIceMedia())
3360 0 : return;
3361 212 : if (not ok) {
3362 0 : JAMI_ERROR("[call:{}] Media ICE negotiation failed", mediaCall->getCallId());
3363 0 : mediaCall->onFailure(PJSIP_SC_NOT_ACCEPTABLE_HERE);
3364 0 : return;
3365 : }
3366 212 : mediaCall->onIceNegoSucceed(negotiatedIce, fromSubcall);
3367 216 : }
3368 :
3369 : void
3370 212 : SIPCall::onIceNegoSucceed(const std::shared_ptr<dhtnet::IceTransport>& negotiatedIce, bool fromSubcall)
3371 : {
3372 212 : std::lock_guard lk {callMutex_};
3373 :
3374 212 : JAMI_DEBUG("[call:{}] ICE negotiation succeeded", getCallId());
3375 :
3376 : // Check if the call is already ended, so we don't need to restart medias
3377 : // This is typically the case in a multi-device context where one device
3378 : // can stop a call. So do not start medias
3379 212 : if (not inviteSession_ or inviteSession_->state == PJSIP_INV_STATE_DISCONNECTED or not sdp_) {
3380 0 : JAMI_ERROR("[call:{}] ICE negotiation succeeded, but call is in invalid state", getCallId());
3381 0 : return;
3382 : }
3383 212 : if (negotiatedIce != getIceMedia() or (peerSupportsHandover() and not isCurrentMediaNegotiation()))
3384 0 : return;
3385 :
3386 : // Update the negotiated media.
3387 212 : setupNegotiatedMedia();
3388 :
3389 : // If this callback is for a re-invite session then update
3390 : // the ICE media transport.
3391 212 : if (isIceEnabled())
3392 212 : switchToIceReinviteIfNeeded();
3393 :
3394 610 : for (unsigned int idx = 0, compId = 1; idx < rtpStreams_.size(); idx++, compId += 2) {
3395 : // Create sockets for RTP and RTCP, and start the session.
3396 398 : auto& rtpStream = rtpStreams_[idx];
3397 398 : if (not rtpStream.mediaAttribute_ or not rtpStream.mediaAttribute_->enabled_) {
3398 2 : JAMI_DEBUG("[call:{}] Skipping ICE socket for disabled stream @{}", getCallId(), idx);
3399 2 : continue;
3400 : }
3401 396 : rtpStream.rtpSocket_ = newIceSocket(compId);
3402 :
3403 396 : if (not rtcpMuxEnabled_) {
3404 396 : rtpStream.rtcpSocket_ = newIceSocket(compId + 1);
3405 : }
3406 : }
3407 :
3408 : // Start/Restart the media using the new transport
3409 212 : stopAllMedia();
3410 212 : startAllMedia();
3411 212 : if (not fromSubcall and recoveryNegotiation_ and negotiatedIce == recoveryNegotiation_->ice) {
3412 12 : recoveryNegotiation_->phase = RecoveryNegotiation::Phase::MediaReady;
3413 12 : completeRecoveryIfReadyLocked();
3414 : }
3415 212 : reportMediaNegotiationStatus();
3416 212 : }
3417 :
3418 : bool
3419 35 : SIPCall::checkMediaChangeRequest(const std::vector<libjami::MediaMap>& remoteMediaList)
3420 : {
3421 : // The current media is considered to have changed if one of the
3422 : // following condtions is true:
3423 : //
3424 : // - the number of media changed
3425 : // - the type of one of the media changed (unlikely)
3426 : // - one of the media was enabled/disabled
3427 :
3428 35 : JAMI_DEBUG("[call:{}] Received a media change request", getCallId());
3429 :
3430 35 : auto remoteMediaAttrList = MediaAttribute::buildMediaAttributesList(remoteMediaList, isSrtpEnabled());
3431 35 : if (remoteMediaAttrList.size() != rtpStreams_.size())
3432 5 : return true;
3433 :
3434 85 : for (size_t i = 0; i < rtpStreams_.size(); i++) {
3435 55 : if (remoteMediaAttrList[i].type_ != rtpStreams_[i].mediaAttribute_->type_)
3436 0 : return true;
3437 55 : if (remoteMediaAttrList[i].enabled_ != rtpStreams_[i].mediaAttribute_->enabled_)
3438 0 : return true;
3439 : }
3440 :
3441 30 : return false;
3442 35 : }
3443 :
3444 : void
3445 35 : SIPCall::handleMediaChangeRequest(const std::vector<libjami::MediaMap>& remoteMediaList)
3446 : {
3447 35 : JAMI_DEBUG("[call:{}] Handling media change request", getCallId());
3448 :
3449 35 : auto account = getAccount().lock();
3450 35 : if (not account) {
3451 0 : JAMI_ERROR("No account detected");
3452 0 : return;
3453 : }
3454 :
3455 : // If the offered media does not differ from the current local media, the
3456 : // request is answered using the current local media.
3457 35 : if (not checkMediaChangeRequest(remoteMediaList)) {
3458 30 : answerMediaChangeRequest(MediaAttribute::mediaAttributesToMediaMaps(getMediaAttributeList()));
3459 30 : return;
3460 : }
3461 :
3462 5 : if (account->isAutoAnswerEnabled()) {
3463 : // NOTE:
3464 : // Since the auto-answer is enabled in the account, newly
3465 : // added media are accepted too.
3466 : // This also means that if original call was an audio-only call,
3467 : // the local camera will be enabled, unless the video is disabled
3468 : // in the account settings.
3469 :
3470 1 : std::vector<libjami::MediaMap> newMediaList;
3471 1 : newMediaList.reserve(remoteMediaList.size());
3472 2 : for (auto const& stream : rtpStreams_) {
3473 1 : newMediaList.emplace_back(MediaAttribute::toMediaMap(*stream.mediaAttribute_));
3474 : }
3475 :
3476 1 : assert(remoteMediaList.size() > 0);
3477 1 : if (remoteMediaList.size() > newMediaList.size()) {
3478 2 : for (auto idx = newMediaList.size(); idx < remoteMediaList.size(); idx++) {
3479 1 : newMediaList.emplace_back(remoteMediaList[idx]);
3480 : }
3481 : }
3482 1 : answerMediaChangeRequest(newMediaList, true);
3483 1 : return;
3484 1 : }
3485 :
3486 : // Report the media change request.
3487 4 : emitSignal<libjami::CallSignal::MediaChangeRequested>(getAccountId(), getCallId(), remoteMediaList);
3488 35 : }
3489 :
3490 : pj_status_t
3491 36 : SIPCall::onReceiveReinvite(const pjmedia_sdp_session* offer, pjsip_rx_data* rdata)
3492 : {
3493 36 : JAMI_DEBUG("[call:{}] Received a re-invite", getCallId());
3494 36 : if (not rdata or not rdata->msg_info.cseq) {
3495 0 : JAMI_ERROR("[call:{}] Re-INVITE has no CSeq", getCallId());
3496 0 : return PJ_EINVAL;
3497 : }
3498 36 : const auto cseq = rdata->msg_info.cseq->cseq;
3499 0 : auto rejectOffer = [this, rdata](int code) {
3500 0 : pjsip_tx_data* answer = nullptr;
3501 0 : auto status = pjsip_inv_initial_answer(inviteSession_.get(), rdata, code, nullptr, nullptr, &answer);
3502 0 : if (status != PJ_SUCCESS) {
3503 0 : if (answer)
3504 0 : pjsip_tx_data_dec_ref(answer);
3505 0 : JAMI_ERROR("[call:{}] Unable to create re-INVITE rejection: {}",
3506 : getCallId(),
3507 : sip_utils::sip_strerror(status));
3508 0 : return status;
3509 : }
3510 0 : status = pjsip_inv_send_msg(inviteSession_.get(), answer);
3511 0 : if (status != PJ_SUCCESS)
3512 0 : JAMI_ERROR("[call:{}] Unable to send re-INVITE rejection: {}", getCallId(), sip_utils::sip_strerror(status));
3513 0 : return status;
3514 36 : };
3515 36 : auto generation = sip_utils::getHandoverGeneration(rdata);
3516 36 : auto current = recoveryGeneration_.load();
3517 : // Our generation may have advanced since request admission.
3518 36 : if (peerSupportsHandover() and generation and *generation < current) {
3519 0 : return rejectOffer(PJSIP_SC_REQUEST_PENDING);
3520 : }
3521 :
3522 36 : pj_status_t res = PJ_SUCCESS;
3523 :
3524 36 : if (not sdp_) {
3525 0 : JAMI_ERROR("SDP session is invalid");
3526 0 : return res;
3527 : }
3528 :
3529 36 : auto acc = getSIPAccount();
3530 36 : if (not acc) {
3531 0 : JAMI_ERROR("No account detected");
3532 0 : return res;
3533 : }
3534 :
3535 : // Note: For multistream, here we must ignore disabled remote medias, because
3536 : // we will answer from our medias and remote enabled medias.
3537 : // Example: if remote disables its camera and share its screen, the offer will
3538 : // have an active and a disabled media (with port = 0).
3539 : // In this case, if we have only one video, we can just negotiate 1 video instead of 2
3540 : // with 1 disabled.
3541 : // cf. pjmedia_sdp_neg_modify_local_offer2 for more details.
3542 36 : auto const& mediaAttrList = Sdp::getMediaAttributeListFromSdp(offer, true);
3543 36 : if (mediaAttrList.empty()) {
3544 0 : JAMI_WARNING("[call:{}] Rejecting a re-INVITE with no usable media", getCallId());
3545 0 : return rejectOffer(PJSIP_SC_NOT_ACCEPTABLE_HERE);
3546 : }
3547 :
3548 36 : mediaNegotiationGeneration_ = generation.value_or(current);
3549 :
3550 : // A peer recovering from a network change sends its re-INVITE on a new
3551 : // channel, before this dialog follows it there.
3552 36 : if (auto* dialog = inviteSession_ ? inviteSession_->dlg : nullptr;
3553 36 : dialog and dialog->tp_sel.type == PJSIP_TPSELECTOR_TRANSPORT)
3554 36 : peerMovedInReinvite_ = dialog->tp_sel.u.transport != rdata->tp_info.transport;
3555 :
3556 36 : sdp_->clearIce();
3557 36 : sdp_->setActiveRemoteSdpSession(nullptr);
3558 36 : sdp_->setActiveLocalSdpSession(nullptr);
3559 36 : Sdp::printSession(offer, "Remote session (media change request)", SdpDirection::OFFER);
3560 36 : sdp_->setReceivedOffer(offer);
3561 :
3562 36 : if (upnp_) {
3563 0 : openPortsUPnP();
3564 : }
3565 :
3566 36 : pjsip_tx_data* tdata = nullptr;
3567 36 : auto status = pjsip_inv_initial_answer(inviteSession_.get(), rdata, PJSIP_SC_TRYING, NULL, NULL, &tdata);
3568 36 : if (status != PJ_SUCCESS) {
3569 0 : if (tdata)
3570 0 : pjsip_tx_data_dec_ref(tdata);
3571 0 : JAMI_ERROR("[call:{}] Unable to create answer TRYING: {}", getCallId(), sip_utils::sip_strerror(status));
3572 0 : return status;
3573 : }
3574 36 : status = pjsip_inv_send_msg(inviteSession_.get(), tdata);
3575 36 : if (status != PJ_SUCCESS) {
3576 0 : JAMI_ERROR("[call:{}] Unable to send answer TRYING: {}", getCallId(), sip_utils::sip_strerror(status));
3577 0 : return status;
3578 : }
3579 :
3580 : // A newer generation is the caller's recovery offer: the dialog moves to
3581 : // its channel before answering, on the main thread as it takes callMutex_.
3582 36 : std::shared_ptr<pjsip_transport> offerChannel;
3583 36 : if (peerSupportsHandover() and generation and *generation > current and rdata) {
3584 6 : pjsip_transport_add_ref(rdata->tp_info.transport);
3585 12 : offerChannel = {rdata->tp_info.transport, [](pjsip_transport* tp) { pjsip_transport_dec_ref(tp); }};
3586 : }
3587 72 : runOnMainThread([callWkPtr = weak(),
3588 : mediaAttrList,
3589 : offerChannel,
3590 72 : generation = mediaNegotiationGeneration_.load(),
3591 : cseq] {
3592 36 : auto call = callWkPtr.lock();
3593 36 : if (not call)
3594 0 : return;
3595 : {
3596 36 : std::lock_guard lk {call->callMutex_};
3597 36 : if (not call->inviteSession_)
3598 0 : return;
3599 36 : sip_utils::PJDialogLock dialogLock {call->inviteSession_->dlg};
3600 36 : if (not call->isPendingReinviteLocked(cseq)) {
3601 0 : JAMI_DEBUG("[call:{}] Discarding completed re-INVITE {}", call->getCallId(), cseq);
3602 0 : return;
3603 : }
3604 36 : if (call->peerSupportsHandover() and offerChannel) {
3605 6 : auto transport = Manager::instance().sipVoIPLink().sipTransportBroker->addTransport(offerChannel.get());
3606 6 : auto account = std::dynamic_pointer_cast<JamiAccount>(call->getAccount().lock());
3607 18 : if (not account
3608 6 : or not call->adoptRecoveryOfferLocked(transport, account->getContactHeader(transport), generation)) {
3609 0 : call->rejectPendingReinviteLocked(cseq);
3610 0 : return;
3611 : }
3612 6 : }
3613 36 : if (call->peerSupportsHandover()
3614 70 : and (call->mediaNegotiationGeneration_.load() != generation or not call->isCurrentMediaNegotiation())) {
3615 0 : call->rejectPendingReinviteLocked(cseq);
3616 0 : return;
3617 : }
3618 36 : }
3619 36 : dht::ThreadPool::io().run([callWkPtr, mediaAttrList, generation, cseq] {
3620 36 : auto call = callWkPtr.lock();
3621 36 : if (not call)
3622 0 : return;
3623 : {
3624 36 : std::lock_guard lk {call->callMutex_};
3625 36 : if (not call->inviteSession_)
3626 0 : return;
3627 36 : sip_utils::PJDialogLock dialogLock {call->inviteSession_->dlg};
3628 36 : if (not call->isPendingReinviteLocked(cseq)) {
3629 0 : JAMI_DEBUG("[call:{}] Discarding completed re-INVITE {}", call->getCallId(), cseq);
3630 0 : return;
3631 : }
3632 36 : if (call->peerSupportsHandover()
3633 104 : and (call->mediaNegotiationGeneration_.load() != generation
3634 34 : or not call->isCurrentMediaNegotiation())) {
3635 0 : call->rejectPendingReinviteLocked(cseq);
3636 0 : return;
3637 : }
3638 36 : }
3639 : // Report the change request.
3640 36 : auto const& remoteMediaList = MediaAttribute::mediaAttributesToMediaMaps(mediaAttrList);
3641 36 : if (auto conf = call->getConference()) {
3642 1 : conf->handleMediaChangeRequest(call, remoteMediaList);
3643 : } else {
3644 35 : call->handleMediaChangeRequest(remoteMediaList);
3645 36 : }
3646 36 : });
3647 36 : });
3648 :
3649 36 : return res;
3650 36 : }
3651 :
3652 : void
3653 0 : SIPCall::onReceiveOfferIn200OK(const pjmedia_sdp_session* offer)
3654 : {
3655 0 : if (not rtpStreams_.empty()) {
3656 0 : JAMI_ERROR("[call:{}] Unexpected offer in '200 OK' answer", getCallId());
3657 0 : return;
3658 : }
3659 :
3660 0 : auto acc = getSIPAccount();
3661 0 : if (not acc) {
3662 0 : JAMI_ERROR("No account detected");
3663 0 : return;
3664 : }
3665 :
3666 0 : if (not sdp_) {
3667 0 : JAMI_ERROR("Invalid SDP session");
3668 0 : return;
3669 : }
3670 :
3671 0 : JAMI_DEBUG("[call:{}] Received an offer in '200 OK' answer", getCallId());
3672 :
3673 0 : auto mediaList = Sdp::getMediaAttributeListFromSdp(offer);
3674 : // If this method is called, it means we are expecting an offer
3675 : // in the 200OK answer.
3676 0 : if (mediaList.empty()) {
3677 0 : JAMI_WARNING("[call:{}] Remote media list is empty, ignoring", getCallId());
3678 0 : return;
3679 : }
3680 :
3681 0 : Sdp::printSession(offer, "Remote session (offer in 200 OK answer)", SdpDirection::OFFER);
3682 :
3683 0 : sdp_->clearIce();
3684 0 : sdp_->setActiveRemoteSdpSession(nullptr);
3685 0 : sdp_->setActiveLocalSdpSession(nullptr);
3686 :
3687 0 : sdp_->setReceivedOffer(offer);
3688 :
3689 : // If we send an empty offer, video will be accepted only if locally
3690 : // enabled by the user.
3691 0 : for (auto& mediaAttr : mediaList) {
3692 0 : if (mediaAttr.type_ == MediaType::MEDIA_VIDEO and not acc->isVideoEnabled()) {
3693 0 : mediaAttr.enabled_ = false;
3694 : }
3695 : }
3696 :
3697 0 : initMediaStreams(mediaList);
3698 :
3699 0 : sdp_->processIncomingOffer(mediaList);
3700 :
3701 0 : if (upnp_) {
3702 0 : openPortsUPnP();
3703 : }
3704 :
3705 0 : if (isIceEnabled() and remoteHasValidIceAttributes()) {
3706 0 : setupIceResponse();
3707 : }
3708 :
3709 0 : sdp_->startNegotiation();
3710 :
3711 0 : if (pjsip_inv_set_sdp_answer(inviteSession_.get(), sdp_->getLocalSdpSession()) != PJ_SUCCESS) {
3712 0 : JAMI_ERROR("[call:{}] Unable to start media negotiation for a re-invite request", getCallId());
3713 : }
3714 0 : }
3715 :
3716 : void
3717 0 : SIPCall::openPortsUPnP()
3718 : {
3719 0 : if (not sdp_) {
3720 0 : JAMI_ERROR("[call:{}] Current SDP instance is invalid", getCallId());
3721 0 : return;
3722 : }
3723 :
3724 : /**
3725 : * Attempt to open the desired ports with UPnP,
3726 : * if they are used, use the alternative port and update the SDP session with the newly
3727 : * chosen port(s)
3728 : *
3729 : * TODO:
3730 : * No need to request mappings for specfic port numbers. Set the port to '0' to
3731 : * request the first available port (faster and more likely to succeed).
3732 : */
3733 0 : JAMI_DEBUG("[call:{}] Opening ports via UPnP for SDP session", getCallId());
3734 :
3735 : // RTP port.
3736 0 : upnp_->reserveMapping(sdp_->getLocalAudioPort(), dhtnet::upnp::PortType::UDP);
3737 : // RTCP port.
3738 0 : upnp_->reserveMapping(sdp_->getLocalAudioControlPort(), dhtnet::upnp::PortType::UDP);
3739 :
3740 : #ifdef ENABLE_VIDEO
3741 : // RTP port.
3742 0 : upnp_->reserveMapping(sdp_->getLocalVideoPort(), dhtnet::upnp::PortType::UDP);
3743 : // RTCP port.
3744 0 : upnp_->reserveMapping(sdp_->getLocalVideoControlPort(), dhtnet::upnp::PortType::UDP);
3745 : #endif
3746 : }
3747 :
3748 : std::map<std::string, std::string>
3749 390 : SIPCall::getDetails() const
3750 : {
3751 390 : auto acc = getSIPAccount();
3752 390 : if (!acc) {
3753 0 : JAMI_ERROR("No account detected");
3754 0 : return {};
3755 : }
3756 :
3757 390 : auto details = Call::getDetails();
3758 :
3759 390 : details.emplace(libjami::Call::Details::PEER_HOLD, peerHold_ ? TRUE_STR : FALSE_STR);
3760 :
3761 1100 : for (auto const& stream : rtpStreams_) {
3762 710 : if (stream.mediaAttribute_->type_ == MediaType::MEDIA_VIDEO) {
3763 320 : details.emplace(libjami::Call::Details::VIDEO_SOURCE, stream.mediaAttribute_->sourceUri_);
3764 : #ifdef ENABLE_VIDEO
3765 320 : if (auto const& rtpSession = stream.rtpSession_) {
3766 320 : if (auto codec = rtpSession->getCodec()) {
3767 97 : details.emplace(libjami::Call::Details::VIDEO_CODEC, codec->name);
3768 97 : details.emplace(libjami::Call::Details::VIDEO_MIN_BITRATE, std::to_string(codec->minBitrate));
3769 97 : details.emplace(libjami::Call::Details::VIDEO_MAX_BITRATE, std::to_string(codec->maxBitrate));
3770 97 : if (const auto& curvideoRtpSession = std::static_pointer_cast<video::VideoRtpSession>(rtpSession)) {
3771 97 : details.emplace(libjami::Call::Details::VIDEO_BITRATE,
3772 194 : std::to_string(curvideoRtpSession->getVideoBitrateInfo().videoBitrateCurrent));
3773 97 : }
3774 : } else
3775 320 : details.emplace(libjami::Call::Details::VIDEO_CODEC, "");
3776 : }
3777 : #endif
3778 390 : } else if (stream.mediaAttribute_->type_ == MediaType::MEDIA_AUDIO) {
3779 390 : if (auto const& rtpSession = stream.rtpSession_) {
3780 390 : if (auto codec = rtpSession->getCodec()) {
3781 107 : details.emplace(libjami::Call::Details::AUDIO_CODEC, codec->name);
3782 107 : details.emplace(
3783 : libjami::Call::Details::AUDIO_SAMPLE_RATE,
3784 321 : codec->getCodecSpecifications()[libjami::Account::ConfProperties::CodecInfo::SAMPLE_RATE]);
3785 : } else {
3786 283 : details.emplace(libjami::Call::Details::AUDIO_CODEC, "");
3787 283 : details.emplace(libjami::Call::Details::AUDIO_SAMPLE_RATE, "");
3788 390 : }
3789 : }
3790 : }
3791 : }
3792 :
3793 390 : if (not peerRegisteredName_.empty())
3794 2 : details.emplace(libjami::Call::Details::REGISTERED_NAME, peerRegisteredName_);
3795 :
3796 : #ifdef ENABLE_CLIENT_CERT
3797 : std::lock_guard lk {callMutex_};
3798 : if (transport_ and transport_->isSecure()) {
3799 : const auto& tlsInfos = transport_->getTlsInfos();
3800 : if (tlsInfos.cipher != PJ_TLS_UNKNOWN_CIPHER) {
3801 : const auto& cipher = pj_ssl_cipher_name(tlsInfos.cipher);
3802 : details.emplace(libjami::TlsTransport::TLS_CIPHER, cipher ? cipher : "");
3803 : } else {
3804 : details.emplace(libjami::TlsTransport::TLS_CIPHER, "");
3805 : }
3806 : if (tlsInfos.peerCert) {
3807 : details.emplace(libjami::TlsTransport::TLS_PEER_CERT, tlsInfos.peerCert->toString());
3808 : auto ca = tlsInfos.peerCert->issuer;
3809 : unsigned n = 0;
3810 : while (ca) {
3811 : std::ostringstream name_str;
3812 : name_str << libjami::TlsTransport::TLS_PEER_CA_ << n++;
3813 : details.emplace(name_str.str(), ca->toString());
3814 : ca = ca->issuer;
3815 : }
3816 : details.emplace(libjami::TlsTransport::TLS_PEER_CA_NUM, std::to_string(n));
3817 : } else {
3818 : details.emplace(libjami::TlsTransport::TLS_PEER_CERT, "");
3819 : details.emplace(libjami::TlsTransport::TLS_PEER_CA_NUM, "");
3820 : }
3821 : }
3822 : #endif
3823 390 : if (auto transport = getIceMedia()) {
3824 360 : if (transport && transport->isRunning())
3825 106 : details.emplace(libjami::Call::Details::SOCKETS, transport->link().c_str());
3826 390 : }
3827 390 : return details;
3828 390 : }
3829 :
3830 : void
3831 76 : SIPCall::enterConference(std::shared_ptr<Conference> conference)
3832 : {
3833 76 : JAMI_DEBUG("[call:{}] Entering conference [{}]", getCallId(), conference->getConfId());
3834 76 : conf_ = conference;
3835 : // Unbind audio. It will be rebinded in the conference if needed
3836 76 : auto const hasAudio = !getRtpSessionList(MediaType::MEDIA_AUDIO).empty();
3837 76 : if (hasAudio) {
3838 76 : auto& rbPool = Manager::instance().getRingBufferPool();
3839 76 : auto medias = getAudioStreams();
3840 152 : for (const auto& media : medias) {
3841 152 : rbPool.unbindRingBuffers(media.first, RingBufferPool::DEFAULT_ID);
3842 : }
3843 76 : rbPool.flush(RingBufferPool::DEFAULT_ID);
3844 76 : }
3845 :
3846 : #ifdef ENABLE_VIDEO
3847 76 : if (conference->isVideoEnabled())
3848 140 : for (const auto& videoRtp : getRtpSessionList(MediaType::MEDIA_VIDEO))
3849 140 : std::static_pointer_cast<video::VideoRtpSession>(videoRtp)->enterConference(*conference);
3850 : #endif
3851 :
3852 : #ifdef ENABLE_PLUGIN
3853 76 : clearCallAVStreams();
3854 : #endif
3855 76 : }
3856 :
3857 : void
3858 75 : SIPCall::exitConference()
3859 : {
3860 75 : std::lock_guard lk {callMutex_};
3861 75 : JAMI_DEBUG("[call:{}] Leaving conference", getCallId());
3862 :
3863 75 : auto const hasAudio = !getRtpSessionList(MediaType::MEDIA_AUDIO).empty();
3864 75 : if (hasAudio) {
3865 75 : auto& rbPool = Manager::instance().getRingBufferPool();
3866 75 : auto medias = getAudioStreams();
3867 150 : for (const auto& media : medias) {
3868 75 : if (!media.second) {
3869 140 : rbPool.bindRingBuffers(media.first, RingBufferPool::DEFAULT_ID);
3870 : }
3871 : }
3872 75 : rbPool.flush(RingBufferPool::DEFAULT_ID);
3873 75 : }
3874 : #ifdef ENABLE_VIDEO
3875 138 : for (const auto& videoRtp : getRtpSessionList(MediaType::MEDIA_VIDEO))
3876 138 : std::static_pointer_cast<video::VideoRtpSession>(videoRtp)->exitConference();
3877 : #endif
3878 : #ifdef ENABLE_PLUGIN
3879 75 : createCallAVStreams();
3880 : #endif
3881 75 : conf_.reset();
3882 75 : }
3883 :
3884 : void
3885 0 : SIPCall::setActiveMediaStream(const std::string& accountUri,
3886 : const std::string& deviceId,
3887 : const std::string& streamId,
3888 : const bool& state)
3889 : {
3890 0 : auto remoteStreamId = streamId;
3891 : #ifdef ENABLE_VIDEO
3892 : {
3893 0 : std::lock_guard lk(sinksMtx_);
3894 0 : const auto& localIt = local2RemoteSinks_.find(streamId);
3895 0 : if (localIt != local2RemoteSinks_.end()) {
3896 0 : remoteStreamId = localIt->second;
3897 : }
3898 0 : }
3899 : #endif
3900 :
3901 0 : if (Call::conferenceProtocolVersion() == 1) {
3902 0 : Json::Value sinkVal;
3903 0 : sinkVal["active"] = state;
3904 0 : Json::Value mediasObj;
3905 0 : mediasObj[remoteStreamId] = sinkVal;
3906 0 : Json::Value deviceVal;
3907 0 : deviceVal["medias"] = mediasObj;
3908 0 : Json::Value deviceObj;
3909 0 : deviceObj[deviceId] = deviceVal;
3910 0 : Json::Value accountVal;
3911 0 : deviceVal["devices"] = deviceObj;
3912 0 : Json::Value root;
3913 0 : root[accountUri] = deviceVal;
3914 0 : root["version"] = 1;
3915 0 : Call::sendConfOrder(root);
3916 0 : } else if (Call::conferenceProtocolVersion() == 0) {
3917 0 : Json::Value root;
3918 0 : root["activeParticipant"] = accountUri;
3919 0 : Call::sendConfOrder(root);
3920 0 : }
3921 0 : }
3922 :
3923 : #ifdef ENABLE_VIDEO
3924 : void
3925 59 : SIPCall::setRotation(int streamIdx, int rotation)
3926 : {
3927 : // Retrigger on another thread to avoid to lock PJSIP
3928 59 : dht::ThreadPool::io().run([w = weak(), streamIdx, rotation] {
3929 59 : if (auto shared = w.lock()) {
3930 59 : std::lock_guard lk {shared->callMutex_};
3931 59 : shared->rotation_ = rotation;
3932 59 : if (streamIdx == -1) {
3933 0 : for (const auto& videoRtp : shared->getRtpSessionList(MediaType::MEDIA_VIDEO))
3934 0 : std::static_pointer_cast<video::VideoRtpSession>(videoRtp)->setRotation(rotation);
3935 59 : } else if (streamIdx > -1 && streamIdx < static_cast<int>(shared->rtpStreams_.size())) {
3936 : // Apply request for requested stream
3937 59 : auto& stream = shared->rtpStreams_[streamIdx];
3938 59 : if (stream.rtpSession_ && stream.rtpSession_->getMediaType() == MediaType::MEDIA_VIDEO)
3939 59 : std::static_pointer_cast<video::VideoRtpSession>(stream.rtpSession_)->setRotation(rotation);
3940 : }
3941 118 : }
3942 59 : });
3943 59 : }
3944 :
3945 : void
3946 258 : SIPCall::createSinks(ConfInfo& infos)
3947 : {
3948 258 : std::lock_guard lk(callMutex_);
3949 258 : std::lock_guard lkS(sinksMtx_);
3950 258 : if (!hasVideo())
3951 30 : return;
3952 : // Conference info can be delivered from the main thread after the account was removed
3953 228 : auto account = std::dynamic_pointer_cast<JamiAccount>(account_.lock());
3954 228 : if (!account)
3955 0 : return;
3956 :
3957 842 : for (auto& participant : infos) {
3958 614 : if (string_remove_suffix(participant.uri, '@') == account->getUsername()
3959 614 : && participant.device == account->currentDeviceId()) {
3960 578 : for (auto iter = rtpStreams_.begin(); iter != rtpStreams_.end(); iter++) {
3961 386 : if (!iter->mediaAttribute_ || iter->mediaAttribute_->type_ == MediaType::MEDIA_AUDIO) {
3962 192 : continue;
3963 : }
3964 : auto* localVideo
3965 194 : = std::static_pointer_cast<video::VideoRtpSession>(iter->rtpSession_)->getVideoLocal().get();
3966 194 : auto size = std::make_pair(10, 10);
3967 194 : if (localVideo) {
3968 182 : size = std::make_pair(localVideo->getWidth(), localVideo->getHeight());
3969 : }
3970 194 : const auto& mediaAttribute = iter->mediaAttribute_;
3971 194 : if (participant.sinkId.find(mediaAttribute->label_) != std::string::npos) {
3972 189 : local2RemoteSinks_[mediaAttribute->sourceUri_] = participant.sinkId;
3973 189 : participant.sinkId = mediaAttribute->sourceUri_;
3974 189 : participant.videoMuted = mediaAttribute->muted_;
3975 189 : participant.w = size.first;
3976 189 : participant.h = size.second;
3977 189 : participant.x = 0;
3978 189 : participant.y = 0;
3979 : }
3980 : }
3981 : }
3982 : }
3983 :
3984 228 : std::vector<std::shared_ptr<video::VideoFrameActiveWriter>> sinks;
3985 458 : for (const auto& videoRtp : getRtpSessionList(MediaType::MEDIA_VIDEO)) {
3986 230 : auto& videoReceive = std::static_pointer_cast<video::VideoRtpSession>(videoRtp)->getVideoReceive();
3987 230 : if (!videoReceive)
3988 36 : continue;
3989 194 : sinks.emplace_back(std::static_pointer_cast<video::VideoFrameActiveWriter>(videoReceive->getSink()));
3990 228 : }
3991 228 : auto conf = conf_.lock();
3992 228 : const auto& id = conf ? conf->getConfId() : getCallId();
3993 228 : Manager::instance().createSinkClients(id, infos, sinks, callSinksMap_, getAccountId());
3994 288 : }
3995 : #endif
3996 :
3997 : std::vector<std::shared_ptr<RtpSession>>
3998 2209 : SIPCall::getRtpSessionList(MediaType type) const
3999 : {
4000 2209 : std::vector<std::shared_ptr<RtpSession>> rtpList;
4001 2209 : rtpList.reserve(rtpStreams_.size());
4002 6388 : for (auto const& stream : rtpStreams_) {
4003 4179 : if (type == MediaType::MEDIA_ALL || stream.rtpSession_->getMediaType() == type)
4004 2967 : rtpList.emplace_back(stream.rtpSession_);
4005 : }
4006 2209 : return rtpList;
4007 0 : }
4008 :
4009 : void
4010 209 : SIPCall::monitor() const
4011 : {
4012 209 : if (isSubcall())
4013 0 : return;
4014 209 : auto acc = getSIPAccount();
4015 209 : if (!acc) {
4016 0 : JAMI_ERROR("No account detected");
4017 0 : return;
4018 : }
4019 209 : JAMI_LOG("- Call {} with {}:", getCallId(), getPeerNumber());
4020 209 : JAMI_LOG("\t- Duration: {}", dht::print_duration(getCallDuration()));
4021 595 : for (const auto& stream : rtpStreams_)
4022 386 : JAMI_LOG("\t- Media: {}", stream.mediaAttribute_->toString(true));
4023 : #ifdef ENABLE_VIDEO
4024 209 : if (auto codec = getVideoCodec())
4025 209 : JAMI_LOG("\t- Video codec: {}", codec->name);
4026 : #endif
4027 209 : if (auto transport = getIceMedia()) {
4028 200 : if (transport->isRunning())
4029 150 : JAMI_LOG("\t- Media stream(s): {}", transport->link());
4030 209 : }
4031 209 : }
4032 :
4033 : bool
4034 20 : SIPCall::toggleRecording()
4035 : {
4036 20 : pendingRecord_ = true;
4037 20 : if (not readyToRecord_)
4038 5 : return true;
4039 :
4040 : // add streams to recorder before starting the record
4041 15 : if (not Call::isRecording()) {
4042 9 : auto account = getSIPAccount();
4043 9 : if (!account) {
4044 0 : JAMI_ERROR("No account detected");
4045 0 : return false;
4046 : }
4047 18 : auto title = fmt::format("Conversation at %TIMESTAMP between {} and {}", account->getUserUri(), peerUri_);
4048 18 : recorder_->setMetadata(title, ""); // use default description
4049 24 : for (const auto& rtpSession : getRtpSessionList())
4050 24 : rtpSession->initRecorder();
4051 9 : } else {
4052 6 : updateRecState(false);
4053 : }
4054 15 : pendingRecord_ = false;
4055 15 : auto state = Call::toggleRecording();
4056 15 : if (state)
4057 9 : updateRecState(state);
4058 15 : return state;
4059 : }
4060 :
4061 : void
4062 4 : SIPCall::deinitRecorder()
4063 : {
4064 11 : for (const auto& rtpSession : getRtpSessionList())
4065 11 : rtpSession->deinitRecorder();
4066 4 : }
4067 :
4068 : void
4069 226 : SIPCall::InvSessionDeleter::operator()(pjsip_inv_session* inv) const noexcept
4070 : {
4071 226 : if (!inv)
4072 0 : return;
4073 :
4074 : // The session lives in the dialog pool. Dropping the last reference
4075 : // runs inv_session_destroy(), which decrements the dialog session count
4076 : // and, when no transaction is left, destroys the dialog and frees that
4077 : // pool, then goes on to read the session's own fields from the freed
4078 : // memory. pjsip only ever does this while holding the dialog lock,
4079 : // which keeps the dialog alive until the lock is released. We drop our
4080 : // reference from the main thread, outside of any such lock, so take it
4081 : // here. Keep the dialog pointer in a local, as it must outlive inv.
4082 226 : auto* dlg = inv->dlg;
4083 226 : pjsip_dlg_inc_lock(dlg);
4084 :
4085 : // prevent this from getting accessed in callbacks
4086 226 : inv->mod_data[Manager::instance().sipVoIPLink().getModId()] = nullptr;
4087 : // NOTE: the counter is incremented by setInviteSession()
4088 226 : pjsip_inv_dec_ref(inv);
4089 :
4090 : // Actually destroys the dialog if it has no other session.
4091 226 : pjsip_dlg_dec_lock(dlg);
4092 : }
4093 :
4094 : bool
4095 271 : SIPCall::createIceMediaTransport(bool isReinvite)
4096 : {
4097 271 : auto mediaTransport = Manager::instance().getIceTransportFactory()->createTransport(getCallId());
4098 271 : if (mediaTransport) {
4099 271 : JAMI_DEBUG("[call:{}] Successfully created media ICE transport [ice:{}]",
4100 : getCallId(),
4101 : fmt::ptr(mediaTransport.get()));
4102 : } else {
4103 0 : JAMI_ERROR("[call:{}] Failed to create media ICE transport", getCallId());
4104 0 : return {};
4105 : }
4106 :
4107 271 : setIceMedia(mediaTransport, isReinvite);
4108 :
4109 271 : return mediaTransport != nullptr;
4110 271 : }
4111 :
4112 : bool
4113 271 : SIPCall::initIceMediaTransport(bool master, std::optional<dhtnet::IceTransportOptions> options)
4114 : {
4115 271 : auto acc = getSIPAccount();
4116 271 : if (!acc) {
4117 0 : JAMI_ERROR("No account detected");
4118 0 : return false;
4119 : }
4120 :
4121 271 : JAMI_DEBUG("[call:{}] Init media ICE transport", getCallId());
4122 :
4123 271 : auto const& iceMedia = getIceMedia();
4124 271 : if (not iceMedia) {
4125 0 : JAMI_ERROR("[call:{}] Invalid media ICE transport", getCallId());
4126 0 : return false;
4127 : }
4128 :
4129 271 : auto iceOptions = options == std::nullopt ? acc->getIceOptions() : *options;
4130 :
4131 271 : auto optOnInitDone = std::move(iceOptions.onInitDone);
4132 271 : auto optOnNegoDone = std::move(iceOptions.onNegoDone);
4133 271 : auto generation = mediaNegotiationGeneration_.load();
4134 542 : iceOptions.onInitDone = [w = weak(), cb = std::move(optOnInitDone), ice = std::weak_ptr(iceMedia), generation](bool ok) {
4135 271 : runOnMainThread([w = std::move(w), cb = std::move(cb), ice, generation, ok] {
4136 271 : if (cb)
4137 0 : cb(ok);
4138 271 : if (auto call = w.lock())
4139 271 : call->onIceInitDone(ice.lock(), ok, generation);
4140 271 : });
4141 542 : };
4142 542 : iceOptions.onNegoDone = [w = weak(), cb = std::move(optOnNegoDone), ice = std::weak_ptr(iceMedia), generation](bool ok) {
4143 214 : runOnMainThread([w = std::move(w), cb = std::move(cb), ice, generation, ok] {
4144 214 : if (cb)
4145 0 : cb(ok);
4146 214 : if (auto call = w.lock())
4147 214 : call->onIceNegoDone(ice.lock(), ok, generation);
4148 214 : });
4149 485 : };
4150 :
4151 271 : iceOptions.master = master;
4152 271 : iceOptions.streamsCount = static_cast<unsigned>(rtpStreams_.size());
4153 : // Each RTP stream requires a pair of ICE components (RTP + RTCP).
4154 271 : iceOptions.compCountPerStream = ICE_COMP_COUNT_PER_STREAM;
4155 271 : iceOptions.qosType.reserve(rtpStreams_.size() * ICE_COMP_COUNT_PER_STREAM);
4156 767 : for (const auto& stream : rtpStreams_) {
4157 496 : iceOptions.qosType.push_back(stream.mediaAttribute_->type_ == MediaType::MEDIA_AUDIO ? dhtnet::QosType::VOICE
4158 : : dhtnet::QosType::VIDEO);
4159 496 : iceOptions.qosType.push_back(dhtnet::QosType::CONTROL);
4160 : }
4161 :
4162 : // Init ICE.
4163 271 : iceMedia->initIceInstance(iceOptions);
4164 :
4165 271 : return true;
4166 271 : }
4167 :
4168 : std::vector<std::string>
4169 0 : SIPCall::getLocalIceCandidates(unsigned compId) const
4170 : {
4171 0 : std::lock_guard lk(transportMtx_);
4172 0 : if (not iceMedia_) {
4173 0 : JAMI_WARNING("[call:{}] No media ICE transport", getCallId());
4174 0 : return {};
4175 : }
4176 0 : return iceMedia_->getLocalCandidates(compId);
4177 0 : }
4178 :
4179 : void
4180 1537 : SIPCall::resetTransport(std::shared_ptr<dhtnet::IceTransport>&& transport)
4181 : {
4182 : // Move the transport to another thread and destroy it there if possible
4183 1537 : if (transport) {
4184 922 : dht::ThreadPool::io().run([transport = std::move(transport)]() mutable { transport.reset(); });
4185 : }
4186 1537 : }
4187 :
4188 : void
4189 84 : SIPCall::merge(Call& call)
4190 : {
4191 84 : JAMI_DEBUG("[call:{}] Merge subcall {}", getCallId(), call.getCallId());
4192 :
4193 : // This static cast is safe as this method is private and overload Call::merge
4194 84 : auto& subcall = static_cast<SIPCall&>(call);
4195 :
4196 84 : std::lock(callMutex_, subcall.callMutex_);
4197 84 : std::lock_guard lk1 {callMutex_, std::adopt_lock};
4198 84 : std::lock_guard lk2 {subcall.callMutex_, std::adopt_lock};
4199 84 : inviteSession_ = std::move(subcall.inviteSession_);
4200 84 : if (inviteSession_)
4201 84 : inviteSession_->mod_data[Manager::instance().sipVoIPLink().getModId()] = this;
4202 84 : setSipTransport(std::move(subcall.sipTransport_), std::move(subcall.contactHeader_));
4203 84 : sdp_ = std::move(subcall.sdp_);
4204 84 : peerHold_ = subcall.peerHold_;
4205 84 : upnp_ = std::move(subcall.upnp_);
4206 84 : localAudioPort_ = subcall.localAudioPort_;
4207 84 : localVideoPort_ = subcall.localVideoPort_;
4208 84 : peerUserAgent_ = subcall.peerUserAgent_;
4209 84 : peerSupportMultiStream_ = subcall.peerSupportMultiStream_;
4210 84 : peerSupportMultiAudioStream_ = subcall.peerSupportMultiAudioStream_;
4211 84 : peerSupportMultiIce_ = subcall.peerSupportMultiIce_;
4212 : {
4213 84 : std::scoped_lock methodsLock {peerAllowedMethodsMutex_, subcall.peerAllowedMethodsMutex_};
4214 84 : peerAllowedMethods_ = subcall.peerAllowedMethods_;
4215 84 : }
4216 84 : peerSupportReuseIceInReinv_ = subcall.peerSupportReuseIceInReinv_;
4217 84 : peerSupportsHandover_ = subcall.peerSupportsHandover_.load();
4218 :
4219 84 : Call::merge(subcall);
4220 84 : if (isIceEnabled())
4221 84 : startIceMedia();
4222 84 : }
4223 :
4224 : bool
4225 394 : SIPCall::remoteHasValidIceAttributes() const
4226 : {
4227 394 : if (not sdp_) {
4228 0 : throw std::runtime_error("Must have a valid SDP Session");
4229 : }
4230 :
4231 394 : auto rem_ice_attrs = sdp_->getIceAttributes();
4232 394 : if (rem_ice_attrs.ufrag.empty()) {
4233 17 : JAMI_DEBUG("[call:{}] No ICE username fragment attribute in remote SDP", getCallId());
4234 17 : return false;
4235 : }
4236 :
4237 377 : if (rem_ice_attrs.pwd.empty()) {
4238 0 : JAMI_DEBUG("[call:{}] No ICE password attribute in remote SDP", getCallId());
4239 0 : return false;
4240 : }
4241 :
4242 377 : return true;
4243 394 : }
4244 :
4245 : void
4246 461 : SIPCall::setIceMedia(std::shared_ptr<dhtnet::IceTransport> ice, bool isReinvite)
4247 : {
4248 461 : std::lock_guard lk(transportMtx_);
4249 :
4250 461 : if (isReinvite) {
4251 56 : JAMI_DEBUG("[call:{}] Setting re-invite ICE session [{}]", getCallId(), fmt::ptr(ice.get()));
4252 56 : resetTransport(std::move(reinvIceMedia_));
4253 56 : reinvIceMedia_ = std::move(ice);
4254 : } else {
4255 405 : JAMI_DEBUG("[call:{}] Setting ICE session [{}]", getCallId(), fmt::ptr(ice.get()));
4256 405 : resetTransport(std::move(iceMedia_));
4257 405 : iceMedia_ = std::move(ice);
4258 : }
4259 461 : }
4260 :
4261 : void
4262 212 : SIPCall::switchToIceReinviteIfNeeded()
4263 : {
4264 212 : std::lock_guard lk(transportMtx_);
4265 :
4266 212 : if (reinvIceMedia_) {
4267 44 : JAMI_DEBUG("[call:{}] Switching to re-invite ICE session [{}]", getCallId(), fmt::ptr(reinvIceMedia_.get()));
4268 44 : std::swap(reinvIceMedia_, iceMedia_);
4269 : }
4270 :
4271 212 : resetTransport(std::move(reinvIceMedia_));
4272 212 : }
4273 :
4274 : void
4275 128 : SIPCall::setupIceResponse(bool isReinvite, bool withoutUpnpWait)
4276 : {
4277 128 : JAMI_DEBUG("[call:{}] Setup ICE response", getCallId());
4278 :
4279 128 : auto account = getSIPAccount();
4280 128 : if (not account) {
4281 0 : JAMI_ERROR("No account detected");
4282 : }
4283 :
4284 128 : auto opt = account->getIceOptions();
4285 128 : if (withoutUpnpWait)
4286 6 : opt.upnpMappingTimeout = {};
4287 :
4288 : // Attempt to use the discovered public address. If not available,
4289 : // fallback on local address.
4290 128 : opt.accountPublicAddr = account->getPublishedIpAddress();
4291 128 : if (opt.accountPublicAddr) {
4292 120 : opt.accountLocalAddr = dhtnet::ip_utils::getInterfaceAddr(account->getLocalInterface(),
4293 120 : opt.accountPublicAddr.getFamily());
4294 : } else {
4295 : // Just set the local address for both, most likely the account is not
4296 : // registered. Try IPv4 first, fallback to IPv6 for IPv6-only networks.
4297 8 : opt.accountLocalAddr = dhtnet::ip_utils::getInterfaceAddr(account->getLocalInterface(), AF_INET);
4298 8 : if (not opt.accountLocalAddr)
4299 0 : opt.accountLocalAddr = dhtnet::ip_utils::getInterfaceAddr(account->getLocalInterface(), AF_INET6);
4300 8 : opt.accountPublicAddr = opt.accountLocalAddr;
4301 : }
4302 :
4303 128 : if (not opt.accountLocalAddr) {
4304 0 : JAMI_ERROR("[call:{}] No local address, unable to initialize ICE", getCallId());
4305 0 : onFailure(PJSIP_SC_SERVICE_UNAVAILABLE);
4306 0 : return;
4307 : }
4308 :
4309 128 : if (not createIceMediaTransport(isReinvite) or not initIceMediaTransport(false, opt)) {
4310 0 : JAMI_ERROR("[call:{}] ICE initialization failed", getCallId());
4311 : // Fatal condition
4312 : // TODO: what's SIP rfc says about that?
4313 : // (same question in startIceMedia)
4314 0 : onFailure(PJSIP_SC_INTERNAL_SERVER_ERROR);
4315 0 : return;
4316 : }
4317 :
4318 128 : if (isReinvite and recovering_ and not waitingForRecoveryChannel_ and isCurrentMediaNegotiation())
4319 6 : recoveryNegotiation_ = RecoveryNegotiation {recoveryGeneration_, sipTransport_, getIceMedia()};
4320 :
4321 : // Media transport changed, must restart the media.
4322 128 : mediaRestartRequired_ = true;
4323 :
4324 : // WARNING: This call blocks! (need ICE init done)
4325 128 : addLocalIceAttributes();
4326 128 : }
4327 :
4328 : bool
4329 217 : SIPCall::isIceRunning() const
4330 : {
4331 217 : std::lock_guard lk(transportMtx_);
4332 434 : return iceMedia_ and iceMedia_->isRunning();
4333 217 : }
4334 :
4335 : std::unique_ptr<dhtnet::IceSocket>
4336 792 : SIPCall::newIceSocket(unsigned compId)
4337 : {
4338 792 : return std::unique_ptr<dhtnet::IceSocket> {new dhtnet::IceSocket(getIceMedia(), static_cast<int>(compId))};
4339 : }
4340 :
4341 : void
4342 611 : SIPCall::rtpSetupSuccess()
4343 : {
4344 611 : updateRecordingMediaState(true);
4345 611 : }
4346 :
4347 : void
4348 19 : SIPCall::peerRecording(bool state)
4349 : {
4350 19 : auto conference = conf_.lock();
4351 19 : const std::string& id = conference ? conference->getConfId() : getCallId();
4352 19 : if (state) {
4353 11 : JAMI_WARNING("[call:{}] Peer is recording", getCallId());
4354 11 : emitSignal<libjami::CallSignal::RemoteRecordingChanged>(id, getPeerNumber(), true);
4355 : } else {
4356 8 : JAMI_WARNING("Peer stopped recording");
4357 8 : emitSignal<libjami::CallSignal::RemoteRecordingChanged>(id, getPeerNumber(), false);
4358 : }
4359 19 : peerRecording_ = state;
4360 19 : if (auto conf = conf_.lock())
4361 19 : conf->updateRecording();
4362 19 : }
4363 :
4364 : void
4365 5 : SIPCall::peerMuted(bool muted, int streamIdx)
4366 : {
4367 5 : if (muted) {
4368 5 : JAMI_WARNING("Peer muted");
4369 : } else {
4370 0 : JAMI_WARNING("Peer unmuted");
4371 : }
4372 :
4373 5 : if (streamIdx == -1) {
4374 10 : for (const auto& audioRtp : getRtpSessionList(MediaType::MEDIA_AUDIO))
4375 10 : audioRtp->setMuted(muted, RtpSession::Direction::RECV);
4376 0 : } else if (streamIdx > -1 && streamIdx < static_cast<int>(rtpStreams_.size())) {
4377 0 : auto& stream = rtpStreams_[streamIdx];
4378 0 : if (stream.rtpSession_ && stream.rtpSession_->getMediaType() == MediaType::MEDIA_AUDIO)
4379 0 : stream.rtpSession_->setMuted(muted, RtpSession::Direction::RECV);
4380 : }
4381 :
4382 5 : peerMuted_ = muted;
4383 5 : if (auto conf = conf_.lock())
4384 5 : conf->updateMuted();
4385 5 : }
4386 :
4387 : void
4388 0 : SIPCall::peerVoice(bool voice)
4389 : {
4390 0 : peerVoice_ = voice;
4391 :
4392 0 : if (auto conference = conf_.lock()) {
4393 0 : conference->updateVoiceActivity();
4394 : } else {
4395 : // one-to-one call
4396 : // maybe emit signal with partner voice activity
4397 0 : }
4398 0 : }
4399 :
4400 : } // namespace jami
|