Skip to content
Snippets Groups Projects
Select Git revision
  • 987811a540769ca54d8e8752d8fa29b23a7da264
  • master default protected
  • release/202005
  • release/202001
  • release/201912
  • release/201911
  • release/releaseWindowsTestOne
  • release/releaseTest
  • release/releaseWindowsTest
  • release/windowsReleaseTest
  • release/201910
  • release/qt/201910
  • release/windows-test/201910
  • release/201908
  • release/201906
  • release/201905
  • release/201904
  • release/201903
  • release/201902
  • release/201901
  • release/201812
  • 1.0.0
  • 0.3.0
  • 0.2.1
  • 0.2.0
  • 0.1.0
26 results

directrenderer.cpp

Blame
  • Code owners
    Assign users and groups as approvers for specific file changes. Learn more.
    call.cpp 18.96 KiB
    /*
     *  Copyright (C) 2004-2019 Savoir-faire Linux Inc.
     *
     *  Author: Yan Morin <yan.morin@savoirfairelinux.com>
     *  Author: Laurielle Lea <laurielle.lea@savoirfairelinux.com>
     *  Author: Guillaume Roguez <guillaume.roguez@savoirfairelinux.com>
     *
     *  This program is free software; you can redistribute it and/or modify
     *  it under the terms of the GNU General Public License as published by
     *  the Free Software Foundation; either version 3 of the License, or
     *  (at your option) any later version.
     *
     *  This program is distributed in the hope that it will be useful,
     *  but WITHOUT ANY WARRANTY; without even the implied warranty of
     *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     *  GNU General Public License for more details.
     *
     *  You should have received a copy of the GNU General Public License
     *  along with this program; if not, write to the Free Software
     *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
     */
    
    #include "call.h"
    #include "account.h"
    #include "manager.h"
    #include "audio/ringbufferpool.h"
    #include "dring/call_const.h"
    #include "client/ring_signal.h"
    #include "sip/sip_utils.h"
    #include "ip_utils.h"
    #include "array_size.h"
    #include "map_utils.h"
    #include "call_factory.h"
    #include "string_utils.h"
    #include "enumclass_utils.h"
    
    #include "errno.h"
    
    #include <stdexcept>
    #include <system_error>
    #include <algorithm>
    #include <functional>
    #include <utility>
    
    namespace jami {
    
    /// Hangup many calls with same error code, filtered by a predicate
    ///
    /// For each call pointer given by iterating on given \a callptr_list
    /// calls the unary predicate \a pred with this call pointer and hangup the call with given error
    /// code \a errcode when the predicate return true.
    /// The predicate should have <code>bool(Call*) signature</code>.
    inline void
    hangupCallsIf(const Call::SubcallSet& callptr_list, int errcode, const std::function<bool(Call*)>& pred)
    {
        std::for_each(std::begin(callptr_list), std::end(callptr_list),
                      [&](const std::shared_ptr<Call>& call_ptr) {
                          if (pred(call_ptr.get())) {
                              try {
                                  call_ptr->hangup(errcode);
                              } catch (const std::exception& e) {
                                  JAMI_ERR("[call:%s] hangup failed: %s",
                                           call_ptr->getCallId().c_str(), e.what());
                              }
                          }
                      });
    }
    
    /// Hangup many calls with same error code.
    ///
    /// Works as hangupCallsIf() with a predicate that always return true.
    inline void
    hangupCalls(const Call::SubcallSet& callptr_list, int errcode)
    {
        hangupCallsIf(callptr_list, errcode, [](Call*){ return true; });
    }
    
    //==============================================================================
    
    Call::Call(Account& account, const std::string& id, Call::CallType type,
               const std::map<std::string, std::string>& details)
        : id_(id)
        , type_(type)
        , account_(account)
    {
        updateDetails(details);
    
        addStateListener([this](Call::CallState call_state,
                                Call::ConnectionState cnx_state,
                                UNUSED int code) {
            checkPendingIM();
            checkAudio();
    
            // if call just started ringing, schedule call timeout
            if (type_ == CallType::INCOMING and cnx_state == ConnectionState::RINGING) {
                auto timeout = Manager::instance().getRingingTimeout();
                JAMI_DBG("Scheduling call timeout in %d seconds", timeout);
    
               std::weak_ptr<Call> callWkPtr = shared_from_this();
               Manager::instance().scheduler().scheduleIn([callWkPtr]{
                   if (auto callShPtr = callWkPtr.lock()) {
                        if (callShPtr->getConnectionState() == Call::ConnectionState::RINGING) {
                             JAMI_DBG("Call %s is still ringing after timeout, setting state to BUSY",
                                 callShPtr->getCallId().c_str());
                             callShPtr->hangup(PJSIP_SC_BUSY_HERE);
                             Manager::instance().callFailure(*callShPtr);
                        }
                    }
               }, std::chrono::seconds(timeout));
            }
    
            // kill pending subcalls at disconnect
            if (call_state == CallState::OVER)
                hangupCalls(safePopSubcalls(), 0);
        });
    
        time(&timestamp_start_);
        account_.attachCall(id_);
    }
    
    Call::~Call()
    {
        account_.detachCall(id_);
    }
    
    void
    Call::removeCall()
    {
        auto this_ = shared_from_this();
        Manager::instance().callFactory.removeCall(*this);
        setState(CallState::OVER);
        if (Recordable::isRecording())
            Recordable::stopRecording();
    }
    
    const std::string&
    Call::getAccountId() const
    {
        return account_.getAccountID();
    }
    
    Call::ConnectionState
    Call::getConnectionState() const
    {
        std::lock_guard<std::recursive_mutex> lock(callMutex_);
        return connectionState_;
    }
    
    Call::CallState
    Call::getState() const
    {
        std::lock_guard<std::recursive_mutex> lock(callMutex_);
        return callState_;
    }
    
    bool
    Call::validStateTransition(CallState newState)
    {
        // Notice to developper:
        // - list only permitted transition (return true)
        // - let non permitted ones as default case (return false)
    
        // always permited
        if (newState == CallState::OVER)
            return true;
    
        switch (callState_) {
            case CallState::INACTIVE:
                switch (newState) {
                    case CallState::ACTIVE:
                    case CallState::BUSY:
                    case CallState::PEER_BUSY:
                    case CallState::MERROR:
                        return true;
                    default: // INACTIVE, HOLD
                        return false;
                }
    
            case CallState::ACTIVE:
                switch (newState) {
                    case CallState::BUSY:
                    case CallState::PEER_BUSY:
                    case CallState::HOLD:
                    case CallState::MERROR:
                        return true;
                    default: // INACTIVE, ACTIVE
                        return false;
                }
    
            case CallState::HOLD:
                switch (newState) {
                    case CallState::ACTIVE:
                    case CallState::MERROR:
                        return true;
                    default: // INACTIVE, HOLD, BUSY, PEER_BUSY, MERROR
                        return false;
                }
    
            case CallState::BUSY:
                switch (newState) {
                    case CallState::MERROR:
                        return true;
                    default: // INACTIVE, ACTIVE, HOLD, BUSY, PEER_BUSY
                        return false;
                }
    
            default: // MERROR
                return false;
        }
    }
    
    bool
    Call::setState(CallState call_state, ConnectionState cnx_state, signed code)
    {
        std::lock_guard<std::recursive_mutex> lock(callMutex_);
        JAMI_DBG("[call:%s] state change %u/%u, cnx %u/%u, code %d", id_.c_str(),
                 (unsigned)callState_, (unsigned)call_state, (unsigned)connectionState_,
                 (unsigned)cnx_state, code);
    
        if (callState_ != call_state) {
            if (not validStateTransition(call_state)) {
                JAMI_ERR("[call:%s] invalid call state transition from %u to %u",
                         id_.c_str(), (unsigned)callState_, (unsigned)call_state);
                return false;
            }
        } else if (connectionState_ == cnx_state)
            return true; // no changes as no-op
    
        // Emit client state only if changed
        auto old_client_state = getStateStr();
        callState_ = call_state;
        connectionState_ = cnx_state;
        auto new_client_state = getStateStr();
    
        for (auto& l : stateChangedListeners_)
            l(callState_, connectionState_, code);
    
        if (old_client_state != new_client_state) {
            if (not parent_) {
                JAMI_DBG("[call:%s] emit client call state change %s, code %d",
                         id_.c_str(), new_client_state.c_str(), code);
                emitSignal<DRing::CallSignal::StateChange>(id_, new_client_state, code);
            }
        }
    
        return true;
    }
    
    bool
    Call::setState(CallState call_state, signed code)
    {
        std::lock_guard<std::recursive_mutex> lock(callMutex_);
        return setState(call_state, connectionState_, code);
    }
    
    bool
    Call::setState(ConnectionState cnx_state, signed code)
    {
        std::lock_guard<std::recursive_mutex> lock(callMutex_);
        return setState(callState_, cnx_state, code);
    }
    
    std::string
    Call::getStateStr() const
    {
        using namespace DRing::Call;
    
        switch (getState()) {
            case CallState::ACTIVE:
                switch (getConnectionState()) {
                    case ConnectionState::TRYING:
                    case ConnectionState::PROGRESSING:
                        return StateEvent::CONNECTING;
    
                    case ConnectionState::RINGING:
                        return isIncoming() ? StateEvent::INCOMING : StateEvent::RINGING;
    
                    case ConnectionState::DISCONNECTED:
                        return StateEvent::HUNGUP;
    
                    case ConnectionState::CONNECTED:
                    default:
                        return StateEvent::CURRENT;
                }
    
            case CallState::HOLD:
                if(getConnectionState() == ConnectionState::DISCONNECTED)
                    return StateEvent::HUNGUP;
                return StateEvent::HOLD;
    
            case CallState::BUSY:
                return StateEvent::BUSY;
    
            case CallState::PEER_BUSY:
                return StateEvent::PEER_BUSY;
    
            case CallState::INACTIVE:
                switch (getConnectionState()) {
                    case ConnectionState::TRYING:
                    case ConnectionState::PROGRESSING:
                        return StateEvent::CONNECTING;
    
                    case ConnectionState::RINGING:
                        return isIncoming() ? StateEvent::INCOMING : StateEvent::RINGING;
    
                    case ConnectionState::CONNECTED:
                        return StateEvent::CURRENT;
    
                    default:
                        return StateEvent::INACTIVE;
                }
    
            case CallState::OVER:
                return StateEvent::OVER;
    
            case CallState::MERROR:
            default:
                return StateEvent::FAILURE;
        }
    }
    
    bool
    Call::toggleRecording()
    {
        const bool startRecording = Recordable::toggleRecording();
        return startRecording;
    }
    
    void
    Call::updateDetails(const std::map<std::string, std::string>& details)
    {
        const auto& iter = details.find(DRing::Call::Details::AUDIO_ONLY);
        if (iter != std::end(details))
            isAudioOnly_ = iter->second == TRUE_STR;
    }
    
    std::map<std::string, std::string>
    Call::getDetails() const
    {
        return {
            {DRing::Call::Details::CALL_TYPE,        std::to_string((unsigned)type_)},
            {DRing::Call::Details::PEER_NUMBER,      peerNumber_},
            {DRing::Call::Details::DISPLAY_NAME,     peerDisplayName_},
            {DRing::Call::Details::CALL_STATE,       getStateStr()},
            {DRing::Call::Details::CONF_ID,          confID_},
            {DRing::Call::Details::TIMESTAMP_START,  std::to_string(timestamp_start_)},
            {DRing::Call::Details::ACCOUNTID,        getAccountId()},
            {DRing::Call::Details::AUDIO_MUTED,      std::string(bool_to_str(isAudioMuted_))},
            {DRing::Call::Details::VIDEO_MUTED,      std::string(bool_to_str(isVideoMuted_))},
            {DRing::Call::Details::AUDIO_ONLY,       std::string(bool_to_str(isAudioOnly_))},
        };
    }
    
    std::map<std::string, std::string>
    Call::getNullDetails()
    {
        return {
            {DRing::Call::Details::CALL_TYPE,        "0"},
            {DRing::Call::Details::PEER_NUMBER,      ""},
            {DRing::Call::Details::DISPLAY_NAME,     ""},
            {DRing::Call::Details::CALL_STATE,       "UNKNOWN"},
            {DRing::Call::Details::CONF_ID,          ""},
            {DRing::Call::Details::TIMESTAMP_START,  ""},
            {DRing::Call::Details::ACCOUNTID,        ""},
            {DRing::Call::Details::VIDEO_SOURCE,     "UNKNOWN"},
            {DRing::Call::Details::AUDIO_ONLY,       ""},
        };
    }
    
    void
    Call::onTextMessage(std::map<std::string, std::string>&& messages)
    {
        {
            std::lock_guard<std::recursive_mutex> lk {callMutex_};
            if (parent_) {
                pendingInMessages_.emplace_back(std::move(messages), "");
                return;
            }
        }
        Manager::instance().incomingMessage(getCallId(), getPeerNumber(), messages);
    }
    
    void
    Call::peerHungup()
    {
        const auto state = getState();
        const auto aborted = state == CallState::ACTIVE or state == CallState::HOLD;
        setState(ConnectionState::DISCONNECTED,
                 aborted ? ECONNABORTED : ECONNREFUSED);
    }
    
    void
    Call::addSubCall(Call& subcall)
    {
        std::lock_guard<std::recursive_mutex> lk {callMutex_};
    
        // XXX: following check seems wobbly - need comment
        if (connectionState_ == ConnectionState::CONNECTED
            || callState_ == CallState::ACTIVE
            || callState_ == CallState::OVER) {
            subcall.removeCall();
            return;
        }
    
        if (not subcalls_.emplace(getPtr(subcall)).second) {
            JAMI_ERR("[call:%s] add twice subcall %s", getCallId().c_str(), subcall.getCallId().c_str());
            return;
        }
    
        JAMI_DBG("[call:%s] add subcall %s", getCallId().c_str(), subcall.getCallId().c_str());
        subcall.parent_ = getPtr(*this);
    
        for (const auto& msg : pendingOutMessages_)
            subcall.sendTextMessage(msg.first, msg.second);
    
        subcall.addStateListener(
            [&subcall](Call::CallState new_state, Call::ConnectionState new_cstate, UNUSED int code) {
                auto parent = subcall.parent_;
                assert(parent != nullptr); // subcall cannot be "un-parented"
                parent->subcallStateChanged(subcall, new_state, new_cstate);
            });
    }
    
    /// Called by a subcall when its states change (multidevice)
    ///
    /// Its purpose is to manage per device call and try to found the first responding.
    /// Parent call states are managed by these subcalls.
    /// \note this method may decrease the given \a subcall ref count.
    void
    Call::subcallStateChanged(Call& subcall,
                              Call::CallState new_state,
                              Call::ConnectionState new_cstate)
    {
        {
            // This condition happens when a subcall hangups/fails after removed from parent's list.
            // This is normal to keep parent_ != nullptr on the subcall, as it's the way to flag it
            // as an subcall and not a master call.
            // XXX: having a way to unsubscribe the state listener could be better than such test
            std::lock_guard<std::recursive_mutex> lk {callMutex_};
            auto sit = subcalls_.find(getPtr(subcall));
            if (sit == subcalls_.end())
                return;
        }
    
        // We found a responding device: hangup all other subcalls and merge
        if (new_state == CallState::ACTIVE and new_cstate == ConnectionState::CONNECTED) {
            JAMI_DBG("[call:%s] subcall %s answered by peer", getCallId().c_str(),
                     subcall.getCallId().c_str());
    
            hangupCallsIf(safePopSubcalls(), 0, [&](const Call* call){ return call != &subcall; });
            merge(subcall);
            Manager::instance().peerAnsweredCall(*this);
            return;
        }
    
        // Hangup the call if any device hangup
        // XXX: not sure it's what we really want
        if (new_state == CallState::ACTIVE and new_cstate == ConnectionState::DISCONNECTED) {
            JAMI_WARN("[call:%s] subcall %s hangup by peer", getCallId().c_str(),
                      subcall.getCallId().c_str());
    
            hangupCalls(safePopSubcalls(), 0);
            Manager::instance().peerHungupCall(*this);
            removeCall();
            return;
        }
    
        // Subcall is busy or failed
        if (new_state >= CallState::BUSY) {
            if (new_state == CallState::BUSY || new_state == CallState::PEER_BUSY)
                JAMI_WARN("[call:%s] subcall %s busy", getCallId().c_str(), subcall.getCallId().c_str());
            else
                JAMI_WARN("[call:%s] subcall %s failed", getCallId().c_str(), subcall.getCallId().c_str());
            std::lock_guard<std::recursive_mutex> lk {callMutex_};
            subcalls_.erase(getPtr(subcall));
    
            // Parent call fails if last subcall is busy or failed
            if (subcalls_.empty()) {
                if (new_state == CallState::BUSY) {
                    setState(CallState::BUSY, ConnectionState::DISCONNECTED, static_cast<int>(std::errc::device_or_resource_busy));
                } else if (new_state == CallState::PEER_BUSY) {
                    setState(CallState::PEER_BUSY, ConnectionState::DISCONNECTED, static_cast<int>(std::errc::device_or_resource_busy));
                } else {
                    // XXX: first idea was to use std::errc::host_unreachable, but it's not available on some platforms
                    // like mingw.
                    setState(CallState::MERROR, ConnectionState::DISCONNECTED, static_cast<int>(std::errc::io_error));
                }
                removeCall();
            } else {
                JAMI_DBG("[call:%s] remains %zu subcall(s)", getCallId().c_str(), subcalls_.size());
            }
    
            return;
        }
    
        // Copy call/cnx states (forward only)
        if (new_state == CallState::ACTIVE && callState_ == CallState::INACTIVE) {
            setState(new_state);
        }
        if (static_cast<unsigned>(connectionState_) < static_cast<unsigned>(new_cstate)
            and static_cast<unsigned>(new_cstate) <= static_cast<unsigned>(ConnectionState::RINGING)) {
            setState(new_cstate);
        }
    }
    
    /// Replace current call data with ones from the given \a subcall.
    ///
    void
    Call::merge(Call& subcall)
    {
        JAMI_DBG("[call:%s] merge subcall %s", getCallId().c_str(), subcall.getCallId().c_str());
    
        // Merge data
        {
            std::lock(callMutex_, subcall.callMutex_);
            std::lock_guard<std::recursive_mutex> lk1 {callMutex_, std::adopt_lock};
            std::lock_guard<std::recursive_mutex> lk2 {subcall.callMutex_, std::adopt_lock};
            pendingInMessages_ = std::move(subcall.pendingInMessages_);
            if (peerNumber_.empty())
                peerNumber_ = std::move(subcall.peerNumber_);
            peerDisplayName_ = std::move(subcall.peerDisplayName_);
            setState(subcall.getState(), subcall.getConnectionState());
        }
    
        subcall.removeCall();
    }
    
    /// Handle pending IM message
    ///
    /// Used in multi-device context to send pending IM when the master call is connected.
    void
    Call::checkPendingIM()
    {
        std::lock_guard<std::recursive_mutex> lk {callMutex_};
    
        auto state = getStateStr();
        // Let parent call handles IM after the merge
        if (not parent_) {
            if (state == DRing::Call::StateEvent::CURRENT) {
                for (const auto& msg : pendingInMessages_)
                    Manager::instance().incomingMessage(getCallId(), getPeerNumber(), msg.first);
                pendingInMessages_.clear();
    
                for (const auto& msg : pendingOutMessages_)
                    sendTextMessage(msg.first, msg.second);
                pendingOutMessages_.clear();
            }
        }
    }
    
    /// Handle tones for RINGING and BUSY calls
    ///
    void
    Call::checkAudio()
    {
        using namespace DRing::Call;
    
        auto state = getStateStr();
        if (state == StateEvent::RINGING) {
            Manager::instance().peerRingingCall(*this);
        } else if (state == StateEvent::BUSY) {
            Manager::instance().callBusy(*this);
        }
    }
    
    // Helper to safely pop subcalls list
    Call::SubcallSet
    Call::safePopSubcalls()
    {
        std::lock_guard<std::recursive_mutex> lk {callMutex_};
        // std::exchange is C++14
        auto old_value = std::move(subcalls_);
        subcalls_.clear();
        return old_value;
    }
    
    } // namespace jami