LCOV - code coverage report
Current view: top level - foo/src/sip - sipvoiplink.cpp (source / functions) Hit Total Coverage
Test: jami-coverage-filtered.info Lines: 453 772 58.7 %
Date: 2025-12-18 10:07:43 Functions: 43 66 65.2 %

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

Generated by: LCOV version 1.14