LCOV - code coverage report
Current view: top level - src/sip - sipvoiplink.cpp (source / functions) Coverage Total Hit
Test: jami-coverage-filtered.info Lines: 59.3 % 848 503
Test Date: 2026-11-09 10:41:14 Functions: 77.6 % 49 38

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

Generated by: LCOV version 2.0-1