Skip to content
Snippets Groups Projects
Select Git revision
  • master default protected
  • release/202005
  • release/202001
  • release/201912
  • release/201911
  • release/releaseWindowsTestOne
  • release/windowsReleaseTest
  • release/releaseTest
  • release/releaseWindowsTest
  • 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
  • 4.0.0
  • 2.2.0
  • 2.1.0
  • 2.0.1
  • 2.0.0
  • 1.4.1
  • 1.4.0
  • 1.3.0
  • 1.2.0
  • 1.1.0
30 results

sipvoiplink.cpp

Blame
  • Code owners
    Assign users and groups as approvers for specific file changes. Learn more.
    sipvoiplink.cpp 77.89 KiB
    /*
     *  Copyright (C) 2004-2013 Savoir-Faire Linux Inc.
     *  Author: Emmanuel Milou <emmanuel.milou@savoirfairelinux.com>
     *  Author: Yun Liu <yun.liu@savoirfairelinux.com>
     *  Author: Pierre-Luc Bacon <pierre-luc.bacon@savoirfairelinux.com>
     *  Author: Alexandre Savard <alexandre.savard@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.
     *
     *  Additional permission under GNU GPL version 3 section 7:
     *
     *  If you modify this program, or any covered work, by linking or
     *  combining it with the OpenSSL project's OpenSSL library (or a
     *  modified version of that library), containing parts covered by the
     *  terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
     *  grants you additional permission to convey the resulting work.
     *  Corresponding Source for a non-source form of such a combination
     *  shall include the source code for the parts of OpenSSL used as well
     *  as that of the covered work.
     */
    
    #ifdef HAVE_CONFIG_H
    #include "config.h"
    #endif
    
    #include "sip_utils.h"
    
    #include "sipvoiplink.h"
    #include "array_size.h"
    #include "manager.h"
    #include "map_utils.h"
    #include "logger.h"
    
    #include "sip/sdp.h"
    #include "sipcall.h"
    #include "eventthread.h"
    #if HAVE_SDES
    #include "sdes_negotiator.h"
    #endif
    #include "array_size.h"
    
    
    #if HAVE_INSTANT_MESSAGING
    #include "im/instant_messaging.h"
    #endif
    
    #include "audio/audiolayer.h"
    
    #ifdef SFL_VIDEO
    #include "video/video_rtp_session.h"
    #include "client/video_controls.h"
    #endif
    
    #include "client/client.h"
    #include "client/callmanager.h"
    #include "client/configurationmanager.h"
    
    #include "pjsip/sip_endpoint.h"
    #include "pjsip/sip_uri.h"
    #include "pjnath.h"
    
    #include <netinet/in.h>
    #include <arpa/nameser.h>
    #include <arpa/inet.h>
    #include <resolv.h>
    #include <istream>
    #include <utility> // for std::pair
    #include <algorithm>
    
    #ifdef SFL_PRESENCE
    #include "pjsip-simple/presence.h"
    #include "pjsip-simple/publish.h"
    #include "pres_sub_server.h"
    #endif
    
    using namespace sfl;
    
    SIPVoIPLink *SIPVoIPLink::instance_ = 0;
    
    namespace {
    
    /** Environment variable used to set pjsip's logging level */
    #define SIPLOGLEVEL "SIPLOGLEVEL"
    
    /** A map to retreive SFLphone internal call id
     *  Given a SIP call ID (usefull for transaction sucha as transfer)*/
    static std::map<std::string, std::string> transferCallID;
    
    /**************** EXTERN VARIABLES AND FUNCTIONS (callbacks) **************************/
    
    /**
     * Set audio and video (SDP) configuration for a call
     * localport, localip, localexternalport
     * @param call a SIPCall valid pointer
     */
    void setCallMediaLocal(SIPCall* call, const std::string &localIP);
    
    static pj_caching_pool pool_cache, *cp_ = &pool_cache;
    static pj_pool_t *pool_;
    static pjsip_endpoint *endpt_;
    static pjsip_module mod_ua_;
    
    void sdp_media_update_cb(pjsip_inv_session *inv, pj_status_t status);
    void sdp_request_offer_cb(pjsip_inv_session *inv, const pjmedia_sdp_session *offer);
    void sdp_create_offer_cb(pjsip_inv_session *inv, pjmedia_sdp_session **p_offer);
    void invite_session_state_changed_cb(pjsip_inv_session *inv, pjsip_event *e);
    void outgoing_request_forked_cb(pjsip_inv_session *inv, pjsip_event *e);
    void transaction_state_changed_cb(pjsip_inv_session *inv, pjsip_transaction *tsx, pjsip_event *e);
    void registration_cb(pjsip_regc_cbparam *param);
    pj_bool_t transaction_request_cb(pjsip_rx_data *rdata);
    pj_bool_t transaction_response_cb(pjsip_rx_data *rdata) ;
    
    void transfer_client_cb(pjsip_evsub *sub, pjsip_event *event);
    
    /**
     * Send a reINVITE inside an active dialog to modify its state
     * Local SDP session should be modified before calling this method
     * @param sip call
     */
    int SIPSessionReinvite(SIPCall *);
    
    /**
     * Helper function to process refer function on call transfer
     */
    void onCallTransfered(pjsip_inv_session *inv, pjsip_rx_data *rdata);
    
    void handleIncomingOptions(pjsip_rx_data *rdata)
    {
        pjsip_tx_data *tdata;
    
        if (pjsip_endpt_create_response(endpt_, rdata, PJSIP_SC_OK, NULL, &tdata) != PJ_SUCCESS)
            return;
    
    #define ADD_HDR(hdr) do { \
        const pjsip_hdr *cap_hdr = hdr; \
        if (cap_hdr) \
        pjsip_msg_add_hdr (tdata->msg, (pjsip_hdr*) pjsip_hdr_clone (tdata->pool, cap_hdr)); \
    } while (0)
    #define ADD_CAP(cap) ADD_HDR(pjsip_endpt_get_capability(endpt_, cap, NULL));
    
        ADD_CAP(PJSIP_H_ALLOW);
        ADD_CAP(PJSIP_H_ACCEPT);
        ADD_CAP(PJSIP_H_SUPPORTED);
        ADD_HDR(pjsip_evsub_get_allow_events_hdr(NULL));
    
        pjsip_response_addr res_addr;
        pjsip_get_response_addr(tdata->pool, rdata, &res_addr);
    
        if (pjsip_endpt_send_response(endpt_, &res_addr, tdata, NULL, NULL) != PJ_SUCCESS)
            pjsip_tx_data_dec_ref(tdata);
    }
    
    // return PJ_FALSE so that eventuall other modules will handle these requests
    // TODO: move Voicemail to separate module
    pj_bool_t transaction_response_cb(pjsip_rx_data *rdata)
    {
        pjsip_dialog *dlg = pjsip_rdata_get_dlg(rdata);
    
        if (!dlg)
            return PJ_FALSE;
    
        pjsip_transaction *tsx = pjsip_rdata_get_tsx(rdata);
    
        if (!tsx or tsx->method.id != PJSIP_INVITE_METHOD)
            return PJ_FALSE;
    
        if (tsx->status_code / 100 == 2) {
            /**
             * Send an ACK message inside a transaction. PJSIP send automatically, non-2xx ACK response.
             * ACK for a 2xx response must be send using this method.
             */
            pjsip_tx_data *tdata;
    
            if (rdata->msg_info.cseq) {
                pjsip_dlg_create_request(dlg, &pjsip_ack_method, rdata->msg_info.cseq->cseq, &tdata);
                pjsip_dlg_send_request(dlg, tdata, -1, NULL);
            }
        }
    
        return PJ_FALSE;
    }
    
    void updateSDPFromSTUN(SIPCall &call, SIPAccount &account, const SipTransport &transport)
    {
        std::vector<long> socketDescriptors(call.getAudioRtp().getSocketDescriptors());
    
        try {
            std::vector<pj_sockaddr_in> stunPorts(transport.getSTUNAddresses(account, socketDescriptors));
    
            // FIXME: get video sockets
            stunPorts.resize(4);
    
            account.setPublishedAddress(pj_inet_ntoa(stunPorts[0].sin_addr));
            // published IP MUST be updated first, since RTCP depends on it
            call.getLocalSDP()->setPublishedIP(account.getPublishedAddress());
            call.getLocalSDP()->updatePorts(stunPorts);
        } catch (const std::runtime_error &e) {
            ERROR("%s", e.what());
        }
    }
    
    pj_bool_t transaction_request_cb(pjsip_rx_data *rdata)
    {
    
        if (!rdata or !rdata->msg_info.msg) {
            ERROR("rx_data is NULL");
            return PJ_FALSE;
        }
    
        pjsip_method *method = &rdata->msg_info.msg->line.req.method;
    
        if (!method) {
            ERROR("method is NULL");
            return PJ_FALSE;
        }
    
        if (method->id == PJSIP_ACK_METHOD && pjsip_rdata_get_dlg(rdata))
            return PJ_FALSE;
    
        if (!rdata->msg_info.to or !rdata->msg_info.from) {
            ERROR("NULL from/to fields");
            return PJ_FALSE;
        }
    
        pjsip_sip_uri *sip_to_uri = (pjsip_sip_uri *) pjsip_uri_get_uri(rdata->msg_info.to->uri);
        pjsip_sip_uri *sip_from_uri = (pjsip_sip_uri *) pjsip_uri_get_uri(rdata->msg_info.from->uri);
    
        if (!sip_to_uri or !sip_from_uri) {
            ERROR("NULL uri");
            return PJ_FALSE;
        }
    
        std::string userName(sip_to_uri->user.ptr, sip_to_uri->user.slen);
        std::string server(sip_from_uri->host.ptr, sip_from_uri->host.slen);
        std::string account_id(SIPVoIPLink::instance()->guessAccountIdFromNameAndServer(userName, server));
    
        std::string displayName(sip_utils::parseDisplayName(rdata->msg_info.msg_buf));
    
        pjsip_msg_body *body = rdata->msg_info.msg->body;
    
        if (method->id == PJSIP_OTHER_METHOD) {
            pj_str_t *str = &method->name;
            std::string request(str->ptr, str->slen);
    
            if (request.find("NOTIFY") != std::string::npos) {
                if (body and body->data) {
                    int voicemail = 0;
                    int ret = sscanf((const char*) body->data, "Voice-Message: %d/", &voicemail);
    
                    if (ret == 1 and voicemail != 0)
                        Manager::instance().startVoiceMessageNotification(account_id, voicemail);
                }
            }
    
            /* Check that no UAS transaction has been created for this request.
             * If UAS transaction has been created for this request, application
             * MUST send the response statefully using that transaction.
             */
    
            if (!pjsip_rdata_get_tsx(rdata))
                pjsip_endpt_respond_stateless(endpt_, rdata, PJSIP_SC_OK, NULL, NULL, NULL);
            else
                ERROR("Transaction has been created for this request, send response statefully instead");
    
            return PJ_FALSE;
        } else if (method->id == PJSIP_OPTIONS_METHOD) {
            handleIncomingOptions(rdata);
            return PJ_FALSE;
        } else if (method->id != PJSIP_INVITE_METHOD && method->id != PJSIP_ACK_METHOD) {
            pjsip_endpt_respond_stateless(endpt_, rdata, PJSIP_SC_METHOD_NOT_ALLOWED, NULL, NULL, NULL);
            return PJ_FALSE;
        }
    
        SIPAccount *account = Manager::instance().getSipAccount(account_id);
    
        if (!account) {
            ERROR("Could not find account %s", account_id.c_str());
            return PJ_FALSE;
        }
    
        pjmedia_sdp_session *r_sdp;
    
        if (!body || pjmedia_sdp_parse(rdata->tp_info.pool, (char*) body->data, body->len, &r_sdp) != PJ_SUCCESS)
            r_sdp = NULL;
    
        if (account->getActiveAudioCodecs().empty()) {
            pjsip_endpt_respond_stateless(endpt_, rdata,
                                          PJSIP_SC_NOT_ACCEPTABLE_HERE, NULL, NULL,
                                          NULL);
            return PJ_FALSE;
        }
    
        // Verify that we can handle the request
        unsigned options = 0;
    
        if (pjsip_inv_verify_request(rdata, &options, NULL, NULL, endpt_, NULL) != PJ_SUCCESS) {
            pjsip_endpt_respond_stateless(endpt_, rdata, PJSIP_SC_METHOD_NOT_ALLOWED, NULL, NULL, NULL);
            return PJ_FALSE;
        }
    
        Manager::instance().hookPreference.runHook(rdata->msg_info.msg);
    
        SIPCall* call = new SIPCall(Manager::instance().getNewCallID(), Call::INCOMING, cp_, account_id);
    
        // May use the published address as well
        std::string addrToUse = SipTransport::getInterfaceAddrFromName(account->getLocalInterface());
        std::string addrSdp = account->isStunEnabled() or (not account->getPublishedSameasLocal())
                              ? account->getPublishedAddress()
                              : addrToUse;
    
        pjsip_tpselector *tp_sel = SIPVoIPLink::instance()->sipTransport->createTransportSelector(account->transport_, call->getMemoryPool());
    
        if (!tp_sel) {
            ERROR("Could not create transport selector");
            delete call;
            return PJ_FALSE;
        }
    
        char tmp[PJSIP_MAX_URL_SIZE];
        size_t length = pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR, sip_from_uri, tmp, PJSIP_MAX_URL_SIZE);
        std::string peerNumber(tmp, std::min(length, sizeof tmp));
        sip_utils::stripSipUriPrefix(peerNumber);
    
        const std::string remote_user(sip_from_uri->user.ptr, sip_from_uri->user.slen);
        const std::string remote_hostname(sip_from_uri->host.ptr, sip_from_uri->host.slen);
    
        if (not remote_user.empty() and not remote_hostname.empty())
            peerNumber = remote_user + "@" + remote_hostname;
    
        call->setConnectionState(Call::PROGRESSING);
        call->setPeerNumber(peerNumber);
        call->setDisplayName(displayName);
        call->initRecFilename(peerNumber);
    
        setCallMediaLocal(call, addrToUse);
    
        call->getLocalSDP()->setPublishedIP(addrSdp);
    
        call->getAudioRtp().initConfig();
    
        try {
            call->getAudioRtp().initSession();
        } catch (const ost::Socket::Error &err) {
            ERROR("AudioRtp socket error");
            delete call;
            return PJ_FALSE;
        }
    
        if (account->isStunEnabled())
            updateSDPFromSTUN(*call, *account, *SIPVoIPLink::instance()->sipTransport);
    
        if (body and body->len > 0 and call->getAudioRtp().isSdesEnabled()) {
            std::string sdpOffer(static_cast<const char*>(body->data), body->len);
            size_t start = sdpOffer.find("a=crypto:");
    
            // Found crypto header in SDP
            if (start != std::string::npos) {
                CryptoOffer crypto_offer;
                crypto_offer.push_back(std::string(sdpOffer.substr(start, (sdpOffer.size() - start) - 1)));
    
                const size_t size = ARRAYSIZE(sfl::CryptoSuites);
                std::vector<sfl::CryptoSuiteDefinition> localCapabilities(size);
    
                std::copy(sfl::CryptoSuites, sfl::CryptoSuites + size,
                          localCapabilities.begin());
    
    #if HAVE_SDES
                sfl::SdesNegotiator sdesnego(localCapabilities, crypto_offer);
    
                if (sdesnego.negotiate()) {
                    try {
                        call->getAudioRtp().setRemoteCryptoInfo(sdesnego);
                        call->getAudioRtp().initLocalCryptoInfo();
                    } catch (const AudioRtpFactoryException &e) {
                        ERROR("%s", e.what());
                        delete call;
                        return PJ_FALSE;
                    }
                }
    
    #endif
            }
        }
    
        call->getLocalSDP()->receiveOffer(r_sdp, account->getActiveAudioCodecs(), account->getActiveVideoCodecs());
    
        sfl::AudioCodec* ac = Manager::instance().audioCodecFactory.instantiateCodec(PAYLOAD_CODEC_ULAW);
    
        if (!ac) {
            ERROR("Could not instantiate codec");
            delete call;
            return PJ_FALSE;
        }
    
        std::vector<sfl::AudioCodec *> audioCodecs;
        audioCodecs.push_back(ac);
        call->getAudioRtp().start(audioCodecs);
    
        pjsip_dialog *dialog = 0;
    
        if (pjsip_dlg_create_uas(pjsip_ua_instance(), rdata, NULL, &dialog) != PJ_SUCCESS) {
            delete call;
            pjsip_endpt_respond_stateless(endpt_, rdata, PJSIP_SC_INTERNAL_SERVER_ERROR, NULL, NULL, NULL);
            return PJ_FALSE;
        }
    
        pjsip_inv_create_uas(dialog, rdata, call->getLocalSDP()->getLocalSdpSession(), 0, &call->inv);
    
        if (!dialog or pjsip_dlg_set_transport(dialog, tp_sel) != PJ_SUCCESS) {
            ERROR("Could not set transport for dialog");
            delete call;
            return PJ_FALSE;
        }
    
        if (!call->inv) {
            ERROR("Call invite is not initialized");
            delete call;
            return PJ_FALSE;
        }
    
        call->inv->mod_data[mod_ua_.id] = call;
    
        // Check whether Replaces header is present in the request and process accordingly.
        pjsip_dialog *replaced_dlg;
        pjsip_tx_data *response;
    
        if (pjsip_replaces_verify_request(rdata, &replaced_dlg, PJ_FALSE, &response) != PJ_SUCCESS) {
            ERROR("Something wrong with Replaces request.");
            delete call;
    
            // Something wrong with the Replaces header.
            if (response) {
                pjsip_response_addr res_addr;
                pjsip_get_response_addr(response->pool, rdata, &res_addr);
                pjsip_endpt_send_response(endpt_, &res_addr, response,
                                          NULL, NULL);
            } else {
                pjsip_endpt_respond_stateless(endpt_, rdata,
                                              PJSIP_SC_INTERNAL_SERVER_ERROR, NULL, NULL, NULL);
            }
    
            return PJ_FALSE;
        }
    
        // Check if call has been transfered
        pjsip_tx_data *tdata = 0;
    
        // If Replace header present
        if (replaced_dlg) {
            // Always answer the new INVITE with 200 if the replaced call is in early or confirmed state.
            if (pjsip_inv_answer(call->inv, PJSIP_SC_OK, NULL, NULL, &response) == PJ_SUCCESS)
                pjsip_inv_send_msg(call->inv, response);
    
            // Get the INVITE session associated with the replaced dialog.
            pjsip_inv_session *replaced_inv = pjsip_dlg_get_inv_session(replaced_dlg);
    
            // Disconnect the "replaced" INVITE session.
            if (pjsip_inv_end_session(replaced_inv, PJSIP_SC_GONE, NULL, &tdata) == PJ_SUCCESS && tdata)
                pjsip_inv_send_msg(replaced_inv, tdata);
        } else { // Proceed with normal call flow
            if (pjsip_inv_initial_answer(call->inv, rdata, PJSIP_SC_TRYING, NULL, NULL, &tdata) != PJ_SUCCESS) {
                ERROR("Could not answer invite");
                delete call;
                return PJ_FALSE;
            }
    
            if (pjsip_inv_send_msg(call->inv, tdata) != PJ_SUCCESS) {
                ERROR("Could not send msg for invite");
                delete call;
                return PJ_FALSE;
            }
    
            call->setConnectionState(Call::TRYING);
    
            if (pjsip_inv_answer(call->inv, PJSIP_SC_RINGING, NULL, NULL, &tdata) != PJ_SUCCESS) {
                ERROR("Could not answer invite");
                delete call;
                return PJ_FALSE;
            }
    
            // contactStr must stay in scope as long as tdata
            const pj_str_t contactStr(account->getContactHeader());
            sip_utils::addContactHeader(&contactStr, tdata);
    
            if (pjsip_inv_send_msg(call->inv, tdata) != PJ_SUCCESS) {
                ERROR("Could not send msg for invite");
                delete call;
                return PJ_FALSE;
            }
    
            call->setConnectionState(Call::RINGING);
    
            SIPVoIPLink::instance()->addSipCall(call);
            Manager::instance().incomingCall(*call, account_id);
        }
    
        return PJ_FALSE;
    }
    } // end anonymous namespace
    
    /*************************************************************************************************/
    pjsip_endpoint * SIPVoIPLink::getEndpoint()
    {
        return endpt_;
    }
    
    pjsip_module * SIPVoIPLink::getMod()
    {
        return &mod_ua_;
    }
    
    SIPVoIPLink::SIPVoIPLink() : sipTransport(), sipAccountMap_(),
        sipCallMapMutex_(), sipCallMap_(), evThread_(this)
    #ifdef SFL_VIDEO
        , keyframeRequestsMutex_()
        , keyframeRequests_()
    #endif
    {
    
    #define TRY(ret) do { \
        if (ret != PJ_SUCCESS) \
        throw VoipLinkException(#ret " failed"); \
    } while (0)
    
        srand(time(NULL)); // to get random number for RANDOM_PORT
    
        TRY(pj_init());
    
        TRY(pjlib_util_init());
    
        setSipLogLevel();
        TRY(pjnath_init());
    
        pj_caching_pool_init(cp_, &pj_pool_factory_default_policy, 0);
        pool_ = pj_pool_create(&cp_->factory, PACKAGE, 4000, 4000, NULL);
    
        if (!pool_)
            throw VoipLinkException("UserAgent: Could not initialize memory pool");
    
        TRY(pjsip_endpt_create(&cp_->factory, pj_gethostname()->ptr, &endpt_));
    
        sipTransport.reset(new SipTransport(endpt_, cp_, pool_));
    
        if (SipTransport::getSIPLocalIP().empty())
            throw VoipLinkException("UserAgent: Unable to determine network capabilities");
    
        TRY(pjsip_tsx_layer_init_module(endpt_));
        TRY(pjsip_ua_init_module(endpt_, NULL));
        TRY(pjsip_replaces_init_module(endpt_)); // See the Replaces specification in RFC 3891
        TRY(pjsip_100rel_init_module(endpt_));
    
        // Initialize and register sflphone module
        mod_ua_.name = pj_str((char*) PACKAGE);
        mod_ua_.id = -1;
        mod_ua_.priority = PJSIP_MOD_PRIORITY_APPLICATION;
        mod_ua_.on_rx_request = &transaction_request_cb;
        mod_ua_.on_rx_response = &transaction_response_cb;
        TRY(pjsip_endpt_register_module(endpt_, &mod_ua_));
    
        TRY(pjsip_evsub_init_module(endpt_));
        TRY(pjsip_xfer_init_module(endpt_));
    
    #ifdef SFL_PRESENCE
        // presence/publish management
        TRY(pjsip_pres_init_module(endpt_, pjsip_evsub_instance()));
        TRY(pjsip_endpt_register_module(endpt_, &PresSubServer::mod_presence_server));
    #endif
    
        static const pjsip_inv_callback inv_cb = {
            invite_session_state_changed_cb,
            outgoing_request_forked_cb,
            transaction_state_changed_cb,
            sdp_request_offer_cb,
            NULL /* on_rx_reinvite */,
            sdp_create_offer_cb,
            sdp_media_update_cb,
            NULL /* on_send_ack */,
            NULL /* on_redirected */,
        };
        TRY(pjsip_inv_usage_init(endpt_, &inv_cb));
    
        static const pj_str_t allowed[] = {
            CONST_PJ_STR("INFO"),
            CONST_PJ_STR("OPTIONS"),
            CONST_PJ_STR("MESSAGE"),
            CONST_PJ_STR("PUBLISH"),
        };
    
        pjsip_endpt_add_capability(endpt_, &mod_ua_, PJSIP_H_ALLOW, NULL, PJ_ARRAY_SIZE(allowed), allowed);
    
        static const pj_str_t text_plain = CONST_PJ_STR("text/plain");
        pjsip_endpt_add_capability(endpt_, &mod_ua_, PJSIP_H_ACCEPT, NULL, 1, &text_plain);
    
        static const pj_str_t accepted = CONST_PJ_STR("application/sdp");
        pjsip_endpt_add_capability(endpt_, &mod_ua_, PJSIP_H_ACCEPT, NULL, 1, &accepted);
    
        DEBUG("pjsip version %s for %s initialized", pj_get_version(), PJ_OS_NAME);
    
        TRY(pjsip_replaces_init_module(endpt_));
    #undef TRY
    
        handlingEvents_ = true;
        evThread_.start();
    }
    
    SIPVoIPLink::~SIPVoIPLink()
    {
        const int MAX_TIMEOUT_ON_LEAVING = 5;
    
        for (int timeout = 0; pjsip_tsx_layer_get_tsx_count() and timeout < MAX_TIMEOUT_ON_LEAVING; timeout++)
            sleep(1);
    
        handlingEvents_ = false;
        evThread_.join();
    
        const pj_time_val tv = {0, 10};
        pjsip_endpt_handle_events(endpt_, &tv);
        pjsip_endpt_destroy(endpt_);
    
        pj_pool_release(pool_);
        pj_caching_pool_destroy(cp_);
    
        for (auto & a : sipAccountMap_)
            unloadAccount(a);
    
        pj_shutdown();
        clearSipCallMap();
    
        sipAccountMap_.clear();
    }
    
    SIPVoIPLink* SIPVoIPLink::instance()
    {
        if (!instance_) {
            DEBUG("creating SIPVoIPLink instance");
            instance_ = new SIPVoIPLink;
        }
    
        return instance_;
    }
    
    void SIPVoIPLink::destroy()
    {
        delete instance_;
        instance_ = 0;
    }
    
    std::string
    SIPVoIPLink::guessAccountIdFromNameAndServer(const std::string &userName,
            const std::string &server) const
    {
        DEBUG("username = %s, server = %s", userName.c_str(), server.c_str());
        // Try to find the account id from username and server name by full match
    
        std::string result(SIPAccount::IP2IP_PROFILE);
        MatchRank best = MatchRank::NONE;
    
        for (const auto & item : sipAccountMap_) {
            SIPAccount *account = static_cast<SIPAccount*>(item.second);
    
            if (!account)
                continue;
    
            const MatchRank match(account->matches(userName, server, endpt_, pool_));
    
            // return right away if this is a full match
            if (match == MatchRank::FULL) {
                return item.first;
            } else if (match > best) {
                best = match;
                result = item.first;
            }
        }
    
        return result;
    }
    
    void SIPVoIPLink::setSipLogLevel()
    {
        char *envvar = getenv(SIPLOGLEVEL);
        int level = 0;
    
        if (envvar != NULL) {
            std::string loglevel = envvar;
    
            if (!(std::istringstream(loglevel) >> level)) level = 0;
    
            level = level > 6 ? 6 : level;
            level = level < 0 ? 0 : level;
        }
    
        // From 0 (min) to 6 (max)
        pj_log_set_level(level);
    }
    
    // Called from EventThread::run (not main thread)
    bool SIPVoIPLink::getEvent()
    {
        // We have to register the external thread so it could access the pjsip frameworks
        if (!pj_thread_is_registered()) {
    #if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8)
            static thread_local pj_thread_desc desc;
            static thread_local pj_thread_t *this_thread;
    #else
            static __thread pj_thread_desc desc;
            static __thread pj_thread_t *this_thread;
    #endif
            DEBUG("Registering thread");
            pj_thread_register(NULL, desc, &this_thread);
        }
    
        static const pj_time_val timeout = {0, 10};
        pj_status_t ret;
    
        if ((ret = pjsip_endpt_handle_events(endpt_, &timeout)) != PJ_SUCCESS)
            sip_strerror(ret);
    
    #ifdef SFL_VIDEO
        dequeKeyframeRequests();
    #endif
        return handlingEvents_;
    }
    
    void SIPVoIPLink::sendRegister(Account *a)
    {
        SIPAccount *account = static_cast<SIPAccount*>(a);
    
        if (!account)
            throw VoipLinkException("Account is NULL");
        else if (not account->isEnabled()) {
            WARN("Account must be enabled to register, ignoring");
            return;
        }
    
        try {
            sipTransport->createSipTransport(*account);
        } catch (const std::runtime_error &e) {
            ERROR("%s", e.what());
            throw VoipLinkException("Could not create or acquire SIP transport");
        }
    
        account->setRegister(true);
        account->setRegistrationState(RegistrationState::TRYING);
    
        pjsip_regc *regc = account->getRegistrationInfo();
    
        if (pjsip_regc_create(endpt_, (void *) account, &registration_cb, &regc) != PJ_SUCCESS)
            throw VoipLinkException("UserAgent: Unable to create regc structure.");
    
        std::string srvUri(account->getServerUri());
    
        // std::string address, port;
        // findLocalAddressFromUri(srvUri, account->transport_, address, port);
        pj_str_t pjSrv = pj_str((char*) srvUri.c_str());
    
        // Generate the FROM header
        std::string from(account->getFromUri());
        pj_str_t pjFrom = pj_str((char*) from.c_str());
    
        // Get the received header
        std::string received(account->getReceivedParameter());
    
        // Get the contact header
        const pj_str_t pjContact(account->getContactHeader());
    
        if (account->transport_) {
            if (not account->getPublishedSameasLocal() or (not received.empty() and received != account->getPublishedAddress())) {
                pjsip_host_port *via = account->getViaAddr();
                DEBUG("Setting VIA sent-by to %.*s:%d", via->host.slen, via->host.ptr, via->port);
    
                if (pjsip_regc_set_via_sent_by(regc, via, account->transport_) != PJ_SUCCESS)
                    throw VoipLinkException("Unable to set the \"sent-by\" field");
            } else if (account->isStunEnabled()) {
                if (pjsip_regc_set_via_sent_by(regc, account->getViaAddr(), account->transport_) != PJ_SUCCESS)
                    throw VoipLinkException("Unable to set the \"sent-by\" field");
            }
        }
    
        if (pjsip_regc_init(regc, &pjSrv, &pjFrom, &pjFrom, 1, &pjContact, account->getRegistrationExpire()) != PJ_SUCCESS)
            throw VoipLinkException("Unable to initialize account registration structure");
    
        if (account->hasServiceRoute())
            pjsip_regc_set_route_set(regc, sip_utils::createRouteSet(account->getServiceRoute(), pool_));
    
        pjsip_regc_set_credentials(regc, account->getCredentialCount(), account->getCredInfo());
    
        pjsip_hdr hdr_list;
        pj_list_init(&hdr_list);
        std::string useragent(account->getUserAgentName());
        pj_str_t pJuseragent = pj_str((char*) useragent.c_str());
        const pj_str_t STR_USER_AGENT = CONST_PJ_STR("User-Agent");
    
        pjsip_generic_string_hdr *h = pjsip_generic_string_hdr_create(pool_, &STR_USER_AGENT, &pJuseragent);
        pj_list_push_back(&hdr_list, (pjsip_hdr*) h);
        pjsip_regc_add_headers(regc, &hdr_list);
    
        pjsip_tx_data *tdata;
    
        if (pjsip_regc_register(regc, PJ_TRUE, &tdata) != PJ_SUCCESS)
            throw VoipLinkException("Unable to initialize transaction data for account registration");
    
        pjsip_tpselector *tp_sel = sipTransport->createTransportSelector(account->transport_, pool_);
    
        if (tp_sel == NULL)
            throw VoipLinkException("Unable to create transport selector");
    
        if (pjsip_regc_set_transport(regc, tp_sel) != PJ_SUCCESS)
            throw VoipLinkException("Unable to set transport");
    
        // pjsip_regc_send increment the transport ref count by one,
        pj_status_t status;
    
        if ((status = pjsip_regc_send(regc, tdata)) != PJ_SUCCESS) {
            sip_strerror(status);
            throw VoipLinkException("Unable to send account registration request");
        }
    
        account->setRegistrationInfo(regc);
    }
    
    void SIPVoIPLink::sendUnregister(Account *a)
    {
        SIPAccount *account = static_cast<SIPAccount *>(a);
    
        // This may occurs if account failed to register and is in state INVALID
        if (!account->isRegistered()) {
            account->setRegistrationState(RegistrationState::UNREGISTERED);
            return;
        }
    
        // Make sure to cancel any ongoing timers before unregister
        account->stopKeepAliveTimer();
    
        pjsip_regc *regc = account->getRegistrationInfo();
    
        if (!regc)
            throw VoipLinkException("Registration structure is NULL");
    
        pjsip_tx_data *tdata = NULL;
    
        if (pjsip_regc_unregister(regc, &tdata) != PJ_SUCCESS)
            throw VoipLinkException("Unable to unregister sip account");
    
        pj_status_t status;
    
        if ((status = pjsip_regc_send(regc, tdata)) != PJ_SUCCESS) {
            sip_strerror(status);
            throw VoipLinkException("Unable to send request to unregister sip account");
        }
    
        account->setRegister(false);
    }
    
    void SIPVoIPLink::registerKeepAliveTimer(pj_timer_entry &timer, pj_time_val &delay)
    {
        DEBUG("Register new keep alive timer %d with delay %d", timer.id, delay.sec);
    
        if (timer.id == -1)
            WARN("Timer already scheduled");
    
        switch (pjsip_endpt_schedule_timer(endpt_, &timer, &delay)) {
            case PJ_SUCCESS:
                break;
    
            default:
                ERROR("Could not schedule new timer in pjsip endpoint");
    
                /* fallthrough */
            case PJ_EINVAL:
                ERROR("Invalid timer or delay entry");
                break;
    
            case PJ_EINVALIDOP:
                ERROR("Invalid timer entry, maybe already scheduled");
                break;
        }
    }
    
    void SIPVoIPLink::cancelKeepAliveTimer(pj_timer_entry& timer)
    {
        pjsip_endpt_cancel_timer(endpt_, &timer);
    }
    
    bool isValidIpAddress(const std::string &address)
    {
        size_t pos = address.find(":");
        std::string address_without_port(address);
    
        if (pos != std::string::npos)
            address_without_port = address.substr(0, pos);
    
        DEBUG("Testing address %s", address_without_port.c_str());
        struct sockaddr_in sa;
        int result = inet_pton(AF_INET, address_without_port.data(), &(sa.sin_addr));
        return result != 0;
    }
    
    
    Call *SIPVoIPLink::newOutgoingCall(const std::string& id, const std::string& toUrl, const std::string &account_id)
    {
        DEBUG("New outgoing call to %s", toUrl.c_str());
        std::string toCpy = toUrl;
    
        sip_utils::stripSipUriPrefix(toCpy);
    
        const bool IPToIP = isValidIpAddress(toCpy);
        Manager::instance().setIPToIPForCall(id, IPToIP);
    
        if (IPToIP) {
            return SIPNewIpToIpCall(id, toCpy);
        } else {
            return newRegisteredAccountCall(id, toUrl, account_id);
        }
    }
    
    Call *SIPVoIPLink::SIPNewIpToIpCall(const std::string& id, const std::string& to)
    {
        DEBUG("New IP to IP call to %s", to.c_str());
    
        SIPAccount *account = Manager::instance().getIP2IPAccount();
    
        if (!account)
            throw VoipLinkException("Could not retrieve default account for IP2IP call");
    
        SIPCall *call = new SIPCall(id, Call::OUTGOING, cp_, SIPAccount::IP2IP_PROFILE);
    
        call->setIPToIP(true);
        call->initRecFilename(to);
    
        std::string localAddress(SipTransport::getInterfaceAddrFromName(account->getLocalInterface()));
    
        setCallMediaLocal(call, localAddress);
    
        std::string toUri = account->getToUri(to);
        call->setPeerNumber(toUri);
    
        sfl::AudioCodec* ac = Manager::instance().audioCodecFactory.instantiateCodec(PAYLOAD_CODEC_ULAW);
    
        if (!ac) {
            delete call;
            throw VoipLinkException("Could not instantiate codec");
        }
    
        std::vector<sfl::AudioCodec *> audioCodecs;
        audioCodecs.push_back(ac);
    
        // Audio Rtp Session must be initialized before creating initial offer in SDP session
        // since SDES require crypto attribute.
        call->getAudioRtp().initConfig();
        call->getAudioRtp().initSession();
        call->getAudioRtp().initLocalCryptoInfo();
        call->getAudioRtp().start(audioCodecs);
    
        // Building the local SDP offer
        Sdp *localSDP = call->getLocalSDP();
    
        if (account->getPublishedSameasLocal())
            localSDP->setPublishedIP(localAddress);
        else
            localSDP->setPublishedIP(account->getPublishedAddress());
    
        const bool created = localSDP->createOffer(account->getActiveAudioCodecs(), account->getActiveVideoCodecs());
    
        if (not created or not SIPStartCall(call)) {
            delete call;
            throw VoipLinkException("Could not create new call");
        }
    
        return call;
    }
    
    Call *SIPVoIPLink::newRegisteredAccountCall(const std::string& id, const std::string& toUrl, const std::string &account_id)
    {
        DEBUG("UserAgent: New registered account call to %s", toUrl.c_str());
    
        SIPAccount *account = Manager::instance().getSipAccount(account_id);
    
        if (account == NULL) // TODO: We should investigate how we could get rid of this error and create a IP2IP call instead
            throw VoipLinkException("Could not get account for this call");
    
        SIPCall* call = new SIPCall(id, Call::OUTGOING, cp_, account->getAccountID());
    
        // If toUri is not a well formatted sip URI, use account information to process it
        std::string toUri;
    
        if (toUrl.find("sip:") != std::string::npos or
                toUrl.find("sips:") != std::string::npos)
            toUri = toUrl;
        else
            toUri = account->getToUri(toUrl);
    
        call->setPeerNumber(toUri);
        std::string localAddr(SipTransport::getInterfaceAddrFromName(account->getLocalInterface()));
        setCallMediaLocal(call, localAddr);
    
        // May use the published address as well
        std::string addrSdp = account->isStunEnabled() or (not account->getPublishedSameasLocal()) ?
                              account->getPublishedAddress() :
                              SipTransport::getInterfaceAddrFromName(account->getLocalInterface());
    
        // Initialize the session using ULAW as default codec in case of early media
        // The session should be ready to receive media once the first INVITE is sent, before
        // the session initialization is completed
        sfl::AudioCodec* ac = Manager::instance().audioCodecFactory.instantiateCodec(PAYLOAD_CODEC_ULAW);
    
        if (ac == NULL) {
            delete call;
            throw VoipLinkException("Could not instantiate codec for early media");
        }
    
        std::vector<sfl::AudioCodec *> audioCodecs;
        audioCodecs.push_back(ac);
    
        try {
            call->getAudioRtp().initConfig();
            call->getAudioRtp().initSession();
    
            if (account->isStunEnabled())
                updateSDPFromSTUN(*call, *account, *SIPVoIPLink::instance()->sipTransport);
    
            call->getAudioRtp().initLocalCryptoInfo();
            call->getAudioRtp().start(audioCodecs);
        } catch (...) {
            delete call;
            throw VoipLinkException("Could not start rtp session for early media");
        }
    
        call->initRecFilename(toUrl);
    
        Sdp *localSDP = call->getLocalSDP();
        localSDP->setPublishedIP(addrSdp);
        const bool created = localSDP->createOffer(account->getActiveAudioCodecs(), account->getActiveVideoCodecs());
    
        if (not created or not SIPStartCall(call)) {
            delete call;
            throw VoipLinkException("Could not send outgoing INVITE request for new call");
        }
    
        return call;
    }
    
    void
    SIPVoIPLink::answer(Call *call)
    {
        if (!call)
            return;
    
        SIPCall *sipCall = static_cast<SIPCall*>(call);
        SIPAccount *account = Manager::instance().getSipAccount(sipCall->getAccountId());
    
        if (!account) {
            ERROR("Could not find account %s", sipCall->getAccountId().c_str());
            return;
        }
    
        if (!sipCall->inv->neg) {
            WARN("Negotiator is NULL, we've received an INVITE without an SDP");
            pjmedia_sdp_session *dummy = 0;
            sdp_create_offer_cb(sipCall->inv, &dummy);
    
            if (account->isStunEnabled())
                updateSDPFromSTUN(*sipCall, *account, *SIPVoIPLink::instance()->sipTransport);
        }
    
        pj_str_t contact(account->getContactHeader());
        sipCall->setContactHeader(&contact);
        sipCall->answer();
    }
    
    namespace {
    void stopRtpIfCurrent(const std::string &id, SIPCall &call)
    {
        if (Manager::instance().isCurrentCall(id)) {
            call.getAudioRtp().stop();
    #ifdef SFL_VIDEO
            call.getVideoRtp().stop();
    #endif
        }
    }
    }
    
    void
    SIPVoIPLink::hangup(const std::string& id, int reason)
    {
        SIPCall *call = getSipCall(id);
    
        if (!call)
            return;
    
        std::string account_id(call->getAccountId());
        SIPAccount *account = Manager::instance().getSipAccount(account_id);
    
        if (account == NULL)
            throw VoipLinkException("Could not find account for this call");
    
        pjsip_inv_session *inv = call->inv;
    
        if (inv == NULL)
            throw VoipLinkException("No invite session for this call");
    
        pjsip_route_hdr *route = inv->dlg->route_set.next;
    
        while (route and route != &inv->dlg->route_set) {
            char buf[1024];
            int printed = pjsip_hdr_print_on(route, buf, sizeof(buf));
    
            if (printed >= 0) {
                buf[printed] = '\0';
                DEBUG("Route header %s", buf);
            }
    
            route = route->next;
        }
    
        pjsip_tx_data *tdata = NULL;
    
        const int status = reason ? reason :
                           inv->state <= PJSIP_INV_STATE_EARLY and inv->role != PJSIP_ROLE_UAC ?
                           PJSIP_SC_CALL_TSX_DOES_NOT_EXIST :
                           inv->state >= PJSIP_INV_STATE_DISCONNECTED ? PJSIP_SC_DECLINE :
                           0;
    
        // User hangup current call. Notify peer
        if (pjsip_inv_end_session(inv, status, NULL, &tdata) != PJ_SUCCESS || !tdata)
            return;
    
        // contactStr must stay in scope as long as tdata
        const pj_str_t contactStr(account->getContactHeader());
        sip_utils::addContactHeader(&contactStr, tdata);
    
        if (pjsip_inv_send_msg(inv, tdata) != PJ_SUCCESS)
            return;
    
        // Make sure user data is NULL in callbacks
        inv->mod_data[mod_ua_.id] = NULL;
    
        stopRtpIfCurrent(id, *call);
        removeSipCall(id);
    }
    
    void
    SIPVoIPLink::peerHungup(const std::string& id)
    {
        SIPCall *call = getSipCall(id);
    
        if (!call)
            return;
    
        // User hangup current call. Notify peer
        pjsip_tx_data *tdata = NULL;
    
        if (pjsip_inv_end_session(call->inv, 404, NULL, &tdata) != PJ_SUCCESS || !tdata)
            return;
    
        if (pjsip_inv_send_msg(call->inv, tdata) != PJ_SUCCESS)
            return;
    
        // Make sure user data is NULL in callbacks
        call->inv->mod_data[mod_ua_.id ] = NULL;
    
        stopRtpIfCurrent(id, *call);
        removeSipCall(id);
    }
    
    void
    SIPVoIPLink::onhold(const std::string& id)
    {
        SIPCall *call = getSipCall(id);
    
        if (!call)
            return;
    
        call->setState(Call::HOLD);
        call->getAudioRtp().saveLocalContext();
        call->getAudioRtp().stop();
    #ifdef SFL_VIDEO
        call->getVideoRtp().stop();
    #endif
    
        Sdp *sdpSession = call->getLocalSDP();
    
        if (!sdpSession)
            throw VoipLinkException("Could not find sdp session");
    
        sdpSession->removeAttributeFromLocalAudioMedia("sendrecv");
        sdpSession->removeAttributeFromLocalAudioMedia("sendonly");
        sdpSession->addAttributeToLocalAudioMedia("sendonly");
    
    #ifdef SFL_VIDEO
        sdpSession->removeAttributeFromLocalVideoMedia("sendrecv");
        sdpSession->removeAttributeFromLocalVideoMedia("inactive");
        sdpSession->addAttributeToLocalVideoMedia("inactive");
    #endif
    
        SIPSessionReinvite(call);
    }
    
    void
    SIPVoIPLink::offhold(const std::string& id)
    {
        SIPCall *call = getSipCall(id);
    
        if (!call)
            return;
    
        Sdp *sdpSession = call->getLocalSDP();
    
        if (sdpSession == NULL)
            throw VoipLinkException("Could not find sdp session");
    
        try {
            std::vector<sfl::AudioCodec*> sessionMedia(sdpSession->getSessionAudioMedia());
    
            if (sessionMedia.empty()) {
                WARN("Session media is empty");
                return;
            }
    
            std::vector<sfl::AudioCodec*> audioCodecs;
    
            for (auto & i : sessionMedia) {
    
                if (!i)
                    continue;
    
                // Create a new instance for this codec
                sfl::AudioCodec* ac = Manager::instance().audioCodecFactory.instantiateCodec(i->getPayloadType());
    
                if (ac == NULL) {
                    ERROR("Could not instantiate codec %d", i->getPayloadType());
                    throw VoipLinkException("Could not instantiate codec");
                }
    
                audioCodecs.push_back(ac);
            }
    
            if (audioCodecs.empty()) {
                throw VoipLinkException("Could not instantiate any codecs");
            }
    
            call->getAudioRtp().initConfig();
            call->getAudioRtp().initSession();
    
            const std::string account_id(call->getAccountId());
            SIPAccount *account = Manager::instance().getSipAccount(account_id);
    
            if (account and account->isStunEnabled())
                updateSDPFromSTUN(*call, *account, *SIPVoIPLink::instance()->sipTransport);
    
            call->getAudioRtp().restoreLocalContext();
            call->getAudioRtp().initLocalCryptoInfoOnOffHold();
            call->getAudioRtp().start(audioCodecs);
        } catch (const SdpException &e) {
            ERROR("%s", e.what());
            throw VoipLinkException("SDP issue in offhold");
        } catch (const ost::Socket::Error &e) {
            throw VoipLinkException("Socket problem in offhold");
        } catch (const ost::Socket *) {
            throw VoipLinkException("Socket problem in offhold");
        } catch (const AudioRtpFactoryException &) {
            throw VoipLinkException("Socket problem in offhold");
        }
    
        sdpSession->removeAttributeFromLocalAudioMedia("sendrecv");
        sdpSession->removeAttributeFromLocalAudioMedia("sendonly");
        sdpSession->addAttributeToLocalAudioMedia("sendrecv");
    
    #ifdef SFL_VIDEO
        sdpSession->removeAttributeFromLocalVideoMedia("sendrecv");
        sdpSession->removeAttributeFromLocalVideoMedia("sendonly");
        sdpSession->addAttributeToLocalVideoMedia("sendrecv");
    #endif
    
        if (SIPSessionReinvite(call) == PJ_SUCCESS)
            call->setState(Call::ACTIVE);
    }
    
    #if HAVE_INSTANT_MESSAGING
    void SIPVoIPLink::sendTextMessage(const std::string &callID,
                                      const std::string &message,
                                      const std::string &from)
    {
        using namespace sfl::InstantMessaging;
        SIPCall *call = getSipCall(callID);
    
        if (!call)
            return;
    
        /* Send IM message */
        UriList list;
        UriEntry entry;
        entry[sfl::IM_XML_URI] = std::string("\"" + from + "\"");  // add double quotes for xml formating
        list.push_front(entry);
        send_sip_message(call->inv, callID, appendUriList(message, list));
    }
    #endif // HAVE_INSTANT_MESSAGING
    
    void
    SIPVoIPLink::clearSipCallMap()
    {
        std::lock_guard<std::mutex> lock(sipCallMapMutex_);
    
        for (const auto & item : sipCallMap_)
            delete item.second;
    
        sipCallMap_.clear();
    }
    
    
    std::vector<std::string>
    SIPVoIPLink::getCallIDs()
    {
        std::vector<std::string> v;
        std::lock_guard<std::mutex> lock(sipCallMapMutex_);
    
        map_utils::vectorFromMapKeys(sipCallMap_, v);
        return v;
    }
    
    void SIPVoIPLink::addSipCall(SIPCall* call)
    {
        if (!call)
            return;
    
        const std::string id(call->getCallId());
    
        std::lock_guard<std::mutex> lock(sipCallMapMutex_);
    
        if (sipCallMap_.find(id) == sipCallMap_.end())
            sipCallMap_[id] = call;
        else
            ERROR("Call %s is already in the call map", id.c_str());
    }
    
    void SIPVoIPLink::removeSipCall(const std::string& id)
    {
        std::lock_guard<std::mutex> lock(sipCallMapMutex_);
    
        DEBUG("Removing call %s from list", id.c_str());
    
        delete sipCallMap_[id];
        sipCallMap_.erase(id);
    }
    
    SIPCall*
    SIPVoIPLink::getSipCall(const std::string& id)
    {
        std::lock_guard<std::mutex> lock(sipCallMapMutex_);
    
        SipCallMap::iterator iter = sipCallMap_.find(id);
    
        if (iter != sipCallMap_.end())
            return iter->second;
        else {
            DEBUG("No SIP call with ID %s", id.c_str());
            return NULL;
        }
    }
    
    SIPCall*
    SIPVoIPLink::tryGetSIPCall(const std::string &id)
    {
        SIPCall *call = 0;
    
        if (sipCallMapMutex_.try_lock()) {
            SipCallMap::iterator iter = sipCallMap_.find(id);
    
            if (iter != sipCallMap_.end())
                call = iter->second;
    
            sipCallMapMutex_.unlock();
        } else
            ERROR("Could not acquire SIPCallMap mutex");
    
        return call;
    }
    
    bool
    SIPVoIPLink::transferCommon(SIPCall *call, pj_str_t *dst)
    {
        if (!call or !call->inv)
            return false;
    
        pjsip_evsub_user xfer_cb;
        pj_bzero(&xfer_cb, sizeof(xfer_cb));
        xfer_cb.on_evsub_state = &transfer_client_cb;
    
        pjsip_evsub *sub;
    
        if (pjsip_xfer_create_uac(call->inv->dlg, &xfer_cb, &sub) != PJ_SUCCESS)
            return false;
    
        /* Associate this voiplink of call with the client subscription
         * We can not just associate call with the client subscription
         * because after this function, we can no find the cooresponding
         * voiplink from the call any more. But the voiplink is useful!
         */
        pjsip_evsub_set_mod_data(sub, mod_ua_.id, this);
    
        /*
         * Create REFER request.
         */
        pjsip_tx_data *tdata;
    
        if (pjsip_xfer_initiate(sub, dst, &tdata) != PJ_SUCCESS)
            return false;
    
        // Put SIP call id in map in order to retrieve call during transfer callback
        std::string callidtransfer(call->inv->dlg->call_id->id.ptr, call->inv->dlg->call_id->id.slen);
        transferCallID[callidtransfer] = call->getCallId();
    
        /* Send. */
        if (pjsip_xfer_send_request(sub, tdata) != PJ_SUCCESS)
            return false;
    
        return true;
    }
    
    void
    SIPVoIPLink::transfer(const std::string& id, const std::string& to)
    {
        SIPCall *call = getSipCall(id);
    
        if (!call)
            return;
    
        call->stopRecording();
    
        std::string account_id(call->getAccountId());
        SIPAccount *account = Manager::instance().getSipAccount(account_id);
    
        if (account == NULL)
            throw VoipLinkException("Could not find account");
    
        std::string toUri;
        pj_str_t dst = { 0, 0 };
    
        toUri = account->getToUri(to);
        pj_cstr(&dst, toUri.c_str());
        DEBUG("Transferring to %.*s", dst.slen, dst.ptr);
    
        if (!transferCommon(call, &dst))
            throw VoipLinkException("Couldn't transfer");
    }
    
    bool SIPVoIPLink::attendedTransfer(const std::string& id, const std::string& to)
    {
        SIPCall *toCall = getSipCall(to);
    
        if (!toCall)
            return false;
    
        if (!toCall->inv or !toCall->inv->dlg)
            throw VoipLinkException("Couldn't get invite dialog");
    
        pjsip_dialog *target_dlg = toCall->inv->dlg;
        pjsip_uri *uri = (pjsip_uri*) pjsip_uri_get_uri(target_dlg->remote.info->uri);
    
        char str_dest_buf[PJSIP_MAX_URL_SIZE * 2] = { '<' };
        pj_str_t dst = { str_dest_buf, 1 };
    
        dst.slen += pjsip_uri_print(PJSIP_URI_IN_REQ_URI, uri, str_dest_buf + 1, sizeof(str_dest_buf) - 1);
        dst.slen += pj_ansi_snprintf(str_dest_buf + dst.slen,
                                     sizeof(str_dest_buf) - dst.slen,
                                     "?"
                                     "Replaces=%.*s"
                                     "%%3Bto-tag%%3D%.*s"
                                     "%%3Bfrom-tag%%3D%.*s>",
                                     (int)target_dlg->call_id->id.slen,
                                     target_dlg->call_id->id.ptr,
                                     (int)target_dlg->remote.info->tag.slen,
                                     target_dlg->remote.info->tag.ptr,
                                     (int)target_dlg->local.info->tag.slen,
                                     target_dlg->local.info->tag.ptr);
    
        SIPCall *call = getSipCall(id);
    
        if (!call)
            return false;
    
        return transferCommon(call, &dst);
    }
    
    void
    SIPVoIPLink::refuse(const std::string& id)
    {
        SIPCall *call = getSipCall(id);
    
        if (!call)
            return;
    
        if (!call->isIncoming() or call->getConnectionState() == Call::CONNECTED or !call->inv)
            return;
    
        call->getAudioRtp().stop();
    
        pjsip_tx_data *tdata;
    
        if (pjsip_inv_end_session(call->inv, PJSIP_SC_DECLINE, NULL, &tdata) != PJ_SUCCESS)
            return;
    
        if (pjsip_inv_send_msg(call->inv, tdata) != PJ_SUCCESS)
            return;
    
        // Make sure the pointer is NULL in callbacks
        call->inv->mod_data[mod_ua_.id] = NULL;
    
        removeSipCall(id);
    }
    
    namespace {
    void sendSIPInfo(const SIPCall &call, const char *const body, const char *const subtype)
    {
        pj_str_t methodName = CONST_PJ_STR("INFO");
        pjsip_method method;
        pjsip_method_init_np(&method, &methodName);
    
        /* Create request message. */
        pjsip_tx_data *tdata;
    
        if (pjsip_dlg_create_request(call.inv->dlg, &method, -1, &tdata) != PJ_SUCCESS) {
            ERROR("Could not create dialog");
            return;
        }
    
        /* Create "application/<subtype>" message body. */
        pj_str_t content;
        pj_cstr(&content, body);
        const pj_str_t type = CONST_PJ_STR("application");
        pj_str_t pj_subtype;
        pj_cstr(&pj_subtype, subtype);
        tdata->msg->body = pjsip_msg_body_create(tdata->pool, &type, &pj_subtype, &content);
    
        if (tdata->msg->body == NULL)
            pjsip_tx_data_dec_ref(tdata);
        else
            pjsip_dlg_send_request(call.inv->dlg, tdata, mod_ua_.id, NULL);
    }
    
    void
    dtmfSend(SIPCall &call, char code, const std::string &dtmf)
    {
        if (dtmf == SIPAccount::OVERRTP_STR) {
            call.getAudioRtp().sendDtmfDigit(code);
            return;
        } else if (dtmf != SIPAccount::SIPINFO_STR) {
            WARN("Unknown DTMF type %s, defaulting to %s instead",
                 dtmf.c_str(), SIPAccount::SIPINFO_STR);
        } // else : dtmf == SIPINFO
    
        int duration = Manager::instance().voipPreferences.getPulseLength();
        char dtmf_body[1000];
        snprintf(dtmf_body, sizeof dtmf_body - 1, "Signal=%c\r\nDuration=%d\r\n", code, duration);
        sendSIPInfo(call, dtmf_body, "dtmf-relay");
    }
    }
    
    #ifdef SFL_VIDEO
    // Called from a video thread
    void
    SIPVoIPLink::enqueueKeyframeRequest(const std::string &id)
    {
        std::lock_guard<std::mutex> lock(instance_->keyframeRequestsMutex_);
        instance_->keyframeRequests_.push(id);
    }
    
    // Called from SIP event thread
    void
    SIPVoIPLink::dequeKeyframeRequests()
    {
        int max_requests = 20;
    
        while (not keyframeRequests_.empty() and max_requests--) {
            std::lock_guard<std::mutex> lock(keyframeRequestsMutex_);
            const std::string &id(keyframeRequests_.front());
            requestKeyframe(id);
            keyframeRequests_.pop();
        }
    }
    
    // Called from SIP event thread
    void
    SIPVoIPLink::requestKeyframe(const std::string &callID)
    {
        SIPCall *call = 0;
        const int tries = 10;
    
        for (int i = 0; !call and i < tries; ++i)
            call = SIPVoIPLink::instance()->tryGetSIPCall(callID);
    
        if (!call)
            return;
    
        const char * const BODY =
            "<?xml version=\"1.0\" encoding=\"utf-8\" ?>"
            "<media_control><vc_primitive><to_encoder>"
            "<picture_fast_update/>"
            "</to_encoder></vc_primitive></media_control>";
    
        DEBUG("Sending video keyframe request via SIP INFO");
        sendSIPInfo(*call, BODY, "media_control+xml");
    }
    #endif
    
    void
    SIPVoIPLink::carryingDTMFdigits(const std::string& id, char code)
    {
        SIPCall *call = getSipCall(id);
    
        if (!call)
            return;
    
        const std::string accountID(call->getAccountId());
        SIPAccount *account = Manager::instance().getSipAccount(accountID);
    
        if (!account)
            return;
    
        dtmfSend(*call, code, account->getDtmfType());
    }
    
    
    bool
    SIPVoIPLink::SIPStartCall(SIPCall *call)
    {
        std::string account_id(call->getAccountId());
        SIPAccount *account = Manager::instance().getSipAccount(account_id);
    
        if (account == NULL) {
            ERROR("Account is NULL in SIPStartCall");
            return false;
        }
    
        std::string toUri(call->getPeerNumber()); // expecting a fully well formed sip uri
    
        pj_str_t pjTo = pj_str((char*) toUri.c_str());
    
        // Create the from header
        std::string from(account->getFromUri());
        pj_str_t pjFrom = pj_str((char*) from.c_str());
    
        pj_str_t pjContact(account->getContactHeader());
    
        pjsip_dialog *dialog = NULL;
    
        if (pjsip_dlg_create_uac(pjsip_ua_instance(), &pjFrom, &pjContact, &pjTo, NULL, &dialog) != PJ_SUCCESS) {
            ERROR("Unable to create SIP dialogs for user agent client when "
                  "calling %s", toUri.c_str());
            return false;
        }
    
        pj_str_t subj_hdr_name = CONST_PJ_STR("Subject");
        pjsip_hdr* subj_hdr = (pjsip_hdr*) pjsip_parse_hdr(dialog->pool, &subj_hdr_name, (char *) "Phone call", 10, NULL);
    
        pj_list_push_back(&dialog->inv_hdr, subj_hdr);
    
        if (pjsip_inv_create_uac(dialog, call->getLocalSDP()->getLocalSdpSession(), 0, &call->inv) != PJ_SUCCESS) {
            ERROR("Unable to create invite session for user agent client");
            return false;
        }
    
        account->updateDialogViaSentBy(dialog);
    
        if (account->hasServiceRoute())
            pjsip_dlg_set_route_set(dialog, sip_utils::createRouteSet(account->getServiceRoute(), call->inv->pool));
    
        if (account->hasCredentials() and pjsip_auth_clt_set_credentials(&dialog->auth_sess, account->getCredentialCount(), account->getCredInfo()) != PJ_SUCCESS) {
            ERROR("Could not initialize credentials for invite session authentication");
            return false;
        }
    
        call->inv->mod_data[mod_ua_.id] = call;
    
        pjsip_tx_data *tdata;
    
        if (pjsip_inv_invite(call->inv, &tdata) != PJ_SUCCESS) {
            ERROR("Could not initialize invite messager for this call");
            return false;
        }
    
        pjsip_tpselector *tp_sel = sipTransport->createTransportSelector(account->transport_, call->inv->pool);
    
        if (!tp_sel or pjsip_dlg_set_transport(dialog, tp_sel) != PJ_SUCCESS) {
            ERROR("Unable to associate transport for invite session dialog");
            return false;
        }
    
        if (pjsip_inv_send_msg(call->inv, tdata) != PJ_SUCCESS) {
            ERROR("Unable to send invite message for this call");
            return false;
        }
    
        call->setConnectionState(Call::PROGRESSING);
        call->setState(Call::ACTIVE);
        addSipCall(call);
    
        return true;
    }
    
    void
    SIPVoIPLink::SIPCallServerFailure(SIPCall *call)
    {
        std::string id(call->getCallId());
        Manager::instance().callFailure(id);
        removeSipCall(id);
    }
    
    void
    SIPVoIPLink::SIPCallClosed(SIPCall *call)
    {
        const std::string id(call->getCallId());
    
        stopRtpIfCurrent(id, *call);
    
        Manager::instance().peerHungupCall(id);
        removeSipCall(id);
        Manager::instance().checkAudio();
    }
    
    void
    SIPVoIPLink::SIPCallAnswered(SIPCall *call, pjsip_rx_data * /*rdata*/)
    {
        if (call->getConnectionState() != Call::CONNECTED) {
            call->setConnectionState(Call::CONNECTED);
            call->setState(Call::ACTIVE);
            Manager::instance().peerAnsweredCall(call->getCallId());
        }
    }
    
    ///////////////////////////////////////////////////////////////////////////////
    // Private functions
    ///////////////////////////////////////////////////////////////////////////////
    
    namespace {
    int SIPSessionReinvite(SIPCall *call)
    {
        pjmedia_sdp_session *local_sdp = call->getLocalSDP()->getLocalSdpSession();
        pjsip_tx_data *tdata;
    
        if (local_sdp and call->inv and call->inv->pool_prov and
                pjsip_inv_reinvite(call->inv, NULL, local_sdp, &tdata) == PJ_SUCCESS)
            return pjsip_inv_send_msg(call->inv, tdata);
    
        return !PJ_SUCCESS;
    }
    
    void makeCallRing(SIPCall &call)
    {
        call.setConnectionState(Call::RINGING);
        Manager::instance().peerRingingCall(call.getCallId());
    }
    
    void invite_session_state_changed_cb(pjsip_inv_session *inv, pjsip_event *ev)
    {
        if (!inv)
            return;
    
        SIPCall *call = static_cast<SIPCall*>(inv->mod_data[mod_ua_.id]);
    
        if (call == NULL)
            return;
    
        if (ev and inv->state != PJSIP_INV_STATE_CONFIRMED) {
            // Update UI with the current status code and description
            pjsip_transaction * tsx = ev->body.tsx_state.tsx;
            int statusCode = tsx ? tsx->status_code : 404;
    
            if (statusCode) {
                const pj_str_t * description = pjsip_get_status_text(statusCode);
                std::string desc(description->ptr, description->slen);
    
                CallManager *cm = Manager::instance().getClient()->getCallManager();
                cm->sipCallStateChanged(call->getCallId(), desc, statusCode);
            }
        }
    
        SIPVoIPLink *link = SIPVoIPLink::instance();
    
        if (inv->state == PJSIP_INV_STATE_EARLY and ev and ev->body.tsx_state.tsx and
                ev->body.tsx_state.tsx->role == PJSIP_ROLE_UAC) {
            makeCallRing(*call);
        } else if (inv->state == PJSIP_INV_STATE_CONFIRMED and ev) {
            // After we sent or received a ACK - The connection is established
            link->SIPCallAnswered(call, ev->body.tsx_state.src.rdata);
        } else if (inv->state == PJSIP_INV_STATE_DISCONNECTED) {
            std::string accId(call->getAccountId());
    
            switch (inv->cause) {
                    // The call terminates normally - BYE / CANCEL
                case PJSIP_SC_OK:
                case PJSIP_SC_REQUEST_TERMINATED:
                    link->SIPCallClosed(call);
                    break;
    
                case PJSIP_SC_DECLINE:
                    if (inv->role != PJSIP_ROLE_UAC)
                        break;
    
                case PJSIP_SC_NOT_FOUND:
                case PJSIP_SC_REQUEST_TIMEOUT:
                case PJSIP_SC_NOT_ACCEPTABLE_HERE:  /* no compatible codecs */
                case PJSIP_SC_NOT_ACCEPTABLE_ANYWHERE:
                case PJSIP_SC_UNSUPPORTED_MEDIA_TYPE:
                case PJSIP_SC_UNAUTHORIZED:
                case PJSIP_SC_FORBIDDEN:
                case PJSIP_SC_REQUEST_PENDING:
                case PJSIP_SC_ADDRESS_INCOMPLETE:
                default:
                    link->SIPCallServerFailure(call);
                    break;
            }
        }
    }
    
    void sdp_request_offer_cb(pjsip_inv_session *inv, const pjmedia_sdp_session *offer)
    {
        if (!inv)
            return;
    
        SIPCall *call = static_cast<SIPCall*>(inv->mod_data[mod_ua_.id]);
    
        if (!call)
            return;
    
        std::string accId(call->getAccountId());
        SIPAccount *account = Manager::instance().getSipAccount(accId);
    
        if (!account)
            return;
    
        call->getLocalSDP()->receiveOffer(offer, account->getActiveAudioCodecs(), account->getActiveVideoCodecs());
        call->getLocalSDP()->startNegotiation();
    
        pjsip_inv_set_sdp_answer(call->inv, call->getLocalSDP()->getLocalSdpSession());
    }
    
    void sdp_create_offer_cb(pjsip_inv_session *inv, pjmedia_sdp_session **p_offer)
    {
        if (!inv or !p_offer)
            return;
    
        SIPCall *call = static_cast<SIPCall*>(inv->mod_data[mod_ua_.id]);
    
        if (!call)
            return;
    
        std::string accountid(call->getAccountId());
    
        SIPAccount *account = Manager::instance().getSipAccount(accountid);
    
        if (!account)
            return;
    
        std::string address;
    
        if (account->getPublishedSameasLocal())
            address = SipTransport::getInterfaceAddrFromName(account->getLocalInterface());
        else
            address = account->getPublishedAddress();
    
        const std::string addrSdp(address);
    
        setCallMediaLocal(call, address);
    
        Sdp *localSDP = call->getLocalSDP();
        localSDP->setPublishedIP(addrSdp);
        const bool created = localSDP->createOffer(account->getActiveAudioCodecs(), account->getActiveVideoCodecs());
    
        if (created)
            *p_offer = localSDP->getLocalSdpSession();
    }
    
    // This callback is called after SDP offer/answer session has completed.
    void sdp_media_update_cb(pjsip_inv_session *inv, pj_status_t status)
    {
        if (!inv)
            return;
    
        SIPCall *call = static_cast<SIPCall *>(inv->mod_data[mod_ua_.id]);
    
        if (call == NULL) {
            DEBUG("Call declined by peer, SDP negotiation stopped");
            return;
        }
    
        if (status != PJ_SUCCESS) {
            const int reason = inv->state != PJSIP_INV_STATE_NULL and
                               inv->state != PJSIP_INV_STATE_CONFIRMED ?
                               PJSIP_SC_UNSUPPORTED_MEDIA_TYPE : 0;
    
            WARN("Could not negotiate offer");
            const std::string callID(call->getCallId());
            SIPVoIPLink::instance()->hangup(callID, reason);
            // call is now a dangling pointer after calling hangup
            call = 0;
            Manager::instance().callFailure(callID);
            return;
        }
    
        if (!inv->neg) {
            WARN("No negotiator for this session");
            return;
        }
    
        // Retreive SDP session for this call
        Sdp *sdpSession = call->getLocalSDP();
    
        if (!sdpSession) {
            ERROR("No SDP session");
            return;
        }
    
        // Get active session sessions
        const pjmedia_sdp_session *remoteSDP = 0;
    
        if (pjmedia_sdp_neg_get_active_remote(inv->neg, &remoteSDP) != PJ_SUCCESS) {
            ERROR("Active remote not present");
            return;
        }
    
        if (pjmedia_sdp_validate(remoteSDP) != PJ_SUCCESS) {
            ERROR("Invalid remote SDP session");
            return;
        }
    
        const pjmedia_sdp_session *local_sdp;
        pjmedia_sdp_neg_get_active_local(inv->neg, &local_sdp);
    
        if (pjmedia_sdp_validate(local_sdp) != PJ_SUCCESS) {
            ERROR("Invalid local SDP session");
            return;
        }
    
        // Print SDP session
        char buffer[4096];
        memset(buffer, 0, sizeof buffer);
    
        if (pjmedia_sdp_print(remoteSDP, buffer, sizeof buffer) == -1) {
            ERROR("SDP was too big for buffer");
            return;
        }
    
        DEBUG("Remote active SDP Session:\n%s", buffer);
    
        memset(buffer, 0, sizeof buffer);
    
        if (pjmedia_sdp_print(local_sdp, buffer, sizeof buffer) == -1) {
            ERROR("SDP was too big for buffer");
            return;
        }
    
        DEBUG("Local active SDP Session:\n%s", buffer);
    
        // Set active SDP sessions
        sdpSession->setActiveRemoteSdpSession(remoteSDP);
        sdpSession->setActiveLocalSdpSession(local_sdp);
    
        // Update internal field for
        sdpSession->setMediaTransportInfoFromRemoteSdp();
    
        try {
            call->getAudioRtp().updateDestinationIpAddress();
        } catch (const AudioRtpFactoryException &e) {
            ERROR("%s", e.what());
        }
    
        call->getAudioRtp().setDtmfPayloadType(sdpSession->getTelephoneEventType());
    #ifdef SFL_VIDEO
        call->getVideoRtp().updateSDP(*call->getLocalSDP());
        call->getVideoRtp().updateDestination(call->getLocalSDP()->getRemoteIP(), call->getLocalSDP()->getRemoteVideoPort());
        auto localPort = call->getLocalSDP()->getLocalVideoPort();
        if (!localPort)
            localPort = call->getLocalSDP()->getRemoteVideoPort();
        call->getVideoRtp().start(localPort);
    #endif
    
        // Get the crypto attribute containing srtp's cryptographic context (keys, cipher)
        CryptoOffer crypto_offer;
        call->getLocalSDP()->getRemoteSdpCryptoFromOffer(remoteSDP, crypto_offer);
    
    #if HAVE_SDES
        bool nego_success = false;
    
        if (!crypto_offer.empty()) {
            std::vector<sfl::CryptoSuiteDefinition> localCapabilities;
    
            for (size_t i = 0; i < ARRAYSIZE(sfl::CryptoSuites); ++i)
                localCapabilities.push_back(sfl::CryptoSuites[i]);
    
            sfl::SdesNegotiator sdesnego(localCapabilities, crypto_offer);
    
            if (sdesnego.negotiate()) {
                nego_success = true;
    
                try {
                    call->getAudioRtp().setRemoteCryptoInfo(sdesnego);
                    Manager::instance().getClient()->getCallManager()->secureSdesOn(call->getCallId());
                } catch (const AudioRtpFactoryException &e) {
                    ERROR("%s", e.what());
                    Manager::instance().getClient()->getCallManager()->secureSdesOff(call->getCallId());
                }
            } else {
                ERROR("SDES negotiation failure");
                Manager::instance().getClient()->getCallManager()->secureSdesOff(call->getCallId());
            }
        } else {
            DEBUG("No crypto offer available");
        }
    
        // We did not find any crypto context for this media, RTP fallback
        if (!nego_success && call->getAudioRtp().isSdesEnabled()) {
            ERROR("Negotiation failed but SRTP is enabled, fallback on RTP");
            call->getAudioRtp().stop();
            call->getAudioRtp().setSrtpEnabled(false);
    
            const std::string accountID = call->getAccountId();
    
            SIPAccount *sipaccount = Manager::instance().getSipAccount(accountID);
    
            if (sipaccount and sipaccount->getSrtpFallback()) {
                call->getAudioRtp().initSession();
    
                if (sipaccount->isStunEnabled())
                    updateSDPFromSTUN(*call, *sipaccount, *SIPVoIPLink::instance()->sipTransport);
            }
        }
    
    #endif // HAVE_SDES
    
        std::vector<sfl::AudioCodec*> sessionMedia(sdpSession->getSessionAudioMedia());
    
        if (sessionMedia.empty()) {
            WARN("Session media is empty");
            return;
        }
    
        try {
            Manager::instance().startAudioDriverStream();
    
            std::vector<AudioCodec*> audioCodecs;
    
            for (const auto & i : sessionMedia) {
                if (!i)
                    continue;
    
                const int pl = i->getPayloadType();
    
                sfl::AudioCodec *ac = Manager::instance().audioCodecFactory.instantiateCodec(pl);
    
                if (!ac) {
                    ERROR("Could not instantiate codec %d", pl);
                } else {
                    audioCodecs.push_back(ac);
                }
            }
    
            if (not audioCodecs.empty())
                call->getAudioRtp().updateSessionMedia(audioCodecs);
        } catch (const SdpException &e) {
            ERROR("%s", e.what());
        } catch (const std::exception &rtpException) {
            ERROR("%s", rtpException.what());
        }
    }
    
    void outgoing_request_forked_cb(pjsip_inv_session * /*inv*/, pjsip_event * /*e*/)
    {}
    
    bool handle_media_control(pjsip_inv_session * inv, pjsip_transaction *tsx, pjsip_event *event)
    {
        /*
         * Incoming INFO request for media control.
         */
        const pj_str_t STR_APPLICATION = CONST_PJ_STR("application");
        const pj_str_t STR_MEDIA_CONTROL_XML = CONST_PJ_STR("media_control+xml");
        pjsip_rx_data *rdata = event->body.tsx_state.src.rdata;
        pjsip_msg_body *body = rdata->msg_info.msg->body;
    
        if (body and body->len and pj_stricmp(&body->content_type.type, &STR_APPLICATION) == 0 and
                pj_stricmp(&body->content_type.subtype, &STR_MEDIA_CONTROL_XML) == 0) {
            pj_str_t control_st;
    
            /* Apply and answer the INFO request */
            pj_strset(&control_st, (char *) body->data, body->len);
            const pj_str_t PICT_FAST_UPDATE = CONST_PJ_STR("picture_fast_update");
    
            if (pj_strstr(&control_st, &PICT_FAST_UPDATE)) {
    #ifdef SFL_VIDEO
                DEBUG("handling picture fast update request");
                SIPCall *call = static_cast<SIPCall *>(inv->mod_data[mod_ua_.id]);
    
                if (call)
                    call->getVideoRtp().forceKeyFrame();
    
                pjsip_tx_data *tdata;
                pj_status_t status = pjsip_endpt_create_response(tsx->endpt, rdata,
                                     PJSIP_SC_OK, NULL, &tdata);
    
                if (status == PJ_SUCCESS) {
                    status = pjsip_tsx_send_msg(tsx, tdata);
                    return true;
                }
    
    #else
                (void) inv;
                (void) tsx;
    #endif
            }
        }
    
        return false;
    }
    
    void sendOK(pjsip_dialog *dlg, pjsip_rx_data *r_data, pjsip_transaction *tsx)
    {
        pjsip_tx_data* t_data;
    
        if (pjsip_dlg_create_response(dlg, r_data, PJSIP_SC_OK, NULL, &t_data) == PJ_SUCCESS)
            pjsip_dlg_send_response(dlg, tsx, t_data);
    }
    
    void transaction_state_changed_cb(pjsip_inv_session * inv,
                                      pjsip_transaction *tsx, pjsip_event *event)
    {
        if (!tsx or !event or !inv or tsx->role != PJSIP_ROLE_UAS or
                tsx->state != PJSIP_TSX_STATE_TRYING)
            return;
    
        // Handle the refer method
        if (pjsip_method_cmp(&tsx->method, &pjsip_refer_method) == 0) {
            onCallTransfered(inv, event->body.tsx_state.src.rdata);
            return;
        }
    
        if (tsx->role == PJSIP_ROLE_UAS and tsx->state == PJSIP_TSX_STATE_TRYING) {
            if (handle_media_control(inv, tsx, event))
                return;
        }
    
        SIPCall *call = static_cast<SIPCall *>(inv->mod_data[mod_ua_.id]);
    
        if (event->body.rx_msg.rdata) {
            pjsip_rx_data *r_data = event->body.rx_msg.rdata;
    
            if (r_data->msg_info.msg->line.req.method.id == PJSIP_OTHER_METHOD) {
                std::string request(pjsip_rx_data_get_info(r_data));
                DEBUG("%s", request.c_str());
    
                if (request.find("NOTIFY") == std::string::npos and
                        request.find("INFO") != std::string::npos) {
                    sendOK(inv->dlg, r_data, tsx);
                    return;
                }
    
                pjsip_msg_body *body(r_data->msg_info.msg->body);
    
                if (body and body->len > 0) {
                    const std::string msg(static_cast<char *>(body->data), body->len);
                    DEBUG("%s", msg.c_str());
    
                    if (msg.find("Not found") != std::string::npos) {
                        ERROR("Received 404 Not found");
                        sendOK(inv->dlg, r_data, tsx);
                        return;
                    } else if (msg.find("Ringing") != std::string::npos and call) {
                        makeCallRing(*call);
                        sendOK(inv->dlg, r_data, tsx);
                        return;
                    } else if (msg.find("Ok") != std::string::npos) {
                        sendOK(inv->dlg, r_data, tsx);
                        return;
                    }
                }
            }
        }
    
    #if HAVE_INSTANT_MESSAGING
    
        if (!call)
            return;
    
        // Incoming TEXT message
        pjsip_rx_data *r_data = event->body.tsx_state.src.rdata;
    
        // Get the message inside the transaction
        if (!r_data or !r_data->msg_info.msg->body)
            return;
    
        const char *formattedMsgPtr = static_cast<const char*>(r_data->msg_info.msg->body->data);
    
        if (!formattedMsgPtr)
            return;
    
        std::string formattedMessage(formattedMsgPtr, strlen(formattedMsgPtr));
    
        using namespace sfl::InstantMessaging;
    
        try {
            // retreive the recipient-list of this message
            std::string urilist = findTextUriList(formattedMessage);
            UriList list = parseXmlUriList(urilist);
    
            // If no item present in the list, peer is considered as the sender
            std::string from;
    
            if (list.empty()) {
                from = call->getPeerNumber();
            } else {
                from = list.front()[IM_XML_URI];
    
                if (from == "Me")
                    from = call->getPeerNumber();
            }
    
            // strip < and > characters in case of an IP address
            if (from[0] == '<' && from[from.size() - 1] == '>')
                from = from.substr(1, from.size() - 2);
    
            Manager::instance().incomingMessage(call->getCallId(), from, findTextMessage(formattedMessage));
    
            // Respond with a 200/OK
            sendOK(inv->dlg, r_data, tsx);
    
        } catch (const sfl::InstantMessageException &except) {
            ERROR("%s", except.what());
        }
    
    #endif
    }
    
    void checkNatAddress(pjsip_regc_cbparam &param, SIPAccount &account)
    {
        if (!param.rdata or !param.rdata->msg_info.via)
            return;
    
        pj_str_t receivedValue = param.rdata->msg_info.via->recvd_param;
    
        if (receivedValue.slen) {
            std::string publicIpFromReceived(receivedValue.ptr, receivedValue.slen);
            account.setReceivedParameter(publicIpFromReceived);
        }
    
        account.setRPort(param.rdata->msg_info.via->rport_param);
    }
    
    void registration_cb(pjsip_regc_cbparam *param)
    {
        if (param == NULL) {
            ERROR("registration callback parameter is NULL");
            return;
        }
    
        SIPAccount *account = static_cast<SIPAccount *>(param->token);
    
        if (account == NULL) {
            ERROR("account doesn't exist in registration callback");
            return;
        }
    
        if (param->regc != account->getRegistrationInfo())
            return;
    
        const std::string accountID = account->getAccountID();
    
        if (param->status != PJ_SUCCESS) {
            ERROR("SIP registration error %d", param->status);
            account->destroyRegistrationInfo();
            account->stopKeepAliveTimer();
        } else if (param->code < 0 || param->code >= 300) {
            ERROR("SIP registration failed, status=%d (%.*s)",
                  param->code, (int)param->reason.slen, param->reason.ptr);
            account->destroyRegistrationInfo();
            account->stopKeepAliveTimer();
            switch (param->code) {
                case PJSIP_SC_FORBIDDEN:
                    account->setRegistrationState(RegistrationState::ERROR_AUTH);
                    break;
                case PJSIP_SC_NOT_FOUND:
                    account->setRegistrationState(RegistrationState::ERROR_HOST);
                    break;
                case PJSIP_SC_REQUEST_TIMEOUT:
                    account->setRegistrationState(RegistrationState::ERROR_HOST);
                    break;
                case PJSIP_SC_SERVICE_UNAVAILABLE:
                    account->setRegistrationState(RegistrationState::ERROR_SERVICE_UNAVAILABLE);
                    break;
                default:
                    account->setRegistrationState(RegistrationState::ERROR_GENERIC);
            }
        } else if (PJSIP_IS_STATUS_IN_CLASS(param->code, 200)) {
    
            // Update auto registration flag
            account->resetAutoRegistration();
    
            if (param->expiration < 1) {
                account->destroyRegistrationInfo();
                /* Stop keep-alive timer if any. */
                account->stopKeepAliveTimer();
                DEBUG("Unregistration success");
                account->setRegistrationState(RegistrationState::UNREGISTERED);
            } else {
                /* TODO Check and update SIP outbound status first, since the result
                 * will determine if we should update re-registration
                 */
                // update_rfc5626_status(acc, param->rdata);
    
                account->checkNATAddress(param, pool_);
    
                /* TODO Check and update Service-Route header */
                if (account->hasServiceRoute())
                    pjsip_regc_set_route_set(param->regc, sip_utils::createRouteSet(account->getServiceRoute(), pool_));
    
                // start the periodic registration request based on Expire header
                // account determines itself if a keep alive is required
                if (account->isKeepAliveEnabled())
                    account->startKeepAliveTimer();
    
                account->setRegistrationState(RegistrationState::REGISTERED);
            }
        }
    
        /* Check if we need to auto retry registration. Basically, registration
         * failure codes triggering auto-retry are those of temporal failures
         * considered to be recoverable in relatively short term.
         */
        switch (param->code) {
            case PJSIP_SC_REQUEST_TIMEOUT:
            case PJSIP_SC_INTERNAL_SERVER_ERROR:
            case PJSIP_SC_BAD_GATEWAY:
            case PJSIP_SC_SERVICE_UNAVAILABLE:
            case PJSIP_SC_SERVER_TIMEOUT:
                account->scheduleReregistration(endpt_);
                break;
    
            default:
                /* Global failure */
                if (PJSIP_IS_STATUS_IN_CLASS(param->code, 600))
                    account->scheduleReregistration(endpt_);
        }
    
        const pj_str_t *description = pjsip_get_status_text(param->code);
    
        if (param->code && description) {
            std::string state(description->ptr, description->slen);
    
            Manager::instance().getClient()->getConfigurationManager()->sipRegistrationStateChanged(accountID, state, param->code);
            std::pair<int, std::string> details(param->code, state);
            // TODO: there id a race condition for this ressource when closing the application
            account->setRegistrationStateDetailed(details);
            account->setRegistrationExpire(param->expiration);
        }
    
    #undef FAILURE_MESSAGE
    }
    
    void onCallTransfered(pjsip_inv_session *inv, pjsip_rx_data *rdata)
    {
        SIPCall *currentCall = static_cast<SIPCall *>(inv->mod_data[mod_ua_.id]);
    
        if (currentCall == NULL)
            return;
    
        static const pj_str_t str_refer_to = CONST_PJ_STR("Refer-To");
        pjsip_generic_string_hdr *refer_to = static_cast<pjsip_generic_string_hdr*>
                                             (pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &str_refer_to, NULL));
    
        if (!refer_to) {
            pjsip_dlg_respond(inv->dlg, rdata, 400, NULL, NULL, NULL);
            return;
        }
    
        try {
            SIPVoIPLink::instance()->newOutgoingCall(Manager::instance().getNewCallID(),
                    std::string(refer_to->hvalue.ptr, refer_to->hvalue.slen), currentCall->getAccountId());
            Manager::instance().hangupCall(currentCall->getCallId());
        } catch (const VoipLinkException &e) {
            ERROR("%s", e.what());
        }
    }
    
    void transfer_client_cb(pjsip_evsub *sub, pjsip_event *event)
    {
        switch (pjsip_evsub_get_state(sub)) {
            case PJSIP_EVSUB_STATE_ACCEPTED:
                if (!event)
                    return;
    
                pj_assert(event->type == PJSIP_EVENT_TSX_STATE && event->body.tsx_state.type == PJSIP_EVENT_RX_MSG);
                break;
    
            case PJSIP_EVSUB_STATE_TERMINATED:
                pjsip_evsub_set_mod_data(sub, mod_ua_.id, NULL);
                break;
    
            case PJSIP_EVSUB_STATE_ACTIVE: {
                SIPVoIPLink *link = static_cast<SIPVoIPLink *>(pjsip_evsub_get_mod_data(sub, mod_ua_.id));
    
                if (!link or !event)
                    return;
    
                pjsip_rx_data* r_data = event->body.rx_msg.rdata;
    
                if (!r_data)
                    return;
    
                std::string request(pjsip_rx_data_get_info(r_data));
    
                pjsip_status_line status_line = { 500, *pjsip_get_status_text(500) };
    
                if (!r_data->msg_info.msg)
                    return;
    
                if (r_data->msg_info.msg->line.req.method.id == PJSIP_OTHER_METHOD and
                        request.find("NOTIFY") != std::string::npos) {
                    pjsip_msg_body *body = r_data->msg_info.msg->body;
    
                    if (!body)
                        return;
    
                    if (pj_stricmp2(&body->content_type.type, "message") or
                            pj_stricmp2(&body->content_type.subtype, "sipfrag"))
                        return;
    
                    if (pjsip_parse_status_line((char*) body->data, body->len, &status_line) != PJ_SUCCESS)
                        return;
                }
    
                if (!r_data->msg_info.cid)
                    return;
    
                std::string transferID(r_data->msg_info.cid->id.ptr, r_data->msg_info.cid->id.slen);
                SIPCall *call = SIPVoIPLink::instance()->getSipCall(transferCallID[transferID]);
    
                if (!call)
                    return;
    
                if (status_line.code / 100 == 2) {
                    pjsip_tx_data *tdata;
    
                    if (!call->inv)
                        return;
    
                    if (pjsip_inv_end_session(call->inv, PJSIP_SC_GONE, NULL, &tdata) == PJ_SUCCESS)
                        pjsip_inv_send_msg(call->inv, tdata);
    
                    Manager::instance().hangupCall(call->getCallId());
                    pjsip_evsub_set_mod_data(sub, mod_ua_.id, NULL);
                }
    
                break;
            }
    
            default:
                break;
        }
    }
    
    void setCallMediaLocal(SIPCall* call, const std::string &localIP)
    {
        std::string account_id(call->getAccountId());
        SIPAccount *account = Manager::instance().getSipAccount(account_id);
    
        if (!account)
            return;
    
        // Reference: http://www.cs.columbia.edu/~hgs/rtp/faq.html#ports
        // We only want to set ports to new values if they haven't been set
        if (call->getLocalAudioPort() == 0) {
            const unsigned callLocalAudioPort = account->generateAudioPort();
            call->setLocalAudioPort(callLocalAudioPort);
            call->getLocalSDP()->setLocalPublishedAudioPort(callLocalAudioPort);
        }
    
        call->setLocalIp(localIP);
    
    #ifdef SFL_VIDEO
    
        if (call->getLocalVideoPort() == 0) {
            // https://projects.savoirfairelinux.com/issues/17498
            const unsigned int callLocalVideoPort = account->generateVideoPort();
            // this should already be guaranteed by SIPAccount
            assert(call->getLocalAudioPort() != callLocalVideoPort);
    
            call->setLocalVideoPort(callLocalVideoPort);
            call->getLocalSDP()->setLocalPublishedVideoPort(callLocalVideoPort);
        }
    
    #endif
    }
    } // end anonymous namespace
    
    int SIPVoIPLink::getModId()
    {
        return mod_ua_.id;
    }
    
    void SIPVoIPLink::loadIP2IPSettings()
    {
        try {
            const auto iter = sipAccountMap_.find(SIPAccount::IP2IP_PROFILE);
            // if IP2IP doesn't exist yet, create it
            if (iter == sipAccountMap_.end())
                sipAccountMap_[SIPAccount::IP2IP_PROFILE] = new SIPAccount(SIPAccount::IP2IP_PROFILE, true);
    
            SIPAccount *ip2ip = static_cast<SIPAccount*>(sipAccountMap_[SIPAccount::IP2IP_PROFILE]);
    
            sipTransport->createSipTransport(*ip2ip);
            ip2ip->registerVoIPLink();
        } catch (const std::runtime_error &e) {
            ERROR("%s", e.what());
        }
    }