LCOV - code coverage report
Current view: top level - foo/src/sip - sipcall.cpp (source / functions) Hit Total Coverage
Test: jami-coverage-filtered.info Lines: 1367 1942 70.4 %
Date: 2026-04-01 09:29:43 Functions: 311 556 55.9 %

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

Generated by: LCOV version 1.14