LCOV - code coverage report
Current view: top level - src/sip - sipvoiplink.cpp (source / functions) Coverage Total Hit
Test: jami-coverage-filtered.info Lines: 57.7 % 784 452
Test Date: 2026-06-13 09:18:46 Functions: 29.6 % 226 67

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

Generated by: LCOV version 2.0-1