LCOV - code coverage report
Current view: top level - src/sip - sipcall.cpp (source / functions) Coverage Total Hit
Test: jami-coverage-filtered.info Lines: 71.0 % 1944 1381
Test Date: 2026-07-06 08:25:38 Functions: 56.0 % 557 312

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

Generated by: LCOV version 2.0-1