LCOV - code coverage report
Current view: top level - foo/src/jamidht - jamiaccount.cpp (source / functions) Hit Total Coverage
Test: jami-coverage-filtered.info Lines: 1931 2654 72.8 %
Date: 2026-02-28 10:41:24 Functions: 330 532 62.0 %

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

Generated by: LCOV version 1.14