LCOV - code coverage report
Current view: top level - src/jamidht - jamiaccount.cpp (source / functions) Hit Total Coverage
Test: jami-coverage-filtered.info Lines: 1805 2499 72.2 %
Date: 2024-05-08 08:55:44 Functions: 254 388 65.5 %

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

Generated by: LCOV version 1.14