LCOV - code coverage report
Current view: top level - foo/src/jamidht - jamiaccount.cpp (source / functions) Hit Total Coverage
Test: jami-coverage-filtered.info Lines: 1976 2697 73.3 %
Date: 2026-04-01 09:29:43 Functions: 347 546 63.6 %

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

Generated by: LCOV version 1.14