LCOV - code coverage report
Current view: top level - foo/src/sip - sipcall.cpp (source / functions) Hit Total Coverage
Test: jami-coverage-filtered.info Lines: 1403 1987 70.6 %
Date: 2025-10-16 08:11:43 Functions: 162 212 76.4 %

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

Generated by: LCOV version 1.14