LCOV - code coverage report
Current view: top level - foo/src/jamidht - jamiaccount.cpp (source / functions) Hit Total Coverage
Test: jami-coverage-filtered.info Lines: 1936 2685 72.1 %
Date: 2026-04-22 10:25:21 Functions: 345 547 63.1 %

          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             : #ifdef HAVE_CONFIG_H
      19             : #include "config.h"
      20             : #endif
      21             : 
      22             : #include "jamiaccount.h"
      23             : #include "presence_manager.h"
      24             : 
      25             : #include "logger.h"
      26             : 
      27             : #include "accountarchive.h"
      28             : #include "jami_contact.h"
      29             : #include "configkeys.h"
      30             : #include "contact_list.h"
      31             : #include "archive_account_manager.h"
      32             : #include "server_account_manager.h"
      33             : #include "jamidht/channeled_transport.h"
      34             : #include "conversation_channel_handler.h"
      35             : #include "sync_channel_handler.h"
      36             : #include "message_channel_handler.h"
      37             : #include "auth_channel_handler.h"
      38             : #include "transfer_channel_handler.h"
      39             : #include "swarm/swarm_channel_handler.h"
      40             : #include "jami/media_const.h"
      41             : 
      42             : #include "sip/sdp.h"
      43             : #include "sip/sipvoiplink.h"
      44             : #include "sip/sipcall.h"
      45             : #include "sip/siptransport.h"
      46             : #include "connectivity/sip_utils.h"
      47             : 
      48             : #include "uri.h"
      49             : 
      50             : #include "client/jami_signal.h"
      51             : #include "jami/call_const.h"
      52             : #include "jami/account_const.h"
      53             : 
      54             : #include "system_codec_container.h"
      55             : 
      56             : #include "account_schema.h"
      57             : #include "manager.h"
      58             : #include "connectivity/utf8_utils.h"
      59             : #include "connectivity/ip_utils.h"
      60             : 
      61             : #ifdef ENABLE_PLUGIN
      62             : #include "plugin/jamipluginmanager.h"
      63             : #include "plugin/chatservicesmanager.h"
      64             : #endif
      65             : 
      66             : #ifdef ENABLE_VIDEO
      67             : #include "libav_utils.h"
      68             : #endif
      69             : #include "fileutils.h"
      70             : #include "string_utils.h"
      71             : #include "archiver.h"
      72             : #include "data_transfer.h"
      73             : #include "json_utils.h"
      74             : 
      75             : #include "libdevcrypto/Common.h"
      76             : #include "base64.h"
      77             : #include "vcard.h"
      78             : #include "im/instant_messaging.h"
      79             : 
      80             : #include <dhtnet/ice_transport.h>
      81             : #include <dhtnet/ice_transport_factory.h>
      82             : #include <dhtnet/upnp/upnp_control.h>
      83             : #include <dhtnet/multiplexed_socket.h>
      84             : #include <dhtnet/certstore.h>
      85             : 
      86             : #include <opendht/thread_pool.h>
      87             : #include <opendht/peer_discovery.h>
      88             : #include <opendht/http.h>
      89             : 
      90             : #include <yaml-cpp/yaml.h>
      91             : #include <fmt/format.h>
      92             : 
      93             : #include <unistd.h>
      94             : 
      95             : #include <algorithm>
      96             : #include <array>
      97             : #include <cctype>
      98             : #include <charconv>
      99             : #include <cinttypes>
     100             : #include <cstdarg>
     101             : #include <fstream>
     102             : #include <initializer_list>
     103             : #include <memory>
     104             : #include <regex>
     105             : #include <sstream>
     106             : #include <string>
     107             : #include <system_error>
     108             : #include <utility>
     109             : 
     110             : using namespace std::placeholders;
     111             : 
     112             : namespace jami {
     113             : 
     114             : constexpr pj_str_t STR_MESSAGE_ID = jami::sip_utils::CONST_PJ_STR("Message-ID");
     115             : static constexpr const char MIME_TYPE_IMDN[] {"message/imdn+xml"};
     116             : static constexpr const char MIME_TYPE_PIDF[] {"application/pidf+xml"};
     117             : static constexpr const char MIME_TYPE_INVITE_JSON[] {"application/invite+json"};
     118             : static constexpr const char DEVICE_ID_PATH[] {"ring_device"};
     119             : static constexpr auto TREATED_PATH = "treatedImMessages"sv;
     120             : 
     121             : struct VCardMessageCtx
     122             : {
     123             :     std::shared_ptr<std::atomic_int> success;
     124             :     int total;
     125             :     std::string path;
     126             : };
     127             : 
     128             : namespace Migration {
     129             : 
     130             : enum class State { // Contains all the Migration states
     131             :     SUCCESS,
     132             :     INVALID
     133             : };
     134             : 
     135             : std::string
     136           4 : mapStateNumberToString(const State migrationState)
     137             : {
     138             : #define CASE_STATE(X) \
     139             :     case Migration::State::X: \
     140             :         return #X
     141             : 
     142           4 :     switch (migrationState) {
     143           0 :         CASE_STATE(INVALID);
     144           4 :         CASE_STATE(SUCCESS);
     145             :     }
     146           0 :     return {};
     147             : }
     148             : 
     149             : void
     150           4 : setState(const std::string& accountID, const State migrationState)
     151             : {
     152           4 :     emitSignal<libjami::ConfigurationSignal::MigrationEnded>(accountID, mapStateNumberToString(migrationState));
     153           4 : }
     154             : 
     155             : } // namespace Migration
     156             : 
     157             : struct JamiAccount::PendingCall
     158             : {
     159             :     std::chrono::steady_clock::time_point start;
     160             :     std::shared_ptr<IceTransport> ice_sp;
     161             :     std::shared_ptr<IceTransport> ice_tcp_sp;
     162             :     std::weak_ptr<SIPCall> call;
     163             :     std::future<size_t> listen_key;
     164             :     dht::InfoHash call_key;
     165             :     dht::InfoHash from;
     166             :     dht::InfoHash from_account;
     167             :     std::shared_ptr<dht::crypto::Certificate> from_cert;
     168             : };
     169             : 
     170             : struct JamiAccount::PendingMessage
     171             : {
     172             :     std::set<DeviceId> to;
     173             : };
     174             : 
     175             : struct AccountPeerInfo
     176             : {
     177             :     dht::InfoHash accountId;
     178             :     std::string displayName;
     179           0 :     MSGPACK_DEFINE(accountId, displayName)
     180             : };
     181             : 
     182             : struct JamiAccount::DiscoveredPeer
     183             : {
     184             :     std::string displayName;
     185             :     std::unique_ptr<asio::steady_timer> cleanupTimer;
     186             : };
     187             : 
     188             : /**
     189             :  * Track sending state for a single message to one or more devices.
     190             :  */
     191             : class JamiAccount::SendMessageContext
     192             : {
     193             : public:
     194             :     using OnComplete = std::function<void(bool, bool)>;
     195       15071 :     SendMessageContext(OnComplete onComplete)
     196       15071 :         : onComplete(std::move(onComplete))
     197       15071 :     {}
     198             :     /** Track new pending message for device */
     199       13463 :     bool add(const DeviceId& device)
     200             :     {
     201       13463 :         std::lock_guard lk(mtx);
     202       26926 :         return devices.insert(device).second;
     203       13463 :     }
     204             :     /** Call after all messages are sent */
     205       15072 :     void start()
     206             :     {
     207       15072 :         std::unique_lock lk(mtx);
     208       15072 :         started = true;
     209       15072 :         checkComplete(lk);
     210       15072 :     }
     211             :     /** Complete pending message for device */
     212       13246 :     bool complete(const DeviceId& device, bool success)
     213             :     {
     214       13246 :         std::unique_lock lk(mtx);
     215       13246 :         if (devices.erase(device) == 0)
     216           0 :             return false;
     217       13244 :         ++completeCount;
     218       13244 :         if (success)
     219       13240 :             ++successCount;
     220       13244 :         checkComplete(lk);
     221       13247 :         return true;
     222       13247 :     }
     223             :     bool empty() const
     224             :     {
     225             :         std::lock_guard lk(mtx);
     226             :         return devices.empty();
     227             :     }
     228        2929 :     bool pending(const DeviceId& device) const
     229             :     {
     230        2929 :         std::lock_guard lk(mtx);
     231        5858 :         return devices.find(device) != devices.end();
     232        2928 :     }
     233             : 
     234             : private:
     235             :     mutable std::mutex mtx;
     236             :     OnComplete onComplete;
     237             :     std::set<DeviceId> devices;
     238             :     unsigned completeCount = 0;
     239             :     unsigned successCount = 0;
     240             :     bool started {false};
     241             : 
     242       28317 :     void checkComplete(std::unique_lock<std::mutex>& lk)
     243             :     {
     244       28317 :         if (started && (devices.empty() || successCount)) {
     245       15074 :             if (onComplete) {
     246       15071 :                 auto cb = std::move(onComplete);
     247       15067 :                 auto success = successCount != 0;
     248       15067 :                 auto complete = completeCount != 0;
     249       15067 :                 onComplete = {};
     250       15069 :                 lk.unlock();
     251       15070 :                 cb(success, complete);
     252       15072 :             }
     253             :         }
     254       28317 :     }
     255             : };
     256             : 
     257             : static const constexpr std::string_view RING_URI_PREFIX = "ring:";
     258             : static const constexpr std::string_view JAMI_URI_PREFIX = "jami:";
     259             : static const auto PROXY_REGEX = std::regex("(https?://)?([\\w\\.\\-_\\~]+)(:(\\d+)|:\\[(.+)-(.+)\\])?");
     260             : static const constexpr std::string_view PEER_DISCOVERY_JAMI_SERVICE = "jami";
     261             : const constexpr auto PEER_DISCOVERY_EXPIRATION = std::chrono::minutes(1);
     262             : 
     263             : using ValueIdDist = std::uniform_int_distribution<dht::Value::Id>;
     264             : 
     265             : std::string_view
     266       42716 : stripPrefix(std::string_view toUrl)
     267             : {
     268       42716 :     auto dhtf = toUrl.find(RING_URI_PREFIX);
     269       42736 :     if (dhtf != std::string_view::npos) {
     270           0 :         dhtf += RING_URI_PREFIX.size();
     271             :     } else {
     272       42736 :         dhtf = toUrl.find(JAMI_URI_PREFIX);
     273       42745 :         if (dhtf != std::string_view::npos) {
     274           0 :             dhtf += JAMI_URI_PREFIX.size();
     275             :         } else {
     276       42745 :             dhtf = toUrl.find("sips:");
     277       42727 :             dhtf = (dhtf == std::string_view::npos) ? 0 : dhtf + 5;
     278             :         }
     279             :     }
     280       42727 :     while (dhtf < toUrl.length() && toUrl[dhtf] == '/')
     281           0 :         dhtf++;
     282       42737 :     return toUrl.substr(dhtf);
     283             : }
     284             : 
     285             : std::string_view
     286       42684 : parseJamiUri(std::string_view toUrl)
     287             : {
     288       42684 :     auto sufix = stripPrefix(toUrl);
     289       42721 :     if (sufix.length() < 40)
     290           0 :         throw std::invalid_argument("Not a valid Jami URI: " + toUrl);
     291             : 
     292       42728 :     const std::string_view toUri = sufix.substr(0, 40);
     293       42725 :     if (std::find_if_not(toUri.cbegin(), toUri.cend(), ::isxdigit) != toUri.cend())
     294           0 :         throw std::invalid_argument("Not a valid Jami URI: " + toUrl);
     295       42749 :     return toUri;
     296             : }
     297             : 
     298             : static constexpr std::string_view
     299        3446 : dhtStatusStr(dht::NodeStatus status)
     300             : {
     301             :     return status == dht::NodeStatus::Connected
     302        1740 :                ? "connected"sv
     303        3446 :                : (status == dht::NodeStatus::Connecting ? "connecting"sv : "disconnected"sv);
     304             : }
     305             : 
     306         684 : JamiAccount::JamiAccount(const std::string& accountId)
     307             :     : SIPAccountBase(accountId)
     308         684 :     , cachePath_(fileutils::get_cache_dir() / accountId)
     309         684 :     , dataPath_(cachePath_ / "values")
     310        1368 :     , logger_(Logger::dhtLogger(fmt::format("Account {}", accountId)))
     311         684 :     , certStore_ {std::make_shared<dhtnet::tls::CertificateStore>(idPath_, logger_)}
     312         684 :     , dht_(std::make_shared<dht::DhtRunner>())
     313         684 :     , treatedMessages_(cachePath_ / TREATED_PATH)
     314         684 :     , presenceManager_(std::make_unique<PresenceManager>(dht_))
     315         684 :     , connectionManager_ {}
     316        3420 :     , nonSwarmTransferManager_()
     317             : {
     318         684 :     presenceListenerToken_ = presenceManager_->addListener([this](const std::string& uri, bool online) {
     319         644 :         runOnMainThread([w = weak(), uri, online] {
     320         644 :             if (auto sthis = w.lock()) {
     321         644 :                 if (online) {
     322         582 :                     sthis->onTrackedBuddyOnline(uri);
     323         582 :                     sthis->messageEngine_.onPeerOnline(uri);
     324             :                 } else {
     325          62 :                     sthis->onTrackedBuddyOffline(uri);
     326             :                 }
     327         644 :             }
     328         644 :         });
     329         644 :     });
     330         684 : }
     331             : 
     332        1368 : JamiAccount::~JamiAccount() noexcept
     333             : {
     334         684 :     if (dht_)
     335         684 :         dht_->join();
     336         684 : }
     337             : 
     338             : void
     339         706 : JamiAccount::shutdownConnections()
     340             : {
     341         706 :     JAMI_DBG("[Account %s] Shutdown connections", getAccountID().c_str());
     342             : 
     343         706 :     decltype(gitServers_) gservers;
     344             :     {
     345         706 :         std::lock_guard lk(gitServersMtx_);
     346         706 :         gservers = std::move(gitServers_);
     347         706 :     }
     348        1109 :     for (auto& [_id, gs] : gservers)
     349         403 :         gs->stop();
     350             :     {
     351         706 :         std::lock_guard lk(connManagerMtx_);
     352             :         // Just move destruction on another thread.
     353        1412 :         dht::ThreadPool::io().run(
     354         706 :             [conMgr = std::make_shared<decltype(connectionManager_)>(std::move(connectionManager_))] {});
     355         706 :         connectionManager_.reset();
     356         706 :         channelHandlers_.clear();
     357         706 :     }
     358         706 :     if (convModule_) {
     359         601 :         convModule_->shutdownConnections();
     360             :     }
     361             : 
     362         706 :     std::lock_guard lk(sipConnsMtx_);
     363         706 :     sipConns_.clear();
     364         706 : }
     365             : 
     366             : void
     367         679 : JamiAccount::flush()
     368             : {
     369             :     // Class base method
     370         679 :     SIPAccountBase::flush();
     371             : 
     372         679 :     dhtnet::fileutils::removeAll(cachePath_);
     373         679 :     dhtnet::fileutils::removeAll(dataPath_);
     374         679 :     dhtnet::fileutils::removeAll(idPath_, true);
     375         679 : }
     376             : 
     377             : std::shared_ptr<SIPCall>
     378          33 : JamiAccount::newIncomingCall(const std::string& from,
     379             :                              const std::vector<libjami::MediaMap>& mediaList,
     380             :                              const std::shared_ptr<SipTransport>& sipTransp)
     381             : {
     382         132 :     JAMI_DEBUG("New incoming call from {:s} with {:d} media", from, mediaList.size());
     383             : 
     384          33 :     if (sipTransp) {
     385          66 :         auto call = Manager::instance().callFactory.newSipCall(shared(), Call::CallType::INCOMING, mediaList);
     386          33 :         call->setPeerUri(JAMI_URI_PREFIX + from);
     387          33 :         call->setPeerNumber(from);
     388             : 
     389          33 :         call->setSipTransport(sipTransp, getContactHeader(sipTransp));
     390             : 
     391          33 :         return call;
     392          33 :     }
     393             : 
     394           0 :     JAMI_ERR("newIncomingCall: unable to find matching call for %s", from.c_str());
     395           0 :     return nullptr;
     396             : }
     397             : 
     398             : std::shared_ptr<Call>
     399          47 : JamiAccount::newOutgoingCall(std::string_view toUrl, const std::vector<libjami::MediaMap>& mediaList)
     400             : {
     401          47 :     auto uri = Uri(toUrl);
     402          47 :     if (uri.scheme() == Uri::Scheme::SWARM || uri.scheme() == Uri::Scheme::RENDEZVOUS) {
     403             :         // NOTE: In this case newOutgoingCall can act as "resumeConference" and just attach the
     404             :         // host to the current hosted conference. So, no call will be returned in that case.
     405          22 :         return newSwarmOutgoingCallHelper(uri, mediaList);
     406             :     }
     407             : 
     408          25 :     auto& manager = Manager::instance();
     409          25 :     std::shared_ptr<SIPCall> call;
     410             : 
     411             :     // SIP allows sending empty invites, this use case is not used with Jami accounts.
     412          25 :     if (not mediaList.empty()) {
     413          16 :         call = manager.callFactory.newSipCall(shared(), Call::CallType::OUTGOING, mediaList);
     414             :     } else {
     415           9 :         JAMI_WARN("Media list is empty, setting a default list");
     416          18 :         call = manager.callFactory.newSipCall(shared(),
     417             :                                               Call::CallType::OUTGOING,
     418          18 :                                               MediaAttribute::mediaAttributesToMediaMaps(
     419          27 :                                                   createDefaultMediaList(isVideoEnabled())));
     420             :     }
     421             : 
     422          25 :     if (not call)
     423           0 :         return {};
     424             : 
     425          25 :     std::shared_lock lkCM(connManagerMtx_);
     426          25 :     if (!connectionManager_)
     427           0 :         return {};
     428             : 
     429          25 :     connectionManager_->getIceOptions([call, w = weak(), uri = std::move(uri)](auto&& opts) {
     430          25 :         if (call->isIceEnabled()) {
     431          25 :             if (not call->createIceMediaTransport(false)
     432          50 :                 or not call->initIceMediaTransport(true, std::forward<dhtnet::IceTransportOptions>(opts))) {
     433           0 :                 return;
     434             :             }
     435             :         }
     436          25 :         auto shared = w.lock();
     437          25 :         if (!shared)
     438           0 :             return;
     439          25 :         JAMI_DBG() << "New outgoing call with " << uri.toString();
     440          25 :         call->setPeerNumber(uri.authority());
     441          25 :         call->setPeerUri(uri.toString());
     442             : 
     443          25 :         shared->newOutgoingCallHelper(call, uri);
     444          25 :     });
     445             : 
     446          25 :     return call;
     447          47 : }
     448             : 
     449             : void
     450          25 : JamiAccount::newOutgoingCallHelper(const std::shared_ptr<SIPCall>& call, const Uri& uri)
     451             : {
     452         100 :     JAMI_LOG("[Account {}] Calling peer {}", getAccountID(), uri.authority());
     453             :     try {
     454          25 :         startOutgoingCall(call, uri.authority());
     455           0 :     } catch (const std::invalid_argument&) {
     456           0 :         auto suffix = stripPrefix(uri.toString());
     457           0 :         NameDirectory::lookupUri(suffix,
     458           0 :                                  config().nameServer,
     459           0 :                                  [wthis_ = weak(), call](const std::string& regName,
     460             :                                                          const std::string& address,
     461             :                                                          NameDirectory::Response response) {
     462             :                                      // we may run inside an unknown thread, but following code must
     463             :                                      // be called in main thread
     464           0 :                                      runOnMainThread([wthis_, regName, address, response, call]() {
     465           0 :                                          if (response != NameDirectory::Response::found) {
     466           0 :                                              call->onFailure(PJSIP_SC_NOT_FOUND);
     467           0 :                                              return;
     468             :                                          }
     469           0 :                                          if (auto sthis = wthis_.lock()) {
     470             :                                              try {
     471           0 :                                                  sthis->startOutgoingCall(call, address);
     472           0 :                                              } catch (const std::invalid_argument&) {
     473           0 :                                                  call->onFailure(PJSIP_SC_NOT_FOUND);
     474           0 :                                              }
     475             :                                          } else {
     476           0 :                                              call->onFailure(PJSIP_SC_SERVICE_UNAVAILABLE);
     477           0 :                                          }
     478             :                                      });
     479           0 :                                  });
     480           0 :     }
     481          25 : }
     482             : 
     483             : std::shared_ptr<SIPCall>
     484          22 : JamiAccount::newSwarmOutgoingCallHelper(const Uri& uri, const std::vector<libjami::MediaMap>& mediaList)
     485             : {
     486          88 :     JAMI_DEBUG("[Account {}] Calling conversation {}", getAccountID(), uri.authority());
     487             :     return convModule()
     488          66 :         ->call(uri.authority(), mediaList, [this, uri](const auto& accountUri, const auto& deviceId, const auto& call) {
     489          11 :             if (!call)
     490           0 :                 return;
     491          11 :             std::unique_lock lkSipConn(sipConnsMtx_);
     492          12 :             for (auto& [key, value] : sipConns_) {
     493           1 :                 if (key.first != accountUri || key.second != deviceId)
     494           1 :                     continue;
     495           0 :                 if (value.empty())
     496           0 :                     continue;
     497           0 :                 auto& sipConn = value.back();
     498             : 
     499           0 :                 if (!sipConn.channel) {
     500           0 :                     JAMI_WARN("A SIP transport exists without Channel, this is a bug. Please report");
     501           0 :                     continue;
     502             :                 }
     503             : 
     504           0 :                 auto transport = sipConn.transport;
     505           0 :                 if (!transport or !sipConn.channel)
     506           0 :                     continue;
     507           0 :                 call->setState(Call::ConnectionState::PROGRESSING);
     508             : 
     509           0 :                 auto remoted_address = sipConn.channel->getRemoteAddress();
     510             :                 try {
     511           0 :                     onConnectedOutgoingCall(call, uri.authority(), remoted_address);
     512           0 :                     return;
     513           0 :                 } catch (const VoipLinkException&) {
     514             :                     // In this case, the main scenario is that SIPStartCall failed because
     515             :                     // the ICE is dead and the TLS session didn't send any packet on that dead
     516             :                     // link (connectivity change, killed by the operating system, etc)
     517             :                     // Here, we don't need to do anything, the TLS will fail and will delete
     518             :                     // the cached transport
     519           0 :                     continue;
     520             :                 }
     521             :             }
     522          11 :             lkSipConn.unlock();
     523             :             {
     524          11 :                 std::lock_guard lkP(pendingCallsMutex_);
     525          11 :                 pendingCalls_[deviceId].emplace_back(call);
     526          11 :             }
     527             : 
     528             :             // Else, ask for a channel (for future calls/text messages)
     529          11 :             auto type = call->hasVideo() ? "videoCall" : "audioCall";
     530          44 :             JAMI_WARNING("[call {}] No channeled socket with this peer. Send request", call->getCallId());
     531          11 :             requestSIPConnection(accountUri, deviceId, type, true, call);
     532          33 :         });
     533             : }
     534             : 
     535             : void
     536          10 : JamiAccount::handleIncomingConversationCall(const std::string& callId, const std::string& destination)
     537             : {
     538          10 :     auto split = jami::split_string(destination, '/');
     539          10 :     if (split.size() != 4)
     540           0 :         return;
     541          10 :     auto conversationId = std::string(split[0]);
     542          10 :     auto accountUri = std::string(split[1]);
     543          10 :     auto deviceId = std::string(split[2]);
     544          10 :     auto confId = std::string(split[3]);
     545             : 
     546          10 :     if (getUsername() != accountUri || currentDeviceId() != deviceId)
     547           0 :         return;
     548             : 
     549             :     // Avoid concurrent checks in this part
     550          10 :     std::lock_guard lk(rdvMtx_);
     551          10 :     auto isNotHosting = !convModule()->isHosting(conversationId, confId);
     552          10 :     if (confId == "0") {
     553           1 :         auto currentCalls = convModule()->getActiveCalls(conversationId);
     554           1 :         if (!currentCalls.empty()) {
     555           0 :             confId = currentCalls[0]["id"];
     556           0 :             isNotHosting = false;
     557             :         } else {
     558           1 :             confId = callId;
     559           4 :             JAMI_DEBUG("No active call to join, create conference");
     560             :         }
     561           1 :     }
     562          10 :     auto preferences = convModule()->getConversationPreferences(conversationId);
     563          10 :     auto canHost = true;
     564             : #if defined(__ANDROID__) || defined(__APPLE__)
     565             :     // By default, mobile devices SHOULD NOT host conferences.
     566             :     canHost = false;
     567             : #endif
     568          10 :     auto itPref = preferences.find(ConversationPreferences::HOST_CONFERENCES);
     569          10 :     if (itPref != preferences.end()) {
     570           0 :         canHost = itPref->second == TRUE_STR;
     571             :     }
     572             : 
     573          10 :     auto call = getCall(callId);
     574          10 :     if (!call) {
     575           0 :         JAMI_ERROR("Call {} not found", callId);
     576           0 :         return;
     577             :     }
     578             : 
     579          10 :     if (isNotHosting && !canHost) {
     580           0 :         JAMI_DEBUG("Request for hosting a conference declined");
     581           0 :         Manager::instance().hangupCall(getAccountID(), callId);
     582           0 :         return;
     583             :     }
     584             :     // Due to the fact that in a conference, the host is not the one who
     585             :     // provides the initial sdp offer, the following block of code is responsible
     586             :     // for handling the medialist that the host will form his response with.
     587             :     // We always want the hosts response to be the same length as that of the
     588             :     // peer who is asking to join (providing the offer). A priori though the peer
     589             :     // doesn't know what active media streams the host will have so we deal with the
     590             :     // possible cases here.
     591          10 :     std::shared_ptr<Conference> conf;
     592          10 :     std::vector<libjami::MediaMap> currentMediaList;
     593          10 :     if (!isNotHosting) {
     594           7 :         conf = getConference(confId);
     595           7 :         if (!conf) {
     596           0 :             JAMI_ERROR("[conf:{}] Conference not found", confId);
     597           0 :             return;
     598             :         }
     599           7 :         auto hostMedias = conf->currentMediaList();
     600           7 :         auto sipCall = std::dynamic_pointer_cast<SIPCall>(call);
     601           7 :         if (hostMedias.empty()) {
     602           0 :             currentMediaList = MediaAttribute::mediaAttributesToMediaMaps(
     603           0 :                 createDefaultMediaList(call->hasVideo(), true));
     604           7 :         } else if (hostMedias.size() < sipCall->getRtpSessionList().size()) {
     605             :             // First case: host has less media streams than the other person is joining
     606             :             // with. We need to add video media to the host before accepting the offer
     607             :             // This can happen if we host an audio call and someone joins with video
     608           0 :             currentMediaList = hostMedias;
     609           0 :             currentMediaList.push_back(
     610             :                 {{libjami::Media::MediaAttributeKey::MEDIA_TYPE, libjami::Media::MediaAttributeValue::VIDEO},
     611             :                  {libjami::Media::MediaAttributeKey::ENABLED, TRUE_STR},
     612             :                  {libjami::Media::MediaAttributeKey::MUTED, TRUE_STR},
     613             :                  {libjami::Media::MediaAttributeKey::SOURCE, ""},
     614             :                  {libjami::Media::MediaAttributeKey::LABEL, "video_0"}});
     615             :         } else {
     616           7 :             bool hasVideo = false;
     617           7 :             if (sipCall) {
     618           7 :                 const auto rtpSessions = sipCall->getRtpSessionList();
     619           7 :                 hasVideo = std::any_of(rtpSessions.begin(), rtpSessions.end(), [](const auto& session) {
     620          13 :                     return session && session->getMediaType() == MediaType::MEDIA_VIDEO;
     621             :                 });
     622           7 :             }
     623             :             // The second case is that the host has the same or more media
     624             :             // streams than the person joining. In this case we match all their
     625             :             // medias to form our offer. They will then potentially join the call without seeing
     626             :             // seeing all of our medias. For now we deal with this by calling a
     627             :             // requestmediachange once they've joined.
     628          14 :             for (const auto& m : conf->currentMediaList()) {
     629             :                 // We only expect to have 1 audio stream, add it.
     630          13 :                 if (m.at(libjami::Media::MediaAttributeKey::MEDIA_TYPE) == libjami::Media::MediaAttributeValue::AUDIO) {
     631           7 :                     currentMediaList.emplace_back(m);
     632           6 :                 } else if (hasVideo
     633          12 :                            && m.at(libjami::Media::MediaAttributeKey::MEDIA_TYPE)
     634           6 :                                   == libjami::Media::MediaAttributeValue::VIDEO) {
     635           6 :                     currentMediaList.emplace_back(m);
     636           6 :                     break;
     637             :                 }
     638           7 :             }
     639             :         }
     640           7 :     }
     641          10 :     Manager::instance().acceptCall(*call, currentMediaList);
     642             : 
     643          10 :     if (isNotHosting) {
     644          12 :         JAMI_DEBUG("Creating conference for swarm {} with ID {}", conversationId, confId);
     645             :         // Create conference and host it.
     646           3 :         convModule()->hostConference(conversationId, confId, callId);
     647             :     } else {
     648          28 :         JAMI_DEBUG("Adding participant {} for swarm {} with ID {}", callId, conversationId, confId);
     649           7 :         Manager::instance().addAudio(*call);
     650           7 :         conf->addSubCall(callId);
     651           7 :         emitSignal<libjami::CallSignal::ConferenceChanged>(getAccountID(), conf->getConfId(), conf->getStateStr());
     652             :     }
     653          10 : }
     654             : 
     655             : std::shared_ptr<SIPCall>
     656          50 : JamiAccount::createSubCall(const std::shared_ptr<SIPCall>& mainCall)
     657             : {
     658          50 :     auto mediaList = MediaAttribute::mediaAttributesToMediaMaps(mainCall->getMediaAttributeList());
     659         100 :     return Manager::instance().callFactory.newSipCall(shared(), Call::CallType::OUTGOING, mediaList);
     660          50 : }
     661             : 
     662             : void
     663          25 : JamiAccount::startOutgoingCall(const std::shared_ptr<SIPCall>& call, const std::string& toUri)
     664             : {
     665          25 :     if (not accountManager_ or not dht_) {
     666           0 :         call->onFailure(PJSIP_SC_SERVICE_UNAVAILABLE);
     667           0 :         return;
     668             :     }
     669             : 
     670             :     // TODO: for now, we automatically trust all explicitly called peers
     671          25 :     setCertificateStatus(toUri, dhtnet::tls::TrustStore::PermissionStatus::ALLOWED);
     672             : 
     673          25 :     call->setState(Call::ConnectionState::TRYING);
     674          25 :     std::weak_ptr<SIPCall> wCall = call;
     675             : 
     676          25 :     accountManager_->lookupAddress(toUri,
     677          25 :                                    [wCall](const std::string& regName,
     678             :                                            const std::string& /*address*/,
     679             :                                            const NameDirectory::Response& response) {
     680          25 :                                        if (response == NameDirectory::Response::found)
     681           1 :                                            if (auto call = wCall.lock()) {
     682           1 :                                                call->setPeerRegisteredName(regName);
     683           1 :                                            }
     684          25 :                                    });
     685             : 
     686          25 :     dht::InfoHash peer_account(toUri);
     687          25 :     if (!peer_account) {
     688           0 :         throw std::invalid_argument("Invalid peer account: " + toUri);
     689             :     }
     690             : 
     691             :     // Call connected devices
     692          25 :     std::set<DeviceId> devices;
     693          25 :     std::unique_lock lkSipConn(sipConnsMtx_);
     694             :     // NOTE: dummyCall is a call used to avoid to mark the call as failed if the
     695             :     // cached connection is failing with ICE (close event still not detected).
     696          25 :     auto dummyCall = createSubCall(call);
     697             : 
     698          25 :     call->addSubCall(*dummyCall);
     699          25 :     dummyCall->setIceMedia(call->getIceMedia());
     700          25 :     auto sendRequest = [this, wCall, toUri, dummyCall = std::move(dummyCall)](const DeviceId& deviceId,
     701         120 :                                                                               bool eraseDummy) {
     702          49 :         if (eraseDummy) {
     703             :             // Mark the temp call as failed to stop the main call if necessary
     704          25 :             if (dummyCall)
     705          25 :                 dummyCall->onFailure(PJSIP_SC_TEMPORARILY_UNAVAILABLE);
     706          25 :             return;
     707             :         }
     708          24 :         auto call = wCall.lock();
     709          24 :         if (not call)
     710           0 :             return;
     711          24 :         auto state = call->getConnectionState();
     712          24 :         if (state != Call::ConnectionState::PROGRESSING and state != Call::ConnectionState::TRYING)
     713           0 :             return;
     714             : 
     715          24 :         auto dev_call = createSubCall(call);
     716          24 :         dev_call->setPeerNumber(call->getPeerNumber());
     717          24 :         dev_call->setState(Call::ConnectionState::TRYING);
     718          24 :         call->addStateListener([w = weak(), deviceId](Call::CallState, Call::ConnectionState state, int) {
     719          68 :             if (state != Call::ConnectionState::PROGRESSING and state != Call::ConnectionState::TRYING) {
     720          24 :                 if (auto shared = w.lock())
     721          24 :                     shared->callConnectionClosed(deviceId, true);
     722          24 :                 return false;
     723             :             }
     724          44 :             return true;
     725             :         });
     726          24 :         call->addSubCall(*dev_call);
     727          24 :         dev_call->setIceMedia(call->getIceMedia());
     728             :         {
     729          24 :             std::lock_guard lk(pendingCallsMutex_);
     730          24 :             pendingCalls_[deviceId].emplace_back(dev_call);
     731          24 :         }
     732             : 
     733          96 :         JAMI_WARNING("[call {}] No channeled socket with this peer. Send request", call->getCallId());
     734             :         // Else, ask for a channel (for future calls/text messages)
     735          24 :         const auto* type = call->hasVideo() ? "videoCall" : "audioCall";
     736          24 :         requestSIPConnection(toUri, deviceId, type, true, dev_call);
     737          74 :     };
     738             : 
     739          25 :     std::vector<std::shared_ptr<dhtnet::ChannelSocket>> channels;
     740          27 :     for (auto& [key, value] : sipConns_) {
     741           2 :         if (key.first != toUri)
     742           1 :             continue;
     743           1 :         if (value.empty())
     744           0 :             continue;
     745           1 :         auto& sipConn = value.back();
     746             : 
     747           1 :         if (!sipConn.channel) {
     748           0 :             JAMI_WARNING("A SIP transport exists without Channel, this is a bug. Please report");
     749           0 :             continue;
     750           0 :         }
     751             : 
     752           1 :         auto transport = sipConn.transport;
     753           1 :         auto remote_address = sipConn.channel->getRemoteAddress();
     754           1 :         if (!transport or !remote_address)
     755           0 :             continue;
     756             : 
     757           1 :         channels.emplace_back(sipConn.channel);
     758             : 
     759           4 :         JAMI_WARNING("[call {}] A channeled socket is detected with this peer.", call->getCallId());
     760             : 
     761           1 :         auto dev_call = createSubCall(call);
     762           1 :         dev_call->setPeerNumber(call->getPeerNumber());
     763           1 :         dev_call->setSipTransport(transport, getContactHeader(transport));
     764           1 :         call->addSubCall(*dev_call);
     765           1 :         dev_call->setIceMedia(call->getIceMedia());
     766             : 
     767             :         // Set the call in PROGRESSING State because the ICE session
     768             :         // is already ready. Note that this line should be after
     769             :         // addSubcall() to change the state of the main call
     770             :         // and avoid to get an active call in a TRYING state.
     771           1 :         dev_call->setState(Call::ConnectionState::PROGRESSING);
     772             : 
     773             :         {
     774           1 :             std::lock_guard lk(onConnectionClosedMtx_);
     775           1 :             onConnectionClosed_[key.second] = sendRequest;
     776           1 :         }
     777             : 
     778           1 :         call->addStateListener([w = weak(), deviceId = key.second](Call::CallState, Call::ConnectionState state, int) {
     779           3 :             if (state != Call::ConnectionState::PROGRESSING and state != Call::ConnectionState::TRYING) {
     780           1 :                 if (auto shared = w.lock())
     781           1 :                     shared->callConnectionClosed(deviceId, true);
     782           1 :                 return false;
     783             :             }
     784           2 :             return true;
     785             :         });
     786             : 
     787             :         try {
     788           1 :             onConnectedOutgoingCall(dev_call, toUri, remote_address);
     789           0 :         } catch (const VoipLinkException&) {
     790             :             // In this case, the main scenario is that SIPStartCall failed because
     791             :             // the ICE is dead and the TLS session didn't send any packet on that dead
     792             :             // link (connectivity change, killed by the os, etc)
     793             :             // Here, we don't need to do anything, the TLS will fail and will delete
     794             :             // the cached transport
     795           0 :             continue;
     796           0 :         }
     797           1 :         devices.emplace(key.second);
     798           1 :     }
     799             : 
     800          25 :     lkSipConn.unlock();
     801             :     // Note: Send beacon can destroy the socket (if storing last occurence of shared_ptr)
     802             :     // causing sipConn to be destroyed. So, do it while sipConns_ not locked.
     803          26 :     for (const auto& channel : channels)
     804           1 :         channel->sendBeacon();
     805             : 
     806             :     // Find listening devices for this account
     807          50 :     accountManager_->forEachDevice(
     808             :         peer_account,
     809          25 :         [this, devices = std::move(devices), sendRequest](const std::shared_ptr<dht::crypto::PublicKey>& dev) {
     810             :             // Test if already sent via a SIP transport
     811          24 :             auto deviceId = dev->getLongId();
     812          24 :             if (devices.find(deviceId) != devices.end())
     813           0 :                 return;
     814             :             {
     815          24 :                 std::lock_guard lk(onConnectionClosedMtx_);
     816          24 :                 onConnectionClosed_[deviceId] = sendRequest;
     817          24 :             }
     818          24 :             sendRequest(deviceId, false);
     819             :         },
     820          25 :         [wCall](bool ok) {
     821          25 :             if (not ok) {
     822           2 :                 if (auto call = wCall.lock()) {
     823           4 :                     JAMI_WARNING("[call:{}] No devices found", call->getCallId());
     824             :                     // Note: if a P2P connection exists, the call will be at least in CONNECTING
     825           1 :                     if (call->getConnectionState() == Call::ConnectionState::TRYING)
     826           1 :                         call->onFailure(PJSIP_SC_TEMPORARILY_UNAVAILABLE);
     827           2 :                 }
     828             :             }
     829          25 :         });
     830          25 : }
     831             : 
     832             : void
     833          33 : JamiAccount::onConnectedOutgoingCall(const std::shared_ptr<SIPCall>& call,
     834             :                                      const std::string& to_id,
     835             :                                      dhtnet::IpAddr target)
     836             : {
     837          33 :     if (!call)
     838           0 :         return;
     839         132 :     JAMI_LOG("[call:{}] Outgoing call connected to {}", call->getCallId(), to_id);
     840             : 
     841          33 :     const auto localAddress = dhtnet::ip_utils::getInterfaceAddr(getLocalInterface(), target.getFamily());
     842             : 
     843          33 :     dhtnet::IpAddr addrSdp = getPublishedSameasLocal() ? localAddress
     844          33 :                                                        : connectionManager_->getPublishedIpAddress(target.getFamily());
     845             : 
     846             :     // fallback on local address
     847          33 :     if (not addrSdp)
     848           0 :         addrSdp = localAddress;
     849             : 
     850             :     // Building the local SDP offer
     851          33 :     auto& sdp = call->getSDP();
     852             : 
     853          33 :     sdp.setPublishedIP(addrSdp);
     854             : 
     855          33 :     auto mediaAttrList = call->getMediaAttributeList();
     856          33 :     if (mediaAttrList.empty()) {
     857           0 :         JAMI_ERROR("[call:{}] No media. Abort!", call->getCallId());
     858           0 :         return;
     859             :     }
     860             : 
     861          33 :     if (not sdp.createOffer(mediaAttrList)) {
     862           0 :         JAMI_ERROR("[call:{}] Unable to send outgoing INVITE request for new call", call->getCallId());
     863           0 :         return;
     864             :     }
     865             : 
     866             :     // Note: pj_ice_strans_create can call onComplete in the same thread
     867             :     // This means that iceMutex_ in IceTransport can be locked when onInitDone is called
     868             :     // So, we need to run the call creation in the main thread
     869             :     // Also, we do not directly call SIPStartCall before receiving onInitDone, because
     870             :     // there is an inside waitForInitialization that can block the thread.
     871             :     // Note: avoid runMainThread as SIPStartCall use transportMutex
     872          33 :     dht::ThreadPool::io().run([w = weak(), call = std::move(call), target] {
     873          33 :         auto account = w.lock();
     874          33 :         if (not account)
     875           0 :             return;
     876             : 
     877          33 :         if (not account->SIPStartCall(*call, target)) {
     878           0 :             JAMI_ERROR("[call:{}] Unable to send outgoing INVITE request for new call", call->getCallId());
     879             :         }
     880          33 :     });
     881          33 : }
     882             : 
     883             : bool
     884          33 : JamiAccount::SIPStartCall(SIPCall& call, const dhtnet::IpAddr& target)
     885             : {
     886         132 :     JAMI_LOG("[call:{}] Start SIP call", call.getCallId());
     887             : 
     888          33 :     if (call.isIceEnabled())
     889          33 :         call.addLocalIceAttributes();
     890             : 
     891             :     std::string toUri(
     892          66 :         getToUri(call.getPeerNumber() + "@" + target.toString(true))); // expecting a fully well formed sip uri
     893             : 
     894          33 :     pj_str_t pjTo = sip_utils::CONST_PJ_STR(toUri);
     895             : 
     896             :     // Create the from header
     897          33 :     std::string from(getFromUri());
     898          33 :     pj_str_t pjFrom = sip_utils::CONST_PJ_STR(from);
     899             : 
     900          33 :     std::string targetStr = getToUri(target.toString(true));
     901          33 :     pj_str_t pjTarget = sip_utils::CONST_PJ_STR(targetStr);
     902             : 
     903          33 :     auto contact = call.getContactHeader();
     904          33 :     auto pjContact = sip_utils::CONST_PJ_STR(contact);
     905             : 
     906         132 :     JAMI_LOG("[call:{}] Contact header: {} / {} -> {} / {}", call.getCallId(), contact, from, toUri, targetStr);
     907             : 
     908          33 :     auto* local_sdp = call.getSDP().getLocalSdpSession();
     909          33 :     pjsip_dialog* dialog {nullptr};
     910          33 :     pjsip_inv_session* inv {nullptr};
     911          33 :     if (!CreateClientDialogAndInvite(&pjFrom, &pjContact, &pjTo, &pjTarget, local_sdp, &dialog, &inv))
     912           0 :         return false;
     913             : 
     914          33 :     inv->mod_data[link_.getModId()] = &call;
     915          33 :     call.setInviteSession(inv);
     916             : 
     917             :     pjsip_tx_data* tdata;
     918             : 
     919          33 :     if (pjsip_inv_invite(call.inviteSession_.get(), &tdata) != PJ_SUCCESS) {
     920           0 :         JAMI_ERROR("[call:{}] Unable to initialize invite", call.getCallId());
     921           0 :         return false;
     922             :     }
     923             : 
     924             :     pjsip_tpselector tp_sel;
     925          33 :     tp_sel.type = PJSIP_TPSELECTOR_TRANSPORT;
     926          33 :     if (!call.getTransport()) {
     927           0 :         JAMI_ERROR("[call:{}] Unable to get transport", call.getCallId());
     928           0 :         return false;
     929             :     }
     930          33 :     tp_sel.u.transport = call.getTransport()->get();
     931          33 :     if (pjsip_dlg_set_transport(dialog, &tp_sel) != PJ_SUCCESS) {
     932           0 :         JAMI_ERROR("[call:{}] Unable to associate transport for invite session dialog", call.getCallId());
     933           0 :         return false;
     934             :     }
     935             : 
     936         132 :     JAMI_LOG("[call:{}] Sending SIP invite", call.getCallId());
     937             : 
     938             :     // Add user-agent header
     939          33 :     sip_utils::addUserAgentHeader(getUserAgentName(), tdata);
     940             : 
     941          33 :     if (pjsip_inv_send_msg(call.inviteSession_.get(), tdata) != PJ_SUCCESS) {
     942           0 :         JAMI_ERROR("[call:{}] Unable to send invite message", call.getCallId());
     943           0 :         return false;
     944             :     }
     945             : 
     946          33 :     call.setState(Call::CallState::ACTIVE, Call::ConnectionState::PROGRESSING);
     947          33 :     return true;
     948          33 : }
     949             : 
     950             : void
     951        2281 : JamiAccount::saveConfig() const
     952             : {
     953             :     try {
     954        2281 :         YAML::Emitter accountOut;
     955        2281 :         config().serialize(accountOut);
     956        2281 :         auto accountConfig = config().path / "config.yml";
     957        2281 :         std::lock_guard lock(dhtnet::fileutils::getFileLock(accountConfig));
     958        2281 :         std::ofstream fout(accountConfig);
     959        2281 :         fout.write(accountOut.c_str(), static_cast<std::streamsize>(accountOut.size()));
     960        9124 :         JAMI_LOG("Saved account config to {}", accountConfig);
     961        2281 :     } catch (const std::exception& e) {
     962           0 :         JAMI_ERROR("Error saving account config: {}", e.what());
     963           0 :     }
     964        2281 : }
     965             : 
     966             : void
     967         699 : JamiAccount::loadConfig()
     968             : {
     969         699 :     SIPAccountBase::loadConfig();
     970         699 :     registeredName_ = config().registeredName;
     971         699 :     if (accountManager_)
     972          20 :         accountManager_->setAccountDeviceName(config().deviceName);
     973         699 :     if (connectionManager_) {
     974          16 :         if (auto c = connectionManager_->getConfig()) {
     975             :             // Update connectionManager's config
     976          16 :             c->upnpEnabled = config().upnpEnabled;
     977          16 :             c->turnEnabled = config().turnEnabled;
     978          16 :             c->turnServer = config().turnServer;
     979          16 :             c->turnServerUserName = config().turnServerUserName;
     980          16 :             c->turnServerPwd = config().turnServerPwd;
     981          16 :             c->turnServerRealm = config().turnServerRealm;
     982          16 :         }
     983             :     }
     984         699 :     if (config().proxyEnabled) {
     985             :         try {
     986           0 :             auto str = fileutils::loadCacheTextFile(cachePath_ / "dhtproxy", std::chrono::hours(24 * 14));
     987           0 :             Json::Value root;
     988           0 :             if (json::parse(str, root)) {
     989           0 :                 proxyServerCached_ = root[getProxyConfigKey()].asString();
     990             :             }
     991           0 :         } catch (const std::exception& e) {
     992           0 :             JAMI_LOG("[Account {}] Unable to load proxy URL from cache: {}", getAccountID(), e.what());
     993           0 :             proxyServerCached_.clear();
     994           0 :         }
     995             :     } else {
     996         699 :         proxyServerCached_.clear();
     997         699 :         std::error_code ec;
     998         699 :         std::filesystem::remove(cachePath_ / "dhtproxy", ec);
     999             :     }
    1000         699 :     if (not config().dhtProxyServerEnabled) {
    1001         699 :         dhtProxyServer_.reset();
    1002             :     }
    1003         699 :     auto credentials = consumeConfigCredentials();
    1004         699 :     loadAccount(credentials.archive_password_scheme, credentials.archive_password, credentials.archive_path);
    1005         699 : }
    1006             : 
    1007             : bool
    1008           7 : JamiAccount::changeArchivePassword(const std::string& password_old, const std::string& password_new)
    1009             : {
    1010             :     try {
    1011           7 :         if (!accountManager_->changePassword(password_old, password_new)) {
    1012           8 :             JAMI_ERROR("[Account {}] Unable to change archive password", getAccountID());
    1013           2 :             return false;
    1014             :         }
    1015          10 :         editConfig([&](JamiAccountConfig& config) { config.archiveHasPassword = not password_new.empty(); });
    1016           0 :     } catch (const std::exception& ex) {
    1017           0 :         JAMI_ERROR("[Account {}] Unable to change archive password: {}", getAccountID(), ex.what());
    1018           0 :         if (password_old.empty()) {
    1019           0 :             editConfig([&](JamiAccountConfig& config) { config.archiveHasPassword = true; });
    1020           0 :             emitSignal<libjami::ConfigurationSignal::AccountDetailsChanged>(getAccountID(), getAccountDetails());
    1021             :         }
    1022           0 :         return false;
    1023           0 :     }
    1024           5 :     if (password_old != password_new)
    1025           5 :         emitSignal<libjami::ConfigurationSignal::AccountDetailsChanged>(getAccountID(), getAccountDetails());
    1026           5 :     return true;
    1027             : }
    1028             : 
    1029             : bool
    1030           3 : JamiAccount::isPasswordValid(const std::string& password)
    1031             : {
    1032           3 :     return accountManager_ and accountManager_->isPasswordValid(password);
    1033             : }
    1034             : 
    1035             : std::vector<uint8_t>
    1036           0 : JamiAccount::getPasswordKey(const std::string& password)
    1037             : {
    1038           0 :     return accountManager_ ? accountManager_->getPasswordKey(password) : std::vector<uint8_t>();
    1039             : }
    1040             : 
    1041             : bool
    1042           7 : JamiAccount::provideAccountAuthentication(const std::string& credentialsFromUser, const std::string& scheme)
    1043             : {
    1044           7 :     if (auto manager = std::dynamic_pointer_cast<ArchiveAccountManager>(accountManager_)) {
    1045           7 :         return manager->provideAccountAuthentication(credentialsFromUser, scheme);
    1046           7 :     }
    1047           0 :     JAMI_ERR("[LinkDevice] Invalid AccountManager instance while providing current account "
    1048             :              "authentication.");
    1049           0 :     return false;
    1050             : }
    1051             : 
    1052             : int32_t
    1053           5 : JamiAccount::addDevice(const std::string& uriProvided)
    1054             : {
    1055          20 :     JAMI_LOG("[LinkDevice] JamiAccount::addDevice({}, {})", getAccountID(), uriProvided);
    1056           5 :     if (not accountManager_) {
    1057           0 :         JAMI_ERR("[LinkDevice] Invalid AccountManager instance while adding a device.");
    1058           0 :         return static_cast<int32_t>(AccountManager::AddDeviceError::GENERIC);
    1059             :     }
    1060           5 :     auto authHandler = channelHandlers_.find(Uri::Scheme::AUTH);
    1061           5 :     if (authHandler == channelHandlers_.end())
    1062           0 :         return static_cast<int32_t>(AccountManager::AddDeviceError::GENERIC);
    1063          10 :     return accountManager_->addDevice(uriProvided,
    1064           5 :                                       config().archiveHasPassword ? fileutils::ARCHIVE_AUTH_SCHEME_PASSWORD
    1065             :                                                                   : fileutils::ARCHIVE_AUTH_SCHEME_NONE,
    1066          10 :                                       (AuthChannelHandler*) authHandler->second.get());
    1067             : }
    1068             : 
    1069             : bool
    1070           0 : JamiAccount::cancelAddDevice(uint32_t op_token)
    1071             : {
    1072           0 :     if (!accountManager_)
    1073           0 :         return false;
    1074           0 :     return accountManager_->cancelAddDevice(op_token);
    1075             : }
    1076             : 
    1077             : bool
    1078           4 : JamiAccount::confirmAddDevice(uint32_t op_token)
    1079             : {
    1080           4 :     if (!accountManager_)
    1081           0 :         return false;
    1082           4 :     return accountManager_->confirmAddDevice(op_token);
    1083             : }
    1084             : 
    1085             : bool
    1086          38 : JamiAccount::exportArchive(const std::string& destinationPath, std::string_view scheme, const std::string& password)
    1087             : {
    1088          38 :     if (auto* manager = dynamic_cast<ArchiveAccountManager*>(accountManager_.get())) {
    1089          38 :         return manager->exportArchive(destinationPath, scheme, password);
    1090             :     }
    1091           0 :     return false;
    1092             : }
    1093             : 
    1094             : bool
    1095           2 : JamiAccount::setValidity(std::string_view scheme, const std::string& pwd, const dht::InfoHash& id, int64_t validity)
    1096             : {
    1097           2 :     if (auto* manager = dynamic_cast<ArchiveAccountManager*>(accountManager_.get())) {
    1098           2 :         if (manager->setValidity(scheme, pwd, id_, id, validity)) {
    1099           2 :             saveIdentity(id_, idPath_, DEVICE_ID_PATH);
    1100           2 :             return true;
    1101             :         }
    1102             :     }
    1103           0 :     return false;
    1104             : }
    1105             : 
    1106             : void
    1107           4 : JamiAccount::forceReloadAccount()
    1108             : {
    1109           4 :     editConfig([&](JamiAccountConfig& conf) {
    1110           4 :         conf.receipt.clear();
    1111           4 :         conf.receiptSignature.clear();
    1112           4 :     });
    1113           4 :     loadAccount();
    1114           4 : }
    1115             : 
    1116             : void
    1117           2 : JamiAccount::unlinkConversations(const std::set<std::string>& removed)
    1118             : {
    1119           2 :     std::lock_guard lock(configurationMutex_);
    1120           2 :     if (const auto* info = accountManager_->getInfo()) {
    1121           2 :         auto contacts = info->contacts->getContacts();
    1122           4 :         for (auto& [id, c] : contacts) {
    1123           2 :             if (removed.find(c.conversationId) != removed.end()) {
    1124           1 :                 info->contacts->updateConversation(id, "");
    1125           4 :                 JAMI_WARNING("[Account {}] Detected removed conversation ({}) in contact details for {}",
    1126             :                              getAccountID(),
    1127             :                              c.conversationId,
    1128             :                              id.toString());
    1129             :             }
    1130             :         }
    1131           2 :     }
    1132           2 : }
    1133             : 
    1134             : bool
    1135        1034 : JamiAccount::isValidAccountDevice(const dht::crypto::Certificate& cert) const
    1136             : {
    1137        1034 :     if (accountManager_) {
    1138        1034 :         if (const auto* info = accountManager_->getInfo()) {
    1139        1034 :             if (info->contacts)
    1140        1034 :                 return info->contacts->isValidAccountDevice(cert).isValid();
    1141             :         }
    1142             :     }
    1143           0 :     return false;
    1144             : }
    1145             : 
    1146             : bool
    1147           3 : JamiAccount::revokeDevice(const std::string& device, std::string_view scheme, const std::string& password)
    1148             : {
    1149           3 :     if (not accountManager_)
    1150           0 :         return false;
    1151           3 :     return accountManager_
    1152           6 :         ->revokeDevice(device, scheme, password, [this, device](AccountManager::RevokeDeviceResult result) {
    1153           3 :             emitSignal<libjami::ConfigurationSignal::DeviceRevocationEnded>(getAccountID(),
    1154           3 :                                                                             device,
    1155             :                                                                             static_cast<int>(result));
    1156           6 :         });
    1157             :     return true;
    1158             : }
    1159             : 
    1160             : std::pair<std::string, std::string>
    1161         683 : JamiAccount::saveIdentity(const dht::crypto::Identity& id, const std::filesystem::path& path, const std::string& name)
    1162             : {
    1163        1366 :     auto names = std::make_pair(name + ".key", name + ".crt");
    1164         683 :     if (id.first)
    1165         683 :         fileutils::saveFile(path / names.first, id.first->serialize(), 0600);
    1166         683 :     if (id.second)
    1167         683 :         fileutils::saveFile(path / names.second, id.second->getPacked(), 0600);
    1168         683 :     return names;
    1169           0 : }
    1170             : 
    1171             : void
    1172         681 : JamiAccount::scheduleAccountReady() const
    1173             : {
    1174         681 :     const auto accountId = getAccountID();
    1175        1362 :     runOnMainThread([accountId] { Manager::instance().markAccountReady(accountId); });
    1176         681 : }
    1177             : 
    1178             : AccountManager::OnChangeCallback
    1179         699 : JamiAccount::setupAccountCallbacks()
    1180             : {
    1181             :     return AccountManager::OnChangeCallback {
    1182         162 :         [this](const std::string& uri, bool confirmed) { onContactAdded(uri, confirmed); },
    1183          23 :         [this](const std::string& uri, bool banned) { onContactRemoved(uri, banned); },
    1184         134 :         [this](const std::string& uri,
    1185             :                const std::string& conversationId,
    1186             :                const std::vector<uint8_t>& payload,
    1187         134 :                time_t received) { onIncomingTrustRequest(uri, conversationId, payload, received); },
    1188        2836 :         [this](const std::map<DeviceId, KnownDevice>& devices) { onKnownDevicesChanged(devices); },
    1189          84 :         [this](const std::string& conversationId, const std::string& deviceId) {
    1190          84 :             onConversationRequestAccepted(conversationId, deviceId);
    1191          84 :         },
    1192         737 :         [this](const std::string& uri, const std::string& convFromReq) { onContactConfirmed(uri, convFromReq); }};
    1193             : }
    1194             : 
    1195             : void
    1196         162 : JamiAccount::onContactAdded(const std::string& uri, bool confirmed)
    1197             : {
    1198         162 :     if (!id_.first)
    1199           3 :         return;
    1200         159 :     if (jami::Manager::instance().syncOnRegister) {
    1201         159 :         dht::ThreadPool::io().run([w = weak(), uri, confirmed] {
    1202         159 :             if (auto shared = w.lock()) {
    1203         159 :                 if (auto* cm = shared->convModule(true)) {
    1204         159 :                     auto activeConv = cm->getOneToOneConversation(uri);
    1205         159 :                     if (!activeConv.empty())
    1206         159 :                         cm->bootstrap(activeConv);
    1207         159 :                 }
    1208         159 :                 emitSignal<libjami::ConfigurationSignal::ContactAdded>(shared->getAccountID(), uri, confirmed);
    1209         159 :             }
    1210         159 :         });
    1211             :     }
    1212             : }
    1213             : 
    1214             : void
    1215          23 : JamiAccount::onContactRemoved(const std::string& uri, bool banned)
    1216             : {
    1217          23 :     if (!id_.first)
    1218           0 :         return;
    1219          23 :     dht::ThreadPool::io().run([w = weak(), uri, banned] {
    1220          23 :         if (auto shared = w.lock()) {
    1221             :             // Erase linked conversation's requests
    1222          23 :             if (auto* convModule = shared->convModule(true))
    1223          23 :                 convModule->removeContact(uri, banned);
    1224             :             // Remove current connections with contact
    1225             :             // Note: if contact is ourself, we don't close the connection
    1226             :             // because it's used for syncing other conversations.
    1227          23 :             if (shared->connectionManager_ && uri != shared->getUsername()) {
    1228          22 :                 shared->connectionManager_->closeConnectionsWith(uri);
    1229             :             }
    1230             :             // Update client.
    1231          23 :             emitSignal<libjami::ConfigurationSignal::ContactRemoved>(shared->getAccountID(), uri, banned);
    1232          23 :         }
    1233          23 :     });
    1234             : }
    1235             : 
    1236             : void
    1237         134 : JamiAccount::onIncomingTrustRequest(const std::string& uri,
    1238             :                                     const std::string& conversationId,
    1239             :                                     const std::vector<uint8_t>& payload,
    1240             :                                     time_t received)
    1241             : {
    1242         134 :     if (!id_.first)
    1243           0 :         return;
    1244         134 :     dht::ThreadPool::io().run([w = weak(), uri, conversationId, payload, received] {
    1245         134 :         if (auto shared = w.lock()) {
    1246         134 :             shared->clearProfileCache(uri);
    1247         134 :             if (conversationId.empty()) {
    1248             :                 // Old path
    1249           0 :                 emitSignal<libjami::ConfigurationSignal::IncomingTrustRequest>(shared->getAccountID(),
    1250           0 :                                                                                conversationId,
    1251           0 :                                                                                uri,
    1252           0 :                                                                                payload,
    1253             :                                                                                received);
    1254           0 :                 return;
    1255             :             }
    1256             :             // Here account can be initializing
    1257         134 :             if (auto* cm = shared->convModule(true)) {
    1258         134 :                 auto activeConv = cm->getOneToOneConversation(uri);
    1259         134 :                 if (activeConv != conversationId)
    1260          95 :                     cm->onTrustRequest(uri, conversationId, payload, received);
    1261         134 :             }
    1262         134 :         }
    1263             :     });
    1264             : }
    1265             : 
    1266             : void
    1267        2836 : JamiAccount::onKnownDevicesChanged(const std::map<DeviceId, KnownDevice>& devices)
    1268             : {
    1269        2836 :     std::map<std::string, std::string> ids;
    1270     1010593 :     for (auto& d : devices) {
    1271     1007764 :         auto id = d.first.toString();
    1272     1007714 :         auto label = d.second.name.empty() ? id.substr(0, 8) : d.second.name;
    1273     1007700 :         ids.emplace(std::move(id), std::move(label));
    1274     1007750 :     }
    1275        2834 :     runOnMainThread([id = getAccountID(), devices = std::move(ids)] {
    1276        2836 :         emitSignal<libjami::ConfigurationSignal::KnownDevicesChanged>(id, devices);
    1277        2836 :     });
    1278        2836 : }
    1279             : 
    1280             : void
    1281          84 : JamiAccount::onConversationRequestAccepted(const std::string& conversationId, const std::string& deviceId)
    1282             : {
    1283             :     // Note: Do not retrigger on another thread. This has to be done
    1284             :     // at the same time of acceptTrustRequest a synced state between TrustRequest
    1285             :     // and convRequests.
    1286          84 :     if (auto* cm = convModule(true))
    1287          84 :         cm->acceptConversationRequest(conversationId, deviceId);
    1288          84 : }
    1289             : 
    1290             : void
    1291          38 : JamiAccount::onContactConfirmed(const std::string& uri, const std::string& convFromReq)
    1292             : {
    1293          38 :     dht::ThreadPool::io().run([w = weak(), convFromReq, uri] {
    1294          38 :         if (auto shared = w.lock()) {
    1295          38 :             shared->convModule(true);
    1296             :             // Remove cached payload if there is one
    1297          76 :             auto requestPath = shared->cachePath_ / "requests" / uri;
    1298          38 :             dhtnet::fileutils::remove(requestPath);
    1299          76 :         }
    1300          38 :     });
    1301          38 : }
    1302             : 
    1303             : std::unique_ptr<AccountManager::AccountCredentials>
    1304         683 : JamiAccount::buildAccountCredentials(const JamiAccountConfig& conf,
    1305             :                                      const dht::crypto::Identity& id,
    1306             :                                      const std::string& archive_password_scheme,
    1307             :                                      const std::string& archive_password,
    1308             :                                      const std::string& archive_path,
    1309             :                                      bool& migrating,
    1310             :                                      bool& hasPassword)
    1311             : {
    1312         683 :     std::unique_ptr<AccountManager::AccountCredentials> creds;
    1313             : 
    1314         683 :     if (conf.managerUri.empty()) {
    1315         683 :         auto acreds = std::make_unique<ArchiveAccountManager::ArchiveAccountCredentials>();
    1316         683 :         auto archivePath = fileutils::getFullPath(idPath_, conf.archivePath);
    1317             : 
    1318         683 :         if (!archive_path.empty()) {
    1319          39 :             acreds->scheme = "file";
    1320          39 :             acreds->uri = archive_path;
    1321         644 :         } else if (!conf.archive_url.empty() && conf.archive_url == "jami-auth") {
    1322          20 :             JAMI_DEBUG("[Account {}] [LinkDevice] scheme p2p & uri {}", getAccountID(), conf.archive_url);
    1323           5 :             acreds->scheme = "p2p";
    1324           5 :             acreds->uri = conf.archive_url;
    1325         639 :         } else if (std::filesystem::is_regular_file(archivePath)) {
    1326           4 :             acreds->scheme = "local";
    1327           4 :             acreds->uri = archivePath.string();
    1328           4 :             acreds->updateIdentity = id;
    1329           4 :             migrating = true;
    1330             :         }
    1331             : 
    1332         683 :         creds = std::move(acreds);
    1333         683 :     } else {
    1334           0 :         auto screds = std::make_unique<ServerAccountManager::ServerAccountCredentials>();
    1335           0 :         screds->username = conf.managerUsername;
    1336           0 :         creds = std::move(screds);
    1337           0 :     }
    1338             : 
    1339         683 :     creds->password = archive_password;
    1340         683 :     hasPassword = !archive_password.empty();
    1341        1356 :     creds->password_scheme = (hasPassword && archive_password_scheme.empty()) ? fileutils::ARCHIVE_AUTH_SCHEME_PASSWORD
    1342        1356 :                                                                               : archive_password_scheme;
    1343             : 
    1344         683 :     return creds;
    1345           0 : }
    1346             : 
    1347             : void
    1348         681 : JamiAccount::onAuthenticationSuccess(bool migrating,
    1349             :                                      bool hasPassword,
    1350             :                                      const AccountInfo& info,
    1351             :                                      const std::map<std::string, std::string>& configMap,
    1352             :                                      std::string&& receipt,
    1353             :                                      std::vector<uint8_t>&& receiptSignature)
    1354             : {
    1355        2724 :     JAMI_LOG("[Account {}] Auth success! Device: {}", getAccountID(), info.deviceId);
    1356             : 
    1357         681 :     dhtnet::fileutils::check_dir(idPath_, 0700);
    1358             : 
    1359         681 :     auto id = info.identity;
    1360         681 :     editConfig([&](JamiAccountConfig& conf) {
    1361         681 :         std::tie(conf.tlsPrivateKeyFile, conf.tlsCertificateFile) = saveIdentity(id, idPath_, DEVICE_ID_PATH);
    1362         681 :         conf.tlsPassword = {};
    1363             : 
    1364         681 :         auto passwordIt = configMap.find(libjami::Account::ConfProperties::ARCHIVE_HAS_PASSWORD);
    1365        1362 :         conf.archiveHasPassword = (passwordIt != configMap.end() && !passwordIt->second.empty())
    1366        1362 :                                       ? passwordIt->second == "true"
    1367           0 :                                       : hasPassword;
    1368             : 
    1369         681 :         if (not conf.managerUri.empty()) {
    1370           0 :             conf.registeredName = conf.managerUsername;
    1371           0 :             registeredName_ = conf.managerUsername;
    1372             :         }
    1373             : 
    1374         681 :         conf.username = info.accountId;
    1375         681 :         conf.deviceName = accountManager_->getAccountDeviceName();
    1376             : 
    1377         681 :         auto nameServerIt = configMap.find(libjami::Account::ConfProperties::Nameserver::URI);
    1378         681 :         if (nameServerIt != configMap.end() && !nameServerIt->second.empty())
    1379           0 :             conf.nameServer = nameServerIt->second;
    1380             : 
    1381         681 :         auto displayNameIt = configMap.find(libjami::Account::ConfProperties::DISPLAYNAME);
    1382         681 :         if (displayNameIt != configMap.end() && !displayNameIt->second.empty())
    1383          41 :             conf.displayName = displayNameIt->second;
    1384             : 
    1385         681 :         conf.receipt = std::move(receipt);
    1386         681 :         conf.receiptSignature = std::move(receiptSignature);
    1387         681 :         conf.fromMap(configMap);
    1388         681 :     });
    1389             : 
    1390         681 :     id_ = std::move(id);
    1391             :     {
    1392         681 :         std::lock_guard lk(moduleMtx_);
    1393         681 :         convModule_.reset();
    1394         681 :     }
    1395             : 
    1396         681 :     if (migrating)
    1397           4 :         Migration::setState(getAccountID(), Migration::State::SUCCESS);
    1398             : 
    1399         681 :     setRegistrationState(RegistrationState::UNREGISTERED);
    1400             : 
    1401         681 :     if (!info.photo.empty() || !info.displayName.empty()) {
    1402             :         try {
    1403           0 :             auto newProfile = vCard::utils::initVcard();
    1404           0 :             newProfile[std::string(vCard::Property::FORMATTED_NAME)] = info.displayName;
    1405           0 :             newProfile[std::string(vCard::Property::PHOTO)] = info.photo;
    1406             : 
    1407           0 :             const auto& profiles = idPath_ / "profiles";
    1408           0 :             const auto& vCardPath = profiles / fmt::format("{}.vcf", base64::encode(info.accountId));
    1409           0 :             vCard::utils::save(newProfile, vCardPath, profilePath());
    1410             : 
    1411           0 :             runOnMainThread([w = weak(), id = info.accountId, vCardPath] {
    1412           0 :                 if (auto shared = w.lock()) {
    1413           0 :                     emitSignal<libjami::ConfigurationSignal::ProfileReceived>(shared->getAccountID(),
    1414           0 :                                                                               id,
    1415           0 :                                                                               vCardPath.string());
    1416           0 :                 }
    1417           0 :             });
    1418           0 :         } catch (const std::exception& e) {
    1419           0 :             JAMI_WARNING("[Account {}] Unable to save profile after authentication: {}", getAccountID(), e.what());
    1420           0 :         }
    1421             :     }
    1422             : 
    1423         681 :     doRegister();
    1424         681 :     scheduleAccountReady();
    1425         681 : }
    1426             : 
    1427             : void
    1428           0 : JamiAccount::onAuthenticationError(const std::weak_ptr<JamiAccount>& w,
    1429             :                                    bool hadIdentity,
    1430             :                                    bool migrating,
    1431             :                                    std::string accountId,
    1432             :                                    AccountManager::AuthError error,
    1433             :                                    const std::string& message)
    1434             : {
    1435           0 :     JAMI_WARNING("[Account {}] Auth error: {} {}", accountId, (int) error, message);
    1436             : 
    1437           0 :     if ((hadIdentity || migrating) && error == AccountManager::AuthError::INVALID_ARGUMENTS) {
    1438           0 :         Migration::setState(accountId, Migration::State::INVALID);
    1439           0 :         if (auto acc = w.lock())
    1440           0 :             acc->setRegistrationState(RegistrationState::ERROR_NEED_MIGRATION);
    1441           0 :         return;
    1442             :     }
    1443             : 
    1444           0 :     if (auto acc = w.lock())
    1445           0 :         acc->setRegistrationState(RegistrationState::ERROR_GENERIC);
    1446             : 
    1447           0 :     runOnMainThread([accountId = std::move(accountId)] { Manager::instance().removeAccount(accountId, true); });
    1448             : }
    1449             : 
    1450             : // must be called while configurationMutex_ is locked
    1451             : void
    1452         703 : JamiAccount::loadAccount(const std::string& archive_password_scheme,
    1453             :                          const std::string& archive_password,
    1454             :                          const std::string& archive_path)
    1455             : {
    1456         703 :     if (registrationState_ == RegistrationState::INITIALIZING)
    1457          20 :         return;
    1458             : 
    1459        2796 :     JAMI_DEBUG("[Account {:s}] Loading account", getAccountID());
    1460             : 
    1461         699 :     const auto scheduleAccountReady = [accountId = getAccountID()] {
    1462          16 :         runOnMainThread([accountId] {
    1463          16 :             auto& manager = Manager::instance();
    1464          16 :             manager.markAccountReady(accountId);
    1465          16 :         });
    1466         715 :     };
    1467             : 
    1468         699 :     const auto& conf = config();
    1469         699 :     auto callbacks = setupAccountCallbacks();
    1470             : 
    1471             :     try {
    1472         699 :         auto oldIdentity = id_.first ? id_.first->getPublicKey().getLongId() : DeviceId();
    1473             : 
    1474         699 :         if (conf.managerUri.empty()) {
    1475        2097 :             accountManager_ = std::make_shared<ArchiveAccountManager>(
    1476         699 :                 getAccountID(),
    1477             :                 getPath(),
    1478          42 :                 [this]() { return getAccountDetails(); },
    1479         721 :                 [this](DeviceSync&& syncData) {
    1480         721 :                     if (auto* sm = syncModule()) {
    1481         721 :                         auto syncDataPtr = std::make_shared<SyncMsg>();
    1482         721 :                         syncDataPtr->ds = std::move(syncData);
    1483         721 :                         sm->syncWithConnected(syncDataPtr);
    1484         721 :                     }
    1485         721 :                 },
    1486        1398 :                 conf.archivePath.empty() ? "archive.gz" : conf.archivePath,
    1487        1398 :                 conf.nameServer);
    1488             :         } else {
    1489           0 :             accountManager_ = std::make_shared<ServerAccountManager>(getAccountID(),
    1490             :                                                                      getPath(),
    1491           0 :                                                                      conf.managerUri,
    1492           0 :                                                                      conf.nameServer);
    1493             :         }
    1494             : 
    1495         699 :         auto id = accountManager_->loadIdentity(conf.tlsCertificateFile, conf.tlsPrivateKeyFile, conf.tlsPassword);
    1496             : 
    1497         699 :         if (const auto* info
    1498         699 :             = accountManager_->useIdentity(id, conf.receipt, conf.receiptSignature, conf.managerUsername, callbacks)) {
    1499          16 :             id_ = std::move(id);
    1500          16 :             config_->username = info->accountId;
    1501          64 :             JAMI_WARNING("[Account {:s}] Loaded account identity", getAccountID());
    1502             : 
    1503          16 :             if (info->identity.first->getPublicKey().getLongId() != oldIdentity) {
    1504           0 :                 JAMI_WARNING("[Account {:s}] Identity changed", getAccountID());
    1505             :                 {
    1506           0 :                     std::lock_guard lk(moduleMtx_);
    1507           0 :                     convModule_.reset();
    1508           0 :                 }
    1509           0 :                 convModule();
    1510             :             } else {
    1511          16 :                 convModule()->setAccountManager(accountManager_);
    1512             :             }
    1513             : 
    1514          16 :             convModule()->initPresence();
    1515          16 :             if (not isEnabled())
    1516           0 :                 setRegistrationState(RegistrationState::UNREGISTERED);
    1517             : 
    1518          16 :             scheduleAccountReady();
    1519          16 :             return;
    1520             :         }
    1521             : 
    1522         683 :         if (!isEnabled())
    1523           0 :             return;
    1524             : 
    1525        2732 :         JAMI_WARNING("[Account {}] useIdentity failed!", getAccountID());
    1526             : 
    1527         683 :         if (not conf.managerUri.empty() && archive_password.empty()) {
    1528           0 :             Migration::setState(accountID_, Migration::State::INVALID);
    1529           0 :             setRegistrationState(RegistrationState::ERROR_NEED_MIGRATION);
    1530           0 :             return;
    1531             :         }
    1532             : 
    1533         683 :         bool migrating = registrationState_ == RegistrationState::ERROR_NEED_MIGRATION;
    1534         683 :         setRegistrationState(RegistrationState::INITIALIZING);
    1535             : 
    1536         683 :         auto fDeviceKey = dht::ThreadPool::computation().getShared<std::shared_ptr<dht::crypto::PrivateKey>>(
    1537        2049 :             []() { return std::make_shared<dht::crypto::PrivateKey>(dht::crypto::PrivateKey::generate()); });
    1538             : 
    1539         683 :         bool hasPassword = false;
    1540             :         auto creds = buildAccountCredentials(conf,
    1541             :                                              id,
    1542             :                                              archive_password_scheme,
    1543             :                                              archive_password,
    1544             :                                              archive_path,
    1545             :                                              migrating,
    1546         683 :                                              hasPassword);
    1547             : 
    1548        2732 :         JAMI_WARNING("[Account {}] initAuthentication {}", getAccountID(), fmt::ptr(this));
    1549             : 
    1550         683 :         const bool hadIdentity = static_cast<bool>(id.first);
    1551        3415 :         accountManager_->initAuthentication(
    1552             :             fDeviceKey,
    1553        1366 :             ip_utils::getDeviceName(),
    1554         683 :             std::move(creds),
    1555         681 :             [w = weak(), migrating, hasPassword](const AccountInfo& info,
    1556             :                                                  const std::map<std::string, std::string>& configMap,
    1557             :                                                  std::string&& receipt,
    1558         681 :                                                  std::vector<uint8_t>&& receiptSignature) {
    1559         681 :                 if (auto self = w.lock())
    1560        1362 :                     self->onAuthenticationSuccess(migrating,
    1561             :                                                   hasPassword,
    1562             :                                                   info,
    1563             :                                                   configMap,
    1564         681 :                                                   std::move(receipt),
    1565        1362 :                                                   std::move(receiptSignature));
    1566         681 :             },
    1567         683 :             [w = weak(), hadIdentity, accountId = getAccountID(), migrating](AccountManager::AuthError error,
    1568           0 :                                                                              const std::string& message) {
    1569           0 :                 JamiAccount::onAuthenticationError(w, hadIdentity, migrating, accountId, error, message);
    1570           0 :             },
    1571             :             callbacks);
    1572         699 :     } catch (const std::exception& e) {
    1573           0 :         JAMI_WARNING("[Account {}] Error loading account: {}", getAccountID(), e.what());
    1574           0 :         accountManager_.reset();
    1575           0 :         setRegistrationState(RegistrationState::ERROR_GENERIC);
    1576           0 :     }
    1577         715 : }
    1578             : 
    1579             : std::map<std::string, std::string>
    1580        4043 : JamiAccount::getVolatileAccountDetails() const
    1581             : {
    1582        4043 :     auto a = SIPAccountBase::getVolatileAccountDetails();
    1583        4043 :     a.emplace(libjami::Account::VolatileProperties::InstantMessaging::OFF_CALL, TRUE_STR);
    1584        4043 :     auto registeredName = getRegisteredName();
    1585        4043 :     if (not registeredName.empty())
    1586           3 :         a.emplace(libjami::Account::VolatileProperties::REGISTERED_NAME, registeredName);
    1587        4043 :     a.emplace(libjami::Account::ConfProperties::PROXY_SERVER, proxyServerCached_);
    1588        4043 :     a.emplace(libjami::Account::VolatileProperties::DHT_BOUND_PORT, std::to_string(dhtBoundPort_));
    1589        4043 :     a.emplace(libjami::Account::VolatileProperties::DEVICE_ANNOUNCED, deviceAnnounced_ ? TRUE_STR : FALSE_STR);
    1590        4043 :     if (accountManager_) {
    1591        4043 :         if (const auto* info = accountManager_->getInfo()) {
    1592        3316 :             a.emplace(libjami::Account::ConfProperties::DEVICE_ID, info->deviceId);
    1593             :         }
    1594             :     }
    1595        8086 :     return a;
    1596        4043 : }
    1597             : 
    1598             : void
    1599           3 : JamiAccount::lookupName(const std::string& name)
    1600             : {
    1601           3 :     std::lock_guard lock(configurationMutex_);
    1602           3 :     if (accountManager_)
    1603           9 :         accountManager_->lookupUri(name,
    1604           3 :                                    config().nameServer,
    1605           3 :                                    [acc = getAccountID(), name](const std::string& regName,
    1606             :                                                                 const std::string& address,
    1607             :                                                                 NameDirectory::Response response) {
    1608           6 :                                        emitSignal<libjami::ConfigurationSignal::RegisteredNameFound>(acc,
    1609           3 :                                                                                                      name,
    1610             :                                                                                                      (int) response,
    1611             :                                                                                                      address,
    1612             :                                                                                                      regName);
    1613           3 :                                    });
    1614           3 : }
    1615             : 
    1616             : void
    1617           3 : JamiAccount::lookupAddress(const std::string& addr)
    1618             : {
    1619           3 :     std::lock_guard lock(configurationMutex_);
    1620           3 :     auto acc = getAccountID();
    1621           3 :     if (accountManager_)
    1622           3 :         accountManager_->lookupAddress(addr,
    1623           3 :                                        [acc, addr](const std::string& regName,
    1624             :                                                    const std::string& address,
    1625             :                                                    NameDirectory::Response response) {
    1626           6 :                                            emitSignal<libjami::ConfigurationSignal::RegisteredNameFound>(acc,
    1627           3 :                                                                                                          addr,
    1628             :                                                                                                          (int) response,
    1629             :                                                                                                          address,
    1630             :                                                                                                          regName);
    1631           3 :                                        });
    1632           3 : }
    1633             : 
    1634             : void
    1635           1 : JamiAccount::registerName(const std::string& name, const std::string& scheme, const std::string& password)
    1636             : {
    1637           1 :     std::lock_guard lock(configurationMutex_);
    1638           1 :     if (accountManager_)
    1639           1 :         accountManager_
    1640           2 :             ->registerName(name,
    1641             :                            scheme,
    1642             :                            password,
    1643           1 :                            [acc = getAccountID(), name, w = weak()](NameDirectory::RegistrationResponse response,
    1644             :                                                                     const std::string& regName) {
    1645           1 :                                auto res = (int) std::min(response, NameDirectory::RegistrationResponse::error);
    1646           1 :                                if (response == NameDirectory::RegistrationResponse::success) {
    1647           1 :                                    if (auto this_ = w.lock()) {
    1648           1 :                                        if (this_->setRegisteredName(regName)) {
    1649           1 :                                            this_->editConfig(
    1650           1 :                                                [&](JamiAccountConfig& config) { config.registeredName = regName; });
    1651           1 :                                            emitSignal<libjami::ConfigurationSignal::VolatileDetailsChanged>(
    1652           2 :                                                this_->accountID_, this_->getVolatileAccountDetails());
    1653             :                                        }
    1654           1 :                                    }
    1655             :                                }
    1656           1 :                                emitSignal<libjami::ConfigurationSignal::NameRegistrationEnded>(acc, res, name);
    1657           1 :                            });
    1658           1 : }
    1659             : 
    1660             : bool
    1661           0 : JamiAccount::searchUser(const std::string& query)
    1662             : {
    1663           0 :     if (accountManager_)
    1664           0 :         return accountManager_
    1665           0 :             ->searchUser(query,
    1666           0 :                          [acc = getAccountID(), query](const jami::NameDirectory::SearchResult& result,
    1667             :                                                        jami::NameDirectory::Response response) {
    1668           0 :                              jami::emitSignal<libjami::ConfigurationSignal::UserSearchEnded>(acc,
    1669             :                                                                                              (int) response,
    1670           0 :                                                                                              query,
    1671             :                                                                                              result);
    1672           0 :                          });
    1673           0 :     return false;
    1674             : }
    1675             : 
    1676             : void
    1677          66 : JamiAccount::forEachPendingCall(const DeviceId& deviceId, const std::function<void(const std::shared_ptr<SIPCall>&)>& cb)
    1678             : {
    1679          66 :     std::vector<std::shared_ptr<SIPCall>> pc;
    1680             :     {
    1681          66 :         std::lock_guard lk(pendingCallsMutex_);
    1682          66 :         pc = std::move(pendingCalls_[deviceId]);
    1683          66 :     }
    1684          98 :     for (const auto& pendingCall : pc) {
    1685          32 :         cb(pendingCall);
    1686             :     }
    1687          66 : }
    1688             : 
    1689             : void
    1690         600 : JamiAccount::registerAsyncOps()
    1691             : {
    1692         600 :     loadCachedProxyServer([w = weak()](const std::string&) {
    1693         600 :         runOnMainThread([w] {
    1694         600 :             if (auto s = w.lock()) {
    1695         600 :                 std::lock_guard lock(s->configurationMutex_);
    1696         600 :                 s->doRegister_();
    1697        1200 :             }
    1698         600 :         });
    1699         600 :     });
    1700         600 : }
    1701             : 
    1702             : void
    1703        1419 : JamiAccount::doRegister()
    1704             : {
    1705        1419 :     std::lock_guard lock(configurationMutex_);
    1706        1419 :     if (not isUsable()) {
    1707         560 :         JAMI_WARNING("[Account {:s}] Account must be enabled and active to register, ignoring", getAccountID());
    1708         140 :         return;
    1709             :     }
    1710             : 
    1711        5116 :     JAMI_LOG("[Account {:s}] Starting account…", getAccountID());
    1712             : 
    1713             :     // invalid state transitions:
    1714             :     // INITIALIZING: generating/loading certificates, unable to register
    1715             :     // NEED_MIGRATION: old account detected, user needs to migrate
    1716        1279 :     if (registrationState_ == RegistrationState::INITIALIZING
    1717         600 :         || registrationState_ == RegistrationState::ERROR_NEED_MIGRATION)
    1718         679 :         return;
    1719             : 
    1720         600 :     convModule(); // Init conv module before passing in trying
    1721         600 :     setRegistrationState(RegistrationState::TRYING);
    1722         600 :     if (proxyServerCached_.empty()) {
    1723         600 :         registerAsyncOps();
    1724             :     } else {
    1725           0 :         doRegister_();
    1726             :     }
    1727        1419 : }
    1728             : 
    1729             : std::vector<std::string>
    1730         600 : JamiAccount::loadBootstrap() const
    1731             : {
    1732         600 :     std::vector<std::string> bootstrap;
    1733         600 :     std::string_view stream(config().hostname), node_addr;
    1734        1200 :     while (jami::getline(stream, node_addr, ';'))
    1735         600 :         bootstrap.emplace_back(node_addr);
    1736        1200 :     for (const auto& b : bootstrap)
    1737         600 :         JAMI_DBG("[Account %s] Bootstrap node: %s", getAccountID().c_str(), b.c_str());
    1738        1200 :     return bootstrap;
    1739           0 : }
    1740             : 
    1741             : void
    1742          34 : JamiAccount::trackBuddyPresence(const std::string& buddy_id, bool track)
    1743             : {
    1744          34 :     std::string buddyUri;
    1745             :     try {
    1746          34 :         buddyUri = parseJamiUri(buddy_id);
    1747           0 :     } catch (...) {
    1748           0 :         JAMI_ERROR("[Account {:s}] Failed to track presence: invalid URI {:s}", getAccountID(), buddy_id);
    1749           0 :         return;
    1750           0 :     }
    1751         136 :     JAMI_LOG("[Account {:s}] {:s} presence for {:s}", getAccountID(), track ? "Track" : "Untrack", buddy_id);
    1752             : 
    1753          34 :     if (!presenceManager_)
    1754           0 :         return;
    1755             : 
    1756          34 :     if (track) {
    1757          34 :         presenceManager_->trackBuddy(buddyUri);
    1758          34 :         std::lock_guard lock(presenceStateMtx_);
    1759          34 :         auto it = presenceState_.find(buddyUri);
    1760          34 :         if (it != presenceState_.end() && it->second != PresenceState::DISCONNECTED) {
    1761           1 :             emitSignal<libjami::PresenceSignal::NewBuddyNotification>(getAccountID(),
    1762             :                                                                       buddyUri,
    1763           1 :                                                                       static_cast<int>(it->second),
    1764             :                                                                       "");
    1765             :         }
    1766          34 :     } else {
    1767           0 :         presenceManager_->untrackBuddy(buddyUri);
    1768             :     }
    1769          34 : }
    1770             : 
    1771             : std::map<std::string, bool>
    1772           2 : JamiAccount::getTrackedBuddyPresence() const
    1773             : {
    1774           2 :     if (!presenceManager_)
    1775           0 :         return {};
    1776           2 :     return presenceManager_->getTrackedBuddyPresence();
    1777             : }
    1778             : 
    1779             : void
    1780         582 : JamiAccount::onTrackedBuddyOnline(const std::string& contactId)
    1781             : {
    1782        2328 :     JAMI_DEBUG("[Account {:s}] Buddy {} online", getAccountID(), contactId);
    1783         582 :     std::lock_guard lock(presenceStateMtx_);
    1784         582 :     auto& state = presenceState_[contactId];
    1785         582 :     if (state < PresenceState::AVAILABLE) {
    1786         436 :         state = PresenceState::AVAILABLE;
    1787         436 :         emitSignal<libjami::PresenceSignal::NewBuddyNotification>(getAccountID(),
    1788             :                                                                   contactId,
    1789             :                                                                   static_cast<int>(PresenceState::AVAILABLE),
    1790             :                                                                   "");
    1791             :     }
    1792             : 
    1793         582 :     if (auto details = getContactInfo(contactId)) {
    1794          98 :         if (!details->confirmed) {
    1795          54 :             auto convId = convModule()->getOneToOneConversation(contactId);
    1796          54 :             if (convId.empty())
    1797           2 :                 return;
    1798             :             // In this case, the TrustRequest was sent but never confirmed (cause the contact was
    1799             :             // offline maybe) To avoid the contact to never receive the conv request, retry there
    1800          52 :             std::lock_guard lock(configurationMutex_);
    1801          52 :             if (accountManager_) {
    1802             :                 // Retrieve cached payload for trust request.
    1803         104 :                 auto requestPath = cachePath_ / "requests" / contactId;
    1804          52 :                 std::vector<uint8_t> payload;
    1805             :                 try {
    1806          62 :                     payload = fileutils::loadFile(requestPath);
    1807          10 :                 } catch (...) {
    1808          10 :                 }
    1809          52 :                 if (payload.size() >= 64000) {
    1810           4 :                     JAMI_WARNING("[Account {:s}] Trust request for contact {:s} is too big, reset payload",
    1811             :                                  getAccountID(),
    1812             :                                  contactId);
    1813           1 :                     payload.clear();
    1814             :                 }
    1815          52 :                 accountManager_->sendTrustRequest(contactId, convId, payload);
    1816          52 :             }
    1817          54 :         }
    1818         582 :     }
    1819         582 : }
    1820             : 
    1821             : void
    1822          62 : JamiAccount::onTrackedBuddyOffline(const std::string& contactId)
    1823             : {
    1824         248 :     JAMI_DEBUG("[Account {:s}] Buddy {} offline", getAccountID(), contactId);
    1825          62 :     std::lock_guard lock(presenceStateMtx_);
    1826          62 :     auto& state = presenceState_[contactId];
    1827          62 :     if (state > PresenceState::DISCONNECTED) {
    1828          62 :         if (state == PresenceState::CONNECTED) {
    1829           0 :             JAMI_WARNING("[Account {:s}] Buddy {} is not present on the DHT, but P2P connected",
    1830             :                          getAccountID(),
    1831             :                          contactId);
    1832           0 :             return;
    1833             :         }
    1834          62 :         state = PresenceState::DISCONNECTED;
    1835          62 :         emitSignal<libjami::PresenceSignal::NewBuddyNotification>(getAccountID(),
    1836             :                                                                   contactId,
    1837             :                                                                   static_cast<int>(PresenceState::DISCONNECTED),
    1838             :                                                                   "");
    1839             :     }
    1840          62 : }
    1841             : 
    1842             : void
    1843         600 : JamiAccount::doRegister_()
    1844             : {
    1845         600 :     if (registrationState_ != RegistrationState::TRYING) {
    1846           0 :         JAMI_ERROR("[Account {}] Already registered", getAccountID());
    1847           0 :         return;
    1848             :     }
    1849             : 
    1850        2400 :     JAMI_DEBUG("[Account {}] Starting account…", getAccountID());
    1851         600 :     const auto& conf = config();
    1852             : 
    1853             :     try {
    1854         600 :         if (not accountManager_ or not accountManager_->getInfo())
    1855           0 :             throw std::runtime_error("No identity configured for this account.");
    1856             : 
    1857         600 :         if (dht_->isRunning()) {
    1858           8 :             JAMI_ERROR("[Account {}] DHT already running (stopping it first).", getAccountID());
    1859           2 :             dht_->join();
    1860             :         }
    1861             : 
    1862         600 :         convModule()->clearPendingFetch();
    1863             : 
    1864             :         // Look for registered name
    1865         600 :         accountManager_->lookupAddress(accountManager_->getInfo()->accountId,
    1866         600 :                                        [w = weak()](const std::string& regName,
    1867             :                                                     const std::string& /*address*/,
    1868             :                                                     const NameDirectory::Response& response) {
    1869         600 :                                            if (auto this_ = w.lock())
    1870         600 :                                                this_->lookupRegisteredName(regName, response);
    1871         600 :                                        });
    1872             : 
    1873         600 :         dht::DhtRunner::Config config = initDhtConfig(conf);
    1874             : 
    1875             :         // check if dht peer service is enabled
    1876         600 :         if (conf.accountPeerDiscovery or conf.accountPublish) {
    1877           0 :             peerDiscovery_ = std::make_shared<dht::PeerDiscovery>();
    1878           0 :             if (conf.accountPeerDiscovery) {
    1879           0 :                 JAMI_LOG("[Account {}] Starting Jami account discovery…", getAccountID());
    1880           0 :                 startAccountDiscovery();
    1881             :             }
    1882           0 :             if (conf.accountPublish)
    1883           0 :                 startAccountPublish();
    1884             :         }
    1885             : 
    1886         600 :         dht::DhtRunner::Context context = initDhtContext();
    1887             : 
    1888         600 :         dht_->run(conf.dhtPort, config, std::move(context));
    1889         600 :         dhtBoundPort_ = dht_->getBoundPort();
    1890             : 
    1891             :         // Now that the DHT is running and we know the actual bound port,
    1892             :         // request a UPnP mapping for it.
    1893         600 :         if (upnpCtrl_) {
    1894        2304 :             JAMI_LOG("[Account {:s}] UPnP: requesting mapping for DHT port {}", getAccountID(), dhtBoundPort_);
    1895             : 
    1896         576 :             if (dhtUpnpMapping_.isValid()) {
    1897           0 :                 upnpCtrl_->releaseMapping(dhtUpnpMapping_);
    1898             :             }
    1899             : 
    1900         576 :             dhtUpnpMapping_.enableAutoUpdate(true);
    1901             : 
    1902         576 :             dhtnet::upnp::Mapping desired(dhtnet::upnp::PortType::UDP, dhtBoundPort_, dhtBoundPort_);
    1903         576 :             dhtUpnpMapping_.updateFrom(desired);
    1904             : 
    1905         576 :             dhtUpnpMapping_.setNotifyCallback([w = weak()](const dhtnet::upnp::Mapping::sharedPtr_t& mapRes) {
    1906         551 :                 if (auto accPtr = w.lock()) {
    1907         551 :                     auto& dhtMap = accPtr->dhtUpnpMapping_;
    1908         551 :                     const auto& accId = accPtr->getAccountID();
    1909             : 
    1910        2204 :                     JAMI_LOG("[Account {:s}] DHT UPnP mapping changed to {:s}", accId, mapRes->toString(true));
    1911             : 
    1912         551 :                     if (dhtMap.getMapKey() != mapRes->getMapKey() or dhtMap.getState() != mapRes->getState()) {
    1913         544 :                         dhtMap.updateFrom(mapRes);
    1914         544 :                         if (mapRes->getState() == dhtnet::upnp::MappingState::OPEN) {
    1915           0 :                             JAMI_LOG("[Account {:s}] Mapping {:s} successfully allocated", accId, dhtMap.toString());
    1916           0 :                             accPtr->dht_->connectivityChanged();
    1917         544 :                         } else if (mapRes->getState() == dhtnet::upnp::MappingState::FAILED) {
    1918        2176 :                             JAMI_WARNING("[Account {:s}] UPnP mapping failed", accId);
    1919             :                         }
    1920             :                     } else {
    1921           7 :                         dhtMap.updateFrom(mapRes);
    1922             :                     }
    1923         551 :                 }
    1924         551 :             });
    1925             : 
    1926         576 :             upnpCtrl_->reserveMapping(dhtUpnpMapping_);
    1927         576 :         }
    1928             : 
    1929        1200 :         for (const auto& bootstrap : loadBootstrap())
    1930        1200 :             dht_->bootstrap(bootstrap);
    1931             : 
    1932         600 :         accountManager_->setDht(dht_);
    1933             : 
    1934         600 :         if (conf.dhtProxyServerEnabled) {
    1935           0 :             dht::ProxyServerConfig proxyConfig;
    1936           0 :             proxyConfig.port = conf.dhtProxyServerPort;
    1937           0 :             proxyConfig.identity = id_;
    1938           0 :             dhtProxyServer_ = std::make_shared<dht::DhtProxyServer>(dht_, proxyConfig);
    1939           0 :         } else {
    1940         600 :             dhtProxyServer_.reset();
    1941             :         }
    1942             : 
    1943         600 :         std::unique_lock lkCM(connManagerMtx_);
    1944         600 :         initConnectionManager();
    1945         600 :         connectionManager_->dhtStarted();
    1946        1311 :         connectionManager_->onICERequest([this](const DeviceId& deviceId) { return onICERequest(deviceId); });
    1947         600 :         connectionManager_->onChannelRequest([this](const std::shared_ptr<dht::crypto::Certificate>& cert,
    1948        2041 :                                                     const std::string& name) { return onChannelRequest(cert, name); });
    1949         600 :         connectionManager_->onConnectionReady(
    1950        3884 :             [this](const DeviceId& deviceId, const std::string& name, std::shared_ptr<dhtnet::ChannelSocket> channel) {
    1951        3884 :                 onConnectionReady(deviceId, name, std::move(channel));
    1952        3883 :             });
    1953         600 :         lkCM.unlock();
    1954             : 
    1955         600 :         if (!conf.managerUri.empty() && accountManager_) {
    1956           0 :             dynamic_cast<ServerAccountManager*>(accountManager_.get())->onNeedsMigration([this]() {
    1957           0 :                 editConfig([&](JamiAccountConfig& conf) {
    1958           0 :                     conf.receipt.clear();
    1959           0 :                     conf.receiptSignature.clear();
    1960           0 :                 });
    1961           0 :                 Migration::setState(accountID_, Migration::State::INVALID);
    1962           0 :                 setRegistrationState(RegistrationState::ERROR_NEED_MIGRATION);
    1963           0 :             });
    1964           0 :             dynamic_cast<ServerAccountManager*>(accountManager_.get())
    1965           0 :                 ->syncBlueprintConfig([this](const std::map<std::string, std::string>& config) {
    1966           0 :                     editConfig([&](JamiAccountConfig& conf) { conf.fromMap(config); });
    1967           0 :                     emitSignal<libjami::ConfigurationSignal::AccountDetailsChanged>(getAccountID(), getAccountDetails());
    1968           0 :                 });
    1969             :         }
    1970             : 
    1971         600 :         if (presenceManager_)
    1972         600 :             presenceManager_->refresh();
    1973         600 :     } catch (const std::exception& e) {
    1974           0 :         JAMI_ERR("Error registering DHT account: %s", e.what());
    1975           0 :         setRegistrationState(RegistrationState::ERROR_GENERIC);
    1976           0 :     }
    1977             : }
    1978             : 
    1979             : void
    1980         598 : JamiAccount::lookupRegisteredName(const std::string& regName, const NameDirectory::Response& response)
    1981             : {
    1982         598 :     if (response == NameDirectory::Response::found or response == NameDirectory::Response::notFound) {
    1983         598 :         const auto& nameResult = response == NameDirectory::Response::found ? regName : "";
    1984         598 :         if (setRegisteredName(nameResult)) {
    1985           0 :             editConfig([&](JamiAccountConfig& config) { config.registeredName = nameResult; });
    1986           0 :             emitSignal<libjami::ConfigurationSignal::VolatileDetailsChanged>(accountID_, getVolatileAccountDetails());
    1987             :         }
    1988         598 :     }
    1989         598 : }
    1990             : 
    1991             : dht::DhtRunner::Config
    1992         600 : JamiAccount::initDhtConfig(const JamiAccountConfig& conf)
    1993             : {
    1994         600 :     dht::DhtRunner::Config config {};
    1995         600 :     config.dht_config.node_config.network = 0;
    1996         600 :     config.dht_config.node_config.maintain_storage = false;
    1997         600 :     config.dht_config.node_config.persist_path = (cachePath_ / "dhtstate").string();
    1998         600 :     config.dht_config.id = id_;
    1999         600 :     config.dht_config.cert_cache_all = true;
    2000         600 :     config.push_node_id = getAccountID();
    2001         600 :     config.push_token = conf.deviceKey;
    2002         600 :     config.push_topic = conf.notificationTopic;
    2003         600 :     config.push_platform = conf.platform;
    2004         600 :     config.proxy_user_agent = jami::userAgent();
    2005         600 :     config.threaded = true;
    2006         600 :     config.peer_discovery = conf.dhtPeerDiscovery;
    2007         600 :     config.peer_publish = conf.dhtPeerDiscovery;
    2008         600 :     if (conf.proxyEnabled)
    2009           0 :         config.proxy_server = proxyServerCached_;
    2010             : 
    2011         600 :     if (not config.proxy_server.empty()) {
    2012           0 :         JAMI_LOG("[Account {}] Using proxy server {}", getAccountID(), config.proxy_server);
    2013           0 :         if (not config.push_token.empty()) {
    2014           0 :             JAMI_LOG("[Account {}] using push notifications with platform: {}, topic: {}, token: {}",
    2015             :                      getAccountID(),
    2016             :                      config.push_platform,
    2017             :                      config.push_topic,
    2018             :                      config.push_token);
    2019             :         }
    2020             :     }
    2021         600 :     return config;
    2022           0 : }
    2023             : 
    2024             : dht::DhtRunner::Context
    2025         600 : JamiAccount::initDhtContext()
    2026             : {
    2027         600 :     dht::DhtRunner::Context context {};
    2028         600 :     context.peerDiscovery = peerDiscovery_;
    2029         600 :     context.rng = std::make_unique<std::mt19937_64>(dht::crypto::getDerivedRandomEngine(rand));
    2030             : 
    2031         600 :     auto dht_log_level = Manager::instance().dhtLogLevel;
    2032         600 :     if (dht_log_level > 0) {
    2033           0 :         context.logger = logger_;
    2034             :     }
    2035             : 
    2036          21 :     context.certificateStore = [&](const dht::InfoHash& pk_id) {
    2037          21 :         std::vector<std::shared_ptr<dht::crypto::Certificate>> ret;
    2038          42 :         if (auto cert = certStore().getCertificate(pk_id.toString()))
    2039          21 :             ret.emplace_back(std::move(cert));
    2040          84 :         JAMI_LOG("[Account {}] Query for local certificate store: {}: {} found.",
    2041             :                  getAccountID(),
    2042             :                  pk_id.toString(),
    2043             :                  ret.size());
    2044          21 :         return ret;
    2045         600 :     };
    2046             : 
    2047        1264 :     context.certificateStorePkId = [&](const DeviceId& pk_id) {
    2048        1264 :         std::vector<std::shared_ptr<dht::crypto::Certificate>> ret;
    2049        2528 :         if (auto cert = certStore().getCertificate(pk_id.toString()))
    2050        1264 :             ret.emplace_back(std::move(cert));
    2051        5056 :         JAMI_LOG("[Account {}] Query for local certificate store: {}: {} found.",
    2052             :                  getAccountID(),
    2053             :                  pk_id.toString(),
    2054             :                  ret.size());
    2055        1264 :         return ret;
    2056         600 :     };
    2057             : 
    2058        5169 :     context.statusChangedCallback = [this](dht::NodeStatus s4, dht::NodeStatus s6) {
    2059        6892 :         JAMI_LOG("[Account {}] DHT status: IPv4 {}; IPv6 {}", getAccountID(), dhtStatusStr(s4), dhtStatusStr(s6));
    2060             :         RegistrationState state;
    2061        1723 :         auto newStatus = std::max(s4, s6);
    2062        1723 :         switch (newStatus) {
    2063         579 :         case dht::NodeStatus::Connecting:
    2064         579 :             state = RegistrationState::TRYING;
    2065         579 :             break;
    2066        1144 :         case dht::NodeStatus::Connected:
    2067        1144 :             state = RegistrationState::REGISTERED;
    2068        1144 :             break;
    2069           0 :         case dht::NodeStatus::Disconnected:
    2070           0 :             state = RegistrationState::UNREGISTERED;
    2071           0 :             break;
    2072           0 :         default:
    2073           0 :             state = RegistrationState::ERROR_GENERIC;
    2074           0 :             break;
    2075             :         }
    2076             : 
    2077        1723 :         setRegistrationState(state);
    2078        2323 :     };
    2079             : 
    2080        2931 :     context.identityAnnouncedCb = [this](bool ok) {
    2081         594 :         if (!ok) {
    2082          52 :             JAMI_ERROR("[Account {}] Identity announcement failed", getAccountID());
    2083          13 :             return;
    2084             :         }
    2085        2324 :         JAMI_WARNING("[Account {}] Identity announcement succeeded", getAccountID());
    2086         581 :         accountManager_
    2087        1818 :             ->startSync([this](const std::shared_ptr<dht::crypto::Certificate>& crt) { onAccountDeviceFound(crt); },
    2088         581 :                         [this] { onAccountDeviceAnnounced(); },
    2089         581 :                         publishPresence_);
    2090         600 :     };
    2091             : 
    2092         600 :     return context;
    2093           0 : }
    2094             : 
    2095             : void
    2096         656 : JamiAccount::onAccountDeviceFound(const std::shared_ptr<dht::crypto::Certificate>& crt)
    2097             : {
    2098         656 :     if (jami::Manager::instance().syncOnRegister) {
    2099         656 :         if (!crt)
    2100         584 :             return;
    2101         656 :         auto deviceId = crt->getLongId().toString();
    2102         656 :         if (accountManager_->getInfo()->deviceId == deviceId)
    2103         584 :             return;
    2104             : 
    2105          72 :         dht::ThreadPool::io().run([w = weak(), deviceId, crt] {
    2106          72 :             auto shared = w.lock();
    2107          72 :             if (!shared)
    2108           0 :                 return;
    2109          72 :             std::unique_lock lk(shared->connManagerMtx_);
    2110          72 :             shared->initConnectionManager();
    2111          72 :             lk.unlock();
    2112          72 :             std::shared_lock slk(shared->connManagerMtx_);
    2113             :             // NOTE: connectionManager_ and channelHandlers_ get initialized at the
    2114             :             // same time and are both protected by connManagerMtx_, so this check
    2115             :             // ensures that the access to channelHandlers_ below is valid.
    2116          72 :             if (!shared->connectionManager_)
    2117           0 :                 return;
    2118          72 :             shared->requestMessageConnection(shared->getUsername(), crt->getLongId(), "sync");
    2119          72 :             if (!shared->syncModule()->isConnected(crt->getLongId())) {
    2120          71 :                 shared->channelHandlers_[Uri::Scheme::SYNC]
    2121          71 :                     ->connect(crt->getLongId(),
    2122             :                               "",
    2123          71 :                               [](const std::shared_ptr<dhtnet::ChannelSocket>& /*socket*/,
    2124          71 :                                  const DeviceId& /*deviceId*/) {});
    2125             :             }
    2126          72 :         });
    2127         656 :     }
    2128             : }
    2129             : 
    2130             : void
    2131         581 : JamiAccount::onAccountDeviceAnnounced()
    2132             : {
    2133         581 :     if (jami::Manager::instance().syncOnRegister) {
    2134         581 :         deviceAnnounced_ = true;
    2135             : 
    2136             :         // Bootstrap at the end to avoid to be long to load.
    2137         581 :         dht::ThreadPool::io().run([w = weak()] {
    2138         581 :             if (auto shared = w.lock())
    2139         581 :                 shared->convModule()->bootstrap();
    2140         581 :         });
    2141         581 :         emitSignal<libjami::ConfigurationSignal::VolatileDetailsChanged>(accountID_, getVolatileAccountDetails());
    2142             :     }
    2143         581 : }
    2144             : 
    2145             : bool
    2146         711 : JamiAccount::onICERequest(const DeviceId& deviceId)
    2147             : {
    2148         711 :     std::promise<bool> accept;
    2149         711 :     std::future<bool> fut = accept.get_future();
    2150         711 :     accountManager_->findCertificate(deviceId, [this, &accept](const std::shared_ptr<dht::crypto::Certificate>& cert) {
    2151         711 :         if (!cert) {
    2152           0 :             accept.set_value(false);
    2153           0 :             return;
    2154             :         }
    2155         711 :         dht::InfoHash peer_account_id;
    2156         711 :         auto res = accountManager_->onPeerCertificate(cert, this->config().dhtPublicInCalls, peer_account_id);
    2157        2844 :         JAMI_LOG("[Account {}] [device {}] {} ICE request from {}",
    2158             :                  getAccountID(),
    2159             :                  cert->getLongId(),
    2160             :                  res ? "Accepting" : "Discarding",
    2161             :                  peer_account_id);
    2162         711 :         accept.set_value(res);
    2163             :     });
    2164         711 :     fut.wait();
    2165         711 :     auto result = fut.get();
    2166         711 :     return result;
    2167         711 : }
    2168             : 
    2169             : bool
    2170        2041 : JamiAccount::onChannelRequest(const std::shared_ptr<dht::crypto::Certificate>& cert, const std::string& name)
    2171             : {
    2172        8159 :     JAMI_LOG("[Account {}] [device {}] New channel requested: '{}'", getAccountID(), cert->getLongId(), name);
    2173             : 
    2174        2041 :     if (this->config().turnEnabled && turnCache_) {
    2175        2040 :         auto addr = turnCache_->getResolvedTurn();
    2176        2041 :         if (addr == std::nullopt) {
    2177             :             // If TURN is enabled, but no TURN cached, there can be a temporary
    2178             :             // resolution error to solve. Sometimes, a connectivity change is not
    2179             :             // enough, so even if this case is really rare, it should be easy to avoid.
    2180          11 :             turnCache_->refresh();
    2181             :         }
    2182             :     }
    2183             : 
    2184        2040 :     auto uri = Uri(name);
    2185        2041 :     std::shared_lock lk(connManagerMtx_);
    2186        2038 :     auto itHandler = channelHandlers_.find(uri.scheme());
    2187        2040 :     if (itHandler != channelHandlers_.end() && itHandler->second)
    2188        1990 :         return itHandler->second->onRequest(cert, name);
    2189          47 :     return name == "sip";
    2190        2040 : }
    2191             : 
    2192             : void
    2193        3884 : JamiAccount::onConnectionReady(const DeviceId& deviceId,
    2194             :                                const std::string& name,
    2195             :                                std::shared_ptr<dhtnet::ChannelSocket> channel)
    2196             : {
    2197        3884 :     if (channel) {
    2198        3881 :         auto cert = channel->peerCertificate();
    2199        3883 :         if (!cert || !cert->issuer)
    2200           0 :             return;
    2201        3882 :         auto peerId = cert->issuer->getId().toString();
    2202             :         // A connection request can be sent just before member is banned and this must be ignored.
    2203        3882 :         if (accountManager()->getCertificateStatus(peerId) == dhtnet::tls::TrustStore::PermissionStatus::BANNED) {
    2204          66 :             channel->shutdown();
    2205          66 :             return;
    2206             :         }
    2207        3818 :         if (name == "sip") {
    2208          66 :             cacheSIPConnection(std::move(channel), peerId, deviceId);
    2209        3751 :         } else if (name.find("git://") == 0) {
    2210        1660 :             auto sep = name.find_last_of('/');
    2211        1660 :             auto conversationId = name.substr(sep + 1);
    2212        1660 :             auto remoteDevice = name.substr(6, sep - 6);
    2213             : 
    2214        1660 :             if (channel->isInitiator()) {
    2215             :                 // Check if wanted remote is our side (git://remoteDevice/conversationId)
    2216         827 :                 return;
    2217             :             }
    2218             : 
    2219             :             // Check if pull from banned device
    2220         833 :             if (convModule()->isBanned(conversationId, remoteDevice)) {
    2221           0 :                 JAMI_WARNING("[Account {:s}] [Conversation {}] Git server requested, but the "
    2222             :                              "device is unauthorized ({:s}) ",
    2223             :                              getAccountID(),
    2224             :                              conversationId,
    2225             :                              remoteDevice);
    2226           0 :                 channel->shutdown();
    2227           0 :                 return;
    2228             :             }
    2229             : 
    2230         833 :             auto sock = convModule()->gitSocket(deviceId.toString(), conversationId);
    2231         833 :             if (sock == channel) {
    2232             :                 // The onConnectionReady is already used as client (for retrieving messages)
    2233             :                 // So it's not the server socket
    2234           0 :                 return;
    2235             :             }
    2236        3332 :             JAMI_LOG("[Account {:s}] [Conversation {}] [device {}] Git server requested",
    2237             :                      accountID_,
    2238             :                      conversationId,
    2239             :                      deviceId.toString());
    2240         833 :             auto gs = std::make_unique<GitServer>(accountID_, conversationId, channel);
    2241         833 :             syncCnt_.fetch_add(1);
    2242         833 :             gs->setOnFetched([w = weak(), conversationId, deviceId](const std::string& commit) {
    2243        1115 :                 dht::ThreadPool::computation().run([w, conversationId, deviceId, commit]() {
    2244        1115 :                     if (auto shared = w.lock()) {
    2245        1115 :                         shared->convModule()->setFetched(conversationId, deviceId.toString(), commit);
    2246        2230 :                         if (shared->syncCnt_.fetch_sub(1) == 1) {
    2247         311 :                             emitSignal<libjami::ConversationSignal::ConversationCloned>(shared->getAccountID().c_str());
    2248             :                         }
    2249        1115 :                     }
    2250        1115 :                 });
    2251        1115 :             });
    2252         833 :             const dht::Value::Id serverId = ValueIdDist()(rand);
    2253             :             {
    2254         833 :                 std::lock_guard lk(gitServersMtx_);
    2255         833 :                 gitServers_[serverId] = std::move(gs);
    2256         833 :             }
    2257         833 :             channel->onShutdown([w = weak(), serverId](const std::error_code&) {
    2258             :                 // Run on main thread to avoid to be in mxSock's eventLoop
    2259         833 :                 runOnMainThread([serverId, w]() {
    2260         833 :                     if (auto sthis = w.lock()) {
    2261         833 :                         std::lock_guard lk(sthis->gitServersMtx_);
    2262         833 :                         sthis->gitServers_.erase(serverId);
    2263        1666 :                     }
    2264         833 :                 });
    2265         833 :             });
    2266        2487 :         } else {
    2267             :             // TODO move git://
    2268        2091 :             std::shared_lock lk(connManagerMtx_);
    2269        2092 :             auto uri = Uri(name);
    2270        2090 :             auto itHandler = channelHandlers_.find(uri.scheme());
    2271        2092 :             if (itHandler != channelHandlers_.end() && itHandler->second)
    2272        2086 :                 itHandler->second->onReady(cert, name, std::move(channel));
    2273        2090 :         }
    2274        4774 :     }
    2275             : }
    2276             : 
    2277             : void
    2278        1570 : JamiAccount::conversationNeedsSyncing(std::shared_ptr<SyncMsg>&& syncMsg)
    2279             : {
    2280        1570 :     dht::ThreadPool::computation().run([w = weak(), syncMsg] {
    2281        1570 :         if (auto shared = w.lock()) {
    2282        1570 :             const auto& config = shared->config();
    2283             :             // For JAMS account, we must update the server
    2284             :             // for now, only sync with the JAMS server for changes to the conversation list
    2285        1570 :             if (!config.managerUri.empty() && !syncMsg)
    2286           0 :                 if (auto am = shared->accountManager())
    2287           0 :                     am->syncDevices();
    2288        1570 :             if (auto* sm = shared->syncModule())
    2289        1570 :                 sm->syncWithConnected(syncMsg);
    2290        1570 :         }
    2291        1570 :     });
    2292        1570 : }
    2293             : 
    2294             : uint64_t
    2295       14398 : JamiAccount::conversationSendMessage(const std::string& uri,
    2296             :                                      const DeviceId& device,
    2297             :                                      const std::map<std::string, std::string>& msg,
    2298             :                                      uint64_t token)
    2299             : {
    2300             :     // No need to retrigger, sendTextMessage will call
    2301             :     // messageEngine_.sendMessage, already retriggering on
    2302             :     // main thread.
    2303       14398 :     auto deviceId = device ? device.toString() : "";
    2304       28771 :     return sendTextMessage(uri, deviceId, msg, token);
    2305       14387 : }
    2306             : 
    2307             : void
    2308        2099 : JamiAccount::onConversationNeedSocket(const std::string& convId,
    2309             :                                       const std::string& deviceId,
    2310             :                                       ChannelCb&& cb,
    2311             :                                       const std::string& type)
    2312             : {
    2313        2099 :     dht::ThreadPool::io().run([w = weak(), convId, deviceId, cb = std::move(cb), type] {
    2314        2099 :         auto shared = w.lock();
    2315        2100 :         if (!shared)
    2316           0 :             return;
    2317        2100 :         if (auto socket = shared->convModule()->gitSocket(deviceId, convId)) {
    2318        1213 :             if (!cb(socket))
    2319           0 :                 socket->shutdown();
    2320             :             else
    2321        1213 :                 cb({});
    2322        1213 :             return;
    2323        2100 :         }
    2324         887 :         std::shared_lock lkCM(shared->connManagerMtx_);
    2325         886 :         if (!shared->connectionManager_) {
    2326           5 :             lkCM.unlock();
    2327           5 :             cb({});
    2328           5 :             return;
    2329             :         }
    2330             : 
    2331        4409 :         shared->connectionManager_->connectDevice(
    2332         882 :             DeviceId(deviceId),
    2333        2646 :             fmt::format("git://{}/{}", deviceId, convId),
    2334        1764 :             [w, cb = std::move(cb), convId](std::shared_ptr<dhtnet::ChannelSocket> socket, const DeviceId&) {
    2335         882 :                 dht::ThreadPool::io().run([w, cb = std::move(cb), socket = std::move(socket), convId] {
    2336         882 :                     if (socket) {
    2337         833 :                         socket->onShutdown([w, deviceId = socket->deviceId(), convId](const std::error_code&) {
    2338         833 :                             dht::ThreadPool::io().run([w, deviceId, convId] {
    2339         833 :                                 if (auto shared = w.lock())
    2340         833 :                                     shared->convModule()->removeGitSocket(deviceId.toString(), convId);
    2341         833 :                             });
    2342         833 :                         });
    2343         833 :                         if (!cb(socket))
    2344           3 :                             socket->shutdown();
    2345             :                     } else
    2346          49 :                         cb({});
    2347         882 :                 });
    2348         882 :             },
    2349             :             false,
    2350             :             false,
    2351         882 :             type);
    2352        2105 :     });
    2353        2100 : }
    2354             : 
    2355             : void
    2356         546 : JamiAccount::onConversationNeedSwarmSocket(const std::string& convId,
    2357             :                                            const std::string& deviceId,
    2358             :                                            ChannelCb&& cb,
    2359             :                                            const std::string& type)
    2360             : {
    2361         546 :     dht::ThreadPool::io().run([w = weak(), convId, deviceId, cb = std::forward<ChannelCb&&>(cb), type] {
    2362         545 :         auto shared = w.lock();
    2363         546 :         if (!shared)
    2364           0 :             return;
    2365         546 :         auto* cm = shared->convModule();
    2366         546 :         std::shared_lock lkCM(shared->connManagerMtx_);
    2367         546 :         if (!shared->connectionManager_ || !cm || cm->isBanned(convId, deviceId)) {
    2368          58 :             asio::post(*Manager::instance().ioContext(), [cb = std::move(cb)] { cb({}); });
    2369          29 :             return;
    2370             :         }
    2371         517 :         DeviceId device(deviceId);
    2372         517 :         auto swarmUri = fmt::format("swarm://{}", convId);
    2373         517 :         if (!shared->connectionManager_->isConnecting(device, swarmUri)) {
    2374         992 :             shared->connectionManager_->connectDevice(
    2375             :                 device,
    2376             :                 swarmUri,
    2377         496 :                 [w,
    2378         496 :                  cb = std::move(cb),
    2379             :                  wam = std::weak_ptr(shared->accountManager())](std::shared_ptr<dhtnet::ChannelSocket> socket,
    2380             :                                                                 const DeviceId& deviceId) {
    2381         496 :                     dht::ThreadPool::io().run([w, wam, cb = std::move(cb), socket = std::move(socket), deviceId] {
    2382         496 :                         if (socket) {
    2383         244 :                             auto shared = w.lock();
    2384         244 :                             auto am = wam.lock();
    2385         244 :                             auto remoteCert = socket->peerCertificate();
    2386         244 :                             if (!remoteCert || !remoteCert->issuer) {
    2387           0 :                                 cb(nullptr);
    2388           0 :                                 return;
    2389             :                             }
    2390         244 :                             auto uri = remoteCert->issuer->getId().toString();
    2391         488 :                             if (!shared || !am
    2392         488 :                                 || am->getCertificateStatus(uri) == dhtnet::tls::TrustStore::PermissionStatus::BANNED) {
    2393           0 :                                 cb(nullptr);
    2394           0 :                                 return;
    2395             :                             }
    2396         244 :                             shared->requestMessageConnection(uri, deviceId, "");
    2397         244 :                         }
    2398         496 :                         cb(socket);
    2399             :                     });
    2400         496 :                 });
    2401             :         }
    2402         575 :     });
    2403         546 : }
    2404             : 
    2405             : void
    2406           1 : JamiAccount::conversationOneToOneReceive(const std::string& convId, const std::string& from)
    2407             : {
    2408           1 :     accountManager_->findCertificate(dht::InfoHash(from),
    2409           1 :                                      [this, from, convId](const std::shared_ptr<dht::crypto::Certificate>& cert) {
    2410           1 :                                          const auto* info = accountManager_->getInfo();
    2411           1 :                                          if (!cert || !info)
    2412           0 :                                              return;
    2413           2 :                                          info->contacts->onTrustRequest(dht::InfoHash(from),
    2414             :                                                                         cert->getSharedPublicKey(),
    2415             :                                                                         time(nullptr),
    2416             :                                                                         false,
    2417           1 :                                                                         convId,
    2418             :                                                                         {});
    2419             :                                      });
    2420           1 : }
    2421             : 
    2422             : ConversationModule*
    2423       27585 : JamiAccount::convModule(bool noCreation)
    2424             : {
    2425       27585 :     if (noCreation)
    2426        5116 :         return convModule_.get();
    2427       22469 :     if (!accountManager() || currentDeviceId() == "") {
    2428           0 :         JAMI_ERROR("[Account {}] Calling convModule() with an uninitialized account", getAccountID());
    2429           0 :         return nullptr;
    2430             :     }
    2431       22464 :     std::unique_lock lock(configurationMutex_);
    2432       22463 :     std::lock_guard lk(moduleMtx_);
    2433       22465 :     if (!convModule_) {
    2434        1152 :         convModule_ = std::make_unique<ConversationModule>(
    2435         576 :             shared(),
    2436         576 :             accountManager_,
    2437        1570 :             [this](auto&& syncMsg) { conversationNeedsSyncing(std::forward<std::shared_ptr<SyncMsg>>(syncMsg)); },
    2438       14398 :             [this](auto&& uri, auto&& device, auto&& msg, auto token = 0) {
    2439       14398 :                 return conversationSendMessage(uri, device, msg, token);
    2440             :             },
    2441        2099 :             [this](const auto& convId, const auto& deviceId, auto&& cb, const auto& connectionType) {
    2442        2099 :                 onConversationNeedSocket(convId, deviceId, std::forward<decltype(cb)>(cb), connectionType);
    2443        2100 :             },
    2444         546 :             [this](const auto& convId, const auto& deviceId, auto&& cb, const auto& connectionType) {
    2445         546 :                 onConversationNeedSwarmSocket(convId, deviceId, std::forward<decltype(cb)>(cb), connectionType);
    2446         546 :             },
    2447           1 :             [this](const auto& convId, const auto& from) { conversationOneToOneReceive(convId, from); },
    2448        1152 :             autoLoadConversations_);
    2449             :     }
    2450       22467 :     return convModule_.get();
    2451       22468 : }
    2452             : 
    2453             : SyncModule*
    2454        2497 : JamiAccount::syncModule()
    2455             : {
    2456        2497 :     if (!accountManager() || currentDeviceId() == "") {
    2457           0 :         JAMI_ERR() << "Calling syncModule() with an uninitialized account.";
    2458           0 :         return nullptr;
    2459             :     }
    2460        2497 :     std::lock_guard lk(moduleMtx_);
    2461        2497 :     if (!syncModule_)
    2462         564 :         syncModule_ = std::make_unique<SyncModule>(shared());
    2463        2497 :     return syncModule_.get();
    2464        2497 : }
    2465             : 
    2466             : void
    2467       13228 : JamiAccount::onTextMessage(const std::string& id,
    2468             :                            const std::string& from,
    2469             :                            const std::shared_ptr<dht::crypto::Certificate>& peerCert,
    2470             :                            const std::map<std::string, std::string>& payloads)
    2471             : {
    2472             :     try {
    2473       13228 :         const std::string fromUri {parseJamiUri(from)};
    2474       13224 :         SIPAccountBase::onTextMessage(id, fromUri, peerCert, payloads);
    2475       13237 :     } catch (...) {
    2476           0 :     }
    2477       13235 : }
    2478             : 
    2479             : void
    2480           0 : JamiAccount::loadConversation(const std::string& convId)
    2481             : {
    2482           0 :     if (auto* cm = convModule(true))
    2483           0 :         cm->loadSingleConversation(convId);
    2484           0 : }
    2485             : 
    2486             : void
    2487         864 : JamiAccount::doUnregister(bool forceShutdownConnections)
    2488             : {
    2489         864 :     std::unique_lock lock(configurationMutex_);
    2490         864 :     if (registrationState_ >= RegistrationState::ERROR_GENERIC) {
    2491         142 :         return;
    2492             :     }
    2493             : 
    2494         722 :     std::mutex mtx;
    2495         722 :     std::condition_variable cv;
    2496         722 :     bool shutdown_complete {false};
    2497             : 
    2498         722 :     if (peerDiscovery_) {
    2499           0 :         peerDiscovery_->stopPublish(PEER_DISCOVERY_JAMI_SERVICE);
    2500           0 :         peerDiscovery_->stopDiscovery(PEER_DISCOVERY_JAMI_SERVICE);
    2501             :     }
    2502             : 
    2503         722 :     JAMI_WARN("[Account %s] Unregistering account %p", getAccountID().c_str(), this);
    2504         722 :     dht_->shutdown(
    2505         722 :         [&] {
    2506         722 :             JAMI_WARN("[Account %s] DHT shutdown complete", getAccountID().c_str());
    2507         722 :             std::lock_guard lock(mtx);
    2508         722 :             shutdown_complete = true;
    2509         722 :             cv.notify_all();
    2510         722 :         },
    2511             :         true);
    2512             : 
    2513             :     {
    2514         722 :         std::lock_guard lk(pendingCallsMutex_);
    2515         722 :         pendingCalls_.clear();
    2516         722 :     }
    2517             : 
    2518             :     // Stop all current P2P connections if account is disabled
    2519             :     // or if explicitly requested by the caller.
    2520             :     // NOTE: Leaving the connections open is useful when changing an account's config.
    2521         722 :     if (not isEnabled() || forceShutdownConnections)
    2522         706 :         shutdownConnections();
    2523             : 
    2524             :     // Release current UPnP mapping if any.
    2525         722 :     if (upnpCtrl_ and dhtUpnpMapping_.isValid()) {
    2526           0 :         upnpCtrl_->releaseMapping(dhtUpnpMapping_);
    2527             :     }
    2528             : 
    2529             :     {
    2530         722 :         std::unique_lock lock(mtx);
    2531        2038 :         cv.wait(lock, [&] { return shutdown_complete; });
    2532         722 :     }
    2533         722 :     dht_->join();
    2534         722 :     setRegistrationState(RegistrationState::UNREGISTERED);
    2535             : 
    2536         722 :     lock.unlock();
    2537             : 
    2538             : #ifdef ENABLE_PLUGIN
    2539         722 :     jami::Manager::instance().getJamiPluginManager().getChatServicesManager().cleanChatSubjects(getAccountID());
    2540             : #endif
    2541         864 : }
    2542             : 
    2543             : void
    2544        4409 : JamiAccount::setRegistrationState(RegistrationState state, int detail_code, const std::string& detail_str)
    2545             : {
    2546        4409 :     if (registrationState_ != state) {
    2547        3153 :         if (state == RegistrationState::REGISTERED) {
    2548        2380 :             JAMI_WARNING("[Account {}] Connected", getAccountID());
    2549         595 :             turnCache_->refresh();
    2550         595 :             if (connectionManager_)
    2551         587 :                 connectionManager_->storeActiveIpAddress();
    2552        2558 :         } else if (state == RegistrationState::TRYING) {
    2553        2400 :             JAMI_WARNING("[Account {}] Connecting…", getAccountID());
    2554             :         } else {
    2555        1958 :             deviceAnnounced_ = false;
    2556        7832 :             JAMI_WARNING("[Account {}] Disconnected", getAccountID());
    2557             :         }
    2558             :     }
    2559             :     // Update registrationState_ & emit signals
    2560        4409 :     Account::setRegistrationState(state, detail_code, detail_str);
    2561        4409 : }
    2562             : 
    2563             : void
    2564           0 : JamiAccount::reloadContacts()
    2565             : {
    2566           0 :     accountManager_->reloadContacts();
    2567           0 : }
    2568             : 
    2569             : void
    2570           0 : JamiAccount::connectivityChanged()
    2571             : {
    2572           0 :     if (not isUsable()) {
    2573             :         // nothing to do
    2574           0 :         return;
    2575             :     }
    2576           0 :     JAMI_WARNING("[{}] connectivityChanged", getAccountID());
    2577             : 
    2578           0 :     if (auto* cm = convModule())
    2579           0 :         cm->connectivityChanged();
    2580           0 :     dht_->connectivityChanged();
    2581             :     {
    2582           0 :         std::shared_lock lkCM(connManagerMtx_);
    2583           0 :         if (connectionManager_) {
    2584           0 :             connectionManager_->connectivityChanged();
    2585             :             // reset cache
    2586           0 :             connectionManager_->setPublishedAddress({});
    2587             :         }
    2588           0 :     }
    2589             : }
    2590             : 
    2591             : bool
    2592           0 : JamiAccount::findCertificate(const dht::InfoHash& h,
    2593             :                              std::function<void(const std::shared_ptr<dht::crypto::Certificate>&)>&& cb)
    2594             : {
    2595           0 :     if (accountManager_)
    2596           0 :         return accountManager_->findCertificate(h, std::move(cb));
    2597           0 :     return false;
    2598             : }
    2599             : 
    2600             : bool
    2601           0 : JamiAccount::findCertificate(const dht::PkId& id,
    2602             :                              std::function<void(const std::shared_ptr<dht::crypto::Certificate>&)>&& cb)
    2603             : {
    2604           0 :     if (accountManager_)
    2605           0 :         return accountManager_->findCertificate(id, std::move(cb));
    2606           0 :     return false;
    2607             : }
    2608             : 
    2609             : bool
    2610          22 : JamiAccount::findCertificate(const std::string& crt_id)
    2611             : {
    2612          22 :     if (accountManager_)
    2613          22 :         return accountManager_->findCertificate(dht::InfoHash(crt_id));
    2614           0 :     return false;
    2615             : }
    2616             : 
    2617             : bool
    2618          25 : JamiAccount::setCertificateStatus(const std::string& cert_id, dhtnet::tls::TrustStore::PermissionStatus status)
    2619             : {
    2620          25 :     bool done = accountManager_ ? accountManager_->setCertificateStatus(cert_id, status) : false;
    2621          25 :     if (done) {
    2622          22 :         findCertificate(cert_id);
    2623          22 :         emitSignal<libjami::ConfigurationSignal::CertificateStateChanged>(getAccountID(),
    2624             :                                                                           cert_id,
    2625             :                                                                           dhtnet::tls::TrustStore::statusToStr(status));
    2626             :     }
    2627          25 :     return done;
    2628             : }
    2629             : 
    2630             : bool
    2631           0 : JamiAccount::setCertificateStatus(const std::shared_ptr<crypto::Certificate>& cert,
    2632             :                                   dhtnet::tls::TrustStore::PermissionStatus status,
    2633             :                                   bool local)
    2634             : {
    2635           0 :     bool done = accountManager_ ? accountManager_->setCertificateStatus(cert, status, local) : false;
    2636           0 :     if (done) {
    2637           0 :         findCertificate(cert->getLongId().toString());
    2638           0 :         emitSignal<libjami::ConfigurationSignal::CertificateStateChanged>(getAccountID(),
    2639           0 :                                                                           cert->getLongId().toString(),
    2640             :                                                                           dhtnet::tls::TrustStore::statusToStr(status));
    2641             :     }
    2642           0 :     return done;
    2643             : }
    2644             : 
    2645             : std::vector<std::string>
    2646           0 : JamiAccount::getCertificatesByStatus(dhtnet::tls::TrustStore::PermissionStatus status)
    2647             : {
    2648           0 :     if (accountManager_)
    2649           0 :         return accountManager_->getCertificatesByStatus(status);
    2650           0 :     return {};
    2651             : }
    2652             : 
    2653             : bool
    2654           0 : JamiAccount::isMessageTreated(dht::Value::Id id)
    2655             : {
    2656           0 :     std::lock_guard lock(messageMutex_);
    2657           0 :     return !treatedMessages_.add(id);
    2658           0 : }
    2659             : 
    2660             : bool
    2661          13 : JamiAccount::sha3SumVerify() const
    2662             : {
    2663          13 :     return !noSha3sumVerification_;
    2664             : }
    2665             : 
    2666             : #ifdef LIBJAMI_TEST
    2667             : void
    2668           1 : JamiAccount::noSha3sumVerification(bool newValue)
    2669             : {
    2670           1 :     noSha3sumVerification_ = newValue;
    2671           1 : }
    2672             : #endif
    2673             : 
    2674             : std::map<std::string, std::string>
    2675           0 : JamiAccount::getKnownDevices() const
    2676             : {
    2677           0 :     std::lock_guard lock(configurationMutex_);
    2678           0 :     if (not accountManager_ or not accountManager_->getInfo())
    2679           0 :         return {};
    2680           0 :     std::map<std::string, std::string> ids;
    2681           0 :     for (const auto& d : accountManager_->getKnownDevices()) {
    2682           0 :         auto id = d.first.toString();
    2683           0 :         auto label = d.second.name.empty() ? id.substr(0, 8) : d.second.name;
    2684           0 :         ids.emplace(std::move(id), std::move(label));
    2685           0 :     }
    2686           0 :     return ids;
    2687           0 : }
    2688             : 
    2689             : void
    2690           0 : JamiAccount::loadCachedUrl(const std::string& url,
    2691             :                            const std::filesystem::path& cachePath,
    2692             :                            const std::chrono::seconds& cacheDuration,
    2693             :                            const std::function<void(const dht::http::Response& response)>& cb)
    2694             : {
    2695           0 :     dht::ThreadPool::io().run([cb, url, cachePath, cacheDuration, w = weak()]() {
    2696             :         try {
    2697           0 :             std::string data;
    2698             :             {
    2699           0 :                 std::lock_guard lk(dhtnet::fileutils::getFileLock(cachePath));
    2700           0 :                 data = fileutils::loadCacheTextFile(cachePath, cacheDuration);
    2701           0 :             }
    2702           0 :             dht::http::Response ret;
    2703           0 :             ret.body = std::move(data);
    2704           0 :             ret.status_code = 200;
    2705           0 :             cb(ret);
    2706           0 :         } catch (const std::exception& e) {
    2707           0 :             JAMI_LOG("Failed to load '{}' from '{}': {}", url, cachePath, e.what());
    2708             : 
    2709           0 :             if (auto sthis = w.lock()) {
    2710             :                 auto req = std::make_shared<dht::http::Request>(
    2711           0 :                     *Manager::instance().ioContext(), url, [cb, cachePath, w](const dht::http::Response& response) {
    2712           0 :                         if (response.status_code == 200) {
    2713             :                             try {
    2714           0 :                                 std::lock_guard lk(dhtnet::fileutils::getFileLock(cachePath));
    2715           0 :                                 fileutils::saveFile(cachePath,
    2716           0 :                                                     (const uint8_t*) response.body.data(),
    2717             :                                                     response.body.size(),
    2718             :                                                     0600);
    2719           0 :                                 JAMI_LOG("Cached result to '{}'", cachePath);
    2720           0 :                             } catch (const std::exception& ex) {
    2721           0 :                                 JAMI_WARNING("Failed to save result to '{}': {}", cachePath, ex.what());
    2722           0 :                             }
    2723           0 :                             cb(response);
    2724             :                         } else {
    2725             :                             try {
    2726           0 :                                 if (std::filesystem::exists(cachePath)) {
    2727           0 :                                     JAMI_WARNING("Failed to download URL, using cached data");
    2728           0 :                                     std::string data;
    2729             :                                     {
    2730           0 :                                         std::lock_guard lk(dhtnet::fileutils::getFileLock(cachePath));
    2731           0 :                                         data = fileutils::loadTextFile(cachePath);
    2732           0 :                                     }
    2733           0 :                                     dht::http::Response ret;
    2734           0 :                                     ret.body = std::move(data);
    2735           0 :                                     ret.status_code = 200;
    2736           0 :                                     cb(ret);
    2737           0 :                                 } else
    2738           0 :                                     throw std::runtime_error("No cached data");
    2739           0 :                             } catch (...) {
    2740           0 :                                 cb(response);
    2741           0 :                             }
    2742             :                         }
    2743           0 :                         if (auto req = response.request.lock())
    2744           0 :                             if (auto sthis = w.lock())
    2745           0 :                                 sthis->requests_.erase(req);
    2746           0 :                     });
    2747           0 :                 sthis->requests_.emplace(req);
    2748           0 :                 req->send();
    2749           0 :             }
    2750           0 :         }
    2751           0 :     });
    2752           0 : }
    2753             : 
    2754             : void
    2755         600 : JamiAccount::loadCachedProxyServer(std::function<void(const std::string& proxy)> cb)
    2756             : {
    2757         600 :     const auto& conf = config();
    2758         600 :     if (conf.proxyEnabled and proxyServerCached_.empty()) {
    2759           0 :         JAMI_DEBUG("[Account {:s}] Loading DHT proxy URL: {:s}", getAccountID(), conf.proxyListUrl);
    2760           0 :         if (conf.proxyListUrl.empty() or not conf.proxyListEnabled) {
    2761           0 :             cb(getDhtProxyServer(conf.proxyServer));
    2762             :         } else {
    2763           0 :             loadCachedUrl(conf.proxyListUrl,
    2764           0 :                           cachePath_ / "dhtproxylist",
    2765           0 :                           std::chrono::hours(24 * 3),
    2766           0 :                           [w = weak(), cb = std::move(cb)](const dht::http::Response& response) {
    2767           0 :                               if (auto sthis = w.lock()) {
    2768           0 :                                   if (response.status_code == 200) {
    2769           0 :                                       cb(sthis->getDhtProxyServer(response.body));
    2770             :                                   } else {
    2771           0 :                                       cb(sthis->getDhtProxyServer(sthis->config().proxyServer));
    2772             :                                   }
    2773           0 :                               }
    2774           0 :                           });
    2775             :         }
    2776             :     } else {
    2777         600 :         cb(proxyServerCached_);
    2778             :     }
    2779         600 : }
    2780             : 
    2781             : std::string
    2782           0 : JamiAccount::getDhtProxyServer(const std::string& serverList)
    2783             : {
    2784           0 :     if (proxyServerCached_.empty()) {
    2785           0 :         std::vector<std::string> proxys;
    2786             :         // Split the list of servers
    2787           0 :         std::sregex_iterator begin = {serverList.begin(), serverList.end(), PROXY_REGEX}, end;
    2788           0 :         for (auto it = begin; it != end; ++it) {
    2789           0 :             auto& match = *it;
    2790           0 :             if (match[5].matched and match[6].matched) {
    2791             :                 try {
    2792           0 :                     auto start = std::stoi(match[5]), end = std::stoi(match[6]);
    2793           0 :                     for (auto p = start; p <= end; p++)
    2794           0 :                         proxys.emplace_back(match[1].str() + match[2].str() + ":" + std::to_string(p));
    2795           0 :                 } catch (...) {
    2796           0 :                     JAMI_WARN("Malformed proxy, ignore it");
    2797           0 :                     continue;
    2798           0 :                 }
    2799             :             } else {
    2800           0 :                 proxys.emplace_back(match[0].str());
    2801             :             }
    2802           0 :         }
    2803           0 :         if (proxys.empty())
    2804           0 :             return {};
    2805             :         // Select one of the list as the current proxy.
    2806           0 :         auto randIt = proxys.begin();
    2807           0 :         std::advance(randIt, std::uniform_int_distribution<unsigned long>(0, proxys.size() - 1)(rand));
    2808           0 :         proxyServerCached_ = *randIt;
    2809             :         // Cache it!
    2810           0 :         dhtnet::fileutils::check_dir(cachePath_, 0700);
    2811           0 :         auto proxyCachePath = cachePath_ / "dhtproxy";
    2812           0 :         std::ofstream file(proxyCachePath);
    2813           0 :         JAMI_DEBUG("Cache DHT proxy server: {}", proxyServerCached_);
    2814           0 :         Json::Value node(Json::objectValue);
    2815           0 :         node[getProxyConfigKey()] = proxyServerCached_;
    2816           0 :         if (file.is_open())
    2817           0 :             file << node;
    2818             :         else
    2819           0 :             JAMI_WARNING("Unable to write into {}", proxyCachePath);
    2820           0 :     }
    2821           0 :     return proxyServerCached_;
    2822             : }
    2823             : 
    2824             : MatchRank
    2825           0 : JamiAccount::matches(std::string_view userName, std::string_view server) const
    2826             : {
    2827           0 :     if (not accountManager_ or not accountManager_->getInfo())
    2828           0 :         return MatchRank::NONE;
    2829             : 
    2830           0 :     if (userName == accountManager_->getInfo()->accountId || server == accountManager_->getInfo()->accountId
    2831           0 :         || userName == accountManager_->getInfo()->deviceId) {
    2832           0 :         JAMI_LOG("Matching account ID in request with username {}", userName);
    2833           0 :         return MatchRank::FULL;
    2834             :     } else {
    2835           0 :         return MatchRank::NONE;
    2836             :     }
    2837             : }
    2838             : 
    2839             : std::string
    2840          38 : JamiAccount::getFromUri() const
    2841             : {
    2842          38 :     std::string uri = "<sip:" + accountManager_->getInfo()->accountId + "@ring.dht>";
    2843          38 :     if (not config().displayName.empty())
    2844          76 :         return "\"" + config().displayName + "\" " + uri;
    2845           0 :     return uri;
    2846          38 : }
    2847             : 
    2848             : std::string
    2849         100 : JamiAccount::getToUri(const std::string& to) const
    2850             : {
    2851         100 :     auto username = to;
    2852         100 :     string_replace(username, "sip:", "");
    2853         200 :     return fmt::format("<sips:{};transport=tls>", username);
    2854         100 : }
    2855             : 
    2856             : std::string
    2857           7 : getDisplayed(const std::string& conversationId, const std::string& messageId)
    2858             : {
    2859             :     // implementing https://tools.ietf.org/rfc/rfc5438.txt
    2860             :     return fmt::format("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n"
    2861             :                        "<imdn><message-id>{}</message-id>\n"
    2862             :                        "{}"
    2863             :                        "<display-notification><status><displayed/></status></display-notification>\n"
    2864             :                        "</imdn>",
    2865             :                        messageId,
    2866          14 :                        conversationId.empty() ? "" : "<conversation>" + conversationId + "</conversation>");
    2867             : }
    2868             : 
    2869             : std::string
    2870           7 : getPIDF(const std::string& note)
    2871             : {
    2872             :     // implementing https://datatracker.ietf.org/doc/html/rfc3863
    2873             :     return fmt::format("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
    2874             :                        "<presence xmlns=\"urn:ietf:params:xml:ns:pidf\">\n"
    2875             :                        "    <tuple>\n"
    2876             :                        "    <status>\n"
    2877             :                        "        <basic>{}</basic>\n"
    2878             :                        "    </status>\n"
    2879             :                        "    </tuple>\n"
    2880             :                        "</presence>",
    2881          14 :                        note);
    2882             : }
    2883             : 
    2884             : void
    2885           5 : JamiAccount::setIsComposing(const std::string& conversationUri, bool isWriting)
    2886             : {
    2887           5 :     Uri uri(conversationUri);
    2888           5 :     std::string conversationId = {};
    2889           5 :     if (uri.scheme() == Uri::Scheme::SWARM) {
    2890           5 :         conversationId = uri.authority();
    2891             :     } else {
    2892           0 :         return;
    2893             :     }
    2894             : 
    2895           5 :     if (auto* cm = convModule(true)) {
    2896           5 :         if (auto typer = cm->getTypers(conversationId)) {
    2897           5 :             if (isWriting)
    2898           4 :                 typer->addTyper(getUsername(), true);
    2899             :             else
    2900           1 :                 typer->removeTyper(getUsername(), true);
    2901           5 :         }
    2902             :     }
    2903           5 : }
    2904             : 
    2905             : bool
    2906           9 : JamiAccount::setMessageDisplayed(const std::string& conversationUri, const std::string& messageId, int status)
    2907             : {
    2908           9 :     Uri uri(conversationUri);
    2909           9 :     std::string conversationId = {};
    2910           9 :     if (uri.scheme() == Uri::Scheme::SWARM)
    2911           9 :         conversationId = uri.authority();
    2912           9 :     auto sendMessage = status == (int) libjami::Account::MessageStates::DISPLAYED && isReadReceiptEnabled();
    2913           9 :     if (!conversationId.empty())
    2914           9 :         sendMessage &= convModule()->onMessageDisplayed(getUsername(), conversationId, messageId);
    2915           9 :     if (sendMessage)
    2916          14 :         sendInstantMessage(uri.authority(), {{MIME_TYPE_IMDN, getDisplayed(conversationId, messageId)}});
    2917           9 :     return true;
    2918           9 : }
    2919             : 
    2920             : std::string
    2921          66 : JamiAccount::getContactHeader(const std::shared_ptr<SipTransport>& sipTransport)
    2922             : {
    2923          66 :     if (sipTransport and sipTransport->get() != nullptr) {
    2924          66 :         auto* transport = sipTransport->get();
    2925          66 :         auto* td = reinterpret_cast<tls::AbstractSIPTransport::TransportData*>(transport);
    2926          66 :         auto address = td->self->getLocalAddress().toString(true);
    2927          66 :         bool reliable = transport->flag & PJSIP_TRANSPORT_RELIABLE;
    2928             :         return fmt::format("\"{}\" <sips:{}{}{};transport={}>",
    2929          66 :                            config().displayName,
    2930          66 :                            id_.second->getId().toString(),
    2931          66 :                            address.empty() ? "" : "@",
    2932             :                            address,
    2933         198 :                            reliable ? "tls" : "dtls");
    2934          66 :     } else {
    2935           0 :         JAMI_ERR("getContactHeader: no SIP transport provided");
    2936           0 :         return fmt::format("\"{}\" <sips:{}@ring.dht>", config().displayName, id_.second->getId().toString());
    2937             :     }
    2938             : }
    2939             : 
    2940             : void
    2941          67 : JamiAccount::addContact(const std::string& uri, bool confirmed)
    2942             : {
    2943          67 :     dht::InfoHash h(uri);
    2944          67 :     if (not h) {
    2945           4 :         JAMI_ERROR("addContact: invalid contact URI");
    2946           1 :         return;
    2947             :     }
    2948          66 :     auto conversation = convModule()->getOneToOneConversation(uri);
    2949          66 :     if (!confirmed && conversation.empty())
    2950          66 :         conversation = convModule()->startConversation(ConversationMode::ONE_TO_ONE, h);
    2951          66 :     std::unique_lock lock(configurationMutex_);
    2952          66 :     if (accountManager_)
    2953          66 :         accountManager_->addContact(h, confirmed, conversation);
    2954             :     else
    2955           0 :         JAMI_WARNING("[Account {}] addContact: account not loaded", getAccountID());
    2956          66 : }
    2957             : 
    2958             : void
    2959          19 : JamiAccount::removeContact(const std::string& uri, bool ban)
    2960             : {
    2961          19 :     std::lock_guard lock(configurationMutex_);
    2962          19 :     if (accountManager_)
    2963          19 :         accountManager_->removeContact(uri, ban);
    2964             :     else
    2965           0 :         JAMI_WARNING("[Account {}] removeContact: account not loaded", getAccountID());
    2966          19 : }
    2967             : 
    2968             : std::map<std::string, std::string>
    2969           6 : JamiAccount::getContactDetails(const std::string& uri) const
    2970             : {
    2971           6 :     std::lock_guard lock(configurationMutex_);
    2972          12 :     return accountManager_ ? accountManager_->getContactDetails(uri) : std::map<std::string, std::string> {};
    2973           6 : }
    2974             : 
    2975             : std::optional<Contact>
    2976         582 : JamiAccount::getContactInfo(const std::string& uri) const
    2977             : {
    2978         582 :     std::lock_guard lock(configurationMutex_);
    2979        1164 :     return accountManager_ ? accountManager_->getContactInfo(uri) : std::nullopt;
    2980         582 : }
    2981             : 
    2982             : std::vector<std::map<std::string, std::string>>
    2983           1 : JamiAccount::getContacts(bool includeRemoved) const
    2984             : {
    2985           1 :     std::lock_guard lock(configurationMutex_);
    2986           1 :     if (not accountManager_)
    2987           0 :         return {};
    2988           1 :     const auto& contacts = accountManager_->getContacts(includeRemoved);
    2989           1 :     std::vector<std::map<std::string, std::string>> ret;
    2990           1 :     ret.reserve(contacts.size());
    2991           2 :     for (const auto& c : contacts) {
    2992           1 :         auto details = c.second.toMap();
    2993           1 :         if (not details.empty()) {
    2994           1 :             details["id"] = c.first.toString();
    2995           1 :             ret.emplace_back(std::move(details));
    2996             :         }
    2997           1 :     }
    2998           1 :     return ret;
    2999           1 : }
    3000             : 
    3001             : /* trust requests */
    3002             : 
    3003             : std::vector<std::map<std::string, std::string>>
    3004         609 : JamiAccount::getTrustRequests() const
    3005             : {
    3006         609 :     std::lock_guard lock(configurationMutex_);
    3007        1218 :     return accountManager_ ? accountManager_->getTrustRequests() : std::vector<std::map<std::string, std::string>> {};
    3008         609 : }
    3009             : 
    3010             : bool
    3011          28 : JamiAccount::acceptTrustRequest(const std::string& from, bool includeConversation)
    3012             : {
    3013          28 :     dht::InfoHash h(from);
    3014          28 :     if (not h) {
    3015           0 :         JAMI_ERROR("addContact: invalid contact URI");
    3016           0 :         return false;
    3017             :     }
    3018          28 :     std::unique_lock lock(configurationMutex_);
    3019          28 :     if (accountManager_) {
    3020          28 :         if (!accountManager_->acceptTrustRequest(from, includeConversation)) {
    3021             :             // Note: unused for swarm
    3022             :             // Typically the case where the trust request doesn't exists, only incoming DHT messages
    3023           0 :             return accountManager_->addContact(h, true);
    3024             :         }
    3025          28 :         return true;
    3026             :     }
    3027           0 :     JAMI_WARNING("[Account {}] acceptTrustRequest: account not loaded", getAccountID());
    3028           0 :     return false;
    3029          28 : }
    3030             : 
    3031             : bool
    3032           2 : JamiAccount::discardTrustRequest(const std::string& from)
    3033             : {
    3034             :     // Remove 1:1 generated conv requests
    3035           2 :     auto requests = getTrustRequests();
    3036           4 :     for (const auto& req : requests) {
    3037           2 :         if (req.at(libjami::Account::TrustRequest::FROM) == from) {
    3038           2 :             convModule()->declineConversationRequest(req.at(libjami::Account::TrustRequest::CONVERSATIONID));
    3039             :         }
    3040             :     }
    3041             : 
    3042             :     // Remove trust request
    3043           2 :     std::lock_guard lock(configurationMutex_);
    3044           2 :     if (accountManager_)
    3045           2 :         return accountManager_->discardTrustRequest(from);
    3046           0 :     JAMI_WARNING("[Account {:s}] discardTrustRequest: account not loaded", getAccountID());
    3047           0 :     return false;
    3048           2 : }
    3049             : 
    3050             : void
    3051           3 : JamiAccount::declineConversationRequest(const std::string& conversationId)
    3052             : {
    3053           3 :     auto peerId = convModule()->peerFromConversationRequest(conversationId);
    3054           3 :     convModule()->declineConversationRequest(conversationId);
    3055           3 :     if (!peerId.empty()) {
    3056           3 :         std::lock_guard lock(configurationMutex_);
    3057           3 :         if (const auto* info = accountManager_->getInfo()) {
    3058             :             // Verify if we have a trust request with this peer + convId
    3059           3 :             auto req = info->contacts->getTrustRequest(dht::InfoHash(peerId));
    3060           6 :             if (req.find(libjami::Account::TrustRequest::CONVERSATIONID) != req.end()
    3061           6 :                 && req.at(libjami::Account::TrustRequest::CONVERSATIONID) == conversationId) {
    3062           1 :                 accountManager_->discardTrustRequest(peerId);
    3063           4 :                 JAMI_DEBUG("[Account {:s}] Declined trust request with {:s}", getAccountID(), peerId);
    3064             :             }
    3065           3 :         }
    3066           3 :     }
    3067           3 : }
    3068             : 
    3069             : void
    3070          57 : JamiAccount::sendTrustRequest(const std::string& to, const std::vector<uint8_t>& payload)
    3071             : {
    3072          57 :     dht::InfoHash h(to);
    3073          57 :     if (not h) {
    3074           0 :         JAMI_ERROR("addContact: invalid contact URI");
    3075           0 :         return;
    3076             :     }
    3077             :     // Here we cache payload sent by the client
    3078          57 :     auto requestPath = cachePath_ / "requests";
    3079          57 :     dhtnet::fileutils::recursive_mkdir(requestPath, 0700);
    3080          57 :     auto cachedFile = requestPath / to;
    3081          57 :     std::ofstream req(cachedFile, std::ios::trunc | std::ios::binary);
    3082          57 :     if (!req.is_open()) {
    3083           0 :         JAMI_ERROR("Unable to write data to {}", cachedFile);
    3084           0 :         return;
    3085             :     }
    3086             : 
    3087          57 :     if (not payload.empty()) {
    3088           3 :         req.write(reinterpret_cast<const char*>(payload.data()), static_cast<std::streamsize>(payload.size()));
    3089             :     }
    3090             : 
    3091          57 :     if (payload.size() >= 64000) {
    3092           1 :         JAMI_WARN() << "Trust request is too big. Remove payload";
    3093             :     }
    3094             : 
    3095          57 :     auto conversation = convModule()->getOneToOneConversation(to);
    3096          57 :     if (conversation.empty())
    3097           0 :         conversation = convModule()->startConversation(ConversationMode::ONE_TO_ONE, h);
    3098          57 :     if (not conversation.empty()) {
    3099          57 :         std::lock_guard lock(configurationMutex_);
    3100          57 :         if (accountManager_)
    3101         114 :             accountManager_->sendTrustRequest(to,
    3102             :                                               conversation,
    3103         114 :                                               payload.size() >= 64000 ? std::vector<uint8_t> {} : payload);
    3104             :         else
    3105           0 :             JAMI_WARNING("[Account {}] sendTrustRequest: account not loaded", getAccountID());
    3106          57 :     } else
    3107           0 :         JAMI_WARNING("[Account {}] sendTrustRequest: account not loaded", getAccountID());
    3108          57 : }
    3109             : 
    3110             : void
    3111           0 : JamiAccount::forEachDevice(const dht::InfoHash& to,
    3112             :                            std::function<void(const std::shared_ptr<dht::crypto::PublicKey>&)>&& op,
    3113             :                            std::function<void(bool)>&& end)
    3114             : {
    3115           0 :     accountManager_->forEachDevice(to, std::move(op), std::move(end));
    3116           0 : }
    3117             : 
    3118             : uint64_t
    3119       14383 : JamiAccount::sendTextMessage(const std::string& to,
    3120             :                              const std::string& deviceId,
    3121             :                              const std::map<std::string, std::string>& payloads,
    3122             :                              uint64_t refreshToken,
    3123             :                              bool onlyConnected)
    3124             : {
    3125       14383 :     Uri uri(to);
    3126       14377 :     if (uri.scheme() == Uri::Scheme::SWARM) {
    3127           0 :         sendInstantMessage(uri.authority(), payloads);
    3128           0 :         return 0;
    3129             :     }
    3130             : 
    3131       14379 :     std::string toUri;
    3132             :     try {
    3133       14372 :         toUri = parseJamiUri(to);
    3134           0 :     } catch (...) {
    3135           0 :         JAMI_ERROR("Failed to send a text message due to an invalid URI {}", to);
    3136           0 :         return 0;
    3137           0 :     }
    3138       14375 :     if (payloads.size() != 1) {
    3139           0 :         JAMI_ERROR("Multi-part im is not supported yet by JamiAccount");
    3140           0 :         return 0;
    3141             :     }
    3142       14374 :     return SIPAccountBase::sendTextMessage(toUri, deviceId, payloads, refreshToken, onlyConnected);
    3143       14387 : }
    3144             : 
    3145             : void
    3146       15091 : JamiAccount::sendMessage(const std::string& to,
    3147             :                          const std::string& deviceId,
    3148             :                          const std::map<std::string, std::string>& payloads,
    3149             :                          uint64_t token,
    3150             :                          bool retryOnTimeout,
    3151             :                          bool onlyConnected)
    3152             : {
    3153       15091 :     std::string toUri;
    3154             :     try {
    3155       15091 :         toUri = parseJamiUri(to);
    3156           0 :     } catch (...) {
    3157           0 :         JAMI_ERROR("[Account {}] Failed to send a text message due to an invalid URI {}", getAccountID(), to);
    3158           0 :         if (!onlyConnected)
    3159           0 :             messageEngine_.onMessageSent(to, token, false, deviceId);
    3160           0 :         return;
    3161           0 :     }
    3162       15091 :     if (payloads.size() != 1) {
    3163           0 :         JAMI_ERROR("Multi-part im is not supported");
    3164           0 :         if (!onlyConnected)
    3165           0 :             messageEngine_.onMessageSent(toUri, token, false, deviceId);
    3166           0 :         return;
    3167             :     }
    3168             : 
    3169             :     // Use the Message channel if available
    3170       15091 :     std::shared_lock clk(connManagerMtx_);
    3171       15090 :     auto* handler = static_cast<MessageChannelHandler*>(channelHandlers_[Uri::Scheme::MESSAGE].get());
    3172       15091 :     if (!handler) {
    3173          19 :         clk.unlock();
    3174          19 :         if (!onlyConnected)
    3175          19 :             messageEngine_.onMessageSent(to, token, false, deviceId);
    3176          19 :         return;
    3177             :     }
    3178             : 
    3179             :     auto devices = std::make_shared<SendMessageContext>(
    3180       43370 :         [w = weak(), to, token, deviceId, onlyConnected, retryOnTimeout](bool success, bool sent) {
    3181       15070 :             if (auto acc = w.lock())
    3182       15067 :                 acc->onMessageSent(to, token, deviceId, success, onlyConnected, sent && retryOnTimeout);
    3183       30143 :         });
    3184             : 
    3185       13247 :     auto completed = [w = weak(), to, devices](const DeviceId& device,
    3186             :                                                const std::shared_ptr<dhtnet::ChannelSocket>& conn,
    3187             :                                                bool success) {
    3188       13247 :         if (!success)
    3189           4 :             if (auto acc = w.lock()) {
    3190           4 :                 std::shared_lock clk(acc->connManagerMtx_);
    3191           4 :                 if (auto* handler = static_cast<MessageChannelHandler*>(
    3192           4 :                         acc->channelHandlers_[Uri::Scheme::MESSAGE].get())) {
    3193           4 :                     handler->closeChannel(to, device, conn);
    3194             :                 }
    3195           8 :             }
    3196       13247 :         devices->complete(device, success);
    3197       28318 :     };
    3198             : 
    3199       15072 :     const auto& payload = *payloads.begin();
    3200       15072 :     auto msg = std::make_shared<MessageChannelHandler::Message>();
    3201       15071 :     msg->id = token;
    3202       15071 :     msg->t = payload.first;
    3203       15071 :     msg->c = payload.second;
    3204       15071 :     auto device = deviceId.empty() ? DeviceId() : DeviceId(deviceId);
    3205       15070 :     if (deviceId.empty()) {
    3206        3465 :         auto conns = handler->getChannels(toUri);
    3207        3467 :         clk.unlock();
    3208        5382 :         for (const auto& conn : conns) {
    3209        1915 :             auto connDevice = conn->deviceId();
    3210        1915 :             if (!devices->add(connDevice))
    3211         216 :                 continue;
    3212        1699 :             dht::ThreadPool::io().run([completed, connDevice, conn, msg] {
    3213        1699 :                 completed(connDevice, conn, MessageChannelHandler::sendMessage(conn, *msg));
    3214        1699 :             });
    3215             :         }
    3216        3467 :     } else {
    3217       11605 :         if (auto conn = handler->getChannel(toUri, device)) {
    3218       11548 :             clk.unlock();
    3219       11548 :             devices->add(device);
    3220       11548 :             dht::ThreadPool::io().run([completed, device, conn, msg] {
    3221       11548 :                 completed(device, conn, MessageChannelHandler::sendMessage(conn, *msg));
    3222       11548 :             });
    3223       11548 :             devices->start();
    3224       11548 :             return;
    3225       11605 :         }
    3226             :     }
    3227        3524 :     if (clk)
    3228          57 :         clk.unlock();
    3229             : 
    3230        3524 :     devices->start();
    3231             : 
    3232        3524 :     if (onlyConnected)
    3233          22 :         return;
    3234             :     // We are unable to send the message directly, try connecting
    3235             : 
    3236             :     // Get conversation id, which will be used by the iOS notification extension
    3237             :     // to load the conversation.
    3238        2912 :     auto extractIdFromJson = [](const std::string& jsonData) -> std::string {
    3239        2912 :         Json::Value parsed;
    3240        2912 :         if (json::parse(jsonData, parsed)) {
    3241        2912 :             auto value = parsed.get("id", Json::nullValue);
    3242        2912 :             if (value && value.isString()) {
    3243        2912 :                 return value.asString();
    3244             :             }
    3245        2912 :         } else {
    3246           0 :             JAMI_WARNING("Unable to parse jsonData to get conversation ID");
    3247             :         }
    3248           0 :         return "";
    3249        2912 :     };
    3250             : 
    3251             :     // get request type
    3252        3502 :     auto payload_type = msg->t;
    3253        3502 :     if (payload_type == MIME_TYPE_GIT) {
    3254        2912 :         std::string id = extractIdFromJson(msg->c);
    3255        2912 :         if (!id.empty()) {
    3256        2911 :             payload_type += "/" + id;
    3257             :         }
    3258        2911 :     }
    3259             : 
    3260        3502 :     if (deviceId.empty()) {
    3261        3445 :         auto toH = dht::InfoHash(toUri);
    3262             :         // Find listening devices for this account
    3263        3444 :         accountManager_->forEachDevice(toH,
    3264        3460 :                                        [this, to, devices, payload_type, currentDevice = DeviceId(currentDeviceId())](
    3265        2929 :                                            const std::shared_ptr<dht::crypto::PublicKey>& dev) {
    3266             :                                            // Test if already sent
    3267        3460 :                                            auto deviceId = dev->getLongId();
    3268        3460 :                                            if (deviceId == currentDevice || devices->pending(deviceId)) {
    3269         531 :                                                return;
    3270             :                                            }
    3271             : 
    3272             :                                            // Else, ask for a channel to send the message
    3273        2929 :                                            dht::ThreadPool::io().run([this, to, deviceId, payload_type]() {
    3274        2929 :                                                requestMessageConnection(to, deviceId, payload_type);
    3275        2929 :                                            });
    3276             :                                        });
    3277             :     } else {
    3278          57 :         requestMessageConnection(to, device, payload_type);
    3279             :     }
    3280       61390 : }
    3281             : 
    3282             : void
    3283       15068 : JamiAccount::onMessageSent(
    3284             :     const std::string& to, uint64_t id, const std::string& deviceId, bool success, bool onlyConnected, bool retry)
    3285             : {
    3286       15068 :     if (!onlyConnected)
    3287       15040 :         messageEngine_.onMessageSent(to, id, success, deviceId);
    3288             : 
    3289       15071 :     if (!success) {
    3290        1834 :         if (retry)
    3291           4 :             messageEngine_.onPeerOnline(to, deviceId);
    3292             :     }
    3293       15071 : }
    3294             : 
    3295             : dhtnet::IceTransportOptions
    3296          44 : JamiAccount::getIceOptions() const
    3297             : {
    3298          44 :     return connectionManager_->getIceOptions();
    3299             : }
    3300             : 
    3301             : void
    3302          11 : JamiAccount::getIceOptions(std::function<void(dhtnet::IceTransportOptions&&)> cb) const
    3303             : {
    3304          11 :     return connectionManager_->getIceOptions(std::move(cb));
    3305             : }
    3306             : 
    3307             : dhtnet::IpAddr
    3308          34 : JamiAccount::getPublishedIpAddress(uint16_t family) const
    3309             : {
    3310          34 :     return connectionManager_->getPublishedIpAddress(family);
    3311             : }
    3312             : 
    3313             : bool
    3314           0 : JamiAccount::setPushNotificationToken(const std::string& token)
    3315             : {
    3316           0 :     if (SIPAccountBase::setPushNotificationToken(token)) {
    3317           0 :         JAMI_WARNING("[Account {:s}] setPushNotificationToken: {:s}", getAccountID(), token);
    3318           0 :         if (dht_)
    3319           0 :             dht_->setPushNotificationToken(token);
    3320           0 :         return true;
    3321             :     }
    3322           0 :     return false;
    3323             : }
    3324             : 
    3325             : bool
    3326           0 : JamiAccount::setPushNotificationTopic(const std::string& topic)
    3327             : {
    3328           0 :     if (SIPAccountBase::setPushNotificationTopic(topic)) {
    3329           0 :         if (dht_)
    3330           0 :             dht_->setPushNotificationTopic(topic);
    3331           0 :         return true;
    3332             :     }
    3333           0 :     return false;
    3334             : }
    3335             : 
    3336             : bool
    3337           0 : JamiAccount::setPushNotificationConfig(const std::map<std::string, std::string>& data)
    3338             : {
    3339           0 :     if (SIPAccountBase::setPushNotificationConfig(data)) {
    3340           0 :         if (dht_) {
    3341           0 :             dht_->setPushNotificationPlatform(config_->platform);
    3342           0 :             dht_->setPushNotificationTopic(config_->notificationTopic);
    3343           0 :             dht_->setPushNotificationToken(config_->deviceKey);
    3344             :         }
    3345           0 :         return true;
    3346             :     }
    3347           0 :     return false;
    3348             : }
    3349             : 
    3350             : /**
    3351             :  * To be called by clients with relevant data when a push notification is received.
    3352             :  */
    3353             : void
    3354           0 : JamiAccount::pushNotificationReceived(const std::string& /*from*/, const std::map<std::string, std::string>& data)
    3355             : {
    3356           0 :     auto ret_future = dht_->pushNotificationReceived(data);
    3357           0 :     dht::ThreadPool::computation().run([id = getAccountID(), ret_future = ret_future.share()] {
    3358           0 :         JAMI_WARNING("[Account {:s}] pushNotificationReceived: {}", id, (uint8_t) ret_future.get());
    3359           0 :     });
    3360           0 : }
    3361             : 
    3362             : std::string
    3363           0 : JamiAccount::getUserUri() const
    3364             : {
    3365           0 :     if (not registeredName_.empty())
    3366           0 :         return JAMI_URI_PREFIX + registeredName_;
    3367           0 :     return JAMI_URI_PREFIX + config().username;
    3368             : }
    3369             : 
    3370             : std::vector<libjami::Message>
    3371           0 : JamiAccount::getLastMessages(const uint64_t& base_timestamp)
    3372             : {
    3373           0 :     return SIPAccountBase::getLastMessages(base_timestamp);
    3374             : }
    3375             : 
    3376             : void
    3377           0 : JamiAccount::startAccountPublish()
    3378             : {
    3379           0 :     AccountPeerInfo info_pub;
    3380           0 :     info_pub.accountId = dht::InfoHash(accountManager_->getInfo()->accountId);
    3381           0 :     info_pub.displayName = config().displayName;
    3382           0 :     peerDiscovery_->startPublish<AccountPeerInfo>(PEER_DISCOVERY_JAMI_SERVICE, info_pub);
    3383           0 : }
    3384             : 
    3385             : void
    3386           0 : JamiAccount::startAccountDiscovery()
    3387             : {
    3388           0 :     auto id = dht::InfoHash(accountManager_->getInfo()->accountId);
    3389           0 :     peerDiscovery_
    3390           0 :         ->startDiscovery<AccountPeerInfo>(PEER_DISCOVERY_JAMI_SERVICE, [this, id](AccountPeerInfo&& v, dht::SockAddr&&) {
    3391           0 :             std::lock_guard lc(discoveryMapMtx_);
    3392             :             // Make sure that account itself will not be recorded
    3393           0 :             if (v.accountId != id) {
    3394             :                 // Create or find the old one
    3395           0 :                 auto& dp = discoveredPeers_[v.accountId];
    3396           0 :                 dp.displayName = v.displayName;
    3397           0 :                 discoveredPeerMap_[v.accountId.toString()] = v.displayName;
    3398           0 :                 if (!dp.cleanupTimer) {
    3399             :                     // Avoid repeat reception of same peer
    3400           0 :                     JAMI_LOG("Account discovered: {}: {}", v.displayName, v.accountId.to_c_str());
    3401             :                     // Send Added Peer and corrsponding accoundID
    3402           0 :                     emitSignal<libjami::PresenceSignal::NearbyPeerNotification>(getAccountID(),
    3403           0 :                                                                                 v.accountId.toString(),
    3404             :                                                                                 0,
    3405           0 :                                                                                 v.displayName);
    3406           0 :                     dp.cleanupTimer = std::make_unique<asio::steady_timer>(*Manager::instance().ioContext(),
    3407           0 :                                                                            PEER_DISCOVERY_EXPIRATION);
    3408             :                 }
    3409           0 :                 dp.cleanupTimer->expires_after(PEER_DISCOVERY_EXPIRATION);
    3410           0 :                 dp.cleanupTimer->async_wait(
    3411           0 :                     [w = weak(), p = v.accountId, a = v.displayName](const asio::error_code& ec) {
    3412           0 :                         if (ec)
    3413           0 :                             return;
    3414           0 :                         if (auto this_ = w.lock()) {
    3415             :                             {
    3416           0 :                                 std::lock_guard lc(this_->discoveryMapMtx_);
    3417           0 :                                 this_->discoveredPeers_.erase(p);
    3418           0 :                                 this_->discoveredPeerMap_.erase(p.toString());
    3419           0 :                             }
    3420             :                             // Send deleted peer
    3421           0 :                             emitSignal<libjami::PresenceSignal::NearbyPeerNotification>(this_->getAccountID(),
    3422           0 :                                                                                         p.toString(),
    3423             :                                                                                         1,
    3424           0 :                                                                                         a);
    3425           0 :                         }
    3426           0 :                         JAMI_INFO("Account removed from discovery list: %s", a.c_str());
    3427             :                     });
    3428             :             }
    3429           0 :         });
    3430           0 : }
    3431             : 
    3432             : std::map<std::string, std::string>
    3433           0 : JamiAccount::getNearbyPeers() const
    3434             : {
    3435           0 :     return discoveredPeerMap_;
    3436             : }
    3437             : 
    3438             : void
    3439           0 : JamiAccount::sendProfileToPeers()
    3440             : {
    3441           0 :     if (!connectionManager_)
    3442           0 :         return;
    3443           0 :     std::set<std::string> peers;
    3444           0 :     const auto& accountUri = accountManager_->getInfo()->accountId;
    3445             :     // TODO: avoid using getConnectionList
    3446           0 :     for (const auto& connection : connectionManager_->getConnectionList()) {
    3447           0 :         const auto& device = connection.at("device");
    3448           0 :         const auto& peer = connection.at("peer");
    3449           0 :         if (!peers.emplace(peer).second)
    3450           0 :             continue;
    3451           0 :         if (peer == accountUri) {
    3452           0 :             sendProfile("", accountUri, device);
    3453           0 :             continue;
    3454             :         }
    3455           0 :         const auto& conversationId = convModule()->getOneToOneConversation(peer);
    3456           0 :         if (!conversationId.empty()) {
    3457           0 :             sendProfile(conversationId, peer, device);
    3458             :         }
    3459           0 :     }
    3460           0 : }
    3461             : 
    3462             : void
    3463           0 : JamiAccount::updateProfile(const std::string& displayName,
    3464             :                            const std::string& avatar,
    3465             :                            const std::string& fileType,
    3466             :                            int32_t flag)
    3467             : {
    3468             :     // if the fileType is empty then only the display name will be upated
    3469             : 
    3470           0 :     const auto& accountUri = accountManager_->getInfo()->accountId;
    3471           0 :     const auto& path = profilePath();
    3472           0 :     const auto& profiles = idPath_ / "profiles";
    3473             : 
    3474             :     try {
    3475           0 :         if (!std::filesystem::exists(profiles)) {
    3476           0 :             std::filesystem::create_directories(profiles);
    3477             :         }
    3478           0 :     } catch (const std::exception& e) {
    3479           0 :         JAMI_ERROR("Failed to create profiles directory: {}", e.what());
    3480           0 :         return;
    3481           0 :     }
    3482             : 
    3483           0 :     const auto& vCardPath = profiles / fmt::format("{}.vcf", base64::encode(accountUri));
    3484             : 
    3485           0 :     auto profile = getProfileVcard();
    3486           0 :     if (profile.empty()) {
    3487           0 :         profile = vCard::utils::initVcard();
    3488             :     }
    3489             : 
    3490           0 :     profile[std::string(vCard::Property::FORMATTED_NAME)] = displayName;
    3491           0 :     editConfig([&](JamiAccountConfig& config) { config.displayName = displayName; });
    3492           0 :     emitSignal<libjami::ConfigurationSignal::AccountDetailsChanged>(getAccountID(), getAccountDetails());
    3493             : 
    3494           0 :     if (!fileType.empty()) {
    3495           0 :         const std::string& key = "PHOTO;ENCODING=BASE64;TYPE=" + fileType;
    3496           0 :         if (flag == 0) {
    3497           0 :             vCard::utils::removeByKey(profile, vCard::Property::PHOTO);
    3498           0 :             const auto& avatarPath = std::filesystem::path(avatar);
    3499           0 :             if (std::filesystem::exists(avatarPath)) {
    3500             :                 try {
    3501           0 :                     profile[key] = base64::encode(fileutils::loadFile(avatarPath));
    3502           0 :                 } catch (const std::exception& e) {
    3503           0 :                     JAMI_ERROR("Failed to load avatar: {}", e.what());
    3504           0 :                 }
    3505             :             }
    3506           0 :         } else if (flag == 1) {
    3507           0 :             vCard::utils::removeByKey(profile, vCard::Property::PHOTO);
    3508           0 :             profile[key] = avatar;
    3509             :         }
    3510           0 :     }
    3511           0 :     if (flag == 2) {
    3512           0 :         vCard::utils::removeByKey(profile, vCard::Property::PHOTO);
    3513             :     }
    3514             :     try {
    3515           0 :         vCard::utils::save(profile, vCardPath, path);
    3516           0 :         emitSignal<libjami::ConfigurationSignal::ProfileReceived>(getAccountID(), accountUri, path.string());
    3517             : 
    3518             :         // Delete all profile sent markers:
    3519           0 :         std::error_code ec;
    3520           0 :         std::filesystem::remove_all(cachePath_ / "vcard", ec);
    3521           0 :         sendProfileToPeers();
    3522           0 :     } catch (const std::exception& e) {
    3523           0 :         JAMI_ERROR("Error writing profile: {}", e.what());
    3524           0 :     }
    3525           0 : }
    3526             : 
    3527             : void
    3528         699 : JamiAccount::setActiveCodecs(const std::vector<unsigned>& list)
    3529             : {
    3530         699 :     Account::setActiveCodecs(list);
    3531         699 :     if (!hasActiveCodec(MEDIA_AUDIO))
    3532         679 :         setCodecActive(AV_CODEC_ID_OPUS);
    3533         699 :     if (!hasActiveCodec(MEDIA_VIDEO)) {
    3534         679 :         setCodecActive(AV_CODEC_ID_HEVC);
    3535         679 :         setCodecActive(AV_CODEC_ID_H264);
    3536         679 :         setCodecActive(AV_CODEC_ID_VP8);
    3537             :     }
    3538         699 :     config_->activeCodecs = getActiveCodecs(MEDIA_ALL);
    3539         699 : }
    3540             : 
    3541             : void
    3542          11 : JamiAccount::sendInstantMessage(const std::string& convId, const std::map<std::string, std::string>& msg)
    3543             : {
    3544          11 :     auto members = convModule()->getConversationMembers(convId);
    3545          11 :     if (convId.empty() && members.empty()) {
    3546             :         // TODO remove, it's for old API for contacts
    3547           0 :         sendTextMessage(convId, "", msg);
    3548           0 :         return;
    3549             :     }
    3550          33 :     for (const auto& m : members) {
    3551          22 :         const auto& uri = m.at("uri");
    3552          22 :         auto token = std::uniform_int_distribution<uint64_t> {1, JAMI_ID_MAX_VAL}(rand);
    3553             :         // Announce to all members that a new message is sent
    3554          22 :         sendMessage(uri, "", msg, token, false, true);
    3555             :     }
    3556          11 : }
    3557             : 
    3558             : bool
    3559       13235 : JamiAccount::handleMessage(const std::shared_ptr<dht::crypto::Certificate>& cert,
    3560             :                            const std::string& from,
    3561             :                            const std::pair<std::string, std::string>& m)
    3562             : {
    3563       13235 :     if (not cert or not cert->issuer)
    3564           0 :         return true; // stop processing message
    3565             : 
    3566       13235 :     if (cert->issuer->getId().to_view() != from) {
    3567           0 :         JAMI_WARNING("[Account {}] [device {}] handleMessage: invalid author {}",
    3568             :                      getAccountID(),
    3569             :                      cert->issuer->getId().to_view(),
    3570             :                      from);
    3571           0 :         return true;
    3572             :     }
    3573       13236 :     if (m.first == MIME_TYPE_GIT) {
    3574       12817 :         Json::Value json;
    3575       12818 :         if (!json::parse(m.second, json)) {
    3576           0 :             return true;
    3577             :         }
    3578             : 
    3579             :         // fetchNewCommits will do heavy stuff like fetching, avoid to block SIP socket
    3580       25633 :         dht::ThreadPool::io().run([w = weak(),
    3581             :                                    from,
    3582       12815 :                                    deviceId = json["deviceId"].asString(),
    3583       12813 :                                    id = json["id"].asString(),
    3584       12817 :                                    commit = json["commit"].asString()] {
    3585       12818 :             if (auto shared = w.lock()) {
    3586       12816 :                 if (auto* cm = shared->convModule())
    3587       12818 :                     cm->fetchNewCommits(from, deviceId, id, commit);
    3588       12810 :             }
    3589       12813 :         });
    3590       12818 :         return true;
    3591       13237 :     } else if (m.first == MIME_TYPE_INVITE) {
    3592         136 :         convModule()->onNeedConversationRequest(from, m.second);
    3593         136 :         return true;
    3594         283 :     } else if (m.first == MIME_TYPE_INVITE_JSON) {
    3595         264 :         Json::Value json;
    3596         264 :         if (!json::parse(m.second, json)) {
    3597           0 :             return true;
    3598             :         }
    3599         264 :         convModule()->onConversationRequest(from, json);
    3600         264 :         return true;
    3601         283 :     } else if (m.first == MIME_TYPE_IM_COMPOSING) {
    3602             :         try {
    3603           4 :             static const std::regex COMPOSING_REGEX("<state>\\s*(\\w+)\\s*<\\/state>");
    3604           4 :             std::smatch matched_pattern;
    3605           4 :             std::regex_search(m.second, matched_pattern, COMPOSING_REGEX);
    3606           4 :             bool isComposing {false};
    3607           4 :             if (matched_pattern.ready() && !matched_pattern.empty() && matched_pattern[1].matched) {
    3608           4 :                 isComposing = matched_pattern[1] == "active";
    3609             :             }
    3610           4 :             static const std::regex CONVID_REGEX("<conversation>\\s*(\\w+)\\s*<\\/conversation>");
    3611           4 :             std::regex_search(m.second, matched_pattern, CONVID_REGEX);
    3612           4 :             std::string conversationId = "";
    3613           4 :             if (matched_pattern.ready() && !matched_pattern.empty() && matched_pattern[1].matched) {
    3614           4 :                 conversationId = matched_pattern[1];
    3615             :             }
    3616           4 :             if (!conversationId.empty()) {
    3617           4 :                 if (auto* cm = convModule(true)) {
    3618           4 :                     if (auto typer = cm->getTypers(conversationId)) {
    3619           4 :                         if (isComposing)
    3620           3 :                             typer->addTyper(from);
    3621             :                         else
    3622           1 :                             typer->removeTyper(from);
    3623           4 :                     }
    3624             :                 }
    3625             :             }
    3626           4 :             return true;
    3627           4 :         } catch (const std::exception& e) {
    3628           0 :             JAMI_WARNING("Error parsing composing state: {}", e.what());
    3629           0 :         }
    3630          15 :     } else if (m.first == MIME_TYPE_IMDN) {
    3631             :         try {
    3632           9 :             static const std::regex IMDN_MSG_ID_REGEX("<message-id>\\s*(\\w+)\\s*<\\/message-id>");
    3633           9 :             std::smatch matched_pattern;
    3634             : 
    3635           9 :             std::regex_search(m.second, matched_pattern, IMDN_MSG_ID_REGEX);
    3636           9 :             std::string messageId;
    3637           9 :             if (matched_pattern.ready() && !matched_pattern.empty() && matched_pattern[1].matched) {
    3638           9 :                 messageId = matched_pattern[1];
    3639             :             } else {
    3640           0 :                 JAMI_WARNING("Message displayed: unable to parse message ID");
    3641           0 :                 return true;
    3642             :             }
    3643             : 
    3644           9 :             static const std::regex STATUS_REGEX("<status>\\s*<(\\w+)\\/>\\s*<\\/status>");
    3645           9 :             std::regex_search(m.second, matched_pattern, STATUS_REGEX);
    3646           9 :             bool isDisplayed {false};
    3647           9 :             if (matched_pattern.ready() && !matched_pattern.empty() && matched_pattern[1].matched) {
    3648           9 :                 isDisplayed = matched_pattern[1] == "displayed";
    3649             :             } else {
    3650           0 :                 JAMI_WARNING("Message displayed: unable to parse status");
    3651           0 :                 return true;
    3652             :             }
    3653             : 
    3654           9 :             static const std::regex CONVID_REGEX("<conversation>\\s*(\\w+)\\s*<\\/conversation>");
    3655           9 :             std::regex_search(m.second, matched_pattern, CONVID_REGEX);
    3656           9 :             std::string conversationId = "";
    3657           9 :             if (matched_pattern.ready() && !matched_pattern.empty() && matched_pattern[1].matched) {
    3658           9 :                 conversationId = matched_pattern[1];
    3659             :             }
    3660             : 
    3661           9 :             if (!isReadReceiptEnabled())
    3662           0 :                 return true;
    3663           9 :             if (isDisplayed) {
    3664           9 :                 if (convModule()->onMessageDisplayed(from, conversationId, messageId)) {
    3665          32 :                     JAMI_DEBUG("[message {}] Displayed by peer", messageId);
    3666          16 :                     emitSignal<libjami::ConfigurationSignal::AccountMessageStatusChanged>(
    3667           8 :                         accountID_,
    3668             :                         conversationId,
    3669             :                         from,
    3670             :                         messageId,
    3671             :                         static_cast<int>(libjami::Account::MessageStates::DISPLAYED));
    3672             :                 }
    3673             :             }
    3674           9 :             return true;
    3675           9 :         } catch (const std::exception& e) {
    3676           0 :             JAMI_ERROR("Error parsing display notification: {}", e.what());
    3677           0 :         }
    3678           6 :     } else if (m.first == MIME_TYPE_PIDF) {
    3679           6 :         std::smatch matched_pattern;
    3680           6 :         static const std::regex BASIC_REGEX("<basic>([\\w\\s]+)<\\/basic>");
    3681           6 :         std::regex_search(m.second, matched_pattern, BASIC_REGEX);
    3682           6 :         std::string customStatus {};
    3683           6 :         if (matched_pattern.ready() && !matched_pattern.empty() && matched_pattern[1].matched) {
    3684           6 :             customStatus = matched_pattern[1];
    3685           6 :             emitSignal<libjami::PresenceSignal::NewBuddyNotification>(getAccountID(),
    3686             :                                                                       from,
    3687             :                                                                       static_cast<int>(PresenceState::CONNECTED),
    3688             :                                                                       customStatus);
    3689             :         } else {
    3690           0 :             JAMI_WARNING("Presence: unable to parse status");
    3691             :         }
    3692           6 :         return true;
    3693           6 :     }
    3694             : 
    3695           0 :     return false;
    3696             : }
    3697             : 
    3698             : void
    3699          94 : JamiAccount::callConnectionClosed(const DeviceId& deviceId, bool eraseDummy)
    3700             : {
    3701          94 :     std::function<void(const DeviceId&, bool)> cb;
    3702             :     {
    3703          94 :         std::lock_guard lk(onConnectionClosedMtx_);
    3704          94 :         auto it = onConnectionClosed_.find(deviceId);
    3705          94 :         if (it != onConnectionClosed_.end()) {
    3706          25 :             if (eraseDummy) {
    3707          25 :                 cb = std::move(it->second);
    3708          25 :                 onConnectionClosed_.erase(it);
    3709             :             } else {
    3710             :                 // In this case a new subcall is created and the callback
    3711             :                 // will be re-called once with eraseDummy = true
    3712           0 :                 cb = it->second;
    3713             :             }
    3714             :         }
    3715          94 :     }
    3716          94 :     dht::ThreadPool::io().run([w = weak(), cb = std::move(cb), id = deviceId, erase = std::move(eraseDummy)] {
    3717          94 :         if (auto acc = w.lock()) {
    3718          94 :             if (cb)
    3719          25 :                 cb(id, erase);
    3720          94 :         }
    3721          94 :     });
    3722          94 : }
    3723             : 
    3724             : void
    3725        3302 : JamiAccount::requestMessageConnection(const std::string& peerId,
    3726             :                                       const DeviceId& deviceId,
    3727             :                                       const std::string& connectionType)
    3728             : {
    3729        3302 :     std::shared_lock lk(connManagerMtx_);
    3730        3302 :     auto* handler = static_cast<MessageChannelHandler*>(channelHandlers_[Uri::Scheme::MESSAGE].get());
    3731        3302 :     if (!handler)
    3732           3 :         return;
    3733        3299 :     if (deviceId) {
    3734        3299 :         if (auto connected = handler->getChannel(peerId, deviceId)) {
    3735        1869 :             return;
    3736        3299 :         }
    3737             :     } else {
    3738           0 :         auto connected = handler->getChannels(peerId);
    3739           0 :         if (!connected.empty()) {
    3740           0 :             return;
    3741             :         }
    3742           0 :     }
    3743        1430 :     handler->connect(
    3744             :         deviceId,
    3745             :         "",
    3746         779 :         [w = weak(), peerId](const std::shared_ptr<dhtnet::ChannelSocket>& socket, const DeviceId& deviceId) {
    3747         779 :             if (socket)
    3748         691 :                 dht::ThreadPool::io().run([w, peerId, deviceId] {
    3749         692 :                     if (auto acc = w.lock()) {
    3750         692 :                         acc->messageEngine_.onPeerOnline(peerId);
    3751         692 :                         acc->messageEngine_.onPeerOnline(peerId, deviceId.toString(), true);
    3752         692 :                         if (!acc->presenceNote_.empty()) {
    3753             :                             // If a presence note is set, send it to this device.
    3754           4 :                             auto token = std::uniform_int_distribution<uint64_t> {1, JAMI_ID_MAX_VAL}(acc->rand);
    3755          16 :                             std::map<std::string, std::string> msg = {{MIME_TYPE_PIDF, getPIDF(acc->presenceNote_)}};
    3756           4 :                             acc->sendMessage(peerId, deviceId.toString(), msg, token, false, true);
    3757           4 :                         }
    3758         692 :                         acc->convModule()->syncConversations(peerId, deviceId.toString());
    3759         692 :                     }
    3760         691 :                 });
    3761         779 :         },
    3762             :         connectionType);
    3763        3302 : }
    3764             : 
    3765             : void
    3766          35 : JamiAccount::requestSIPConnection(const std::string& peerId,
    3767             :                                   const DeviceId& deviceId,
    3768             :                                   const std::string& connectionType,
    3769             :                                   bool forceNewConnection,
    3770             :                                   const std::shared_ptr<SIPCall>& pc)
    3771             : {
    3772          35 :     if (peerId == getUsername()) {
    3773           0 :         if (!syncModule()->isConnected(deviceId))
    3774           0 :             channelHandlers_[Uri::Scheme::SYNC]->connect(deviceId,
    3775             :                                                          "",
    3776           0 :                                                          [](const std::shared_ptr<dhtnet::ChannelSocket>& /*socket*/,
    3777           0 :                                                             const DeviceId& /*deviceId*/) {});
    3778             :     }
    3779             : 
    3780         140 :     JAMI_LOG("[Account {}] Request SIP connection to peer {} on device {}", getAccountID(), peerId, deviceId);
    3781             : 
    3782             :     // If a connection already exists or is in progress, no need to do this
    3783          35 :     std::lock_guard lk(sipConnsMtx_);
    3784          35 :     auto id = std::make_pair(peerId, deviceId);
    3785             : 
    3786          35 :     if (sipConns_.find(id) != sipConns_.end()) {
    3787           0 :         JAMI_LOG("[Account {}] A SIP connection with {} already exists", getAccountID(), deviceId);
    3788           0 :         return;
    3789             :     }
    3790             :     // If not present, create it
    3791          35 :     std::shared_lock lkCM(connManagerMtx_);
    3792          35 :     if (!connectionManager_)
    3793           0 :         return;
    3794             :     // Note, Even if we send 50 "sip" request, the connectionManager_ will only use one socket.
    3795             :     // however, this will still ask for multiple channels, so only ask
    3796             :     // if there is no pending request
    3797          35 :     if (!forceNewConnection && connectionManager_->isConnecting(deviceId, "sip")) {
    3798           0 :         JAMI_LOG("[Account {}] Already connecting to {}", getAccountID(), deviceId);
    3799           0 :         return;
    3800             :     }
    3801         140 :     JAMI_LOG("[Account {}] Ask {} for a new SIP channel", getAccountID(), deviceId);
    3802          35 :     dhtnet::ConnectDeviceOptions options;
    3803          35 :     options.noNewSocket = false;
    3804          35 :     options.forceNewSocket = forceNewConnection;
    3805          35 :     options.connType = connectionType;
    3806          35 :     options.channelTimeout = 3s;
    3807         105 :     connectionManager_->connectDevice(
    3808             :         deviceId,
    3809             :         "sip",
    3810          70 :         [w = weak(), id = std::move(id), pc = std::move(pc)](const std::shared_ptr<dhtnet::ChannelSocket>& socket,
    3811             :                                                              const DeviceId&) {
    3812          35 :             if (socket)
    3813          32 :                 return;
    3814           3 :             auto shared = w.lock();
    3815           3 :             if (!shared)
    3816           0 :                 return;
    3817             :             // If this is triggered, this means that the
    3818             :             // connectDevice didn't get any response from the DHT.
    3819             :             // Stop searching pending call.
    3820           3 :             shared->callConnectionClosed(id.second, true);
    3821           3 :             if (pc)
    3822           3 :                 pc->onFailure(PJSIP_SC_TEMPORARILY_UNAVAILABLE);
    3823           3 :         },
    3824             :         options);
    3825          35 : }
    3826             : 
    3827             : bool
    3828         352 : JamiAccount::isConnectedWith(const DeviceId& deviceId) const
    3829             : {
    3830         352 :     std::shared_lock lkCM(connManagerMtx_);
    3831         352 :     if (connectionManager_)
    3832         352 :         return connectionManager_->isConnected(deviceId);
    3833           0 :     return false;
    3834         352 : }
    3835             : 
    3836             : void
    3837           3 : JamiAccount::sendPresenceNote(const std::string& note)
    3838             : {
    3839           3 :     if (const auto* info = accountManager_->getInfo()) {
    3840           3 :         if (!info || !info->contacts)
    3841           0 :             return;
    3842           3 :         presenceNote_ = note;
    3843           3 :         auto contacts = info->contacts->getContacts();
    3844           3 :         std::vector<std::pair<std::string, DeviceId>> keys;
    3845             :         {
    3846           3 :             std::shared_lock lkCM(connManagerMtx_);
    3847           3 :             auto* handler = static_cast<MessageChannelHandler*>(channelHandlers_[Uri::Scheme::MESSAGE].get());
    3848           3 :             if (!handler)
    3849           0 :                 return;
    3850           5 :             for (const auto& contact : contacts) {
    3851           2 :                 auto peerId = contact.first.toString();
    3852           2 :                 auto channels = handler->getChannels(peerId);
    3853           4 :                 for (const auto& channel : channels) {
    3854           2 :                     keys.emplace_back(peerId, channel->deviceId());
    3855             :                 }
    3856           2 :             }
    3857           3 :         }
    3858           3 :         auto token = std::uniform_int_distribution<uint64_t> {1, JAMI_ID_MAX_VAL}(rand);
    3859          12 :         std::map<std::string, std::string> msg = {{MIME_TYPE_PIDF, getPIDF(presenceNote_)}};
    3860           5 :         for (auto& key : keys) {
    3861           2 :             sendMessage(key.first, key.second.toString(), msg, token, false, true);
    3862             :         }
    3863           3 :     }
    3864             : }
    3865             : 
    3866             : void
    3867         620 : JamiAccount::sendProfile(const std::string& convId, const std::string& peerUri, const std::string& deviceId)
    3868             : {
    3869         620 :     auto accProfilePath = profilePath();
    3870         620 :     if (not std::filesystem::is_regular_file(accProfilePath))
    3871         613 :         return;
    3872           8 :     auto currentSha3 = fileutils::sha3File(accProfilePath);
    3873             :     // VCard sync for peerUri
    3874           8 :     if (not needToSendProfile(peerUri, deviceId, currentSha3)) {
    3875           0 :         JAMI_DEBUG("[Account {}] [device {}] Peer {} already got an up-to-date vCard",
    3876             :                    getAccountID(),
    3877             :                    deviceId,
    3878             :                    peerUri);
    3879           0 :         return;
    3880             :     }
    3881             :     // We need a new channel
    3882          24 :     transferFile(convId,
    3883          16 :                  accProfilePath.string(),
    3884             :                  deviceId,
    3885             :                  "profile.vcf",
    3886             :                  "",
    3887             :                  0,
    3888             :                  0,
    3889             :                  currentSha3,
    3890             :                  fileutils::lastWriteTimeInSeconds(accProfilePath),
    3891           8 :                  [accId = getAccountID(), peerUri, deviceId]() {
    3892             :                      // Mark the VCard as sent
    3893          12 :                      auto sendDir = fileutils::get_cache_dir() / accId / "vcard" / peerUri;
    3894           6 :                      auto path = sendDir / deviceId;
    3895           6 :                      dhtnet::fileutils::recursive_mkdir(sendDir);
    3896           6 :                      std::lock_guard lock(dhtnet::fileutils::getFileLock(path));
    3897           6 :                      if (std::filesystem::is_regular_file(path))
    3898           0 :                          return;
    3899           6 :                      std::ofstream p(path);
    3900           6 :                  });
    3901         621 : }
    3902             : 
    3903             : bool
    3904           8 : JamiAccount::needToSendProfile(const std::string& peerUri, const std::string& deviceId, const std::string& sha3Sum)
    3905             : {
    3906           8 :     std::string previousSha3 {};
    3907           8 :     auto vCardPath = cachePath_ / "vcard";
    3908           8 :     auto sha3Path = vCardPath / "sha3";
    3909           8 :     dhtnet::fileutils::check_dir(vCardPath, 0700);
    3910             :     try {
    3911          11 :         previousSha3 = fileutils::loadTextFile(sha3Path);
    3912           3 :     } catch (...) {
    3913           3 :         fileutils::saveFile(sha3Path, (const uint8_t*) sha3Sum.data(), sha3Sum.size(), 0600);
    3914           3 :         return true;
    3915           3 :     }
    3916           5 :     if (sha3Sum != previousSha3) {
    3917             :         // Incorrect sha3 stored. Update it
    3918           0 :         dhtnet::fileutils::removeAll(vCardPath, true);
    3919           0 :         dhtnet::fileutils::check_dir(vCardPath, 0700);
    3920           0 :         fileutils::saveFile(sha3Path, (const uint8_t*) sha3Sum.data(), sha3Sum.size(), 0600);
    3921           0 :         return true;
    3922             :     }
    3923           5 :     auto peerPath = vCardPath / peerUri;
    3924           5 :     dhtnet::fileutils::recursive_mkdir(peerPath);
    3925           5 :     return not std::filesystem::is_regular_file(peerPath / deviceId);
    3926           8 : }
    3927             : 
    3928             : void
    3929         134 : JamiAccount::clearProfileCache(const std::string& peerUri)
    3930             : {
    3931         134 :     std::error_code ec;
    3932         134 :     std::filesystem::remove_all(cachePath_ / "vcard" / peerUri, ec);
    3933         134 : }
    3934             : 
    3935             : std::filesystem::path
    3936         623 : JamiAccount::profilePath() const
    3937             : {
    3938         623 :     return idPath_ / "profile.vcf";
    3939             : }
    3940             : 
    3941             : void
    3942          66 : JamiAccount::cacheSIPConnection(std::shared_ptr<dhtnet::ChannelSocket>&& socket,
    3943             :                                 const std::string& peerId,
    3944             :                                 const DeviceId& deviceId)
    3945             : {
    3946          66 :     std::unique_lock lk(sipConnsMtx_);
    3947             :     // Verify that the connection is not already cached
    3948          66 :     SipConnectionKey key(peerId, deviceId);
    3949          66 :     auto& connections = sipConns_[key];
    3950          68 :     auto conn = std::find_if(connections.begin(), connections.end(), [&](const auto& v) { return v.channel == socket; });
    3951          66 :     if (conn != connections.end()) {
    3952           0 :         JAMI_WARNING("[Account {}] Channel socket already cached with this peer", getAccountID());
    3953           0 :         return;
    3954             :     }
    3955             : 
    3956             :     // Convert to SIP transport
    3957          66 :     auto onShutdown = [w = weak(), peerId, key, socket]() {
    3958          66 :         dht::ThreadPool::io().run([w = std::move(w), peerId, key, socket] {
    3959          66 :             auto shared = w.lock();
    3960          66 :             if (!shared)
    3961           0 :                 return;
    3962          66 :             shared->shutdownSIPConnection(socket, key.first, key.second);
    3963             :             // The connection can be closed during the SIP initialization, so
    3964             :             // if this happens, the request should be re-sent to ask for a new
    3965             :             // SIP channel to make the call pass through
    3966          66 :             shared->callConnectionClosed(key.second, false);
    3967          66 :         });
    3968         132 :     };
    3969         132 :     auto sip_tr = link_.sipTransportBroker->getChanneledTransport(shared(), socket, std::move(onShutdown));
    3970          66 :     if (!sip_tr) {
    3971           0 :         JAMI_ERROR("No channeled transport found");
    3972           0 :         return;
    3973             :     }
    3974             :     // Store the connection
    3975          66 :     connections.emplace_back(SipConnection {sip_tr, socket});
    3976         264 :     JAMI_WARNING("[Account {:s}] [device {}] New SIP channel opened", getAccountID(), deviceId);
    3977          66 :     lk.unlock();
    3978             : 
    3979             :     // Retry messages
    3980          66 :     messageEngine_.onPeerOnline(peerId);
    3981          66 :     messageEngine_.onPeerOnline(peerId, deviceId.toString(), true);
    3982             : 
    3983             :     // Connect pending calls
    3984          66 :     forEachPendingCall(deviceId, [&](const auto& pc) {
    3985          32 :         if (pc->getConnectionState() != Call::ConnectionState::TRYING
    3986          32 :             and pc->getConnectionState() != Call::ConnectionState::PROGRESSING)
    3987           0 :             return;
    3988          32 :         pc->setSipTransport(sip_tr, getContactHeader(sip_tr));
    3989          32 :         pc->setState(Call::ConnectionState::PROGRESSING);
    3990          32 :         if (auto remote_address = socket->getRemoteAddress()) {
    3991             :             try {
    3992          32 :                 onConnectedOutgoingCall(pc, peerId, remote_address);
    3993           0 :             } catch (const VoipLinkException&) {
    3994             :                 // In this case, the main scenario is that SIPStartCall failed because
    3995             :                 // the ICE is dead and the TLS session didn't send any packet on that dead
    3996             :                 // link (connectivity change, killed by the os, etc)
    3997             :                 // Here, we don't need to do anything, the TLS will fail and will delete
    3998             :                 // the cached transport
    3999             :             }
    4000             :         }
    4001             :     });
    4002          66 : }
    4003             : 
    4004             : void
    4005          66 : JamiAccount::shutdownSIPConnection(const std::shared_ptr<dhtnet::ChannelSocket>& channel,
    4006             :                                    const std::string& peerId,
    4007             :                                    const DeviceId& deviceId)
    4008             : {
    4009          66 :     std::unique_lock lk(sipConnsMtx_);
    4010          66 :     SipConnectionKey key(peerId, deviceId);
    4011          66 :     auto it = sipConns_.find(key);
    4012          66 :     if (it != sipConns_.end()) {
    4013          33 :         auto& conns = it->second;
    4014          67 :         conns.erase(std::remove_if(conns.begin(), conns.end(), [&](auto v) { return v.channel == channel; }),
    4015          33 :                     conns.end());
    4016          33 :         if (conns.empty()) {
    4017          32 :             sipConns_.erase(it);
    4018             :         }
    4019             :     }
    4020          66 :     lk.unlock();
    4021             :     // Shutdown after removal to let the callbacks do stuff if needed
    4022          66 :     if (channel)
    4023          66 :         channel->shutdown();
    4024          66 : }
    4025             : 
    4026             : std::string_view
    4027       29806 : JamiAccount::currentDeviceId() const
    4028             : {
    4029       29806 :     if (!accountManager_ or not accountManager_->getInfo())
    4030           0 :         return {};
    4031       29802 :     return accountManager_->getInfo()->deviceId;
    4032             : }
    4033             : 
    4034             : std::shared_ptr<TransferManager>
    4035         159 : JamiAccount::dataTransfer(const std::string& id)
    4036             : {
    4037         159 :     if (id.empty())
    4038          73 :         return nonSwarmTransferManager_;
    4039          86 :     if (auto* cm = convModule())
    4040          86 :         return cm->dataTransfer(id);
    4041           0 :     return {};
    4042             : }
    4043             : 
    4044             : void
    4045           0 : JamiAccount::monitor()
    4046             : {
    4047           0 :     JAMI_DEBUG("[Account {:s}] Monitor connections", getAccountID());
    4048           0 :     JAMI_DEBUG("[Account {:s}] Using proxy: {:s}", getAccountID(), proxyServerCached_);
    4049             : 
    4050           0 :     if (auto* cm = convModule())
    4051           0 :         cm->monitor();
    4052           0 :     std::shared_lock lkCM(connManagerMtx_);
    4053           0 :     if (connectionManager_)
    4054           0 :         connectionManager_->monitor();
    4055           0 : }
    4056             : 
    4057             : std::vector<std::map<std::string, std::string>>
    4058           0 : JamiAccount::getConnectionList(const std::string& conversationId)
    4059             : {
    4060           0 :     std::shared_lock lkCM(connManagerMtx_);
    4061           0 :     if (connectionManager_ && conversationId.empty()) {
    4062           0 :         return connectionManager_->getConnectionList();
    4063           0 :     } else if (connectionManager_ && convModule_) {
    4064           0 :         std::vector<std::map<std::string, std::string>> connectionList;
    4065           0 :         if (auto conv = convModule_->getConversation(conversationId)) {
    4066           0 :             for (const auto& deviceId : conv->getDeviceIdList()) {
    4067           0 :                 auto connections = connectionManager_->getConnectionList(deviceId);
    4068           0 :                 connectionList.reserve(connectionList.size() + connections.size());
    4069           0 :                 std::move(connections.begin(), connections.end(), std::back_inserter(connectionList));
    4070           0 :             }
    4071           0 :         }
    4072           0 :         return connectionList;
    4073           0 :     } else {
    4074           0 :         return {};
    4075             :     }
    4076           0 : }
    4077             : 
    4078             : std::vector<std::map<std::string, std::string>>
    4079           0 : JamiAccount::getConversationConnectivity(const std::string& conversationId)
    4080             : {
    4081           0 :     std::shared_lock lkCM(connManagerMtx_);
    4082           0 :     if (convModule_) {
    4083           0 :         if (auto conv = convModule_->getConversation(conversationId)) {
    4084           0 :             return conv->getConnectivity();
    4085           0 :         }
    4086             :     }
    4087           0 :     return {};
    4088           0 : }
    4089             : 
    4090             : std::vector<std::map<std::string, std::string>>
    4091           0 : JamiAccount::getConversationTrackedMembers(const std::string& conversationId)
    4092             : {
    4093           0 :     std::shared_lock lkCM(connManagerMtx_);
    4094           0 :     if (convModule_) {
    4095           0 :         if (auto conv = convModule_->getConversation(conversationId)) {
    4096           0 :             return conv->getTrackedMembers();
    4097           0 :         }
    4098             :     }
    4099           0 :     return {};
    4100           0 : }
    4101             : 
    4102             : std::vector<std::map<std::string, std::string>>
    4103           0 : JamiAccount::getChannelList(const std::string& connectionId)
    4104             : {
    4105           0 :     std::shared_lock lkCM(connManagerMtx_);
    4106           0 :     if (!connectionManager_)
    4107           0 :         return {};
    4108           0 :     return connectionManager_->getChannelList(connectionId);
    4109           0 : }
    4110             : 
    4111             : void
    4112          14 : JamiAccount::sendFile(const std::string& conversationId,
    4113             :                       const std::filesystem::path& path,
    4114             :                       const std::string& name,
    4115             :                       const std::string& replyTo)
    4116             : {
    4117          14 :     std::error_code ec;
    4118          14 :     if (!std::filesystem::is_regular_file(path, ec)) {
    4119           0 :         JAMI_ERROR("Invalid filename '{}'", path);
    4120           0 :         emitSignal<libjami::ConversationSignal::OnConversationError>(getAccountID(),
    4121             :                                                                      conversationId,
    4122             :                                                                      EVALIDFETCH,
    4123             :                                                                      "Invalid filename.");
    4124           0 :         return;
    4125             :     }
    4126             : 
    4127          14 :     auto fileSize = std::filesystem::file_size(path, ec);
    4128          14 :     if (ec || fileSize == static_cast<decltype(fileSize)>(-1)) {
    4129           0 :         JAMI_ERROR("Negative file size, user probably doesn't have the appropriate permissions for '{}'", path);
    4130           0 :         emitSignal<libjami::ConversationSignal::OnConversationError>(
    4131           0 :             getAccountID(),
    4132             :             conversationId,
    4133             :             EVALIDFETCH,
    4134             :             "Negative file size, could be due to insufficient file permissions.");
    4135           0 :         return;
    4136             :     }
    4137             : 
    4138             :     // NOTE: this sendMessage is in a computation thread because
    4139             :     // sha3sum can take quite some time to computer if the user decide
    4140             :     // to send a big file
    4141          14 :     dht::ThreadPool::computation().run([w = weak(), conversationId, path, name, fileSize, replyTo]() {
    4142          14 :         if (auto shared = w.lock()) {
    4143          14 :             Json::Value value;
    4144          14 :             auto tid = jami::generateUID(shared->rand);
    4145          14 :             auto displayName = name.empty() ? path.filename().string() : name;
    4146          14 :             value["tid"] = std::to_string(tid);
    4147          14 :             value["displayName"] = displayName;
    4148          14 :             value["totalSize"] = std::to_string(fileSize);
    4149          14 :             value["sha3sum"] = fileutils::sha3File(path);
    4150          14 :             value["type"] = "application/data-transfer+json";
    4151             : 
    4152          70 :             shared->convModule()->sendMessage(
    4153          14 :                 conversationId,
    4154          14 :                 std::move(value),
    4155          14 :                 replyTo,
    4156             :                 true,
    4157          42 :                 [accId = shared->getAccountID(), conversationId, tid, displayName, path](const std::string& commitId) {
    4158             :                     // Create a symlink to answer to re-ask
    4159          28 :                     auto filelinkPath = fileutils::get_data_dir() / accId / "conversation_data" / conversationId
    4160          42 :                                         / getFileId(commitId, std::to_string(tid), displayName);
    4161          14 :                     if (path != filelinkPath && !std::filesystem::is_symlink(filelinkPath)) {
    4162          14 :                         if (!fileutils::createFileLink(filelinkPath, path, true)) {
    4163           0 :                             JAMI_WARNING("Unable to create symlink for file transfer {} - {}. Copy file",
    4164             :                                          filelinkPath,
    4165             :                                          path);
    4166           0 :                             std::error_code ec;
    4167           0 :                             auto success = std::filesystem::copy_file(path, filelinkPath, ec);
    4168           0 :                             if (ec || !success) {
    4169           0 :                                 JAMI_ERROR("Unable to copy file for file transfer {} - {}", filelinkPath, path);
    4170             :                                 // Signal to notify clients that the operation failed.
    4171             :                                 // The fileId field sends the filePath.
    4172             :                                 // libjami::DataTransferEventCode::unsupported (2) is unused elsewhere.
    4173           0 :                                 emitSignal<libjami::DataTransferSignal::DataTransferEvent>(
    4174           0 :                                     accId,
    4175           0 :                                     conversationId,
    4176             :                                     commitId,
    4177           0 :                                     path.string(),
    4178             :                                     uint32_t(libjami::DataTransferEventCode::invalid));
    4179             :                             } else {
    4180             :                                 // Signal to notify clients that the file is copied and can be
    4181             :                                 // safely deleted. The fileId field sends the filePath.
    4182             :                                 // libjami::DataTransferEventCode::created (1) is unused elsewhere.
    4183           0 :                                 emitSignal<libjami::DataTransferSignal::DataTransferEvent>(
    4184           0 :                                     accId,
    4185           0 :                                     conversationId,
    4186             :                                     commitId,
    4187           0 :                                     path.string(),
    4188             :                                     uint32_t(libjami::DataTransferEventCode::created));
    4189             :                             }
    4190             :                         } else {
    4191          28 :                             emitSignal<libjami::DataTransferSignal::DataTransferEvent>(
    4192          14 :                                 accId,
    4193          14 :                                 conversationId,
    4194             :                                 commitId,
    4195          28 :                                 path.string(),
    4196             :                                 uint32_t(libjami::DataTransferEventCode::created));
    4197             :                         }
    4198             :                     }
    4199          14 :                 });
    4200          28 :         }
    4201          14 :     });
    4202             : }
    4203             : 
    4204             : void
    4205           8 : JamiAccount::transferFile(const std::string& conversationId,
    4206             :                           const std::string& path,
    4207             :                           const std::string& deviceId,
    4208             :                           const std::string& fileId,
    4209             :                           const std::string& interactionId,
    4210             :                           size_t start,
    4211             :                           size_t end,
    4212             :                           const std::string& sha3Sum,
    4213             :                           uint64_t lastWriteTime,
    4214             :                           std::function<void()> onFinished)
    4215             : {
    4216           8 :     std::string modified;
    4217           8 :     if (lastWriteTime != 0) {
    4218          16 :         modified = fmt::format("&modified={}", lastWriteTime);
    4219             :     }
    4220          16 :     auto fid = fileId == "profile.vcf" ? fmt::format("profile.vcf?sha3={}{}", sha3Sum, modified) : fileId;
    4221           8 :     auto channelName = conversationId.empty()
    4222             :                            ? fmt::format("{}profile.vcf?sha3={}{}", DATA_TRANSFER_SCHEME, sha3Sum, modified)
    4223          16 :                            : fmt::format("{}{}/{}/{}", DATA_TRANSFER_SCHEME, conversationId, currentDeviceId(), fid);
    4224           8 :     std::shared_lock lkCM(connManagerMtx_);
    4225           8 :     if (!connectionManager_)
    4226           0 :         return;
    4227          40 :     connectionManager_->connectDevice(
    4228          16 :         DeviceId(deviceId),
    4229             :         channelName,
    4230           8 :         [this,
    4231             :          conversationId,
    4232           8 :          path = std::move(path),
    4233             :          fileId,
    4234             :          interactionId,
    4235             :          start,
    4236             :          end,
    4237           8 :          onFinished = std::move(onFinished)](std::shared_ptr<dhtnet::ChannelSocket> socket, const DeviceId&) {
    4238           8 :             if (!socket)
    4239           0 :                 return;
    4240          40 :             dht::ThreadPool::io().run([w = weak(),
    4241           8 :                                        path = std::move(path),
    4242           8 :                                        socket = std::move(socket),
    4243           8 :                                        conversationId = std::move(conversationId),
    4244           8 :                                        fileId,
    4245           8 :                                        interactionId,
    4246             :                                        start,
    4247             :                                        end,
    4248           8 :                                        onFinished = std::move(onFinished)] {
    4249           8 :                 if (auto shared = w.lock())
    4250           8 :                     if (auto dt = shared->dataTransfer(conversationId))
    4251          16 :                         dt->transferFile(socket, fileId, interactionId, path, start, end, std::move(onFinished));
    4252           8 :             });
    4253             :         });
    4254           8 : }
    4255             : 
    4256             : void
    4257          13 : JamiAccount::askForFileChannel(const std::string& conversationId,
    4258             :                                const std::string& deviceId,
    4259             :                                const std::string& interactionId,
    4260             :                                const std::string& fileId,
    4261             :                                size_t start,
    4262             :                                size_t end)
    4263             : {
    4264          29 :     auto tryDevice = [=](const auto& did) {
    4265          29 :         std::shared_lock lkCM(connManagerMtx_);
    4266          29 :         if (!connectionManager_)
    4267           0 :             return;
    4268             : 
    4269          29 :         auto channelName = fmt::format("{}{}/{}/{}", DATA_TRANSFER_SCHEME, conversationId, currentDeviceId(), fileId);
    4270          29 :         if (start != 0 || end != 0) {
    4271           6 :             channelName += fmt::format("?start={}&end={}", start, end);
    4272             :         }
    4273             :         // We can avoid to negotiate new sessions, as the file notif
    4274             :         // probably came from an online device or last connected device.
    4275          58 :         connectionManager_->connectDevice(
    4276             :             did,
    4277             :             channelName,
    4278          41 :             [w = weak(),
    4279          29 :              conversationId,
    4280          29 :              fileId,
    4281          29 :              interactionId,
    4282             :              start](const std::shared_ptr<dhtnet::ChannelSocket>& channel, const DeviceId&) {
    4283          29 :                 if (!channel)
    4284          17 :                     return;
    4285          12 :                 dht::ThreadPool::io().run([w, conversationId, channel, fileId, interactionId, start] {
    4286          12 :                     auto shared = w.lock();
    4287          12 :                     if (!shared)
    4288           0 :                         return;
    4289          12 :                     auto dt = shared->dataTransfer(conversationId);
    4290          12 :                     if (!dt)
    4291           0 :                         return;
    4292          12 :                     if (interactionId.empty())
    4293           0 :                         dt->onIncomingProfile(channel);
    4294             :                     else
    4295          12 :                         dt->onIncomingFileTransfer(fileId, channel, start);
    4296          12 :                 });
    4297             :             },
    4298             :             false);
    4299          42 :     };
    4300             : 
    4301          13 :     if (!deviceId.empty()) {
    4302             :         // Only ask for device
    4303           1 :         tryDevice(DeviceId(deviceId));
    4304             :     } else {
    4305             :         // Only ask for connected devices. For others we will attempt
    4306             :         // with new peer online
    4307          40 :         for (const auto& m : convModule()->getConversationMembers(conversationId)) {
    4308          28 :             accountManager_->forEachDevice(dht::InfoHash(m.at("uri")),
    4309          28 :                                            [tryDevice](const std::shared_ptr<dht::crypto::PublicKey>& dev) {
    4310          28 :                                                tryDevice(dev->getLongId());
    4311          28 :                                            });
    4312          12 :         }
    4313             :     }
    4314          13 : }
    4315             : 
    4316             : void
    4317          58 : JamiAccount::askForProfile(const std::string& conversationId, const std::string& deviceId, const std::string& memberUri)
    4318             : {
    4319          58 :     std::shared_lock lkCM(connManagerMtx_);
    4320          58 :     if (!connectionManager_)
    4321           0 :         return;
    4322             : 
    4323          58 :     auto channelName = fmt::format("{}{}/profile/{}.vcf", DATA_TRANSFER_SCHEME, conversationId, memberUri);
    4324             :     // We can avoid to negotiate new sessions, as the file notif
    4325             :     // probably came from an online device or last connected device.
    4326         174 :     connectionManager_->connectDevice(
    4327         116 :         DeviceId(deviceId),
    4328             :         channelName,
    4329         107 :         [this, conversationId](const std::shared_ptr<dhtnet::ChannelSocket>& channel, const DeviceId&) {
    4330          58 :             if (!channel)
    4331           9 :                 return;
    4332          49 :             dht::ThreadPool::io().run([w = weak(), conversationId, channel] {
    4333          49 :                 if (auto shared = w.lock())
    4334          49 :                     if (auto dt = shared->dataTransfer(conversationId))
    4335          98 :                         dt->onIncomingProfile(channel);
    4336          49 :             });
    4337             :         },
    4338             :         false);
    4339          58 : }
    4340             : 
    4341             : void
    4342        2199 : JamiAccount::onPeerConnected(const std::string& peerId, bool connected)
    4343             : {
    4344        2199 :     auto isOnline = presenceManager_ && presenceManager_->isOnline(peerId);
    4345        3298 :     auto newState = connected ? PresenceState::CONNECTED
    4346        1099 :                               : (isOnline ? PresenceState::AVAILABLE : PresenceState::DISCONNECTED);
    4347             : 
    4348        2199 :     runOnMainThread([w = weak(), peerId, newState] {
    4349        2199 :         if (auto sthis = w.lock()) {
    4350        2199 :             std::lock_guard lock(sthis->presenceStateMtx_);
    4351        2199 :             auto& state = sthis->presenceState_[peerId];
    4352        2199 :             if (state != newState) {
    4353        2199 :                 state = newState;
    4354        2199 :                 emitSignal<libjami::PresenceSignal::NewBuddyNotification>(sthis->getAccountID(),
    4355        2199 :                                                                           peerId,
    4356             :                                                                           static_cast<int>(newState),
    4357             :                                                                           "");
    4358             :             }
    4359        4398 :         }
    4360        2199 :     });
    4361        2199 : }
    4362             : 
    4363             : void
    4364         672 : JamiAccount::initConnectionManager()
    4365             : {
    4366         672 :     if (!nonSwarmTransferManager_)
    4367         567 :         nonSwarmTransferManager_ = std::make_shared<TransferManager>(accountID_,
    4368         567 :                                                                      config().username,
    4369             :                                                                      "",
    4370        1701 :                                                                      dht::crypto::getDerivedRandomEngine(rand));
    4371         672 :     if (!connectionManager_) {
    4372         578 :         auto connectionManagerConfig = std::make_shared<dhtnet::ConnectionManager::Config>();
    4373         578 :         connectionManagerConfig->ioContext = Manager::instance().ioContext();
    4374         578 :         connectionManagerConfig->dht = dht();
    4375         578 :         connectionManagerConfig->certStore = certStore_;
    4376         578 :         connectionManagerConfig->id = identity();
    4377         578 :         connectionManagerConfig->upnpCtrl = upnpCtrl_;
    4378         578 :         connectionManagerConfig->turnServer = config().turnServer;
    4379         578 :         connectionManagerConfig->upnpEnabled = config().upnpEnabled;
    4380         578 :         connectionManagerConfig->turnServerUserName = config().turnServerUserName;
    4381         578 :         connectionManagerConfig->turnServerPwd = config().turnServerPwd;
    4382         578 :         connectionManagerConfig->turnServerRealm = config().turnServerRealm;
    4383         578 :         connectionManagerConfig->turnEnabled = config().turnEnabled;
    4384         578 :         connectionManagerConfig->cachePath = cachePath_;
    4385         578 :         if (Manager::instance().dhtnetLogLevel > 0) {
    4386           0 :             connectionManagerConfig->logger = logger_;
    4387             :         }
    4388         578 :         connectionManagerConfig->factory = Manager::instance().getIceTransportFactory();
    4389         578 :         connectionManagerConfig->turnCache = turnCache_;
    4390         578 :         connectionManagerConfig->rng = std::make_unique<std::mt19937_64>(dht::crypto::getDerivedRandomEngine(rand));
    4391         578 :         connectionManagerConfig->legacyMode = dhtnet::LegacyMode::Supported;
    4392         578 :         connectionManager_ = std::make_unique<dhtnet::ConnectionManager>(connectionManagerConfig);
    4393        1156 :         channelHandlers_[Uri::Scheme::SWARM] = std::make_unique<SwarmChannelHandler>(shared(),
    4394        1156 :                                                                                      *connectionManager_.get());
    4395        1156 :         channelHandlers_[Uri::Scheme::GIT] = std::make_unique<ConversationChannelHandler>(shared(),
    4396        1156 :                                                                                           *connectionManager_.get());
    4397         578 :         channelHandlers_[Uri::Scheme::SYNC] = std::make_unique<SyncChannelHandler>(shared(), *connectionManager_.get());
    4398         578 :         channelHandlers_[Uri::Scheme::DATA_TRANSFER]
    4399        1156 :             = std::make_unique<TransferChannelHandler>(shared(), *connectionManager_.get());
    4400        1734 :         channelHandlers_[Uri::Scheme::MESSAGE] = std::make_unique<MessageChannelHandler>(
    4401         578 :             *connectionManager_.get(),
    4402       13233 :             [this](const auto& cert, std::string& type, const std::string& content) {
    4403       26469 :                 onTextMessage("", cert->issuer->getId().toString(), cert, {{type, content}});
    4404       13236 :             },
    4405        2200 :             [w = weak()](const std::string& peer, bool connected) {
    4406        2200 :                 asio::post(*Manager::instance().ioContext(), [w, peer, connected] {
    4407        2200 :                     if (auto acc = w.lock())
    4408        2200 :                         acc->onPeerConnected(peer, connected);
    4409        2200 :                 });
    4410        2777 :             });
    4411         578 :         channelHandlers_[Uri::Scheme::AUTH] = std::make_unique<AuthChannelHandler>(shared(), *connectionManager_.get());
    4412             : 
    4413             : #if TARGET_OS_IOS
    4414             :         connectionManager_->oniOSConnected([&](const std::string& connType, dht::InfoHash peer_h) {
    4415             :             if ((connType == "videoCall" || connType == "audioCall") && jami::Manager::instance().isIOSExtension) {
    4416             :                 bool hasVideo = connType == "videoCall";
    4417             :                 emitSignal<libjami::ConversationSignal::CallConnectionRequest>("", peer_h.toString(), hasVideo);
    4418             :                 return true;
    4419             :             }
    4420             :             return false;
    4421             :         });
    4422             : #endif
    4423         578 :     }
    4424         672 : }
    4425             : 
    4426             : void
    4427         911 : JamiAccount::updateUpnpController()
    4428             : {
    4429         911 :     Account::updateUpnpController();
    4430         911 :     if (connectionManager_) {
    4431          47 :         auto config = connectionManager_->getConfig();
    4432          47 :         if (config)
    4433          47 :             config->upnpCtrl = upnpCtrl_;
    4434          47 :     }
    4435         911 : }
    4436             : 
    4437             : } // namespace jami

Generated by: LCOV version 1.14