LCOV - code coverage report
Current view: top level - foo/src/sip - sipvoiplink.cpp (source / functions) Hit Total Coverage
Test: jami-coverage-filtered.info Lines: 452 773 58.5 %
Date: 2026-04-01 09:29:43 Functions: 43 68 63.2 %

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

Generated by: LCOV version 1.14