LCOV - code coverage report
Current view: top level - src/jamidht - jamiaccount.cpp (source / functions) Hit Total Coverage
Test: jami-coverage-filtered.info Lines: 1960 2673 73.3 %
Date: 2024-12-21 08:56:24 Functions: 299 489 61.1 %

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

Generated by: LCOV version 1.14