LCOV - code coverage report
Current view: top level - src/sip - sipcall.cpp (source / functions) Hit Total Coverage
Test: jami-coverage-filtered.info Lines: 1402 1955 71.7 %
Date: 2024-04-18 08:01:59 Functions: 144 194 74.2 %

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

Generated by: LCOV version 1.14