LCOV - code coverage report
Current view: top level - src/sip - sipvoiplink.cpp (source / functions) Hit Total Coverage
Test: jami-coverage-filtered.info Lines: 487 790 61.6 %
Date: 2024-04-18 08:01:59 Functions: 36 54 66.7 %

          Line data    Source code
       1             : /*
       2             :  *  Copyright (C) 2004-2024 Savoir-faire Linux Inc.
       3             :  *
       4             :  *  Author: Emmanuel Milou <emmanuel.milou@savoirfairelinux.com>
       5             :  *  Author: Yun Liu <yun.liu@savoirfairelinux.com>
       6             :  *  Author: Pierre-Luc Bacon <pierre-luc.bacon@savoirfairelinux.com>
       7             :  *  Author: Alexandre Savard <alexandre.savard@savoirfairelinux.com>
       8             :  *  Author: Guillaume Roguez <guillaume.roguez@savoirfairelinux.com>
       9             :  *
      10             :  *  This program is free software; you can redistribute it and/or modify
      11             :  *  it under the terms of the GNU General Public License as published by
      12             :  *  the Free Software Foundation; either version 3 of the License, or
      13             :  *  (at your option) any later version.
      14             :  *
      15             :  *  This program is distributed in the hope that it will be useful,
      16             :  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
      17             :  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      18             :  *  GNU General Public License for more details.
      19             :  *
      20             :  *  You should have received a copy of the GNU General Public License
      21             :  *  along with this program; if not, write to the Free Software
      22             :  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
      23             :  */
      24             : 
      25             : #include "sip/sipvoiplink.h"
      26             : 
      27             : #ifdef HAVE_CONFIG_H
      28             : #include "config.h"
      29             : #endif
      30             : 
      31             : #include "sdp.h"
      32             : #include "sip/sipcall.h"
      33             : #include "sip/sipaccount.h"
      34             : 
      35             : #include "jamidht/jamiaccount.h"
      36             : 
      37             : #include "manager.h"
      38             : 
      39             : #include "im/instant_messaging.h"
      40             : #include "system_codec_container.h"
      41             : #include "audio/audio_rtp_session.h"
      42             : 
      43             : #ifdef ENABLE_VIDEO
      44             : #include "video/video_rtp_session.h"
      45             : #include "client/videomanager.h"
      46             : #endif
      47             : 
      48             : #include "pres_sub_server.h"
      49             : 
      50             : #include "connectivity/sip_utils.h"
      51             : #include "string_utils.h"
      52             : #include "logger.h"
      53             : 
      54             : #include <dhtnet/ip_utils.h>
      55             : #include <opendht/thread_pool.h>
      56             : 
      57             : #include <pjsip/sip_endpoint.h>
      58             : #include <pjsip/sip_uri.h>
      59             : 
      60             : #include <pjsip-simple/presence.h>
      61             : #include <pjsip-simple/publish.h>
      62             : 
      63             : // Only PJSIP 2.10 is supported.
      64             : #if PJ_VERSION_NUM < (2 << 24 | 10 << 16)
      65             : #error "Unsupported PJSIP version (requires version 2.10+)"
      66             : #endif
      67             : 
      68             : #include <istream>
      69             : #include <algorithm>
      70             : #include <regex>
      71             : 
      72             : namespace jami {
      73             : 
      74             : using sip_utils::CONST_PJ_STR;
      75             : 
      76             : /**************** EXTERN VARIABLES AND FUNCTIONS (callbacks) **************************/
      77             : 
      78             : static pjsip_endpoint* endpt_;
      79             : static pjsip_module mod_ua_;
      80             : 
      81             : static void invite_session_state_changed_cb(pjsip_inv_session* inv, pjsip_event* e);
      82             : static void outgoing_request_forked_cb(pjsip_inv_session* inv, pjsip_event* e);
      83             : static void transaction_state_changed_cb(pjsip_inv_session* inv,
      84             :                                          pjsip_transaction* tsx,
      85             :                                          pjsip_event* e);
      86             : // Called when an SDP offer is found in answer. This will occur
      87             : // when we send an empty invite (no SDP). In this case, we should
      88             : // expect an offer in a the 200 OK message
      89             : static void on_rx_offer2(pjsip_inv_session* inv, struct pjsip_inv_on_rx_offer_cb_param* param);
      90             : // Called when a re-invite is received
      91             : static pj_status_t reinvite_received_cb(pjsip_inv_session* inv,
      92             :                                         const pjmedia_sdp_session* offer,
      93             :                                         pjsip_rx_data* rdata);
      94             : // Called to request an SDP offer if the peer sent an invite or
      95             : // a re-invite with no SDP. In this, we must provide an offer in
      96             : // the answer (200 OK) if we accept the call
      97             : static void sdp_create_offer_cb(pjsip_inv_session* inv, pjmedia_sdp_session** p_offer);
      98             : // Called to report media (SDP) negotiation result
      99             : static void sdp_media_update_cb(pjsip_inv_session* inv, pj_status_t status);
     100             : 
     101             : static std::shared_ptr<SIPCall> getCallFromInvite(pjsip_inv_session* inv);
     102             : #ifdef DEBUG_SIP_REQUEST_MSG
     103             : static void processInviteResponseHelper(pjsip_inv_session* inv, pjsip_event* e);
     104             : #endif
     105             : 
     106             : static pj_bool_t
     107           3 : handleIncomingOptions(pjsip_rx_data* rdata)
     108             : {
     109             :     pjsip_tx_data* tdata;
     110             : 
     111           3 :     auto dlg = pjsip_rdata_get_dlg(rdata);
     112           3 :     if (dlg) {
     113           0 :         JAMI_INFO("Processing in-dialog option request");
     114           0 :         if (pjsip_dlg_create_response(dlg, rdata, PJSIP_SC_OK, NULL, &tdata) != PJ_SUCCESS) {
     115           0 :             JAMI_ERR("Failed to create in-dialog response for option request");
     116           0 :             return PJ_FALSE;
     117             :         }
     118             :     } else {
     119           3 :         JAMI_INFO("Processing out-of-dialog option request");
     120           3 :         if (pjsip_endpt_create_response(endpt_, rdata, PJSIP_SC_OK, NULL, &tdata) != PJ_SUCCESS) {
     121           0 :             JAMI_ERR("Failed to create out-of-dialog response for option request");
     122           0 :             return PJ_FALSE;
     123             :         }
     124             :     }
     125             : 
     126             : #define ADD_HDR(hdr) \
     127             :     do { \
     128             :         const pjsip_hdr* cap_hdr = hdr; \
     129             :         if (cap_hdr) \
     130             :             pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*) pjsip_hdr_clone(tdata->pool, cap_hdr)); \
     131             :     } while (0)
     132             : #define ADD_CAP(cap) ADD_HDR(pjsip_endpt_get_capability(endpt_, cap, NULL));
     133             : 
     134           3 :     ADD_CAP(PJSIP_H_ALLOW);
     135           3 :     ADD_CAP(PJSIP_H_ACCEPT);
     136           3 :     ADD_CAP(PJSIP_H_SUPPORTED);
     137           3 :     ADD_HDR(pjsip_evsub_get_allow_events_hdr(NULL));
     138             : 
     139           3 :     if (dlg) {
     140           0 :         if (pjsip_dlg_send_response(dlg, pjsip_rdata_get_tsx(rdata), tdata) != PJ_SUCCESS) {
     141           0 :             JAMI_ERR("Failed to send in-dialog response for option request");
     142           0 :             return PJ_FALSE;
     143             :         }
     144             : 
     145           0 :         JAMI_INFO("Sent in-dialog response for option request");
     146           0 :         return PJ_TRUE;
     147             :     }
     148             : 
     149             :     pjsip_response_addr res_addr;
     150           3 :     pjsip_get_response_addr(tdata->pool, rdata, &res_addr);
     151             : 
     152           3 :     if (pjsip_endpt_send_response(endpt_, &res_addr, tdata, NULL, NULL) != PJ_SUCCESS) {
     153           0 :         pjsip_tx_data_dec_ref(tdata);
     154           0 :         JAMI_ERR("Failed to send out-of-dialog response for option request");
     155           0 :         return PJ_FALSE;
     156             :     }
     157             : 
     158           3 :     JAMI_INFO("Sent out-of-dialog response for option request");
     159           3 :     return PJ_TRUE;
     160             : }
     161             : 
     162             : // return PJ_FALSE so that eventually other modules will handle these requests
     163             : // TODO: move Voicemail to separate module
     164             : static pj_bool_t
     165           0 : transaction_response_cb(pjsip_rx_data* rdata)
     166             : {
     167           0 :     pjsip_dialog* dlg = pjsip_rdata_get_dlg(rdata);
     168             : 
     169           0 :     if (!dlg)
     170           0 :         return PJ_FALSE;
     171             : 
     172           0 :     pjsip_transaction* tsx = pjsip_rdata_get_tsx(rdata);
     173             : 
     174           0 :     if (!tsx or tsx->method.id != PJSIP_INVITE_METHOD)
     175           0 :         return PJ_FALSE;
     176             : 
     177           0 :     if (tsx->status_code / 100 == 2) {
     178             :         /**
     179             :          * Send an ACK message inside a transaction. PJSIP send automatically, non-2xx ACK response.
     180             :          * ACK for a 2xx response must be send using this method.
     181             :          */
     182             :         pjsip_tx_data* tdata;
     183             : 
     184           0 :         if (rdata->msg_info.cseq) {
     185           0 :             pjsip_dlg_create_request(dlg, &pjsip_ack_method, rdata->msg_info.cseq->cseq, &tdata);
     186           0 :             pjsip_dlg_send_request(dlg, tdata, -1, NULL);
     187             :         }
     188             :     }
     189             : 
     190           0 :     return PJ_FALSE;
     191             : }
     192             : 
     193             : static pj_status_t
     194       29214 : try_respond_stateless(pjsip_endpoint* endpt,
     195             :                       pjsip_rx_data* rdata,
     196             :                       int st_code,
     197             :                       const pj_str_t* st_text,
     198             :                       const pjsip_hdr* hdr_list,
     199             :                       const pjsip_msg_body* body)
     200             : {
     201             :     /* Check that no UAS transaction has been created for this request.
     202             :      * If UAS transaction has been created for this request, application
     203             :      * MUST send the response statefully using that transaction.
     204             :      */
     205       29214 :     if (!pjsip_rdata_get_tsx(rdata))
     206       29214 :         return pjsip_endpt_respond_stateless(endpt, rdata, st_code, st_text, hdr_list, body);
     207             :     else
     208           0 :         JAMI_ERR("Transaction has been created for this request, send response "
     209             :                  "statefully instead");
     210             : 
     211           0 :     return !PJ_SUCCESS;
     212             : }
     213             : 
     214             : template<typename T>
     215             : bool
     216       29312 : is_uninitialized(std::weak_ptr<T> const& weak)
     217             : {
     218             :     using wt = std::weak_ptr<T>;
     219       29312 :     return !weak.owner_before(wt {}) && !wt {}.owner_before(weak);
     220             : }
     221             : 
     222             : static pj_bool_t
     223       29308 : transaction_request_cb(pjsip_rx_data* rdata)
     224             : {
     225       29308 :     if (!rdata or !rdata->msg_info.msg) {
     226           0 :         JAMI_ERR("rx_data is NULL");
     227           0 :         return PJ_FALSE;
     228             :     }
     229             : 
     230       29311 :     pjsip_method* method = &rdata->msg_info.msg->line.req.method;
     231             : 
     232       29311 :     if (!method) {
     233           0 :         JAMI_ERR("method is NULL");
     234           0 :         return PJ_FALSE;
     235             :     }
     236             : 
     237       29311 :     if (method->id == PJSIP_ACK_METHOD && pjsip_rdata_get_dlg(rdata))
     238           0 :         return PJ_FALSE;
     239             : 
     240       29311 :     if (!rdata->msg_info.to or !rdata->msg_info.from or !rdata->msg_info.via) {
     241           0 :         JAMI_ERR("Missing From, To or Via fields");
     242           0 :         return PJ_FALSE;
     243             :     }
     244             : 
     245             :     const auto sip_to_uri = reinterpret_cast<pjsip_sip_uri*>(
     246       29312 :         pjsip_uri_get_uri(rdata->msg_info.to->uri));
     247             :     const auto sip_from_uri = reinterpret_cast<pjsip_sip_uri*>(
     248       29311 :         pjsip_uri_get_uri(rdata->msg_info.from->uri));
     249       29312 :     const pjsip_host_port& sip_via = rdata->msg_info.via->sent_by;
     250             : 
     251       29312 :     if (!sip_to_uri or !sip_from_uri or !sip_via.host.ptr) {
     252           0 :         JAMI_ERR("NULL uri");
     253           0 :         return PJ_FALSE;
     254             :     }
     255             : 
     256       29312 :     std::string_view toUsername(sip_to_uri->user.ptr, sip_to_uri->user.slen);
     257       29307 :     std::string_view toHost(sip_to_uri->host.ptr, sip_to_uri->host.slen);
     258       29306 :     std::string_view viaHostname(sip_via.host.ptr, sip_via.host.slen);
     259       29309 :     const std::string_view remote_user(sip_from_uri->user.ptr, sip_from_uri->user.slen);
     260       29310 :     const std::string_view remote_hostname(sip_from_uri->host.ptr, sip_from_uri->host.slen);
     261       29311 :     std::string peerNumber;
     262       29309 :     if (not remote_user.empty() and not remote_hostname.empty())
     263       29307 :         peerNumber = remote_user + "@" + remote_hostname;
     264             :     else {
     265             :         char tmp[PJSIP_MAX_URL_SIZE];
     266           0 :         size_t length = pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR,
     267             :                                         sip_from_uri,
     268             :                                         tmp,
     269           0 :                                         PJSIP_MAX_URL_SIZE);
     270           0 :         peerNumber = sip_utils::stripSipUriPrefix(std::string_view(tmp, length));
     271             :     }
     272             : 
     273       29307 :     auto transport = Manager::instance().sipVoIPLink().sipTransportBroker->addTransport(
     274       29304 :         rdata->tp_info.transport);
     275             : 
     276       29312 :     std::shared_ptr<SIPAccountBase> account;
     277             :     // If transport account is default-constructed, guessing account is allowed
     278       29312 :     const auto& waccount = transport ? transport->getAccount() : std::weak_ptr<SIPAccountBase> {};
     279       29312 :     if (is_uninitialized(waccount)) {
     280          26 :         account = Manager::instance().sipVoIPLink().guessAccount(toUsername,
     281             :                                                                  viaHostname,
     282          13 :                                                                  remote_hostname);
     283          13 :         if (not account)
     284           0 :             return PJ_FALSE;
     285          13 :         if (not transport and account->getAccountType() == SIPAccount::ACCOUNT_TYPE) {
     286           0 :             if (not(transport = std::static_pointer_cast<SIPAccount>(account)->getTransport())) {
     287           0 :                 JAMI_ERR("No suitable transport to answer this call.");
     288           0 :                 return PJ_FALSE;
     289             :             }
     290           0 :             JAMI_WARN("Using transport from account.");
     291             :         }
     292       29298 :     } else if (!(account = waccount.lock())) {
     293           0 :         JAMI_ERR("Dropping SIP request: account is expired.");
     294           0 :         return PJ_FALSE;
     295             :     }
     296             : 
     297       29312 :     pjsip_msg_body* body = rdata->msg_info.msg->body;
     298             : 
     299       29312 :     if (method->id == PJSIP_OTHER_METHOD) {
     300       29214 :         std::string_view request = sip_utils::as_view(method->name);
     301             : 
     302       29213 :         if (request.find(sip_utils::SIP_METHODS::NOTIFY) != std::string_view::npos) {
     303           0 :             if (body and body->data) {
     304           0 :                 std::string_view body_view(static_cast<char*>(body->data), body->len);
     305           0 :                 auto pos = body_view.find("Voice-Message: ");
     306           0 :                 if (pos != std::string_view::npos) {
     307           0 :                     int newCount {0};
     308           0 :                     int oldCount {0};
     309           0 :                     int urgentCount {0};
     310           0 :                     std::string sp(body_view.substr(pos));
     311           0 :                     int ret = sscanf(sp.c_str(),
     312             :                                      "Voice-Message: %d/%d (%d/",
     313             :                                      &newCount,
     314             :                                      &oldCount,
     315             :                                      &urgentCount);
     316             : 
     317             :                     // According to rfc3842
     318             :                     // urgent messages are optional
     319           0 :                     if (ret >= 2)
     320           0 :                         emitSignal<libjami::CallSignal::VoiceMailNotify>(account->getAccountID(),
     321             :                                                                          newCount,
     322             :                                                                          oldCount,
     323             :                                                                          urgentCount);
     324           0 :                 }
     325             :             }
     326       29213 :         } else if (request.find(sip_utils::SIP_METHODS::MESSAGE) != std::string_view::npos) {
     327             :             // Reply 200 immediately (RFC 3428, ch. 7)
     328       29214 :             try_respond_stateless(endpt_, rdata, PJSIP_SC_OK, nullptr, nullptr, nullptr);
     329             :             // Process message content in case of multi-part body
     330       29214 :             auto payloads = im::parseSipMessage(rdata->msg_info.msg);
     331       29204 :             if (payloads.size() > 0) {
     332       29202 :                 constexpr pj_str_t STR_MESSAGE_ID = jami::sip_utils::CONST_PJ_STR("Message-ID");
     333             :                 auto* msgId = (pjsip_generic_string_hdr*)
     334       29202 :                     pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &STR_MESSAGE_ID, nullptr);
     335       29208 :                 std::string id = {};
     336       29208 :                 if (!msgId) {
     337             :                     // Supports imdn message format https://tools.ietf.org/html/rfc5438#section-7.1.1.3
     338           0 :                     constexpr pj_str_t STR_IMDN_MESSAGE_ID = jami::sip_utils::CONST_PJ_STR(
     339             :                         "imdn.Message-ID");
     340             :                     msgId = (pjsip_generic_string_hdr*)
     341           0 :                         pjsip_msg_find_hdr_by_name(rdata->msg_info.msg,
     342             :                                                    &STR_IMDN_MESSAGE_ID,
     343             :                                                    nullptr);
     344             :                 }
     345       29208 :                 if (msgId)
     346       29208 :                     id = std::string(msgId->hvalue.ptr, msgId->hvalue.slen);
     347             : 
     348       29208 :                 if (not id.empty()) {
     349             :                     try {
     350             :                         // Mark message as treated
     351       29206 :                         auto intid = from_hex_string(id);
     352       29209 :                         auto acc = std::dynamic_pointer_cast<JamiAccount>(account);
     353       29212 :                         if (acc and acc->isMessageTreated(intid))
     354        6560 :                             return PJ_FALSE;
     355       29213 :                     } catch (...) {
     356           0 :                     }
     357             :                 }
     358       22651 :                 account->onTextMessage(id, peerNumber, std::string(transport->deviceId()), payloads);
     359       29212 :             }
     360       22654 :             return PJ_FALSE;
     361       29213 :         }
     362             : 
     363           0 :         try_respond_stateless(endpt_, rdata, PJSIP_SC_OK, NULL, NULL, NULL);
     364             : 
     365           0 :         return PJ_FALSE;
     366          98 :     } else if (method->id == PJSIP_OPTIONS_METHOD) {
     367           3 :         return handleIncomingOptions(rdata);
     368          95 :     } else if (method->id != PJSIP_INVITE_METHOD && method->id != PJSIP_ACK_METHOD) {
     369           0 :         try_respond_stateless(endpt_, rdata, PJSIP_SC_METHOD_NOT_ALLOWED, NULL, NULL, NULL);
     370           0 :         return PJ_FALSE;
     371             :     }
     372             : 
     373          95 :     if (method->id == PJSIP_INVITE_METHOD) {
     374             :         // Log headers of received INVITE
     375          95 :         JAMI_INFO("Received a SIP INVITE request");
     376          95 :         sip_utils::logMessageHeaders(&rdata->msg_info.msg->hdr);
     377             :     }
     378             : 
     379          95 :     pjmedia_sdp_session* r_sdp {nullptr};
     380          95 :     if (body) {
     381          95 :         if (pjmedia_sdp_parse(rdata->tp_info.pool, (char*) body->data, body->len, &r_sdp)
     382          95 :             != PJ_SUCCESS) {
     383           0 :             JAMI_WARN("Failed to parse the SDP in offer");
     384           0 :             r_sdp = nullptr;
     385             :         }
     386             :     }
     387             : 
     388          95 :     if (not account->hasActiveCodec(MEDIA_AUDIO)) {
     389           0 :         try_respond_stateless(endpt_, rdata, PJSIP_SC_NOT_ACCEPTABLE_HERE, NULL, NULL, NULL);
     390           0 :         return PJ_FALSE;
     391             :     }
     392             : 
     393             :     // Verify that we can handle the request
     394          95 :     unsigned options = 0;
     395             : 
     396          95 :     if (pjsip_inv_verify_request(rdata, &options, NULL, NULL, endpt_, NULL) != PJ_SUCCESS) {
     397           0 :         JAMI_ERR("Couldn't verify INVITE request in secure dialog.");
     398           0 :         try_respond_stateless(endpt_, rdata, PJSIP_SC_METHOD_NOT_ALLOWED, NULL, NULL, NULL);
     399           0 :         return PJ_FALSE;
     400             :     }
     401             : 
     402             :     // Build the initial media using the remote offer.
     403          95 :     auto localMediaList = Sdp::getMediaAttributeListFromSdp(r_sdp);
     404             : 
     405             :     // To enable video, it must be enabled in the remote and locally (i.e. in the account)
     406         264 :     for (auto& media : localMediaList) {
     407         169 :         if (media.type_ == MediaType::MEDIA_VIDEO) {
     408          73 :             media.enabled_ &= account->isVideoEnabled();
     409             :         }
     410             :     }
     411             : 
     412         285 :     auto call = account->newIncomingCall(std::string(remote_user),
     413         190 :                                          MediaAttribute::mediaAttributesToMediaMaps(localMediaList),
     414         190 :                                          transport);
     415          95 :     if (!call) {
     416           0 :         return PJ_FALSE;
     417             :     }
     418             : 
     419          95 :     call->setPeerUaVersion(sip_utils::getPeerUserAgent(rdata));
     420             :     // The username can be used to join specific calls in conversations
     421          95 :     call->toUsername(std::string(toUsername));
     422             : 
     423             :     // FIXME : for now, use the same address family as the SIP transport
     424          95 :     auto family = pjsip_transport_type_get_af(
     425          95 :         pjsip_transport_get_type_from_flag(transport->get()->flag));
     426             : 
     427          95 :     dhtnet::IpAddr addrSdp;
     428          95 :     if (account->getUPnPActive()) {
     429             :         /* use UPnP addr, or published addr if its set */
     430           0 :         addrSdp = account->getPublishedSameasLocal() ? account->getUPnPIpAddress()
     431           0 :                                                      : account->getPublishedIpAddress();
     432             :     } else {
     433         190 :         addrSdp = account->isStunEnabled() or (not account->getPublishedSameasLocal())
     434          95 :                       ? account->getPublishedIpAddress()
     435          95 :                       : dhtnet::ip_utils::getInterfaceAddr(account->getLocalInterface(), family);
     436             :     }
     437             : 
     438             :     /* fallback on local address */
     439          95 :     if (not addrSdp)
     440           0 :         addrSdp = dhtnet::ip_utils::getInterfaceAddr(account->getLocalInterface(), family);
     441             : 
     442             :     // Try to obtain display name from From: header first, fallback on Contact:
     443          95 :     auto peerDisplayName = sip_utils::parseDisplayName(rdata->msg_info.from);
     444          95 :     if (peerDisplayName.empty()) {
     445           0 :         if (auto hdr = static_cast<const pjsip_contact_hdr*>(
     446           0 :                 pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_CONTACT, nullptr))) {
     447           0 :             peerDisplayName = sip_utils::parseDisplayName(hdr);
     448             :         }
     449             :     }
     450             : 
     451          95 :     call->setPeerNumber(peerNumber);
     452          95 :     call->setPeerUri(account->getToUri(peerNumber));
     453          95 :     call->setPeerDisplayName(peerDisplayName);
     454          95 :     call->setState(Call::ConnectionState::PROGRESSING);
     455          95 :     call->getSDP().setPublishedIP(addrSdp);
     456          95 :     call->setPeerAllowMethods(sip_utils::getPeerAllowMethods(rdata));
     457             : 
     458             :     // Set the temporary media list. Might change when we receive
     459             :     // the accept from the client.
     460          95 :     if (r_sdp != nullptr) {
     461          95 :         call->getSDP().setReceivedOffer(r_sdp);
     462             :     }
     463             : 
     464          95 :     pjsip_dialog* dialog = nullptr;
     465          95 :     if (pjsip_dlg_create_uas_and_inc_lock(pjsip_ua_instance(), rdata, nullptr, &dialog)
     466          95 :         != PJ_SUCCESS) {
     467           0 :         JAMI_ERR("Could not create uas");
     468           0 :         call.reset();
     469           0 :         try_respond_stateless(endpt_,
     470             :                               rdata,
     471             :                               PJSIP_SC_INTERNAL_SERVER_ERROR,
     472             :                               nullptr,
     473             :                               nullptr,
     474             :                               nullptr);
     475           0 :         return PJ_FALSE;
     476             :     }
     477             : 
     478          95 :     pjsip_tpselector tp_sel = SIPVoIPLink::getTransportSelector(transport->get());
     479          95 :     if (!dialog or pjsip_dlg_set_transport(dialog, &tp_sel) != PJ_SUCCESS) {
     480           0 :         JAMI_ERR("Could not set transport for dialog");
     481           0 :         if (dialog)
     482           0 :             pjsip_dlg_dec_lock(dialog);
     483           0 :         return PJ_FALSE;
     484             :     }
     485             : 
     486          95 :     pjsip_inv_session* inv = nullptr;
     487             :     // Create UAS for the invite.
     488             :     // The SDP is not set here, it will be done when the call is
     489             :     // accepted and the media attributes of the answer are known.
     490          95 :     pjsip_inv_create_uas(dialog, rdata, NULL, PJSIP_INV_SUPPORT_ICE, &inv);
     491          95 :     if (!inv) {
     492           0 :         JAMI_ERR("Call invite is not initialized");
     493           0 :         pjsip_dlg_dec_lock(dialog);
     494           0 :         return PJ_FALSE;
     495             :     }
     496             : 
     497             :     // dialog is now owned by invite
     498          95 :     pjsip_dlg_dec_lock(dialog);
     499             : 
     500          95 :     inv->mod_data[mod_ua_.id] = call.get();
     501          95 :     call->setInviteSession(inv);
     502             : 
     503             :     // Check whether Replaces header is present in the request and process accordingly.
     504             :     pjsip_dialog* replaced_dlg;
     505             :     pjsip_tx_data* response;
     506             : 
     507          95 :     if (pjsip_replaces_verify_request(rdata, &replaced_dlg, PJ_FALSE, &response) != PJ_SUCCESS) {
     508           0 :         JAMI_ERR("Something wrong with Replaces request.");
     509           0 :         call.reset();
     510             : 
     511             :         // Something wrong with the Replaces header.
     512           0 :         if (response) {
     513             :             pjsip_response_addr res_addr;
     514           0 :             pjsip_get_response_addr(response->pool, rdata, &res_addr);
     515           0 :             pjsip_endpt_send_response(endpt_, &res_addr, response, NULL, NULL);
     516             :         } else {
     517           0 :             try_respond_stateless(endpt_, rdata, PJSIP_SC_INTERNAL_SERVER_ERROR, NULL, NULL, NULL);
     518             :         }
     519             : 
     520           0 :         return PJ_FALSE;
     521             :     }
     522             : 
     523             :     // Check if call has been transferred
     524          95 :     pjsip_tx_data* tdata = nullptr;
     525             : 
     526          95 :     if (pjsip_inv_initial_answer(call->inviteSession_.get(),
     527             :                                  rdata,
     528             :                                  PJSIP_SC_TRYING,
     529             :                                  NULL,
     530             :                                  NULL,
     531             :                                  &tdata)
     532          95 :         != PJ_SUCCESS) {
     533           0 :         JAMI_ERR("Could not create answer TRYING");
     534           0 :         return PJ_FALSE;
     535             :     }
     536             : 
     537             :     // Add user-agent header
     538          95 :     sip_utils::addUserAgentHeader(account->getUserAgentName(), tdata);
     539             : 
     540          95 :     if (pjsip_inv_send_msg(call->inviteSession_.get(), tdata) != PJ_SUCCESS) {
     541           0 :         JAMI_ERR("Could not send msg TRYING");
     542           0 :         return PJ_FALSE;
     543             :     }
     544             : 
     545          95 :     call->setState(Call::ConnectionState::TRYING);
     546             : 
     547          95 :     if (pjsip_inv_answer(call->inviteSession_.get(), PJSIP_SC_RINGING, NULL, NULL, &tdata)
     548          95 :         != PJ_SUCCESS) {
     549           0 :         JAMI_ERR("Could not create answer RINGING");
     550           0 :         return PJ_FALSE;
     551             :     }
     552             : 
     553          95 :     sip_utils::addContactHeader(call->getContactHeader(), tdata);
     554          95 :     if (pjsip_inv_send_msg(call->inviteSession_.get(), tdata) != PJ_SUCCESS) {
     555           0 :         JAMI_ERR("Could not send msg RINGING");
     556           0 :         return PJ_FALSE;
     557             :     }
     558             : 
     559          95 :     call->setState(Call::ConnectionState::RINGING);
     560             : 
     561          95 :     Manager::instance().incomingCall(account->getAccountID(), *call);
     562             : 
     563          95 :     if (replaced_dlg) {
     564             :         // Get the INVITE session associated with the replaced dialog.
     565           0 :         auto replaced_inv = pjsip_dlg_get_inv_session(replaced_dlg);
     566             : 
     567             :         // Disconnect the "replaced" INVITE session.
     568           0 :         if (pjsip_inv_end_session(replaced_inv, PJSIP_SC_GONE, nullptr, &tdata) == PJ_SUCCESS
     569           0 :             && tdata) {
     570           0 :             pjsip_inv_send_msg(replaced_inv, tdata);
     571             :         }
     572             : 
     573             :         // Close call at application level
     574           0 :         if (auto replacedCall = getCallFromInvite(replaced_inv))
     575           0 :             replacedCall->hangup(PJSIP_SC_OK);
     576             :     }
     577             : 
     578          95 :     return PJ_FALSE;
     579       29307 : }
     580             : 
     581             : static void
     582        2413 : tp_state_callback(pjsip_transport* tp,
     583             :                   pjsip_transport_state state,
     584             :                   const pjsip_transport_state_info* info)
     585             : {
     586        2413 :     if (auto& broker = Manager::instance().sipVoIPLink().sipTransportBroker)
     587        2413 :         broker->transportStateChanged(tp, state, info);
     588             :     else
     589           0 :         JAMI_ERR("SIPVoIPLink with invalid SipTransportBroker");
     590        2413 : }
     591             : 
     592             : /*************************************************************************************************/
     593             : 
     594             : pjsip_endpoint*
     595       58515 : SIPVoIPLink::getEndpoint()
     596             : {
     597       58515 :     return endpt_;
     598             : }
     599             : 
     600             : pjsip_module*
     601           0 : SIPVoIPLink::getMod()
     602             : {
     603           0 :     return &mod_ua_;
     604             : }
     605             : 
     606             : pj_pool_t*
     607           5 : SIPVoIPLink::getPool() noexcept
     608             : {
     609           5 :     return pool_.get();
     610             : }
     611             : 
     612             : pj_caching_pool*
     613        1667 : SIPVoIPLink::getCachingPool() noexcept
     614             : {
     615        1667 :     return &cp_;
     616             : }
     617             : 
     618          32 : SIPVoIPLink::SIPVoIPLink()
     619          32 :     : pool_(nullptr, pj_pool_release)
     620             : {
     621             : #define TRY(ret) \
     622             :     do { \
     623             :         if ((ret) != PJ_SUCCESS) \
     624             :             throw VoipLinkException(#ret " failed"); \
     625             :     } while (0)
     626             : 
     627          32 :     pj_caching_pool_init(&cp_, &pj_pool_factory_default_policy, 0);
     628          32 :     pool_.reset(pj_pool_create(&cp_.factory, PACKAGE, 64 * 1024, 4096, nullptr));
     629          32 :     if (!pool_)
     630           0 :         throw VoipLinkException("UserAgent: Could not initialize memory pool");
     631             : 
     632          32 :     TRY(pjsip_endpt_create(&cp_.factory, pj_gethostname()->ptr, &endpt_));
     633             : 
     634          32 :     auto ns = dhtnet::ip_utils::getLocalNameservers();
     635          32 :     if (not ns.empty()) {
     636          32 :         std::vector<pj_str_t> dns_nameservers(ns.size());
     637          32 :         std::vector<pj_uint16_t> dns_ports(ns.size());
     638          96 :         for (unsigned i = 0, n = ns.size(); i < n; i++) {
     639             :             char hbuf[NI_MAXHOST];
     640          64 :             if (auto ret = getnameinfo((sockaddr*) &ns[i],
     641          64 :                                        ns[i].getLength(),
     642             :                                        hbuf,
     643             :                                        sizeof(hbuf),
     644             :                                        nullptr,
     645             :                                        0,
     646             :                                        NI_NUMERICHOST)) {
     647           0 :                 JAMI_WARN("Error printing SIP nameserver: %s", gai_strerror(ret));
     648             :             } else {
     649          64 :                 JAMI_DBG("Using SIP nameserver: %s", hbuf);
     650          64 :                 pj_strdup2(pool_.get(), &dns_nameservers[i], hbuf);
     651          64 :                 dns_ports[i] = ns[i].getPort();
     652             :             }
     653             :         }
     654             :         pj_dns_resolver* resv;
     655          32 :         if (auto ret = pjsip_endpt_create_resolver(endpt_, &resv)) {
     656           0 :             JAMI_WARN("Error creating SIP DNS resolver: %s", sip_utils::sip_strerror(ret).c_str());
     657             :         } else {
     658          32 :             if (auto ret = pj_dns_resolver_set_ns(resv,
     659          32 :                                                   dns_nameservers.size(),
     660          32 :                                                   dns_nameservers.data(),
     661          32 :                                                   dns_ports.data())) {
     662           0 :                 JAMI_WARN("Error setting SIP DNS servers: %s", sip_utils::sip_strerror(ret).c_str());
     663             :             } else {
     664          32 :                 if (auto ret = pjsip_endpt_set_resolver(endpt_, resv)) {
     665           0 :                     JAMI_WARN("Error setting pjsip DNS resolver: %s",
     666             :                               sip_utils::sip_strerror(ret).c_str());
     667             :                 }
     668             :             }
     669             :         }
     670          32 :     }
     671             : 
     672          32 :     sipTransportBroker.reset(new SipTransportBroker(endpt_));
     673             : 
     674          32 :     auto status = pjsip_tpmgr_set_state_cb(pjsip_endpt_get_tpmgr(endpt_), tp_state_callback);
     675          32 :     if (status != PJ_SUCCESS)
     676           0 :         JAMI_ERR("Can't set transport callback: %s", sip_utils::sip_strerror(status).c_str());
     677             : 
     678          32 :     TRY(pjsip_tsx_layer_init_module(endpt_));
     679          32 :     TRY(pjsip_ua_init_module(endpt_, nullptr));
     680          32 :     TRY(pjsip_replaces_init_module(endpt_)); // See the Replaces specification in RFC 3891
     681          32 :     TRY(pjsip_100rel_init_module(endpt_));
     682             : 
     683             :     // Initialize and register ring module
     684          32 :     mod_ua_.name = sip_utils::CONST_PJ_STR(PACKAGE);
     685          32 :     mod_ua_.id = -1;
     686          32 :     mod_ua_.priority = PJSIP_MOD_PRIORITY_APPLICATION;
     687          32 :     mod_ua_.on_rx_request = &transaction_request_cb;
     688          32 :     mod_ua_.on_rx_response = &transaction_response_cb;
     689          32 :     TRY(pjsip_endpt_register_module(endpt_, &mod_ua_));
     690             : 
     691          32 :     TRY(pjsip_evsub_init_module(endpt_));
     692          32 :     TRY(pjsip_xfer_init_module(endpt_));
     693             : 
     694             :     // presence/publish management
     695          32 :     TRY(pjsip_pres_init_module(endpt_, pjsip_evsub_instance()));
     696          32 :     TRY(pjsip_endpt_register_module(endpt_, &PresSubServer::mod_presence_server));
     697             : 
     698             :     static const pjsip_inv_callback inv_cb = {
     699             :         invite_session_state_changed_cb,
     700             :         outgoing_request_forked_cb,
     701             :         transaction_state_changed_cb,
     702             :         nullptr /* on_rx_offer */,
     703             :         on_rx_offer2,
     704             :         reinvite_received_cb,
     705             :         sdp_create_offer_cb,
     706             :         sdp_media_update_cb,
     707             :         nullptr /* on_send_ack */,
     708             :         nullptr /* on_redirected */,
     709             :     };
     710          32 :     TRY(pjsip_inv_usage_init(endpt_, &inv_cb));
     711             : 
     712             :     static constexpr pj_str_t allowed[] = {
     713             :         CONST_PJ_STR(sip_utils::SIP_METHODS::INFO),
     714             :         CONST_PJ_STR(sip_utils::SIP_METHODS::OPTIONS),
     715             :         CONST_PJ_STR(sip_utils::SIP_METHODS::MESSAGE),
     716             :         CONST_PJ_STR(sip_utils::SIP_METHODS::PUBLISH),
     717             :     };
     718             : 
     719          32 :     pjsip_endpt_add_capability(endpt_,
     720             :                                &mod_ua_,
     721             :                                PJSIP_H_ALLOW,
     722             :                                nullptr,
     723             :                                PJ_ARRAY_SIZE(allowed),
     724             :                                allowed);
     725             : 
     726             :     static constexpr pj_str_t text_plain = CONST_PJ_STR("text/plain");
     727          32 :     pjsip_endpt_add_capability(endpt_, &mod_ua_, PJSIP_H_ACCEPT, nullptr, 1, &text_plain);
     728             : 
     729             :     static constexpr pj_str_t accepted = CONST_PJ_STR("application/sdp");
     730          32 :     pjsip_endpt_add_capability(endpt_, &mod_ua_, PJSIP_H_ACCEPT, nullptr, 1, &accepted);
     731             : 
     732             :     static constexpr pj_str_t iscomposing = CONST_PJ_STR("application/im-iscomposing+xml");
     733          32 :     pjsip_endpt_add_capability(endpt_, &mod_ua_, PJSIP_H_ACCEPT, nullptr, 1, &iscomposing);
     734             : 
     735          32 :     TRY(pjsip_replaces_init_module(endpt_));
     736             : #undef TRY
     737             : 
     738          64 :     sipThread_ = std::thread([this] {
     739       13924 :         while (running_)
     740       13892 :             handleEvents();
     741          32 :     });
     742             : 
     743          32 :     JAMI_DBG("SIPVoIPLink@%p", this);
     744          32 : }
     745             : 
     746          32 : SIPVoIPLink::~SIPVoIPLink() {}
     747             : 
     748             : void
     749          32 : SIPVoIPLink::shutdown()
     750             : {
     751          32 :     JAMI_DBG("Shutdown SIPVoIPLink@%p...", this);
     752             :     // Remaining calls should not happen as possible upper callbacks
     753             :     // may be called and another instance of SIPVoIPLink can be re-created!
     754             : 
     755          32 :     if (not Manager::instance().callFactory.empty(Call::LinkType::SIP))
     756           0 :         JAMI_ERR("%zu SIP calls remains!",
     757             :                  Manager::instance().callFactory.callCount(Call::LinkType::SIP));
     758             : 
     759          32 :     sipTransportBroker->shutdown();
     760          32 :     pjsip_tpmgr_set_state_cb(pjsip_endpt_get_tpmgr(endpt_), nullptr);
     761             : 
     762          32 :     running_ = false;
     763          32 :     sipThread_.join();
     764          32 :     pjsip_endpt_destroy(endpt_);
     765          32 :     pool_.reset();
     766          32 :     pj_caching_pool_destroy(&cp_);
     767          32 :     sipTransportBroker.reset();
     768             : 
     769          32 :     JAMI_DBG("SIPVoIPLink@%p is shutdown", this);
     770          32 : }
     771             : 
     772             : std::shared_ptr<SIPAccountBase>
     773          13 : SIPVoIPLink::guessAccount(std::string_view userName,
     774             :                           std::string_view server,
     775             :                           std::string_view fromUri) const
     776             : {
     777          13 :     JAMI_DBG("username = %.*s, server = %.*s, from = %.*s",
     778             :              (int) userName.size(),
     779             :              userName.data(),
     780             :              (int) server.size(),
     781             :              server.data(),
     782             :              (int) fromUri.size(),
     783             :              fromUri.data());
     784             :     // Try to find the account id from username and server name by full match
     785             : 
     786          13 :     std::shared_ptr<SIPAccountBase> result;
     787          13 :     std::shared_ptr<SIPAccountBase> IP2IPAccount;
     788          13 :     MatchRank best = MatchRank::NONE;
     789             : 
     790             :     // SIP accounts
     791          38 :     for (const auto& account : Manager::instance().getAllAccounts<SIPAccount>()) {
     792          29 :         const MatchRank match(account->matches(userName, server));
     793             : 
     794             :         // return right away if this is a full match
     795          29 :         if (match == MatchRank::FULL) {
     796           4 :             return account;
     797          25 :         } else if (match > best) {
     798           7 :             best = match;
     799           7 :             result = account;
     800          18 :         } else if (!IP2IPAccount && account->isIP2IP()) {
     801             :             // Allow IP2IP calls if an account exists for this type of calls
     802           9 :             IP2IPAccount = account;
     803             :         }
     804          13 :     }
     805             : 
     806           9 :     return result ? result : IP2IPAccount;
     807          13 : }
     808             : 
     809             : // Called from EventThread::run (not main thread)
     810             : void
     811       13892 : SIPVoIPLink::handleEvents()
     812             : {
     813       13892 :     const pj_time_val timeout = {1, 0};
     814       13892 :     if (auto ret = pjsip_endpt_handle_events(endpt_, &timeout))
     815           0 :         JAMI_ERR("pjsip_endpt_handle_events failed with error %s",
     816             :                  sip_utils::sip_strerror(ret).c_str());
     817       13892 : }
     818             : 
     819             : void
     820           0 : SIPVoIPLink::registerKeepAliveTimer(pj_timer_entry& timer, pj_time_val& delay)
     821             : {
     822           0 :     JAMI_DEBUG("Register new keep alive timer {:d} with delay {:d}", timer.id, delay.sec);
     823             : 
     824           0 :     if (timer.id == -1)
     825           0 :         JAMI_WARN("Timer already scheduled");
     826             : 
     827           0 :     switch (pjsip_endpt_schedule_timer(endpt_, &timer, &delay)) {
     828           0 :     case PJ_SUCCESS:
     829           0 :         break;
     830             : 
     831           0 :     default:
     832           0 :         JAMI_ERR("Could not schedule new timer in pjsip endpoint");
     833             : 
     834             :         /* fallthrough */
     835           0 :     case PJ_EINVAL:
     836           0 :         JAMI_ERR("Invalid timer or delay entry");
     837           0 :         break;
     838             : 
     839           0 :     case PJ_EINVALIDOP:
     840           0 :         JAMI_ERR("Invalid timer entry, maybe already scheduled");
     841           0 :         break;
     842             :     }
     843           0 : }
     844             : 
     845             : void
     846           0 : SIPVoIPLink::cancelKeepAliveTimer(pj_timer_entry& timer)
     847             : {
     848           0 :     pjsip_endpt_cancel_timer(endpt_, &timer);
     849           0 : }
     850             : 
     851             : ///////////////////////////////////////////////////////////////////////////////
     852             : // Private functions
     853             : ///////////////////////////////////////////////////////////////////////////////
     854             : 
     855             : static std::shared_ptr<SIPCall>
     856        5180 : getCallFromInvite(pjsip_inv_session* inv)
     857             : {
     858        5180 :     if (auto call_ptr = static_cast<SIPCall*>(inv->mod_data[mod_ua_.id]))
     859        4493 :         return std::static_pointer_cast<SIPCall>(call_ptr->shared_from_this());
     860         687 :     return nullptr;
     861             : }
     862             : 
     863             : static void
     864         914 : invite_session_state_changed_cb(pjsip_inv_session* inv, pjsip_event* ev)
     865             : {
     866         914 :     if (inv == nullptr or ev == nullptr) {
     867           0 :         throw VoipLinkException("unexpected null pointer");
     868             :     }
     869             : 
     870         914 :     auto call = getCallFromInvite(inv);
     871         914 :     if (not call)
     872          94 :         return;
     873             : 
     874         820 :     if (ev->type != PJSIP_EVENT_TSX_STATE and ev->type != PJSIP_EVENT_TX_MSG
     875          86 :         and ev->type != PJSIP_EVENT_RX_MSG) {
     876           0 :         JAMI_WARN("[call:%s] INVITE@%p state changed to %d (%s): unexpected event type %d",
     877             :                   call->getCallId().c_str(),
     878             :                   inv,
     879             :                   inv->state,
     880             :                   pjsip_inv_state_name(inv->state),
     881             :                   ev->type);
     882           0 :         return;
     883             :     }
     884             : 
     885         820 :     decltype(pjsip_transaction::status_code) status_code = 0;
     886             : 
     887         820 :     if (ev->type == PJSIP_EVENT_TSX_STATE) {
     888         648 :         const auto tsx = ev->body.tsx_state.tsx;
     889         648 :         status_code = tsx ? tsx->status_code : PJSIP_SC_NOT_FOUND;
     890         648 :         const pj_str_t* description = pjsip_get_status_text(status_code);
     891             : 
     892         648 :         JAMI_DBG("[call:%s] INVITE@%p state changed to %d (%s): cause=%d, tsx@%p status %d (%.*s)",
     893             :                  call->getCallId().c_str(),
     894             :                  inv,
     895             :                  inv->state,
     896             :                  pjsip_inv_state_name(inv->state),
     897             :                  inv->cause,
     898             :                  tsx,
     899             :                  status_code,
     900             :                  (int) description->slen,
     901             :                  description->ptr);
     902         172 :     } else if (ev->type == PJSIP_EVENT_TX_MSG) {
     903          86 :         JAMI_DBG("[call:%s] INVITE@%p state changed to %d (%s): cause=%d (TX_MSG)",
     904             :                  call->getCallId().c_str(),
     905             :                  inv,
     906             :                  inv->state,
     907             :                  pjsip_inv_state_name(inv->state),
     908             :                  inv->cause);
     909             :     }
     910         820 :     pjsip_rx_data* rdata {nullptr};
     911         820 :     if (ev->type == PJSIP_EVENT_RX_MSG) {
     912          86 :         rdata = ev->body.rx_msg.rdata;
     913         734 :     } else if (ev->type == PJSIP_EVENT_TSX_STATE and ev->body.tsx_state.type == PJSIP_EVENT_RX_MSG) {
     914         268 :         rdata = ev->body.tsx_state.src.rdata;
     915             :     }
     916         820 :     if (rdata != nullptr) {
     917         354 :         call->setPeerUaVersion(sip_utils::getPeerUserAgent(rdata));
     918         354 :         auto methods = sip_utils::getPeerAllowMethods(rdata);
     919         354 :         if (not methods.empty()) {
     920         183 :             call->setPeerAllowMethods(std::move(methods));
     921             :         }
     922         354 :     }
     923             : 
     924         820 :     switch (inv->state) {
     925         189 :     case PJSIP_INV_STATE_EARLY:
     926         189 :         if (status_code == PJSIP_SC_RINGING)
     927         189 :             call->onPeerRinging();
     928         189 :         break;
     929             : 
     930         172 :     case PJSIP_INV_STATE_CONFIRMED:
     931             :         // After we sent or received a ACK - The connection is established
     932         172 :         call->onAnswered();
     933         172 :         break;
     934             : 
     935          95 :     case PJSIP_INV_STATE_DISCONNECTED:
     936          95 :         switch (inv->cause) {
     937             :         // When a peer's device replies busy
     938           0 :         case PJSIP_SC_BUSY_HERE:
     939           0 :             call->onBusyHere();
     940           0 :             break;
     941             :         // When the peer manually refuse the call
     942           2 :         case PJSIP_SC_DECLINE:
     943             :         case PJSIP_SC_BUSY_EVERYWHERE:
     944           2 :             if (inv->role != PJSIP_ROLE_UAC)
     945           1 :                 break;
     946             :             // close call
     947           1 :             call->onClosed();
     948           1 :             break;
     949             :         // The call terminates normally - BYE / CANCEL
     950          91 :         case PJSIP_SC_OK:
     951             :         case PJSIP_SC_REQUEST_TERMINATED:
     952          91 :             call->onClosed();
     953          91 :             break;
     954             : 
     955             :         // Error/unhandled conditions
     956           2 :         default:
     957           2 :             call->onFailure(inv->cause);
     958           2 :             break;
     959             :         }
     960          95 :         break;
     961             : 
     962         364 :     default:
     963         364 :         break;
     964             :     }
     965         914 : }
     966             : 
     967             : static void
     968          29 : on_rx_offer2(pjsip_inv_session* inv, struct pjsip_inv_on_rx_offer_cb_param* param)
     969             : {
     970          29 :     if (not param or not param->offer) {
     971           0 :         JAMI_ERR("Invalid offer");
     972          29 :         return;
     973             :     }
     974             : 
     975          29 :     auto call = getCallFromInvite(inv);
     976          29 :     if (not call)
     977           0 :         return;
     978             : 
     979             :     // This callback is called whenever a new media offer is found in a
     980             :     // SIP message, typically in a re-invite and in a '200 OK' (as a
     981             :     // response to an empty invite).
     982             :     // Here we only handle the second case. The first case is handled
     983             :     // in reinvite_received_cb.
     984          29 :     if (inv->cause != PJSIP_SC_OK) {
     985             :         // Silently ignore if it's not a '200 OK'
     986          29 :         return;
     987             :     }
     988             : 
     989           0 :     if (auto call = getCallFromInvite(inv)) {
     990           0 :         if (auto const& account = call->getAccount().lock()) {
     991           0 :             call->onReceiveOfferIn200OK(param->offer);
     992           0 :         }
     993           0 :     }
     994          29 : }
     995             : 
     996             : static pj_status_t
     997          29 : reinvite_received_cb(pjsip_inv_session* inv, const pjmedia_sdp_session* offer, pjsip_rx_data* rdata)
     998             : {
     999          29 :     if (!offer)
    1000           0 :         return !PJ_SUCCESS;
    1001          29 :     if (auto call = getCallFromInvite(inv)) {
    1002          29 :         if (auto const& account = call->getAccount().lock()) {
    1003          29 :             return call->onReceiveReinvite(offer, rdata);
    1004          29 :         }
    1005          29 :     }
    1006             : 
    1007             :     // Return success if there is no matching call. The re-invite
    1008             :     // should be ignored.
    1009           0 :     return PJ_SUCCESS;
    1010             : }
    1011             : 
    1012             : static void
    1013           0 : sdp_create_offer_cb(pjsip_inv_session* inv, pjmedia_sdp_session** p_offer)
    1014             : {
    1015           0 :     auto call = getCallFromInvite(inv);
    1016           0 :     if (not call)
    1017           0 :         return;
    1018             : 
    1019           0 :     auto account = call->getSIPAccount();
    1020           0 :     if (not account) {
    1021           0 :         JAMI_ERR("No account detected");
    1022           0 :         return;
    1023             :     }
    1024             : 
    1025           0 :     if (account->isEmptyOffersEnabled()) {
    1026             :         // Skip if the client wants to send an empty offer.
    1027           0 :         JAMI_DBG("Client requested to send an empty offer (no SDP)");
    1028           0 :         return;
    1029             :     }
    1030             : 
    1031           0 :     auto family = pj_AF_INET();
    1032             :     // FIXME : for now, use the same address family as the SIP transport
    1033           0 :     if (auto dlg = inv->dlg) {
    1034           0 :         if (dlg->tp_sel.type == PJSIP_TPSELECTOR_TRANSPORT) {
    1035           0 :             if (auto tr = dlg->tp_sel.u.transport)
    1036           0 :                 family = tr->local_addr.addr.sa_family;
    1037           0 :         } else if (dlg->tp_sel.type == PJSIP_TPSELECTOR_TRANSPORT) {
    1038           0 :             if (auto tr = dlg->tp_sel.u.listener)
    1039           0 :                 family = tr->local_addr.addr.sa_family;
    1040             :         }
    1041             :     }
    1042           0 :     auto ifaceAddr = dhtnet::ip_utils::getInterfaceAddr(account->getLocalInterface(), family);
    1043             : 
    1044           0 :     dhtnet::IpAddr address;
    1045           0 :     if (account->getUPnPActive()) {
    1046             :         /* use UPnP addr, or published addr if its set */
    1047           0 :         address = account->getPublishedSameasLocal() ? account->getUPnPIpAddress()
    1048           0 :                                                      : account->getPublishedIpAddress();
    1049             :     } else {
    1050           0 :         address = account->getPublishedSameasLocal() ? ifaceAddr : account->getPublishedIpAddress();
    1051             :     }
    1052             : 
    1053             :     /* fallback on local address */
    1054           0 :     if (not address)
    1055           0 :         address = ifaceAddr;
    1056             : 
    1057           0 :     auto& sdp = call->getSDP();
    1058           0 :     sdp.setPublishedIP(address);
    1059             : 
    1060             :     // This list should be provided by the client. Kept for backward compatibility.
    1061           0 :     auto const& mediaList = call->getMediaAttributeList();
    1062           0 :     if (mediaList.empty()) {
    1063           0 :         throw VoipLinkException("Unexpected empty media attribute list");
    1064             :     }
    1065             : 
    1066           0 :     JAMI_DBG("Creating a SDP offer using the following media:");
    1067           0 :     for (auto const& media : mediaList) {
    1068           0 :         JAMI_DBG("[call %s] Media %s", call->getCallId().c_str(), media.toString(true).c_str());
    1069             :     }
    1070             : 
    1071           0 :     const bool created = sdp.createOffer(mediaList);
    1072             : 
    1073           0 :     if (created and p_offer != nullptr)
    1074           0 :         *p_offer = sdp.getLocalSdpSession();
    1075           0 : }
    1076             : 
    1077             : static const pjmedia_sdp_session*
    1078         230 : get_active_remote_sdp(pjsip_inv_session* inv)
    1079             : {
    1080         230 :     const pjmedia_sdp_session* sdp_session {};
    1081             : 
    1082         230 :     if (pjmedia_sdp_neg_get_active_remote(inv->neg, &sdp_session) != PJ_SUCCESS) {
    1083           0 :         JAMI_ERR("Active remote not present");
    1084           0 :         return nullptr;
    1085             :     }
    1086             : 
    1087         230 :     if (pjmedia_sdp_validate(sdp_session) != PJ_SUCCESS) {
    1088           0 :         JAMI_ERR("Invalid remote SDP session");
    1089           0 :         return nullptr;
    1090             :     }
    1091             : 
    1092         230 :     return sdp_session;
    1093             : }
    1094             : 
    1095             : static const pjmedia_sdp_session*
    1096         230 : get_active_local_sdp(pjsip_inv_session* inv)
    1097             : {
    1098         230 :     const pjmedia_sdp_session* sdp_session {};
    1099             : 
    1100         230 :     if (pjmedia_sdp_neg_get_active_local(inv->neg, &sdp_session) != PJ_SUCCESS) {
    1101           0 :         JAMI_ERR("Active local not present");
    1102           0 :         return nullptr;
    1103             :     }
    1104             : 
    1105         230 :     if (pjmedia_sdp_validate(sdp_session) != PJ_SUCCESS) {
    1106           0 :         JAMI_ERR("Invalid local SDP session");
    1107           0 :         return nullptr;
    1108             :     }
    1109             : 
    1110         230 :     return sdp_session;
    1111             : }
    1112             : 
    1113             : // This callback is called after SDP offer/answer session has completed.
    1114             : static void
    1115         231 : sdp_media_update_cb(pjsip_inv_session* inv, pj_status_t status)
    1116             : {
    1117         231 :     auto call = getCallFromInvite(inv);
    1118         231 :     if (not call)
    1119           0 :         return;
    1120             : 
    1121         231 :     JAMI_DBG("[call:%s] INVITE@%p media update: status %d", call->getCallId().c_str(), inv, status);
    1122             : 
    1123         231 :     if (status != PJ_SUCCESS) {
    1124           2 :         const int reason = inv->state != PJSIP_INV_STATE_NULL
    1125           1 :                                    and inv->state != PJSIP_INV_STATE_CONFIRMED
    1126           2 :                                ? PJSIP_SC_UNSUPPORTED_MEDIA_TYPE
    1127             :                                : 0;
    1128             : 
    1129           1 :         JAMI_WARN("[call:%s] SDP offer failed, reason %d", call->getCallId().c_str(), reason);
    1130             : 
    1131           1 :         call->hangup(reason);
    1132           1 :         return;
    1133             :     }
    1134             : 
    1135             :     // Fetch SDP data from request
    1136         230 :     const auto localSDP = get_active_local_sdp(inv);
    1137         230 :     const auto remoteSDP = get_active_remote_sdp(inv);
    1138             : 
    1139             :     // Update our SDP manager
    1140         230 :     auto& sdp = call->getSDP();
    1141         230 :     sdp.setActiveLocalSdpSession(localSDP);
    1142         230 :     if (localSDP != nullptr) {
    1143         230 :         Sdp::printSession(localSDP, "Local active session:", sdp.getSdpDirection());
    1144             :     }
    1145             : 
    1146         230 :     sdp.setActiveRemoteSdpSession(remoteSDP);
    1147         230 :     if (remoteSDP != nullptr) {
    1148         230 :         Sdp::printSession(remoteSDP, "Remote active session:", sdp.getSdpDirection());
    1149             :     }
    1150             : 
    1151         230 :     call->onMediaNegotiationComplete();
    1152         231 : }
    1153             : 
    1154             : static void
    1155           0 : outgoing_request_forked_cb(pjsip_inv_session* /*inv*/, pjsip_event* /*e*/)
    1156           0 : {}
    1157             : 
    1158             : static bool
    1159         225 : handleMediaControl(SIPCall& call, pjsip_msg_body* body)
    1160             : {
    1161             :     /*
    1162             :      * Incoming INFO request for media control.
    1163             :      */
    1164         225 :     constexpr pj_str_t STR_APPLICATION = CONST_PJ_STR("application");
    1165         225 :     constexpr pj_str_t STR_MEDIA_CONTROL_XML = CONST_PJ_STR("media_control+xml");
    1166             : 
    1167         225 :     if (body->len and pj_stricmp(&body->content_type.type, &STR_APPLICATION) == 0
    1168         450 :         and pj_stricmp(&body->content_type.subtype, &STR_MEDIA_CONTROL_XML) == 0) {
    1169         225 :         auto body_msg = std::string_view((char*) body->data, (size_t) body->len);
    1170             : 
    1171             :         /* Apply and answer the INFO request */
    1172             :         static constexpr auto PICT_FAST_UPDATE = "picture_fast_update"sv;
    1173             :         static constexpr auto STREAM_ID = "stream_id"sv;
    1174             :         static constexpr auto DEVICE_ORIENTATION = "device_orientation"sv;
    1175             :         static constexpr auto RECORDING_STATE = "recording_state"sv;
    1176             :         static constexpr auto MUTE_STATE = "mute_state"sv;
    1177             :         static constexpr auto VOICE_ACTIVITY = "voice_activity"sv;
    1178             : 
    1179         225 :         int streamIdx = -1;
    1180         225 :         if (body_msg.find(STREAM_ID) != std::string_view::npos) {
    1181             :             // Note: here we use the index of the RTP stream, not it's label!
    1182             :             // Indeed, both side will have different labels as they have different call ids
    1183         196 :             static const std::regex STREAMID_REGEX("<stream_id>([0-9]+)</stream_id>");
    1184         196 :             std::svmatch matched_pattern;
    1185         196 :             std::regex_search(body_msg, matched_pattern, STREAMID_REGEX);
    1186         196 :             if (matched_pattern.ready() && !matched_pattern.empty() && matched_pattern[1].matched) {
    1187             :                 try {
    1188         196 :                     streamIdx = std::stoi(matched_pattern[1]);
    1189           0 :                 } catch (const std::exception& e) {
    1190           0 :                     JAMI_WARN("Error parsing stream index: %s", e.what());
    1191           0 :                 }
    1192             :             }
    1193         196 :         }
    1194             : 
    1195         225 :         if (body_msg.find(PICT_FAST_UPDATE) != std::string_view::npos) {
    1196         148 :             call.sendKeyframe(streamIdx);
    1197         225 :             return true;
    1198          77 :         } else if (body_msg.find(DEVICE_ORIENTATION) != std::string_view::npos) {
    1199          48 :             static const std::regex ORIENTATION_REGEX("device_orientation=([-+]?[0-9]+)");
    1200             : 
    1201          48 :             std::svmatch matched_pattern;
    1202          48 :             std::regex_search(body_msg, matched_pattern, ORIENTATION_REGEX);
    1203             : 
    1204          48 :             if (matched_pattern.ready() && !matched_pattern.empty() && matched_pattern[1].matched) {
    1205             :                 try {
    1206          48 :                     int rotation = -std::stoi(matched_pattern[1]);
    1207          48 :                     while (rotation <= -180)
    1208           0 :                         rotation += 360;
    1209          48 :                     while (rotation > 180)
    1210           0 :                         rotation -= 360;
    1211          48 :                     JAMI_WARN("Rotate video %d deg.", rotation);
    1212             : #ifdef ENABLE_VIDEO
    1213          48 :                     call.setRotation(streamIdx, rotation);
    1214             : #endif
    1215           0 :                 } catch (const std::exception& e) {
    1216           0 :                     JAMI_WARN("Error parsing angle: %s", e.what());
    1217           0 :                 }
    1218          48 :                 return true;
    1219             :             }
    1220          77 :         } else if (body_msg.find(RECORDING_STATE) != std::string_view::npos) {
    1221          21 :             static const std::regex REC_REGEX("recording_state=([0-1])");
    1222          21 :             std::svmatch matched_pattern;
    1223          21 :             std::regex_search(body_msg, matched_pattern, REC_REGEX);
    1224             : 
    1225          21 :             if (matched_pattern.ready() && !matched_pattern.empty() && matched_pattern[1].matched) {
    1226             :                 try {
    1227          21 :                     bool state = std::stoi(matched_pattern[1]);
    1228          21 :                     call.peerRecording(state);
    1229           0 :                 } catch (const std::exception& e) {
    1230           0 :                     JAMI_WARN("Error parsing state remote recording: %s", e.what());
    1231           0 :                 }
    1232          21 :                 return true;
    1233             :             }
    1234          29 :         } else if (body_msg.find(MUTE_STATE) != std::string_view::npos) {
    1235           8 :             static const std::regex REC_REGEX("mute_state=([0-1])");
    1236           8 :             std::svmatch matched_pattern;
    1237           8 :             std::regex_search(body_msg, matched_pattern, REC_REGEX);
    1238             : 
    1239           8 :             if (matched_pattern.ready() && !matched_pattern.empty() && matched_pattern[1].matched) {
    1240             :                 try {
    1241           8 :                     bool state = std::stoi(matched_pattern[1]);
    1242           8 :                     call.peerMuted(state, streamIdx);
    1243           0 :                 } catch (const std::exception& e) {
    1244           0 :                     JAMI_WARN("Error parsing state remote mute: %s", e.what());
    1245           0 :                 }
    1246           8 :                 return true;
    1247             :             }
    1248           8 :         } else if (body_msg.find(VOICE_ACTIVITY) != std::string_view::npos) {
    1249           0 :             static const std::regex REC_REGEX("voice_activity=([0-1])");
    1250           0 :             std::svmatch matched_pattern;
    1251           0 :             std::regex_search(body_msg, matched_pattern, REC_REGEX);
    1252             : 
    1253           0 :             if (matched_pattern.ready() && !matched_pattern.empty() && matched_pattern[1].matched) {
    1254             :                 try {
    1255           0 :                     bool state = std::stoi(matched_pattern[1]);
    1256           0 :                     call.peerVoice(state);
    1257           0 :                 } catch (const std::exception& e) {
    1258           0 :                     JAMI_WARN("Error parsing state remote voice: %s", e.what());
    1259           0 :                 }
    1260           0 :                 return true;
    1261             :             }
    1262           0 :         }
    1263             :     }
    1264             : 
    1265           0 :     return false;
    1266             : }
    1267             : 
    1268             : /**
    1269             :  * Helper function to process refer function on call transfer
    1270             :  */
    1271             : static bool
    1272           2 : transferCall(SIPCall& call, const std::string& refer_to)
    1273             : {
    1274           2 :     const auto& callId = call.getCallId();
    1275           2 :     JAMI_WARN("[call:%s] Trying to transfer to %s", callId.c_str(), refer_to.c_str());
    1276             :     try {
    1277           4 :         Manager::instance().newOutgoingCall(refer_to,
    1278           4 :                                             call.getAccountId(),
    1279           4 :                                             MediaAttribute::mediaAttributesToMediaMaps(
    1280           4 :                                                 call.getMediaAttributeList()));
    1281           2 :         Manager::instance().hangupCall(call.getAccountId(), callId);
    1282           0 :     } catch (const std::exception& e) {
    1283           0 :         JAMI_ERR("[call:%s] SIP transfer failed: %s", callId.c_str(), e.what());
    1284           0 :         return false;
    1285           0 :     }
    1286           2 :     return true;
    1287             : }
    1288             : 
    1289             : static void
    1290         227 : replyToRequest(pjsip_inv_session* inv, pjsip_rx_data* rdata, int status_code)
    1291             : {
    1292         227 :     const auto ret = pjsip_dlg_respond(inv->dlg, rdata, status_code, nullptr, nullptr, nullptr);
    1293         227 :     if (ret != PJ_SUCCESS)
    1294           0 :         JAMI_WARN("SIP: failed to reply %d to request", status_code);
    1295         227 : }
    1296             : 
    1297             : static void
    1298           2 : onRequestRefer(pjsip_inv_session* inv, pjsip_rx_data* rdata, pjsip_msg* msg, SIPCall& call)
    1299             : {
    1300             :     static constexpr pj_str_t str_refer_to = CONST_PJ_STR("Refer-To");
    1301             : 
    1302           2 :     if (auto refer_to = static_cast<pjsip_generic_string_hdr*>(
    1303           2 :             pjsip_msg_find_hdr_by_name(msg, &str_refer_to, nullptr))) {
    1304             :         // RFC 3515, 2.4.2: reply bad request if no or too many refer-to header.
    1305           4 :         if (static_cast<void*>(refer_to->next) == static_cast<void*>(&msg->hdr)
    1306           2 :             or !pjsip_msg_find_hdr_by_name(msg, &str_refer_to, refer_to->next)) {
    1307           2 :             replyToRequest(inv, rdata, PJSIP_SC_ACCEPTED);
    1308           2 :             transferCall(call, std::string(refer_to->hvalue.ptr, refer_to->hvalue.slen));
    1309             : 
    1310             :             // RFC 3515, 2.4.4: we MUST handle the processing using NOTIFY msgs
    1311             :             // But your current design doesn't permit that
    1312           2 :             return;
    1313             :         } else
    1314           0 :             JAMI_ERR("[call:%s] REFER: too many Refer-To headers", call.getCallId().c_str());
    1315             :     } else
    1316           0 :         JAMI_ERR("[call:%s] REFER: no Refer-To header", call.getCallId().c_str());
    1317             : 
    1318           0 :     replyToRequest(inv, rdata, PJSIP_SC_BAD_REQUEST);
    1319             : }
    1320             : 
    1321             : static void
    1322         225 : onRequestInfo(pjsip_inv_session* inv, pjsip_rx_data* rdata, pjsip_msg* msg, SIPCall& call)
    1323             : {
    1324         225 :     if (!msg->body or handleMediaControl(call, msg->body))
    1325         225 :         replyToRequest(inv, rdata, PJSIP_SC_OK);
    1326         225 : }
    1327             : 
    1328             : static void
    1329           0 : onRequestNotify(pjsip_inv_session* /*inv*/, pjsip_rx_data* /*rdata*/, pjsip_msg* msg, SIPCall& call)
    1330             : {
    1331           0 :     if (!msg->body)
    1332           0 :         return;
    1333             : 
    1334           0 :     const std::string bodyText {static_cast<char*>(msg->body->data), msg->body->len};
    1335           0 :     JAMI_DBG("[call:%s] NOTIFY body start - %p\n%s\n[call:%s] NOTIFY body end - %p",
    1336             :              call.getCallId().c_str(),
    1337             :              msg->body,
    1338             :              bodyText.c_str(),
    1339             :              call.getCallId().c_str(),
    1340             :              msg->body);
    1341             : 
    1342             :     // TODO
    1343           0 : }
    1344             : 
    1345             : static void
    1346        3976 : transaction_state_changed_cb(pjsip_inv_session* inv, pjsip_transaction* tsx, pjsip_event* event)
    1347             : {
    1348        3976 :     auto call = getCallFromInvite(inv);
    1349        3976 :     if (not call)
    1350         593 :         return;
    1351             : 
    1352             : #ifdef DEBUG_SIP_REQUEST_MSG
    1353             :     processInviteResponseHelper(inv, event);
    1354             : #endif
    1355             : 
    1356             :     // We process here only incoming request message
    1357        3383 :     if (tsx->role != PJSIP_ROLE_UAS or tsx->state != PJSIP_TSX_STATE_TRYING
    1358         449 :         or event->body.tsx_state.type != PJSIP_EVENT_RX_MSG) {
    1359        2934 :         return;
    1360             :     }
    1361             : 
    1362         449 :     const auto rdata = event->body.tsx_state.src.rdata;
    1363         449 :     if (!rdata) {
    1364           0 :         JAMI_ERR("[INVITE:%p] SIP RX request without rx data", inv);
    1365           0 :         return;
    1366             :     }
    1367             : 
    1368         449 :     const auto msg = rdata->msg_info.msg;
    1369         449 :     if (msg->type != PJSIP_REQUEST_MSG) {
    1370           0 :         JAMI_ERR("[INVITE:%p] SIP RX request without msg", inv);
    1371           0 :         return;
    1372             :     }
    1373             : 
    1374             :     // Using method name to dispatch
    1375         449 :     auto methodName = sip_utils::as_view(msg->line.req.method.name);
    1376         449 :     JAMI_DBG("[INVITE:%p] RX SIP method %d (%.*s)",
    1377             :              inv,
    1378             :              msg->line.req.method.id,
    1379             :              (int) methodName.size(),
    1380             :              methodName.data());
    1381             : 
    1382             : #ifdef DEBUG_SIP_REQUEST_MSG
    1383             :     char msgbuf[1000];
    1384             :     pjsip_msg_print(msg, msgbuf, sizeof msgbuf);
    1385             :     JAMI_DBG("%s", msgbuf);
    1386             : #endif // DEBUG_SIP_MESSAGE
    1387             : 
    1388         449 :     if (methodName == sip_utils::SIP_METHODS::REFER)
    1389           2 :         onRequestRefer(inv, rdata, msg, *call);
    1390         447 :     else if (methodName == sip_utils::SIP_METHODS::INFO)
    1391         225 :         onRequestInfo(inv, rdata, msg, *call);
    1392         222 :     else if (methodName == sip_utils::SIP_METHODS::NOTIFY)
    1393           0 :         onRequestNotify(inv, rdata, msg, *call);
    1394         222 :     else if (methodName == sip_utils::SIP_METHODS::OPTIONS)
    1395           0 :         handleIncomingOptions(rdata);
    1396         222 :     else if (methodName == sip_utils::SIP_METHODS::MESSAGE) {
    1397         187 :         if (msg->body)
    1398         187 :             runOnMainThread([call, m = im::parseSipMessage(msg)]() mutable {
    1399         187 :                 call->onTextMessage(std::move(m));
    1400         181 :             });
    1401             :     }
    1402        3976 : }
    1403             : 
    1404             : #ifdef DEBUG_SIP_REQUEST_MSG
    1405             : static void
    1406             : processInviteResponseHelper(pjsip_inv_session* inv, pjsip_event* event)
    1407             : {
    1408             :     if (event->body.tsx_state.type != PJSIP_EVENT_RX_MSG)
    1409             :         return;
    1410             : 
    1411             :     const auto rdata = event->body.tsx_state.src.rdata;
    1412             :     if (rdata == nullptr or rdata->msg_info.msg == nullptr)
    1413             :         return;
    1414             : 
    1415             :     const auto msg = rdata->msg_info.msg;
    1416             :     if (msg->type != PJSIP_RESPONSE_MSG)
    1417             :         return;
    1418             : 
    1419             :     // Only handle the following responses
    1420             :     switch (msg->line.status.code) {
    1421             :     case PJSIP_SC_TRYING:
    1422             :     case PJSIP_SC_RINGING:
    1423             :     case PJSIP_SC_OK:
    1424             :         break;
    1425             :     default:
    1426             :         return;
    1427             :     }
    1428             : 
    1429             :     JAMI_INFO("[INVITE:%p] SIP RX response: reason %.*s, status code %i",
    1430             :               inv,
    1431             :               (int) msg->line.status.reason.slen,
    1432             :               msg->line.status.reason.ptr,
    1433             :               msg->line.status.code);
    1434             : 
    1435             :     sip_utils::logMessageHeaders(&msg->hdr);
    1436             : }
    1437             : #endif
    1438             : 
    1439             : int
    1440         595 : SIPVoIPLink::getModId()
    1441             : {
    1442         595 :     return mod_ua_.id;
    1443             : }
    1444             : 
    1445             : void
    1446           0 : SIPVoIPLink::createSDPOffer(pjsip_inv_session* inv)
    1447             : {
    1448           0 :     if (inv == nullptr) {
    1449           0 :         throw VoipLinkException("Invite session can not be null");
    1450             :     }
    1451           0 :     sdp_create_offer_cb(inv, nullptr);
    1452           0 : }
    1453             : 
    1454             : // Thread-safe DNS resolver callback mapping
    1455             : class SafeResolveCallbackMap
    1456             : {
    1457             : public:
    1458             :     using ResolveCallback = std::function<void(pj_status_t, const pjsip_server_addresses*)>;
    1459             : 
    1460           3 :     void registerCallback(uintptr_t key, ResolveCallback&& cb)
    1461             :     {
    1462           3 :         std::lock_guard lk(mutex_);
    1463           3 :         cbMap_.emplace(key, std::move(cb));
    1464           3 :     }
    1465             : 
    1466           3 :     void process(uintptr_t key, pj_status_t status, const pjsip_server_addresses* addr)
    1467             :     {
    1468           3 :         std::lock_guard lk(mutex_);
    1469           3 :         auto it = cbMap_.find(key);
    1470           3 :         if (it != cbMap_.end()) {
    1471           3 :             it->second(status, addr);
    1472           3 :             cbMap_.erase(it);
    1473             :         }
    1474           3 :     }
    1475             : 
    1476             : private:
    1477             :     std::mutex mutex_;
    1478             :     std::map<uintptr_t, ResolveCallback> cbMap_;
    1479             : };
    1480             : 
    1481             : static SafeResolveCallbackMap&
    1482           6 : getResolveCallbackMap()
    1483             : {
    1484           6 :     static SafeResolveCallbackMap map;
    1485           6 :     return map;
    1486             : }
    1487             : 
    1488             : static void
    1489           3 : resolver_callback(pj_status_t status, void* token, const struct pjsip_server_addresses* addr)
    1490             : {
    1491           3 :     getResolveCallbackMap().process((uintptr_t) token, status, addr);
    1492           3 : }
    1493             : 
    1494             : void
    1495           3 : SIPVoIPLink::resolveSrvName(const std::string& name,
    1496             :                             pjsip_transport_type_e type,
    1497             :                             SrvResolveCallback&& cb)
    1498             : {
    1499             :     // PJSIP limits hostname to be longer than PJ_MAX_HOSTNAME.
    1500             :     // But, resolver prefix the given name by a string like "_sip._udp."
    1501             :     // causing a check against PJ_MAX_HOSTNAME to be useless.
    1502             :     // It's not easy to pre-determinate as it's implementation dependent.
    1503             :     // So we just choose a security marge enough for most cases, preventing a crash later
    1504             :     // in the call of pjsip_endpt_resolve().
    1505           3 :     if (name.length() > (PJ_MAX_HOSTNAME - 12)) {
    1506           0 :         JAMI_ERR("Hostname is too long");
    1507           0 :         cb({});
    1508           0 :         return;
    1509             :     }
    1510             : 
    1511             :     // extract port if name is in form "server:port"
    1512             :     int port;
    1513             :     pj_ssize_t name_size;
    1514           3 :     const auto n = name.rfind(':');
    1515           3 :     if (n != std::string::npos) {
    1516           0 :         port = std::atoi(name.c_str() + n + 1);
    1517           0 :         name_size = n;
    1518             :     } else {
    1519           3 :         port = 0;
    1520           3 :         name_size = name.size();
    1521             :     }
    1522           3 :     JAMI_DBG("try to resolve '%s' (port: %u)", name.c_str(), port);
    1523             : 
    1524           3 :     pjsip_host_info host_info {
    1525             :         /*.flag = */ 0,
    1526             :         /*.type = */ type,
    1527           3 :         /*.addr = */ {{(char*) name.c_str(), name_size}, port},
    1528           3 :     };
    1529             : 
    1530           3 :     const auto token = std::hash<std::string>()(name + std::to_string(type));
    1531           6 :     getResolveCallbackMap().registerCallback(
    1532           3 :         token, [=, cb = std::move(cb)](pj_status_t s, const pjsip_server_addresses* r) {
    1533             :             try {
    1534           3 :                 if (s != PJ_SUCCESS || !r) {
    1535           0 :                     JAMI_WARN("Can't resolve \"%s\" using pjsip_endpt_resolve, trying getaddrinfo.",
    1536             :                               name.c_str());
    1537           0 :                     dht::ThreadPool::io().run([=, cb = std::move(cb)]() {
    1538           0 :                         auto ips = dhtnet::ip_utils::getAddrList(name.c_str());
    1539           0 :                         runOnMainThread(std::bind(cb, std::move(ips)));
    1540           0 :                     });
    1541           0 :                 } else {
    1542           3 :                     std::vector<dhtnet::IpAddr> ips;
    1543           3 :                     ips.reserve(r->count);
    1544           6 :                     for (unsigned i = 0; i < r->count; i++)
    1545           3 :                         ips.push_back(r->entry[i].addr);
    1546           3 :                     cb(ips);
    1547           3 :                 }
    1548           0 :             } catch (const std::exception& e) {
    1549           0 :                 JAMI_ERR("Error resolving address: %s", e.what());
    1550           0 :                 cb({});
    1551           0 :             }
    1552           3 :         });
    1553             : 
    1554           3 :     pjsip_endpt_resolve(endpt_, pool_.get(), &host_info, (void*) token, resolver_callback);
    1555             : }
    1556             : 
    1557             : #define RETURN_IF_NULL(A, ...) \
    1558             :     if ((A) == NULL) { \
    1559             :         JAMI_WARN(__VA_ARGS__); \
    1560             :         return; \
    1561             :     }
    1562             : 
    1563             : #define RETURN_FALSE_IF_NULL(A, ...) \
    1564             :     if ((A) == NULL) { \
    1565             :         JAMI_WARN(__VA_ARGS__); \
    1566             :         return false; \
    1567             :     }
    1568             : 
    1569             : void
    1570          25 : SIPVoIPLink::findLocalAddressFromTransport(pjsip_transport* transport,
    1571             :                                            pjsip_transport_type_e transportType,
    1572             :                                            const std::string& host,
    1573             :                                            std::string& addr,
    1574             :                                            pj_uint16_t& port) const
    1575             : {
    1576             :     // Initialize the sip port with the default SIP port
    1577          25 :     port = pjsip_transport_get_default_port_for_type(transportType);
    1578             : 
    1579             :     // Initialize the sip address with the hostname
    1580          25 :     addr = sip_utils::as_view(*pj_gethostname());
    1581             : 
    1582             :     // Update address and port with active transport
    1583          25 :     RETURN_IF_NULL(transport,
    1584             :                    "Transport is NULL in findLocalAddress, using local address %s :%d",
    1585             :                    addr.c_str(),
    1586             :                    port);
    1587             : 
    1588             :     // get the transport manager associated with the SIP enpoint
    1589          25 :     auto tpmgr = pjsip_endpt_get_tpmgr(endpt_);
    1590          25 :     RETURN_IF_NULL(tpmgr,
    1591             :                    "Transport manager is NULL in findLocalAddress, using local address %s :%d",
    1592             :                    addr.c_str(),
    1593             :                    port);
    1594             : 
    1595          25 :     const pj_str_t pjstring(CONST_PJ_STR(host));
    1596             : 
    1597          25 :     auto tp_sel = getTransportSelector(transport);
    1598          25 :     pjsip_tpmgr_fla2_param param
    1599          25 :         = {transportType, &tp_sel, pjstring, PJ_FALSE, {nullptr, 0}, 0, nullptr};
    1600          25 :     if (pjsip_tpmgr_find_local_addr2(tpmgr, pool_.get(), &param) != PJ_SUCCESS) {
    1601           0 :         JAMI_WARN("Could not retrieve local address and port from transport, using %s :%d",
    1602             :                   addr.c_str(),
    1603             :                   port);
    1604           0 :         return;
    1605             :     }
    1606             : 
    1607             :     // Update local address based on the transport type
    1608          25 :     addr = sip_utils::as_view(param.ret_addr);
    1609             : 
    1610             :     // Determine the local port based on transport information
    1611          25 :     port = param.ret_port;
    1612             : }
    1613             : 
    1614             : bool
    1615           0 : SIPVoIPLink::findLocalAddressFromSTUN(pjsip_transport* transport,
    1616             :                                       pj_str_t* stunServerName,
    1617             :                                       int stunPort,
    1618             :                                       std::string& addr,
    1619             :                                       pj_uint16_t& port) const
    1620             : {
    1621             :     // WARN: this code use pjstun_get_mapped_addr2 that works
    1622             :     // in IPv4 only.
    1623             :     // WARN: this function is blocking (network request).
    1624             : 
    1625             :     // Initialize the sip port with the default SIP port
    1626           0 :     port = sip_utils::DEFAULT_SIP_PORT;
    1627             : 
    1628             :     // Get Local IP address
    1629           0 :     auto localIp = dhtnet::ip_utils::getLocalAddr(pj_AF_INET());
    1630           0 :     if (not localIp) {
    1631           0 :         JAMI_WARN("Failed to find local IP");
    1632           0 :         return false;
    1633             :     }
    1634             : 
    1635           0 :     addr = localIp.toString();
    1636             : 
    1637             :     // Update address and port with active transport
    1638           0 :     RETURN_FALSE_IF_NULL(transport,
    1639             :                          "Transport is NULL in findLocalAddress, using local address %s:%u",
    1640             :                          addr.c_str(),
    1641             :                          port);
    1642             : 
    1643           0 :     JAMI_DBG("STUN mapping of '%s:%u'", addr.c_str(), port);
    1644             : 
    1645             :     pj_sockaddr_in mapped_addr;
    1646           0 :     pj_sock_t sipSocket = pjsip_udp_transport_get_socket(transport);
    1647           0 :     const pjstun_setting stunOpt
    1648           0 :         = {PJ_TRUE, localIp.getFamily(), *stunServerName, stunPort, *stunServerName, stunPort};
    1649           0 :     pj_status_t stunStatus = pjstun_get_mapped_addr2(&cp_.factory,
    1650             :                                                            &stunOpt,
    1651             :                                                            1,
    1652             :                                                            &sipSocket,
    1653             :                                                            &mapped_addr);
    1654             : 
    1655           0 :     switch (stunStatus) {
    1656           0 :     case PJLIB_UTIL_ESTUNNOTRESPOND:
    1657           0 :         JAMI_ERROR("No response from STUN server {:s}",
    1658             :                  sip_utils::as_view(*stunServerName));
    1659           0 :         return false;
    1660             : 
    1661           0 :     case PJLIB_UTIL_ESTUNSYMMETRIC:
    1662           0 :         JAMI_ERR("Different mapped addresses are returned by servers.");
    1663           0 :         return false;
    1664             : 
    1665           0 :     case PJ_SUCCESS:
    1666           0 :         port = pj_sockaddr_in_get_port(&mapped_addr);
    1667           0 :         addr = dhtnet::IpAddr((const sockaddr_in&) mapped_addr).toString(true);
    1668           0 :         JAMI_DEBUG("STUN server {:s} replied '{}'", sip_utils::as_view(*stunServerName), addr);
    1669           0 :         return true;
    1670             : 
    1671           0 :     default: // use given address, silent any not handled error
    1672           0 :         JAMI_WARNING("Error from STUN server {:s}, using source address",
    1673             :                   sip_utils::as_view(*stunServerName));
    1674           0 :         return false;
    1675             :     }
    1676             : }
    1677             : #undef RETURN_IF_NULL
    1678             : #undef RETURN_FALSE_IF_NULL
    1679             : } // namespace jami

Generated by: LCOV version 1.14