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: 2025-12-18 10:07:43 Functions: 19 25 76.0 %

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

Generated by: LCOV version 1.14