LCOV - code coverage report
Current view: top level - foo/src/sip - sipcall.cpp (source / functions) Hit Total Coverage
Test: jami-coverage-filtered.info Lines: 1347 1933 69.7 %
Date: 2026-02-28 10:41:24 Functions: 303 552 54.9 %

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

Generated by: LCOV version 1.14