LCOV - code coverage report
Current view: top level - src/sip - sipcall.cpp (source / functions) Coverage Total Hit
Test: jami-coverage-filtered.info Lines: 71.7 % 2502 1793
Test Date: 2026-11-09 10:41:14 Functions: 89.0 % 191 170

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

Generated by: LCOV version 2.0-1