LCOV - code coverage report
Current view: top level - foo/src/jamidht - jamiaccount.cpp (source / functions) Hit Total Coverage
Test: jami-coverage-filtered.info Lines: 1951 2754 70.8 %
Date: 2025-10-16 08:11:43 Functions: 314 521 60.3 %

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

Generated by: LCOV version 1.14