LCOV - code coverage report
Current view: top level - foo/src/sip - sipaccountbase.cpp (source / functions) Hit Total Coverage
Test: jami-coverage-filtered.info Lines: 109 164 66.5 %
Date: 2026-04-01 09:29:43 Functions: 19 25 76.0 %

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

Generated by: LCOV version 1.14