LCOV - code coverage report
Current view: top level - src/jamidht/swarm - swarm_manager.cpp (source / functions) Coverage Total Hit
Test: jami-coverage-filtered.info Lines: 88.2 % 768 677
Test Date: 2026-11-09 10:41:14 Functions: 98.3 % 60 59

            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              : #include "swarm_manager.h"
      19              : #include "jamidht/timestamp.h"
      20              : #include <dhtnet/multiplexed_socket.h>
      21              : #include <dhtnet/channel_utils.h>
      22              : #include <opendht/thread_pool.h>
      23              : 
      24              : namespace jami {
      25              : 
      26              : using namespace swarm_protocol;
      27              : 
      28              : static bool
      29           11 : isNewerLease(const MobileLease& candidate, const MobileLease& reference)
      30              : {
      31           11 :     return candidate.expires_at > reference.expires_at
      32           11 :            || (candidate.expires_at == reference.expires_at && candidate.issued_at > reference.issued_at);
      33              : }
      34              : 
      35         1184 : SwarmManager::SwarmManager(const NodeId& id,
      36              :                            bool isMobile,
      37              :                            const std::mt19937_64& rand,
      38              :                            ToConnectCb&& toConnectCb,
      39              :                            std::string conversationId,
      40              :                            MobileLeaseProvider mobileLeaseProvider,
      41              :                            MobileLeaseIssuerValidator mobileLeaseIssuerValidator,
      42              :                            CertificateProvider certificateProvider,
      43         1184 :                            CertificateFetcher certificateFetcher)
      44         1184 :     : id_(id)
      45         1184 :     , isMobile_(isMobile)
      46         1184 :     , conversationId_(std::move(conversationId))
      47         1184 :     , rd(rand)
      48         1184 :     , mobileLeaseProvider_(std::move(mobileLeaseProvider))
      49         1184 :     , mobileLeaseIssuerValidator_(std::move(mobileLeaseIssuerValidator))
      50         1184 :     , certificateProvider_(std::move(certificateProvider))
      51         1184 :     , certificateFetcher_(std::move(certificateFetcher))
      52         3552 :     , toConnectCb_(toConnectCb)
      53              : {
      54         1184 :     routing_table.setId(id);
      55         1184 : }
      56              : 
      57         2368 : SwarmManager::~SwarmManager()
      58              : {
      59         1184 :     if (!isShutdown_)
      60          280 :         shutdown();
      61         1184 : }
      62              : 
      63              : bool
      64         4040 : SwarmManager::setKnownNodes(const std::vector<NodeId>& known_nodes)
      65              : {
      66         4040 :     isShutdown_ = false;
      67         4040 :     std::vector<NodeId> newNodes;
      68              :     {
      69         4040 :         std::lock_guard lock(mutex);
      70         8381 :         for (const auto& nodeId : known_nodes) {
      71         4341 :             if (addKnownNode(nodeId)) {
      72         1147 :                 newNodes.emplace_back(nodeId);
      73              :             }
      74              :         }
      75         4040 :     }
      76              : 
      77         4040 :     if (newNodes.empty()) {
      78              :         // Members coming back online are re-announced with the devices we already
      79              :         // know, so addKnownNode() reports nothing new and the block below is
      80              :         // skipped. When the routing table holds no connected node, that announce
      81              :         // is the only chance we get to retry: nothing else calls maintainBuckets()
      82              :         // and the swarm stays silent until the account is reloaded. Retry the
      83              :         // candidates we already have instead of dropping the notification.
      84         3056 :         if (isConnected())
      85         1616 :             return false;
      86         1440 :         dht::ThreadPool::io().run([w = weak()] {
      87         1440 :             if (auto shared = w.lock())
      88         1440 :                 shared->maintainBuckets();
      89         1440 :         });
      90         1440 :         return false;
      91              :     }
      92              : 
      93          984 :     dht::ThreadPool::io().run([w = weak(), newNodes = std::move(newNodes)] {
      94          984 :         auto shared = w.lock();
      95          984 :         if (!shared)
      96            0 :             return;
      97              :         // If we detect a new node which already got a TCP link
      98              :         // we can use it to speed-up the bootstrap (because opening
      99              :         // a new channel will be easy)
     100          984 :         std::set<NodeId> toConnect;
     101         2131 :         for (const auto& nodeId : newNodes) {
     102         1147 :             if (shared->toConnectCb_ && shared->toConnectCb_(nodeId))
     103          410 :                 toConnect.emplace(nodeId);
     104              :         }
     105          984 :         shared->maintainBuckets(toConnect);
     106          984 :     });
     107          984 :     return true;
     108         4040 : }
     109              : 
     110              : void
     111          643 : SwarmManager::setMobileNodes(const std::vector<NodeId>& mobile_nodes)
     112              : {
     113          643 :     bool changed = false;
     114              :     {
     115          643 :         std::lock_guard lock(mutex);
     116          643 :         const auto now = toSecondsSinceEpoch(std::chrono::system_clock::now());
     117          643 :         if (!conversationId_.empty() && now >= LEGACY_MOBILE_NODE_SUNSET)
     118            1 :             return;
     119          843 :         for (const auto& nodeId : mobile_nodes) {
     120          201 :             changed |= addMobileNodes(nodeId);
     121          201 :             if (!conversationId_.empty() && !mobileNodeLeases_.contains(nodeId))
     122            0 :                 changed |= legacyMobileNodeExpiries_.try_emplace(nodeId, LEGACY_MOBILE_NODE_SUNSET).second;
     123              :         }
     124          642 :         scheduleMobileLeaseExpiryInternal();
     125          643 :     }
     126          642 :     if (changed)
     127           21 :         emitMobileNodesChanged();
     128              : }
     129              : 
     130              : void
     131           16 : SwarmManager::setMobileNodes(const std::vector<MobileNodeInfo>& mobile_nodes, bool requireLease)
     132              : {
     133           16 :     bool changed = false;
     134           16 :     size_t records = 0;
     135           34 :     for (const auto& mobile : mobile_nodes) {
     136           18 :         if (records++ == MAX_MOBILE_NODE_INFOS)
     137            0 :             break;
     138           18 :         changed |= setMobileNodeInfo(mobile, requireLease);
     139              :     }
     140           16 :     if (changed)
     141            6 :         emitMobileNodesChanged();
     142           16 : }
     143              : 
     144              : bool
     145           26 : SwarmManager::setMobileNodeInfo(const MobileNodeInfo& mobile,
     146              :                                 bool requireLease,
     147              :                                 const std::shared_ptr<dhtnet::ChannelSocketInterface>& source)
     148              : {
     149           26 :     if (mobile.id == id_)
     150            0 :         return false;
     151              : 
     152           26 :     if (!mobile.lease) {
     153            6 :         if (requireLease)
     154            1 :             return false;
     155              :         const auto now = static_cast<uint64_t>(
     156            5 :             std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch())
     157            5 :                 .count());
     158            5 :         if (!conversationId_.empty() && now >= LEGACY_MOBILE_NODE_SUNSET)
     159            1 :             return false;
     160            4 :         std::lock_guard lock(mutex);
     161            4 :         bool changed = addMobileNodes(mobile.id);
     162            4 :         if (!conversationId_.empty() && !mobileNodeLeases_.contains(mobile.id)) {
     163            0 :             changed |= legacyMobileNodeExpiries_.try_emplace(mobile.id, LEGACY_MOBILE_NODE_SUNSET).second;
     164            0 :             scheduleMobileLeaseExpiryInternal();
     165              :         }
     166            4 :         return changed;
     167            4 :     }
     168              : 
     169           20 :     const auto& lease = *mobile.lease;
     170           20 :     if (lease.device_id != mobile.id || !precheckLease(lease))
     171            2 :         return false;
     172              : 
     173              :     {
     174              :         // Gossip re-announces the same lease every round: skip the whole
     175              :         // resolution when what we already verified is at least as good.
     176           18 :         std::lock_guard lock(mutex);
     177           18 :         auto known = mobileNodeLeases_.find(mobile.id);
     178           18 :         if (known != mobileNodeLeases_.end() && !isNewerLease(lease, known->second))
     179            1 :             return false;
     180           18 :     }
     181              : 
     182              :     // The certificate is never gossiped: resolve it from the account certificate
     183              :     // store, which already holds every device we ever connected to (the swarm
     184              :     // channel pins its TLS peer certificate) and everything resolved before.
     185           17 :     if (certificateProvider_) {
     186           12 :         if (auto certificate = certificateProvider_(mobile.id)) {
     187            6 :             if (!verifyLease(*certificate, lease))
     188            1 :                 return false;
     189            5 :             std::lock_guard lock(mutex);
     190            5 :             return commitLeaseInternal(lease);
     191           17 :         }
     192              :     }
     193              : 
     194           11 :     std::lock_guard lock(mutex);
     195           11 :     enqueuePendingLeaseInternal(lease, source);
     196           11 :     return false;
     197           11 : }
     198              : 
     199              : void
     200         3459 : SwarmManager::addChannel(const std::shared_ptr<dhtnet::ChannelSocketInterface>& channel)
     201              : {
     202              :     // JAMI_WARNING("[SwarmManager {}] addChannel! with {}", fmt::ptr(this), channel->deviceId().to_view());
     203         3459 :     if (channel) {
     204         3459 :         auto emit = false;
     205         3459 :         auto added = false;
     206              :         {
     207         3459 :             std::lock_guard lock(mutex);
     208         3459 :             emit = routing_table.isEmpty();
     209         3459 :             auto bucket = routing_table.findBucket(channel->deviceId());
     210         3459 :             added = routing_table.addNode(channel, bucket);
     211         3459 :         }
     212         3459 :         if (added) {
     213         2093 :             std::error_code ec;
     214         2093 :             resetNodeExpiry(ec, channel, id_);
     215              :         }
     216         3459 :         receiveMessage(channel);
     217         3459 :         if (emit && onConnectionChanged_) {
     218              :             // If it's the first channel we add, we're now connected!
     219          763 :             JAMI_DEBUG("[SwarmManager {}] Bootstrap: Connected!", fmt::ptr(this));
     220          763 :             onConnectionChanged_(true);
     221              :         }
     222              :     }
     223         3459 : }
     224              : 
     225              : void
     226         1100 : SwarmManager::removeNode(const NodeId& nodeId)
     227              : {
     228         1100 :     std::unique_lock lk(mutex);
     229         1100 :     if (isConnectedWith(nodeId)) {
     230          968 :         removeNodeInternal(nodeId);
     231          968 :         lk.unlock();
     232          968 :         maintainBuckets();
     233              :     }
     234         1100 : }
     235              : 
     236              : void
     237          255 : SwarmManager::changeMobility(const NodeId& nodeId, bool isMobile)
     238              : {
     239              :     {
     240          255 :         std::lock_guard lock(mutex);
     241          255 :         auto bucket = routing_table.findBucket(nodeId);
     242          255 :         bucket->changeMobility(nodeId, isMobile);
     243          255 :     }
     244          255 :     emitMobileNodesChanged();
     245          255 : }
     246              : 
     247              : bool
     248         1778 : SwarmManager::isConnectedWith(const NodeId& deviceId)
     249              : {
     250         1778 :     return routing_table.hasNode(deviceId);
     251              : }
     252              : 
     253              : void
     254         1282 : SwarmManager::shutdown()
     255              : {
     256         1282 :     if (isShutdown_) {
     257           36 :         return;
     258              :     }
     259         1246 :     isShutdown_ = true;
     260         1246 :     std::lock_guard lock(mutex);
     261         1246 :     mobileLeaseExpiryTimer_.cancel();
     262         1246 :     for (auto& [peer, state] : outstandingCertRequests_)
     263            0 :         if (state.timer)
     264            0 :             state.timer->cancel();
     265         1246 :     outstandingCertRequests_.clear();
     266         1246 :     routing_table.shutdownAllNodes();
     267         1246 : }
     268              : 
     269              : void
     270           34 : SwarmManager::restart()
     271              : {
     272           34 :     isShutdown_ = false;
     273           34 :     std::lock_guard lock(mutex);
     274           34 :     scheduleMobileLeaseExpiryInternal();
     275           34 : }
     276              : 
     277              : bool
     278         5014 : SwarmManager::addKnownNode(const NodeId& nodeId)
     279              : {
     280         5014 :     return routing_table.addKnownNode(nodeId);
     281              : }
     282              : 
     283              : bool
     284          216 : SwarmManager::addMobileNodes(const NodeId& nodeId)
     285              : {
     286          216 :     if (id_ != nodeId) {
     287          214 :         return routing_table.addMobileNode(nodeId);
     288              :     }
     289            2 :     return false;
     290              : }
     291              : 
     292              : bool
     293          640 : SwarmManager::isMobileNodeCurrentInternal(const NodeId& nodeId, int64_t now) const
     294              : {
     295          640 :     if (auto lease = mobileNodeLeases_.find(nodeId); lease != mobileNodeLeases_.end())
     296           28 :         return lease->second.expires_at > now;
     297          612 :     if (auto legacy = legacyMobileNodeExpiries_.find(nodeId); legacy != legacyMobileNodeExpiries_.end())
     298            0 :         return legacy->second > now;
     299          612 :     return conversationId_.empty();
     300              : }
     301              : 
     302              : bool
     303           34 : SwarmManager::precheckLease(const MobileLease& lease) const
     304              : {
     305           34 :     if (lease.format_version != 1 || lease.conversation_id != conversationId_ || lease.conversation_id.empty()
     306           33 :         || lease.conversation_id.size() > MAX_MOBILE_LEASE_IDENTIFIER_SIZE || lease.signature.empty()
     307           33 :         || lease.signature.size() > MAX_MOBILE_LEASE_SIGNATURE_SIZE || !lease.issuer_id
     308           68 :         || !mobileLeaseIssuerValidator_ || !mobileLeaseIssuerValidator_(lease.issuer_id))
     309            3 :         return false;
     310              : 
     311           31 :     constexpr auto MAX_CLOCK_SKEW = std::chrono::seconds(5 * 60);
     312           31 :     const auto now = std::chrono::system_clock::now();
     313           31 :     const auto issued = std::chrono::system_clock::time_point(std::chrono::seconds(lease.issued_at));
     314           31 :     const auto expires = std::chrono::system_clock::time_point(std::chrono::seconds(lease.expires_at));
     315           62 :     if (issued > now + MAX_CLOCK_SKEW || expires <= now || expires <= issued
     316           62 :         || expires - issued > MAX_MOBILE_LEASE_DURATION)
     317            0 :         return false;
     318           31 :     return true;
     319              : }
     320              : 
     321              : bool
     322           13 : SwarmManager::verifyLease(const dht::crypto::Certificate& certificate, const MobileLease& lease) const
     323              : {
     324              :     try {
     325           26 :         if (certificate.getLongId() != lease.device_id || !certificate.issuer
     326           26 :             || certificate.issuer->getId() != lease.issuer_id)
     327            0 :             return false;
     328           13 :         dht::crypto::TrustList trust;
     329           13 :         trust.add(*certificate.issuer);
     330           13 :         if (!trust.verify(certificate))
     331            0 :             return false;
     332           13 :         auto certificateExpiry = toSecondsSinceEpoch(certificate.getExpiration());
     333           13 :         if (lease.expires_at > certificateExpiry)
     334            0 :             return false;
     335           13 :         const auto payload = mobileLeasePayload(lease);
     336           13 :         return certificate.getPublicKey().checkSignature(payload, lease.signature);
     337           13 :     } catch (const std::exception& e) {
     338            0 :         JAMI_WARNING("Ignoring invalid mobile lease for {}: {}", lease.device_id, e.what());
     339            0 :         return false;
     340            0 :     }
     341              : }
     342              : 
     343              : bool
     344           11 : SwarmManager::commitLeaseInternal(const MobileLease& lease)
     345              : {
     346           11 :     auto pending = pendingMobileLeases_.find(lease.device_id);
     347           11 :     if (pending != pendingMobileLeases_.end()) {
     348            6 :         if (!isNewerLease(pending->second.lease, lease))
     349            6 :             pendingMobileLeases_.erase(pending);
     350              :     }
     351              : 
     352           11 :     bool changed = addMobileNodes(lease.device_id);
     353           11 :     auto current = mobileNodeLeases_.find(lease.device_id);
     354           11 :     if (current == mobileNodeLeases_.end() || isNewerLease(lease, current->second)) {
     355           11 :         mobileNodeLeases_.insert_or_assign(lease.device_id, lease);
     356           11 :         legacyMobileNodeExpiries_.erase(lease.device_id);
     357           11 :         changed = true;
     358              :     }
     359           11 :     scheduleMobileLeaseExpiryInternal();
     360           11 :     return changed;
     361              : }
     362              : 
     363              : void
     364           11 : SwarmManager::enqueuePendingLeaseInternal(const MobileLease& lease,
     365              :                                           const std::shared_ptr<dhtnet::ChannelSocketInterface>& source)
     366              : {
     367           11 :     const auto& nodeId = lease.device_id;
     368           11 :     auto pending = pendingMobileLeases_.find(nodeId);
     369           11 :     if (pending != pendingMobileLeases_.end()) {
     370            1 :         if (isNewerLease(lease, pending->second.lease))
     371            0 :             pending->second.lease = lease;
     372            1 :         if (source)
     373            1 :             pending->second.source = source;
     374              :     } else {
     375           10 :         if (pendingMobileLeases_.size() >= MAX_PENDING_MOBILE_LEASES) {
     376              :             // Evict the entry that would expire first: it is the least useful to keep resolving.
     377            0 :             auto oldest = std::min_element(pendingMobileLeases_.begin(),
     378              :                                            pendingMobileLeases_.end(),
     379            0 :                                            [](const auto& a, const auto& b) {
     380            0 :                                                return a.second.lease.expires_at < b.second.lease.expires_at;
     381              :                                            });
     382            0 :             if (oldest != pendingMobileLeases_.end() && oldest->second.lease.expires_at >= lease.expires_at)
     383            0 :                 return;
     384            0 :             pendingMobileLeases_.erase(oldest);
     385              :         }
     386           10 :         pendingMobileLeases_.emplace(nodeId, PendingLease {lease, source});
     387              :     }
     388              : 
     389           11 :     if (certFetchInFlight_.count(nodeId))
     390            0 :         return;
     391              : 
     392           11 :     if (source) {
     393            6 :         certFetchInFlight_.emplace(nodeId);
     394            6 :         dht::ThreadPool::io().run([w = weak(), source, nodeId] {
     395            6 :             if (auto shared = w.lock())
     396           18 :                 shared->requestCertificates(source, {nodeId});
     397            6 :         });
     398              :     } else {
     399            5 :         certFetchInFlight_.emplace(nodeId);
     400            5 :         dht::ThreadPool::io().run([w = weak(), nodeId] {
     401            5 :             if (auto shared = w.lock())
     402            5 :                 shared->fetchCertificateFromDht(nodeId);
     403            5 :         });
     404              :     }
     405              : }
     406              : 
     407              : void
     408            4 : SwarmManager::abandonLeaseInternal(const NodeId& nodeId)
     409              : {
     410            4 :     certFetchInFlight_.erase(nodeId);
     411            4 :     pendingMobileLeases_.erase(nodeId);
     412            4 : }
     413              : 
     414              : void
     415            6 : SwarmManager::requestCertificates(const std::shared_ptr<dhtnet::ChannelSocketInterface>& socket,
     416              :                                   const std::vector<NodeId>& ids)
     417              : {
     418            6 :     if (!socket || ids.empty() || isShutdown_) {
     419            0 :         std::lock_guard lock(mutex);
     420            0 :         for (const auto& id : ids)
     421            0 :             certFetchInFlight_.erase(id);
     422            0 :         return;
     423            0 :     }
     424            6 :     const auto peer = NodeId(socket->deviceId());
     425            6 :     CertRequest request;
     426              :     {
     427            6 :         std::lock_guard lock(mutex);
     428            6 :         auto& state = outstandingCertRequests_[peer];
     429            6 :         if (state.timer) {
     430              :             // One request in flight per peer: drop the resolution so that the
     431              :             // next gossip round asks again.
     432            2 :             for (const auto& id : ids)
     433            1 :                 certFetchInFlight_.erase(id);
     434            1 :             return;
     435              :         }
     436           10 :         for (const auto& id : ids) {
     437            5 :             if (request.ids.size() >= MAX_CERT_REQUEST_IDS) {
     438            0 :                 certFetchInFlight_.erase(id);
     439            0 :                 continue;
     440              :             }
     441            5 :             request.ids.emplace_back(id);
     442              :         }
     443            5 :         if (request.ids.empty()) {
     444            0 :             outstandingCertRequests_.erase(peer);
     445            0 :             return;
     446              :         }
     447            5 :         state.ids.insert(request.ids.begin(), request.ids.end());
     448            5 :         state.timer = std::make_shared<asio::steady_timer>(*Manager::instance().ioContext());
     449            5 :         state.timer->expires_after(CERT_REQUEST_TIMEOUT);
     450            5 :         state.timer->async_wait([w = weak(), peer](const asio::error_code& ec) {
     451            5 :             if (ec == asio::error::operation_aborted)
     452            4 :                 return;
     453            1 :             auto shared = w.lock();
     454            1 :             if (!shared)
     455            0 :                 return;
     456            1 :             std::vector<NodeId> unanswered;
     457              :             {
     458            1 :                 std::lock_guard lock(shared->mutex);
     459            1 :                 auto it = shared->outstandingCertRequests_.find(peer);
     460            1 :                 if (it == shared->outstandingCertRequests_.end())
     461            0 :                     return;
     462            1 :                 unanswered.assign(it->second.ids.begin(), it->second.ids.end());
     463            1 :                 shared->outstandingCertRequests_.erase(it);
     464            1 :             }
     465              :             // The peer did not answer: fall back to the DHT.
     466            2 :             for (const auto& id : unanswered)
     467            1 :                 shared->fetchCertificateFromDht(id);
     468            1 :         });
     469            6 :     }
     470              : 
     471            5 :     Message msg;
     472            5 :     msg.is_mobile = isMobile_;
     473            5 :     msg.cert_request = std::move(request);
     474              : 
     475            5 :     msgpack::sbuffer buffer;
     476            5 :     msgpack::packer<msgpack::sbuffer> pk(&buffer);
     477            5 :     pk.pack(msg);
     478              : 
     479            5 :     std::error_code ec;
     480            5 :     socket->write(reinterpret_cast<const unsigned char*>(buffer.data()), buffer.size(), ec);
     481            5 :     if (ec)
     482            0 :         JAMI_ERROR("{}", ec.message());
     483            6 : }
     484              : 
     485              : void
     486            2 : SwarmManager::onCertRequest(const std::shared_ptr<dhtnet::ChannelSocketInterface>& socket, const CertRequest& request)
     487              : {
     488            2 :     if (!socket || request.ids.empty() || !certificateProvider_)
     489            0 :         return;
     490              : 
     491            2 :     std::vector<NodeId> toAnswer;
     492              :     {
     493            2 :         std::lock_guard lock(mutex);
     494            5 :         for (const auto& id : request.ids) {
     495            3 :             if (toAnswer.size() >= MAX_CERT_REQUEST_IDS)
     496            0 :                 break;
     497              :             // A peer must not be able to use the swarm as a generic certificate
     498              :             // oracle: only serve certificates for devices we ourselves announced
     499              :             // as mobile in this conversation, plus our own when we are mobile.
     500            3 :             if (id != id_ && !mobileNodeLeases_.count(id))
     501            2 :                 continue;
     502            1 :             toAnswer.emplace_back(id);
     503              :         }
     504            2 :     }
     505            2 :     if (toAnswer.empty())
     506            1 :         return;
     507              : 
     508            1 :     CertResponse response;
     509            1 :     size_t totalSize = 0;
     510            2 :     for (const auto& id : toAnswer) {
     511            1 :         auto certificate = certificateProvider_(id);
     512            1 :         if (!certificate)
     513            0 :             continue;
     514            1 :         auto packed = certificate->getPacked();
     515            2 :         if (packed.empty() || packed.size() > MAX_MOBILE_CERTIFICATE_SIZE
     516            2 :             || totalSize + packed.size() > MAX_MOBILE_CERTIFICATES_SIZE)
     517            0 :             continue;
     518            1 :         totalSize += packed.size();
     519            1 :         response.certificates.emplace_back(std::move(packed));
     520            1 :     }
     521            1 :     if (response.certificates.empty())
     522            0 :         return;
     523              : 
     524            1 :     Message msg;
     525            1 :     msg.is_mobile = isMobile_;
     526            1 :     msg.cert_response = std::move(response);
     527              : 
     528            1 :     msgpack::sbuffer buffer;
     529            1 :     msgpack::packer<msgpack::sbuffer> pk(&buffer);
     530            1 :     pk.pack(msg);
     531              : 
     532            1 :     std::error_code ec;
     533            1 :     socket->write(reinterpret_cast<const unsigned char*>(buffer.data()), buffer.size(), ec);
     534            1 :     if (ec)
     535            0 :         JAMI_ERROR("{}", ec.message());
     536            2 : }
     537              : 
     538              : void
     539            5 : SwarmManager::onCertResponse(const std::shared_ptr<dhtnet::ChannelSocketInterface>& socket, const CertResponse& response)
     540              : {
     541            5 :     if (!socket)
     542            0 :         return;
     543            5 :     const auto peer = NodeId(socket->deviceId());
     544              : 
     545            5 :     std::set<NodeId> requested;
     546              :     {
     547            5 :         std::lock_guard lock(mutex);
     548            5 :         auto it = outstandingCertRequests_.find(peer);
     549            5 :         if (it == outstandingCertRequests_.end())
     550            1 :             return; // Unsolicited.
     551            4 :         requested = std::move(it->second.ids);
     552            4 :         if (it->second.timer)
     553            4 :             it->second.timer->cancel();
     554            4 :         outstandingCertRequests_.erase(it);
     555            5 :     }
     556              : 
     557            4 :     size_t totalSize = 0;
     558            8 :     for (const auto& packed : response.certificates) {
     559            8 :         if (packed.empty() || packed.size() > MAX_MOBILE_CERTIFICATE_SIZE
     560            8 :             || totalSize + packed.size() > MAX_MOBILE_CERTIFICATES_SIZE)
     561            0 :             break;
     562            4 :         totalSize += packed.size();
     563              :         try {
     564            4 :             auto certificate = std::make_shared<dht::crypto::Certificate>(packed);
     565            4 :             const auto nodeId = certificate->getLongId();
     566            4 :             if (!requested.erase(nodeId))
     567            1 :                 continue; // Not something we asked for.
     568            3 :             onCertificateResolved(nodeId, certificate);
     569            4 :         } catch (const std::exception& e) {
     570            0 :             JAMI_WARNING("Ignoring invalid certificate from {}: {}", peer, e.what());
     571            0 :         }
     572              :     }
     573              : 
     574              :     // Whatever the peer could not provide is worth one DHT lookup.
     575            5 :     for (const auto& nodeId : requested)
     576            1 :         fetchCertificateFromDht(nodeId);
     577            5 : }
     578              : 
     579              : void
     580            7 : SwarmManager::fetchCertificateFromDht(const NodeId& nodeId)
     581              : {
     582            7 :     if (isShutdown_)
     583            0 :         return;
     584            7 :     if (!certificateFetcher_) {
     585            2 :         std::lock_guard lock(mutex);
     586            2 :         abandonLeaseInternal(nodeId);
     587            2 :         return;
     588            2 :     }
     589            5 :     certificateFetcher_(nodeId, [w = weak(), nodeId](const std::shared_ptr<dht::crypto::Certificate>& certificate) {
     590            5 :         auto shared = w.lock();
     591            5 :         if (!shared)
     592            0 :             return;
     593            5 :         if (certificate && certificate->getLongId() == nodeId)
     594            4 :             shared->onCertificateResolved(nodeId, certificate);
     595              :         else {
     596            1 :             std::lock_guard lock(shared->mutex);
     597            1 :             shared->abandonLeaseInternal(nodeId);
     598            1 :         }
     599            5 :     });
     600              : }
     601              : 
     602              : void
     603            7 : SwarmManager::onCertificateResolved(const NodeId& nodeId, const std::shared_ptr<dht::crypto::Certificate>& certificate)
     604              : {
     605            7 :     if (!certificate)
     606            0 :         return;
     607              : 
     608            7 :     std::optional<MobileLease> lease;
     609              :     {
     610            7 :         std::lock_guard lock(mutex);
     611            7 :         certFetchInFlight_.erase(nodeId);
     612            7 :         auto pending = pendingMobileLeases_.find(nodeId);
     613            7 :         if (pending == pendingMobileLeases_.end())
     614            0 :             return;
     615            7 :         lease = pending->second.lease;
     616            7 :     }
     617              : 
     618              :     // Re-run the cheap checks: the lease may have expired while we were resolving.
     619            7 :     if (!precheckLease(*lease) || !verifyLease(*certificate, *lease)) {
     620            1 :         std::lock_guard lock(mutex);
     621            1 :         auto pending = pendingMobileLeases_.find(nodeId);
     622            1 :         if (pending != pendingMobileLeases_.end() && !isNewerLease(pending->second.lease, *lease))
     623            1 :             abandonLeaseInternal(nodeId);
     624            1 :         return;
     625            1 :     }
     626              : 
     627            6 :     bool changed = false;
     628              :     {
     629            6 :         std::lock_guard lock(mutex);
     630            6 :         changed = commitLeaseInternal(*lease);
     631            6 :     }
     632            6 :     if (changed)
     633            6 :         emitMobileNodesChanged();
     634            7 : }
     635              : 
     636              : std::optional<MobileNodeInfo>
     637         4125 : SwarmManager::localMobileNodeInfo()
     638              : {
     639         4125 :     if (!isMobile_ || !mobileLeaseProvider_)
     640         4122 :         return std::nullopt;
     641              : 
     642            3 :     std::lock_guard renewalLock(mobileLeaseRenewalMtx_);
     643              : 
     644            3 :     auto renewalThresholdTime = toSecondsSinceEpoch(std::chrono::system_clock::now() + MOBILE_LEASE_RENEWAL_THRESHOLD);
     645              :     {
     646            3 :         std::lock_guard lock(mutex);
     647            5 :         if (localMobileNodeInfo_ && localMobileNodeInfo_->lease
     648            5 :             && localMobileNodeInfo_->lease->expires_at > renewalThresholdTime)
     649            1 :             return localMobileNodeInfo_;
     650            3 :     }
     651              : 
     652            2 :     auto renewed = mobileLeaseProvider_();
     653            4 :     if (!renewed || renewed->id != id_ || !renewed->lease || renewed->lease->device_id != id_
     654            4 :         || !precheckLease(*renewed->lease))
     655            0 :         return std::nullopt;
     656            2 :     std::lock_guard lock(mutex);
     657            2 :     localMobileNodeInfo_ = std::move(renewed);
     658            2 :     return localMobileNodeInfo_;
     659            3 : }
     660              : 
     661              : void
     662          704 : SwarmManager::scheduleMobileLeaseExpiryInternal()
     663              : {
     664          704 :     mobileLeaseExpiryTimer_.cancel();
     665          704 :     if ((mobileNodeLeases_.empty() && legacyMobileNodeExpiries_.empty()) || isShutdown_)
     666          693 :         return;
     667              : 
     668           11 :     auto nearestExpiry = std::numeric_limits<int64_t>::max();
     669           23 :     for (const auto& [node, lease] : mobileNodeLeases_)
     670           12 :         nearestExpiry = std::min(nearestExpiry, lease.expires_at);
     671           11 :     for (const auto& [node, expiry] : legacyMobileNodeExpiries_)
     672            0 :         nearestExpiry = std::min(nearestExpiry, expiry);
     673           11 :     auto expiryTime = timePointFromSeconds(nearestExpiry);
     674           11 :     const auto now = std::chrono::system_clock::now();
     675           11 :     constexpr auto MAX_TIMER_DELAY_SECONDS = std::chrono::minutes(1);
     676           11 :     const auto delay = std::min<std::chrono::system_clock::duration>(expiryTime > now ? expiryTime - now : std::chrono::seconds(0), MAX_TIMER_DELAY_SECONDS);
     677           11 :     mobileLeaseExpiryTimer_.expires_after(delay);
     678           11 :     mobileLeaseExpiryTimer_.async_wait([w = weak()](const asio::error_code& ec) {
     679           11 :         if (auto shared = w.lock())
     680           11 :             shared->expireMobileLeases(ec);
     681           11 :     });
     682              : }
     683              : 
     684              : void
     685            4 : SwarmManager::expireMobileLeases(const asio::error_code& ec)
     686              : {
     687            4 :     if (ec == asio::error::operation_aborted)
     688            3 :         return;
     689              : 
     690            1 :     bool changed = false;
     691              :     {
     692            1 :         std::lock_guard lock(mutex);
     693            1 :         auto now = toSecondsSinceEpoch(std::chrono::system_clock::now());
     694            2 :         for (auto it = mobileNodeLeases_.begin(); it != mobileNodeLeases_.end();) {
     695            1 :             if (it->second.expires_at > now) {
     696            0 :                 ++it;
     697            0 :                 continue;
     698              :             }
     699            1 :             const auto nodeId = it->first;
     700            1 :             it = mobileNodeLeases_.erase(it);
     701            1 :             auto legacy = legacyMobileNodeExpiries_.find(nodeId);
     702            1 :             if (legacy == legacyMobileNodeExpiries_.end() || legacy->second <= now) {
     703            1 :                 routing_table.removeMobileNode(nodeId);
     704            1 :                 routing_table.findBucket(nodeId)->changeMobility(nodeId, false);
     705              :             }
     706            1 :             changed = true;
     707              :         }
     708            1 :         for (auto it = legacyMobileNodeExpiries_.begin(); it != legacyMobileNodeExpiries_.end();) {
     709            0 :             if (it->second > now) {
     710            0 :                 ++it;
     711            0 :                 continue;
     712              :             }
     713            0 :             const auto nodeId = it->first;
     714            0 :             it = legacyMobileNodeExpiries_.erase(it);
     715            0 :             auto lease = mobileNodeLeases_.find(nodeId);
     716            0 :             if (lease == mobileNodeLeases_.end() || lease->second.expires_at <= now) {
     717            0 :                 routing_table.removeMobileNode(nodeId);
     718            0 :                 routing_table.findBucket(nodeId)->changeMobility(nodeId, false);
     719              :             }
     720            0 :             changed = true;
     721              :         }
     722            1 :         for (auto it = pendingMobileLeases_.begin(); it != pendingMobileLeases_.end();) {
     723            0 :             if (it->second.lease.expires_at > now)
     724            0 :                 ++it;
     725              :             else
     726            0 :                 it = pendingMobileLeases_.erase(it);
     727              :         }
     728            1 :         scheduleMobileLeaseExpiryInternal();
     729            1 :     }
     730            1 :     if (changed)
     731            1 :         emitMobileNodesChanged();
     732              : }
     733              : 
     734              : void
     735          292 : SwarmManager::emitMobileNodesChanged()
     736              : {
     737          292 :     std::lock_guard emissionLock(mobileNodesEmissionMtx_);
     738          292 :     auto mobileNodes = getKnownMobileNodes();
     739          292 :     auto mobileNodeInfos = getKnownMobileNodeInfos();
     740          292 :     OnMobileNodesChanged callback;
     741          292 :     OnMobileNodeInfosChanged infosCallback;
     742              :     {
     743          292 :         std::lock_guard callbackLock(onMobileNodesChangedMtx_);
     744          292 :         callback = onMobileNodesChanged_;
     745          292 :         infosCallback = onMobileNodeInfosChanged_;
     746          292 :     }
     747          292 :     if (callback)
     748           19 :         callback(mobileNodes);
     749          292 :     if (infosCallback)
     750            1 :         infosCallback(mobileNodeInfos);
     751          292 : }
     752              : 
     753              : void
     754         4822 : SwarmManager::maintainBuckets(const std::set<NodeId>& toConnect)
     755              : {
     756         4822 :     std::set<NodeId> nodes = toConnect;
     757         4822 :     std::unique_lock lock(mutex);
     758         4822 :     auto& buckets = routing_table.getBuckets();
     759        11817 :     for (auto it = buckets.begin(); it != buckets.end(); ++it) {
     760         6995 :         auto& bucket = *it;
     761         6995 :         bool myBucket = routing_table.contains(it, id_);
     762         9168 :         auto connecting_nodes = myBucket ? bucket.getConnectingNodesSize()
     763         2173 :                                          : bucket.getConnectingNodesSize() + bucket.getNodesSize();
     764         6995 :         if (connecting_nodes < Bucket::BUCKET_MAX_SIZE) {
     765         5245 :             auto nodesToTry = bucket.getKnownNodesRandom(Bucket::BUCKET_MAX_SIZE - connecting_nodes, rd);
     766         6918 :             for (auto& node : nodesToTry)
     767         1673 :                 routing_table.addConnectingNode(node);
     768              : 
     769         5245 :             nodes.insert(nodesToTry.begin(), nodesToTry.end());
     770         5245 :         }
     771              :     }
     772         4822 :     lock.unlock();
     773         6521 :     for (const auto& node : nodes)
     774         1699 :         tryConnect(node);
     775         4822 : }
     776              : 
     777              : void
     778         2093 : SwarmManager::sendRequest(const std::shared_ptr<dhtnet::ChannelSocketInterface>& socket,
     779              :                           const NodeId& nodeId,
     780              :                           Query q,
     781              :                           int numberNodes)
     782              : {
     783         2093 :     auto selfMobileInfo = localMobileNodeInfo();
     784         4186 :     dht::ThreadPool::io().run(
     785         4186 :         [socket, isMobile = isMobile_, selfMobileInfo = std::move(selfMobileInfo), nodeId, q, numberNodes] {
     786         2093 :             msgpack::sbuffer buffer;
     787         2093 :             msgpack::packer<msgpack::sbuffer> pk(&buffer);
     788         2093 :             Message msg;
     789         2093 :             msg.is_mobile = isMobile;
     790         2093 :             msg.self_mobile_info = selfMobileInfo;
     791         2093 :             msg.request = Request {q, numberNodes, nodeId};
     792         2093 :             pk.pack(msg);
     793              : 
     794         2093 :             std::error_code ec;
     795         2093 :             socket->write(reinterpret_cast<const unsigned char*>(buffer.data()), buffer.size(), ec);
     796         2093 :             if (ec) {
     797            4 :                 JAMI_ERROR("{}", ec.message());
     798              :             }
     799         2093 :         });
     800         2093 : }
     801              : 
     802              : void
     803         2029 : SwarmManager::sendAnswer(const std::shared_ptr<dhtnet::ChannelSocketInterface>& socket, const Message& msg_)
     804              : {
     805         2029 :     if (msg_.request->q != Query::FIND)
     806            0 :         return;
     807              : 
     808         2029 :     auto selfMobileInfo = localMobileNodeInfo();
     809         2029 :     Message msg;
     810              :     {
     811         2029 :         std::lock_guard lock(mutex);
     812         2029 :         auto nodes = routing_table.closestNodes(msg_.request->nodeId, msg_.request->num);
     813         2029 :         auto bucket = routing_table.findBucket(msg_.request->nodeId);
     814         2029 :         const auto& m_nodes = bucket->getMobileNodes();
     815         2029 :         std::vector<NodeId> responseMobileNodes;
     816         2029 :         responseMobileNodes.reserve(m_nodes.size());
     817         2029 :         std::vector<MobileNodeInfo> mobileNodeInfos;
     818         2029 :         mobileNodeInfos.reserve(m_nodes.size());
     819         2029 :         const auto now = toSecondsSinceEpoch(std::chrono::system_clock::now());
     820         2115 :         for (const auto& node : m_nodes) {
     821           86 :             if (!isMobileNodeCurrentInternal(node, now))
     822            0 :                 continue;
     823           86 :             responseMobileNodes.emplace_back(node);
     824           86 :             if (mobileNodeInfos.size() >= MAX_MOBILE_NODE_INFOS)
     825            0 :                 continue;
     826           86 :             auto lease = mobileNodeLeases_.find(node);
     827           86 :             if (lease == mobileNodeLeases_.end()) {
     828           86 :                 if (msg_.v >= 3)
     829           86 :                     continue;
     830            0 :                 mobileNodeInfos.emplace_back(MobileNodeInfo {node, std::nullopt});
     831              :             } else {
     832            0 :                 mobileNodeInfos.emplace_back(MobileNodeInfo {node, lease->second});
     833              :             }
     834              :         }
     835         2029 :         Response toResponse {Query::FOUND, nodes, std::move(responseMobileNodes), std::move(mobileNodeInfos)};
     836              : 
     837         2029 :         msg.is_mobile = isMobile_;
     838         2029 :         msg.self_mobile_info = std::move(selfMobileInfo);
     839         2029 :         msg.response = std::move(toResponse);
     840         2029 :     }
     841              : 
     842         2029 :     msgpack::sbuffer buffer;
     843         2029 :     msgpack::packer<msgpack::sbuffer> pk(&buffer);
     844         2029 :     pk.pack(msg);
     845              : 
     846         2029 :     std::error_code ec;
     847         2029 :     socket->write(reinterpret_cast<const unsigned char*>(buffer.data()), buffer.size(), ec);
     848         2029 :     if (ec) {
     849            8 :         JAMI_ERROR("{}", ec.message());
     850            8 :         return;
     851              :     }
     852         2045 : }
     853              : 
     854              : void
     855         3459 : SwarmManager::receiveMessage(const std::shared_ptr<dhtnet::ChannelSocketInterface>& socket)
     856              : {
     857         6918 :     socket->setOnRecv(dhtnet::buildMsgpackReader<Message>(
     858         6918 :         [w = weak(), wsocket = std::weak_ptr<dhtnet::ChannelSocketInterface>(socket)](Message&& msg) {
     859         4057 :             auto shared = w.lock();
     860         4057 :             auto socket = wsocket.lock();
     861         4057 :             if (!shared || !socket)
     862            0 :                 return std::make_error_code(std::errc::operation_canceled);
     863              : 
     864         4057 :             auto validMobileAnnouncement = msg.v < 3 || shared->conversationId_.empty();
     865         4057 :             if (msg.self_mobile_info && msg.self_mobile_info->id == socket->deviceId()) {
     866              :                 // The peer's own certificate is authenticated by the channel's TLS
     867              :                 // handshake and pinned when the swarm channel was added, so this
     868              :                 // resolves locally without any lookup.
     869            5 :                 validMobileAnnouncement = msg.self_mobile_info->lease
     870           10 :                                           && msg.self_mobile_info->lease->device_id == msg.self_mobile_info->id
     871           10 :                                           && shared->precheckLease(*msg.self_mobile_info->lease);
     872            5 :                 if (validMobileAnnouncement && shared->setMobileNodeInfo(*msg.self_mobile_info, true, socket))
     873            1 :                     shared->emitMobileNodesChanged();
     874              :             }
     875         4057 :             if (msg.is_mobile && validMobileAnnouncement) {
     876          253 :                 if (msg.v < 3 && !shared->conversationId_.empty())
     877            3 :                     shared->setMobileNodes(std::vector<NodeId> {socket->deviceId()});
     878          253 :                 shared->changeMobility(socket->deviceId(), true);
     879              :             }
     880              : 
     881         4057 :             if (msg.cert_request) {
     882            2 :                 shared->onCertRequest(socket, *msg.cert_request);
     883         4055 :             } else if (msg.cert_response) {
     884            5 :                 shared->onCertResponse(socket, *msg.cert_response);
     885         4050 :             } else if (msg.request) {
     886         2029 :                 shared->sendAnswer(socket, msg);
     887              : 
     888         2021 :             } else if (msg.response) {
     889         2015 :                 shared->setKnownNodes(msg.response->nodes);
     890         2015 :                 const auto requireLease = msg.v >= 3 && !shared->conversationId_.empty();
     891         2015 :                 bool changed = false;
     892         2015 :                 size_t records = 0;
     893         2019 :                 for (const auto& mobile : msg.response->mobile_node_infos) {
     894            4 :                     if (records++ == MAX_MOBILE_NODE_INFOS)
     895            0 :                         break;
     896            4 :                     changed |= shared->setMobileNodeInfo(mobile, requireLease, socket);
     897              :                 }
     898         2015 :                 if (changed)
     899            0 :                     shared->emitMobileNodesChanged();
     900         2015 :                 const auto acceptLegacy = msg.v < 3 || shared->conversationId_.empty();
     901         2015 :                 if (acceptLegacy)
     902          611 :                     shared->setMobileNodes(msg.response->mobile_nodes);
     903              :             }
     904         4057 :             return std::error_code();
     905         4057 :         }));
     906              : 
     907         3459 :     socket->onShutdown([w = weak(), deviceId = socket->deviceId()](const std::error_code&) {
     908         1958 :         dht::ThreadPool::io().run([w, deviceId] {
     909         1958 :             auto shared = w.lock();
     910         1958 :             if (shared && !shared->isShutdown_) {
     911         1100 :                 shared->removeNode(deviceId);
     912              :             }
     913         1958 :         });
     914         1958 :     });
     915         3459 : }
     916              : 
     917              : void
     918         2093 : SwarmManager::resetNodeExpiry(const asio::error_code& ec,
     919              :                               const std::shared_ptr<dhtnet::ChannelSocketInterface>& socket,
     920              :                               NodeId node)
     921              : {
     922         2093 :     NodeId idToFind;
     923         2093 :     std::list<Bucket>::iterator bucket;
     924              : 
     925         2093 :     if (ec == asio::error::operation_aborted)
     926            0 :         return;
     927              : 
     928         2093 :     if (!node) {
     929            0 :         bucket = routing_table.findBucket(socket->deviceId());
     930            0 :         idToFind = bucket->randomId(rd);
     931              :     } else {
     932         2093 :         bucket = routing_table.findBucket(node);
     933         2093 :         idToFind = node;
     934              :     }
     935              : 
     936         2093 :     sendRequest(socket, idToFind, Query::FIND, Bucket::BUCKET_MAX_SIZE);
     937              : 
     938         2093 :     if (!node) {
     939            0 :         auto& nodeTimer = bucket->getNodeTimer(socket);
     940            0 :         nodeTimer.expires_after(FIND_PERIOD);
     941            0 :         nodeTimer.async_wait(std::bind(&jami::SwarmManager::resetNodeExpiry,
     942            0 :                                        shared_from_this(),
     943              :                                        std::placeholders::_1,
     944              :                                        socket,
     945            0 :                                        NodeId {}));
     946              :     }
     947              : }
     948              : 
     949              : void
     950         2372 : SwarmManager::tryConnect(const NodeId& nodeId, bool noNewSocket)
     951              : {
     952         2372 :     if (needSocketCb_)
     953         2368 :         needSocketCb_(
     954         4736 :             nodeId.toString(),
     955         4736 :             [w = weak(), nodeId](const std::shared_ptr<dhtnet::ChannelSocketInterface>& socket) {
     956         2208 :                 auto shared = w.lock();
     957         2208 :                 if (!shared || shared->isShutdown_)
     958          562 :                     return true;
     959         1646 :                 if (socket) {
     960         1493 :                     shared->addChannel(socket);
     961         1493 :                     return true;
     962              :                 }
     963          153 :                 std::unique_lock lk(shared->mutex);
     964          153 :                 auto bucket = shared->routing_table.findBucket(nodeId);
     965          153 :                 bucket->removeConnectingNode(nodeId);
     966          153 :                 if (!bucket->hasMobileNode(nodeId))
     967          152 :                     bucket->addKnownNode(nodeId);
     968          153 :                 if (shared->routing_table.getActiveNodesCount() == 0 && shared->onConnectionChanged_) {
     969           98 :                     lk.unlock();
     970           98 :                     JAMI_LOG("[SwarmManager {:p}] Bootstrap: all connections failed", fmt::ptr(shared.get()));
     971           98 :                     shared->onConnectionChanged_(false);
     972              :                 }
     973          153 :                 return true;
     974         2208 :             },
     975              :             noNewSocket);
     976         2372 : }
     977              : 
     978              : void
     979          968 : SwarmManager::removeNodeInternal(const NodeId& nodeId)
     980              : {
     981          968 :     routing_table.removeNode(nodeId);
     982          968 : }
     983              : 
     984              : void
     985          690 : SwarmManager::connectNode(const NodeId& nodeId)
     986              : {
     987              :     {
     988          690 :         std::lock_guard lock(mutex);
     989          690 :         if (isShutdown_)
     990           12 :             return;
     991          678 :         if (isConnectedWith(nodeId))
     992            5 :             return;
     993          673 :         addKnownNode(nodeId);
     994          673 :         if (!routing_table.addConnectingNode(nodeId))
     995            0 :             return;
     996          690 :     }
     997          673 :     tryConnect(nodeId, true);
     998              : }
     999              : 
    1000              : std::vector<NodeId>
    1001           17 : SwarmManager::getAllNodes() const
    1002              : {
    1003           17 :     std::lock_guard lock(mutex);
    1004           34 :     return routing_table.getAllNodes();
    1005           17 : }
    1006              : 
    1007              : std::vector<NodeId>
    1008         2529 : SwarmManager::getConnectedNodes() const
    1009              : {
    1010         2529 :     std::lock_guard lock(mutex);
    1011         5058 :     return routing_table.getConnectedNodes();
    1012         2529 : }
    1013              : 
    1014              : std::vector<NodeId>
    1015           64 : SwarmManager::getMobileNodesToNotify()
    1016              : {
    1017           64 :     std::lock_guard lock(mutex);
    1018          128 :     return routing_table.getMobileNodesToNotify();
    1019           64 : }
    1020              : 
    1021              : std::vector<NodeId>
    1022          329 : SwarmManager::getKnownMobileNodes() const
    1023              : {
    1024          329 :     std::lock_guard lock(mutex);
    1025          658 :     return routing_table.getKnownMobileNodes();
    1026          329 : }
    1027              : 
    1028              : std::vector<MobileNodeInfo>
    1029          346 : SwarmManager::getKnownMobileNodeInfos() const
    1030              : {
    1031          346 :     std::lock_guard lock(mutex);
    1032          346 :     std::vector<MobileNodeInfo> infos;
    1033          346 :     const auto now = toSecondsSinceEpoch(std::chrono::system_clock::now());
    1034          899 :     for (const auto& node : routing_table.getKnownMobileNodes()) {
    1035          553 :         if (!isMobileNodeCurrentInternal(node, now))
    1036           32 :             continue;
    1037          521 :         auto lease = mobileNodeLeases_.find(node);
    1038          521 :         infos.emplace_back(MobileNodeInfo {node,
    1039         1042 :                                            lease == mobileNodeLeases_.end()
    1040          521 :                                                ? std::nullopt
    1041           28 :                                                : std::optional<MobileLease>(lease->second)});
    1042          346 :     }
    1043          692 :     return infos;
    1044          346 : }
    1045              : 
    1046              : std::vector<MobileNodeInfo>
    1047         2461 : SwarmManager::getMobileNodeInfosToNotify()
    1048              : {
    1049         2461 :     std::lock_guard lock(mutex);
    1050         2461 :     std::vector<MobileNodeInfo> infos;
    1051         2461 :     const auto now = toSecondsSinceEpoch(std::chrono::system_clock::now());
    1052         2462 :     for (const auto& node : routing_table.getMobileNodesToNotify()) {
    1053            1 :         if (!isMobileNodeCurrentInternal(node, now))
    1054            1 :             continue;
    1055            0 :         auto lease = mobileNodeLeases_.find(node);
    1056            0 :         infos.emplace_back(MobileNodeInfo {node,
    1057            0 :                                            lease == mobileNodeLeases_.end()
    1058            0 :                                                ? std::nullopt
    1059            0 :                                                : std::optional<MobileLease>(lease->second)});
    1060         2461 :     }
    1061         4922 :     return infos;
    1062         2461 : }
    1063              : 
    1064              : std::vector<std::map<std::string, std::string>>
    1065            2 : SwarmManager::getRoutingTableInfo() const
    1066              : {
    1067            2 :     std::lock_guard lock(mutex);
    1068            2 :     auto stats = routing_table.getRoutingTableStats();
    1069            2 :     const auto toNotify = routing_table.getMobileNodesToNotify();
    1070            2 :     std::set<std::string> responsible;
    1071            4 :     for (const auto& node : toNotify)
    1072            2 :         responsible.emplace(node.toString());
    1073            2 :     std::vector<std::map<std::string, std::string>> result;
    1074            2 :     result.reserve(stats.size());
    1075           11 :     for (const auto& stat : stats) {
    1076           72 :         result.push_back({{"id", stat.id},
    1077            9 :                           {"device", stat.id},
    1078            9 :                           {"status", stat.status},
    1079            9 :                           {"remoteAddress", stat.remoteAddress},
    1080            9 :                           {"mobile", stat.isMobile ? "true" : "false"},
    1081            9 :                           {"responsible", responsible.count(stat.id) ? "true" : "false"}});
    1082            9 :         if (stat.connectionTime != std::chrono::system_clock::time_point::min()) {
    1083            0 :             auto tt = std::chrono::system_clock::to_time_t(stat.connectionTime);
    1084            0 :             result.back().emplace("connectionTime", std::to_string(tt));
    1085              :         }
    1086              :     }
    1087            4 :     return result;
    1088           29 : }
    1089              : 
    1090              : bool
    1091         9255 : SwarmManager::isConnected() const
    1092              : {
    1093         9255 :     std::lock_guard lock(mutex);
    1094        18510 :     return !routing_table.isEmpty();
    1095         9255 : }
    1096              : 
    1097              : void
    1098           16 : SwarmManager::deleteNode(const std::vector<NodeId>& nodes)
    1099              : {
    1100           16 :     bool mobileNodesChanged = false;
    1101              :     {
    1102           16 :         std::lock_guard lock(mutex);
    1103           16 :         auto mobileNodes = routing_table.getKnownMobileNodes();
    1104           32 :         for (const auto& node : nodes) {
    1105           16 :             routing_table.deleteNode(node);
    1106           16 :             mobileNodesChanged |= mobileNodeLeases_.erase(node) != 0;
    1107           16 :             mobileNodesChanged |= legacyMobileNodeExpiries_.erase(node) != 0;
    1108           16 :             pendingMobileLeases_.erase(node);
    1109              :         }
    1110           16 :         scheduleMobileLeaseExpiryInternal();
    1111           16 :         mobileNodesChanged |= mobileNodes != routing_table.getKnownMobileNodes();
    1112           16 :     }
    1113           16 :     if (mobileNodesChanged)
    1114            2 :         emitMobileNodesChanged();
    1115           16 :     maintainBuckets();
    1116           16 : }
    1117              : 
    1118              : } // namespace jami
        

Generated by: LCOV version 2.0-1