LCOV - code coverage report
Current view: top level - src/sip - sipaccountbase.cpp (source / functions) Coverage Total Hit
Test: jami-coverage-filtered.info Lines: 65.9 % 164 108
Test Date: 2026-06-13 09:18:46 Functions: 71.4 % 35 25

            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/sipaccountbase.h"
      19              : #include "sip/sipvoiplink.h"
      20              : 
      21              : #include "account_schema.h"
      22              : #include "manager.h"
      23              : #include "client/jami_signal.h"
      24              : #include "fileutils.h"
      25              : #include "connectivity/sip_utils.h"
      26              : #include "connectivity/utf8_utils.h"
      27              : 
      28              : #ifdef ENABLE_PLUGIN
      29              : #include "plugin/jamipluginmanager.h"
      30              : #include "plugin/streamdata.h"
      31              : #endif
      32              : 
      33              : #include <dhtnet/ice_transport.h>
      34              : #include <dhtnet/ice_transport_factory.h>
      35              : 
      36              : #include <fmt/core.h>
      37              : #include <json/json.h>
      38              : #pragma GCC diagnostic push
      39              : #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
      40              : #include <yaml-cpp/yaml.h>
      41              : #pragma GCC diagnostic pop
      42              : 
      43              : #include <type_traits>
      44              : #include <ctime>
      45              : 
      46              : using namespace std::literals;
      47              : 
      48              : namespace jami {
      49              : 
      50          802 : SIPAccountBase::SIPAccountBase(const std::string& accountID)
      51              :     : Account(accountID)
      52          802 :     , messageEngine_(*this, (fileutils::get_cache_dir() / getAccountID() / "messages").string())
      53         3208 :     , link_(Manager::instance().sipVoIPLink())
      54          802 : {}
      55              : 
      56          802 : SIPAccountBase::~SIPAccountBase() noexcept {}
      57              : 
      58              : bool
      59          101 : SIPAccountBase::CreateClientDialogAndInvite(const pj_str_t* from,
      60              :                                             const pj_str_t* contact,
      61              :                                             const pj_str_t* to,
      62              :                                             const pj_str_t* target,
      63              :                                             const pjmedia_sdp_session* local_sdp,
      64              :                                             pjsip_dialog** dlg,
      65              :                                             pjsip_inv_session** inv)
      66              : {
      67          404 :     JAMI_LOG("Creating SIP dialog: \nFrom: {}\nContact: {}\nTo: {}",
      68              :              sip_utils::as_view(*from),
      69              :              sip_utils::as_view(*contact),
      70              :              sip_utils::as_view(*to));
      71              : 
      72          101 :     if (target) {
      73          364 :         JAMI_LOG("Target: {}", sip_utils::as_view(*target));
      74              :     } else {
      75           40 :         JAMI_LOG("No target provided, using 'to' as target");
      76              :     }
      77              : 
      78          101 :     auto status = pjsip_dlg_create_uac(pjsip_ua_instance(), from, contact, to, target, dlg);
      79          101 :     if (status != PJ_SUCCESS) {
      80            0 :         JAMI_ERROR("Unable to create SIP dialogs for user agent client when calling {} {}",
      81              :                    sip_utils::as_view(*to),
      82              :                    status);
      83            0 :         return false;
      84              :     }
      85              : 
      86          101 :     auto* dialog = *dlg;
      87              : 
      88              :     {
      89              :         // lock dialog until invite session creation; this one will own the dialog after
      90          101 :         sip_utils::PJDialogLock dlg_lock {dialog};
      91              : 
      92              :         // Append "Subject: Phone Call" header
      93          101 :         constexpr auto subj_hdr_name = sip_utils::CONST_PJ_STR("Subject");
      94              :         auto* subj_hdr = reinterpret_cast<pjsip_hdr*>(
      95          101 :             pjsip_parse_hdr(dialog->pool, &subj_hdr_name, const_cast<char*>("Phone call"), 10, nullptr));
      96          101 :         pj_list_push_back(&dialog->inv_hdr, subj_hdr);
      97              : 
      98          101 :         if (pjsip_inv_create_uac(dialog, local_sdp, 0, inv) != PJ_SUCCESS) {
      99            0 :             JAMI_ERROR("Unable to create invite session for user agent client");
     100            0 :             return false;
     101              :         }
     102          101 :     }
     103              : 
     104          101 :     return true;
     105              : }
     106              : 
     107              : void
     108          797 : SIPAccountBase::flush()
     109              : {
     110              :     // Class base method
     111          797 :     Account::flush();
     112          797 :     dhtnet::fileutils::remove(fileutils::get_cache_dir() / getAccountID() / "messages");
     113          797 : }
     114              : 
     115              : void
     116          817 : SIPAccountBase::loadConfig()
     117              : {
     118          817 :     Account::loadConfig();
     119          817 :     const auto& conf = config();
     120          817 :     dhtnet::IpAddr publishedIp {conf.publishedIp};
     121          817 :     if (not conf.publishedSameasLocal and publishedIp)
     122            0 :         setPublishedAddress(publishedIp);
     123          817 :     dhtnet::TurnTransportParams turnParams;
     124          817 :     turnParams.domain = conf.turnServer;
     125          817 :     turnParams.username = conf.turnServerUserName;
     126          817 :     turnParams.password = conf.turnServerPwd;
     127          817 :     turnParams.realm = conf.turnServerRealm;
     128          817 :     if (!turnCache_) {
     129          797 :         auto cachePath = fileutils::get_cache_dir() / getAccountID();
     130         1594 :         turnCache_ = std::make_shared<dhtnet::TurnCache>(getAccountID(),
     131         1594 :                                                          cachePath.string(),
     132         1594 :                                                          Manager::instance().ioContext(),
     133         1594 :                                                          Logger::dhtLogger(),
     134              :                                                          turnParams,
     135         1594 :                                                          conf.turnEnabled);
     136          797 :     }
     137          817 :     turnCache_->reconfigure(turnParams, conf.turnEnabled);
     138          817 : }
     139              : 
     140              : std::map<std::string, std::string>
     141         4658 : SIPAccountBase::getVolatileAccountDetails() const
     142              : {
     143         4658 :     auto a = Account::getVolatileAccountDetails();
     144              : 
     145              :     // replace value from Account for IP2IP
     146         4658 :     if (isIP2IP())
     147           92 :         a[Conf::CONFIG_ACCOUNT_REGISTRATION_STATUS] = "READY";
     148              : 
     149         4658 :     a.emplace(Conf::CONFIG_TRANSPORT_STATE_CODE, std::to_string(transportStatus_));
     150         4658 :     a.emplace(Conf::CONFIG_TRANSPORT_STATE_DESC, transportError_);
     151         4658 :     return a;
     152            0 : }
     153              : 
     154              : void
     155           59 : SIPAccountBase::setRegistrationState(RegistrationState state, int details_code, const std::string& details_str)
     156              : {
     157           59 :     if (state == RegistrationState::REGISTERED && registrationState_ != RegistrationState::REGISTERED)
     158           25 :         messageEngine_.load();
     159           34 :     else if (state != RegistrationState::REGISTERED && registrationState_ == RegistrationState::REGISTERED)
     160            1 :         messageEngine_.save();
     161           59 :     Account::setRegistrationState(state, details_code, details_str);
     162           59 : }
     163              : 
     164              : auto
     165         2376 : SIPAccountBase::getPortsReservation() noexcept -> decltype(getPortsReservation())
     166              : {
     167              :     // Note: static arrays are zero-initialized
     168              :     static std::remove_reference<decltype(getPortsReservation())>::type portsInUse;
     169         2376 :     return portsInUse;
     170              : }
     171              : 
     172              : uint16_t
     173            0 : SIPAccountBase::getRandomEvenPort(const std::pair<uint16_t, uint16_t>& range) const
     174              : {
     175            0 :     std::uniform_int_distribution<uint16_t> dist(range.first / 2, range.second / 2);
     176              :     uint16_t result;
     177            0 :     do {
     178            0 :         result = 2 * dist(rand);
     179            0 :     } while (getPortsReservation()[result / 2]);
     180            0 :     return result;
     181              : }
     182              : 
     183              : uint16_t
     184          792 : SIPAccountBase::acquireRandomEvenPort(const std::pair<uint16_t, uint16_t>& range) const
     185              : {
     186          792 :     std::uniform_int_distribution<uint16_t> dist(range.first / 2, range.second / 2);
     187              :     uint16_t result;
     188              : 
     189          792 :     do {
     190          792 :         result = 2 * dist(rand);
     191          792 :     } while (getPortsReservation()[result / 2]);
     192              : 
     193          792 :     getPortsReservation()[result / 2] = true;
     194          792 :     return result;
     195              : }
     196              : 
     197              : uint16_t
     198            0 : SIPAccountBase::acquirePort(uint16_t port)
     199              : {
     200            0 :     getPortsReservation()[port / 2] = true;
     201            0 :     return port;
     202              : }
     203              : 
     204              : void
     205          792 : SIPAccountBase::releasePort(uint16_t port) noexcept
     206              : {
     207          792 :     getPortsReservation()[port / 2] = false;
     208          792 : }
     209              : 
     210              : uint16_t
     211          396 : SIPAccountBase::generateAudioPort() const
     212              : {
     213          396 :     return acquireRandomEvenPort(config().audioPortRange);
     214              : }
     215              : 
     216              : #ifdef ENABLE_VIDEO
     217              : uint16_t
     218          396 : SIPAccountBase::generateVideoPort() const
     219              : {
     220          396 :     return acquireRandomEvenPort(config().videoPortRange);
     221              : }
     222              : #endif
     223              : 
     224              : dhtnet::IceTransportOptions
     225           17 : SIPAccountBase::getIceOptions() const
     226              : {
     227           17 :     dhtnet::IceTransportOptions opts;
     228           17 :     opts.upnpEnable = getUPnPActive();
     229           17 :     opts.upnpContext = upnpCtrl_ ? upnpCtrl_->upnpContext() : nullptr;
     230           17 :     opts.factory = Manager::instance().getIceTransportFactory();
     231              : 
     232           17 :     if (config().turnEnabled && turnCache_) {
     233            0 :         auto turnAddr = turnCache_->getResolvedTurn();
     234            0 :         if (turnAddr != std::nullopt) {
     235            0 :             opts.turnServers.emplace_back(dhtnet::TurnServerInfo()
     236            0 :                                               .setUri(turnAddr->toString(true))
     237            0 :                                               .setUsername(config().turnServerUserName)
     238            0 :                                               .setPassword(config().turnServerPwd)
     239            0 :                                               .setRealm(config().turnServerRealm));
     240              :         }
     241              :         // NOTE: first test with ipv6 turn was not concluant and resulted in multiple
     242              :         // co issues. So this needs some debug. for now just disable
     243              :         // if (cacheTurnV6_ && *cacheTurnV6_) {
     244              :         //    opts.turnServers.emplace_back(TurnServerInfo()
     245              :         //                                      .setUri(cacheTurnV6_->toString(true))
     246              :         //                                      .setUsername(turnServerUserName_)
     247              :         //                                      .setPassword(turnServerPwd_)
     248              :         //                                      .setRealm(turnServerRealm_));
     249              :         //}
     250              :     }
     251           17 :     return opts;
     252            0 : }
     253              : 
     254              : void
     255        15565 : SIPAccountBase::onTextMessage(const std::string& id,
     256              :                               const std::string& from,
     257              :                               const std::shared_ptr<dht::crypto::Certificate>& peerCert,
     258              :                               const std::map<std::string, std::string>& payloads)
     259              : {
     260        62268 :     JAMI_LOG("[Account {}] [peer {}] Text message received from {}, {:d} part(s)",
     261              :              accountID_,
     262              :              peerCert ? peerCert->getLongId().to_view() : ""sv,
     263              :              from,
     264              :              payloads.size());
     265        15571 :     for (const auto& m : payloads) {
     266        15571 :         if (!utf8_validate(m.first))
     267            0 :             return;
     268        15571 :         if (!utf8_validate(m.second)) {
     269            0 :             JAMI_WARNING("[Account {}] Dropping invalid message with MIME type {}", accountID_, m.first);
     270            0 :             return;
     271              :         }
     272        15571 :         if (handleMessage(peerCert, from, m))
     273        15570 :             return;
     274              :     }
     275              : 
     276              : #ifdef ENABLE_PLUGIN
     277            0 :     auto& pluginChatManager = Manager::instance().getJamiPluginManager().getChatServicesManager();
     278            0 :     if (pluginChatManager.hasHandlers()) {
     279            0 :         pluginChatManager.publishMessage(std::make_shared<JamiMessage>(accountID_, from, true, payloads, false));
     280              :     }
     281              : #endif
     282            0 :     emitSignal<libjami::ConfigurationSignal::IncomingAccountMessage>(accountID_, from, id, payloads);
     283              : 
     284            0 :     libjami::Message message;
     285            0 :     message.from = from;
     286            0 :     message.payloads = payloads;
     287            0 :     message.received = std::time(nullptr);
     288            0 :     std::lock_guard lck(mutexLastMessages_);
     289            0 :     lastMessages_.emplace_back(std::move(message));
     290            0 :     while (lastMessages_.size() > MAX_WAITING_MESSAGES_SIZE) {
     291            0 :         lastMessages_.pop_front();
     292              :     }
     293            0 : }
     294              : 
     295              : dhtnet::IpAddr
     296            8 : SIPAccountBase::getPublishedIpAddress(uint16_t family) const
     297              : {
     298            8 :     if (family == AF_INET)
     299            0 :         return publishedIp_[0];
     300            8 :     if (family == AF_INET6)
     301            0 :         return publishedIp_[1];
     302              : 
     303            8 :     assert(family == AF_UNSPEC);
     304              : 
     305              :     // If family is not set, prefere IPv4 if available. It's more
     306              :     // likely to succeed behind NAT.
     307            8 :     if (publishedIp_[0])
     308            0 :         return publishedIp_[0];
     309            8 :     if (publishedIp_[1])
     310            0 :         return publishedIp_[1];
     311            8 :     return {};
     312              : }
     313              : 
     314              : void
     315            3 : SIPAccountBase::setPublishedAddress(const dhtnet::IpAddr& ip_addr)
     316              : {
     317            3 :     if (ip_addr.getFamily() == AF_INET) {
     318            3 :         publishedIp_[0] = ip_addr;
     319              :     } else {
     320            0 :         publishedIp_[1] = ip_addr;
     321              :     }
     322            3 : }
     323              : 
     324              : std::vector<libjami::Message>
     325            0 : SIPAccountBase::getLastMessages(const uint64_t& base_timestamp)
     326              : {
     327            0 :     std::lock_guard lck(mutexLastMessages_);
     328            0 :     auto it = lastMessages_.begin();
     329            0 :     size_t num = lastMessages_.size();
     330            0 :     while (it != lastMessages_.end() and it->received <= base_timestamp) {
     331            0 :         num--;
     332            0 :         ++it;
     333              :     }
     334            0 :     if (num == 0)
     335            0 :         return {};
     336            0 :     return {it, lastMessages_.end()};
     337            0 : }
     338              : 
     339              : std::vector<MediaAttribute>
     340           75 : SIPAccountBase::createDefaultMediaList(bool addVideo, bool hold)
     341              : {
     342           75 :     std::vector<MediaAttribute> mediaList;
     343           75 :     bool secure = isSrtpEnabled();
     344              :     // Add audio and DTMF events
     345           75 :     mediaList.emplace_back(
     346          150 :         MediaAttribute(MediaType::MEDIA_AUDIO, false, secure, true, "", sip_utils::DEFAULT_AUDIO_STREAMID, hold));
     347              : 
     348              : #ifdef ENABLE_VIDEO
     349              :     // Add video if allowed.
     350           75 :     if (isVideoEnabled() and addVideo) {
     351           75 :         mediaList.emplace_back(
     352          150 :             MediaAttribute(MediaType::MEDIA_VIDEO, false, secure, true, "", sip_utils::DEFAULT_VIDEO_STREAMID, hold));
     353              :     }
     354              : #endif
     355           75 :     return mediaList;
     356            0 : }
     357              : } // namespace jami
        

Generated by: LCOV version 2.0-1