LCOV - code coverage report
Current view: top level - foo/src/jamidht - jamiaccount.cpp (source / functions) Hit Total Coverage
Test: jami-coverage-filtered.info Lines: 1921 2709 70.9 %
Date: 2026-01-22 10:39:23 Functions: 324 532 60.9 %

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

Generated by: LCOV version 1.14