LCOV - code coverage report
Current view: top level - src/jamidht - jamiaccount.cpp (source / functions) Coverage Total Hit
Test: jami-coverage-filtered.info Lines: 70.2 % 2971 2087
Test Date: 2026-07-06 08:25:38 Functions: 61.5 % 613 377

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

Generated by: LCOV version 2.0-1