LCOV - code coverage report
Current view: top level - foo/src/sip - sipcall.cpp (source / functions) Hit Total Coverage
Test: jami-coverage-filtered.info Lines: 1340 1941 69.0 %
Date: 2025-12-18 10:07:43 Functions: 166 220 75.5 %

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

Generated by: LCOV version 1.14