LCOV - code coverage report
Current view: top level - src/sip - sipaccountbase.cpp (source / functions) Hit Total Coverage
Test: jami-coverage-filtered.info Lines: 121 166 72.9 %
Date: 2024-04-25 08:05:53 Functions: 17 21 81.0 %

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

Generated by: LCOV version 1.14