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: 89.2 % 249 222
Test Date: 2026-07-06 08:25:38 Functions: 97.4 % 39 38

            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 <dhtnet/multiplexed_socket.h>
      20              : #include <dhtnet/channel_utils.h>
      21              : #include <opendht/thread_pool.h>
      22              : 
      23              : namespace jami {
      24              : 
      25              : using namespace swarm_protocol;
      26              : 
      27          586 : SwarmManager::SwarmManager(const NodeId& id, const std::mt19937_64& rand, ToConnectCb&& toConnectCb)
      28          586 :     : id_(id)
      29          586 :     , rd(rand)
      30          586 :     , toConnectCb_(toConnectCb)
      31              : {
      32          586 :     routing_table.setId(id);
      33          586 : }
      34              : 
      35          586 : SwarmManager::~SwarmManager()
      36              : {
      37          586 :     if (!isShutdown_)
      38          242 :         shutdown();
      39          586 : }
      40              : 
      41              : bool
      42         2030 : SwarmManager::setKnownNodes(const std::vector<NodeId>& known_nodes)
      43              : {
      44         2030 :     isShutdown_ = false;
      45         2029 :     std::vector<NodeId> newNodes;
      46              :     {
      47         2029 :         std::lock_guard lock(mutex);
      48         4847 :         for (const auto& nodeId : known_nodes) {
      49         2813 :             if (addKnownNode(nodeId)) {
      50          707 :                 newNodes.emplace_back(nodeId);
      51              :             }
      52              :         }
      53         2026 :     }
      54              : 
      55         2036 :     if (newNodes.empty())
      56         1472 :         return false;
      57              : 
      58          561 :     dht::ThreadPool::io().run([w = weak(), newNodes = std::move(newNodes)] {
      59          563 :         auto shared = w.lock();
      60          563 :         if (!shared)
      61            0 :             return;
      62              :         // If we detect a new node which already got a TCP link
      63              :         // we can use it to speed-up the bootstrap (because opening
      64              :         // a new channel will be easy)
      65          563 :         std::set<NodeId> toConnect;
      66         1273 :         for (const auto& nodeId : newNodes) {
      67          712 :             if (shared->toConnectCb_ && shared->toConnectCb_(nodeId))
      68          130 :                 toConnect.emplace(nodeId);
      69              :         }
      70          559 :         shared->maintainBuckets(toConnect);
      71          563 :     });
      72          563 :     return true;
      73         2035 : }
      74              : 
      75              : void
      76         1303 : SwarmManager::setMobileNodes(const std::vector<NodeId>& mobile_nodes)
      77              : {
      78              :     {
      79         1303 :         std::lock_guard lock(mutex);
      80         1320 :         for (const auto& nodeId : mobile_nodes)
      81           16 :             addMobileNodes(nodeId);
      82         1303 :     }
      83         1305 : }
      84              : 
      85              : void
      86         2310 : SwarmManager::addChannel(const std::shared_ptr<dhtnet::ChannelSocketInterface>& channel)
      87              : {
      88              :     // JAMI_WARNING("[SwarmManager {}] addChannel! with {}", fmt::ptr(this), channel->deviceId().to_view());
      89         2310 :     if (channel) {
      90         2310 :         auto emit = false;
      91              :         {
      92         2310 :             std::lock_guard lock(mutex);
      93         2310 :             emit = routing_table.findBucket(getId())->isEmpty();
      94         2310 :             auto bucket = routing_table.findBucket(channel->deviceId());
      95         2310 :             if (routing_table.addNode(channel, bucket)) {
      96         1336 :                 std::error_code ec;
      97         1335 :                 resetNodeExpiry(ec, channel, id_);
      98              :             }
      99         2310 :         }
     100         2310 :         receiveMessage(channel);
     101         2308 :         if (emit && onConnectionChanged_) {
     102              :             // If it's the first channel we add, we're now connected!
     103         1493 :             JAMI_DEBUG("[SwarmManager {}] Bootstrap: Connected!", fmt::ptr(this));
     104          374 :             onConnectionChanged_(true);
     105              :         }
     106              :     }
     107         2309 : }
     108              : 
     109              : void
     110          601 : SwarmManager::removeNode(const NodeId& nodeId)
     111              : {
     112          601 :     std::unique_lock lk(mutex);
     113          601 :     if (isConnectedWith(nodeId)) {
     114          496 :         removeNodeInternal(nodeId);
     115          495 :         lk.unlock();
     116          495 :         maintainBuckets();
     117              :     }
     118          601 : }
     119              : 
     120              : void
     121          228 : SwarmManager::changeMobility(const NodeId& nodeId, bool isMobile)
     122              : {
     123          228 :     std::lock_guard lock(mutex);
     124          227 :     auto bucket = routing_table.findBucket(nodeId);
     125          226 :     bucket->changeMobility(nodeId, isMobile);
     126          227 : }
     127              : 
     128              : bool
     129         1207 : SwarmManager::isConnectedWith(const NodeId& deviceId)
     130              : {
     131         1207 :     return routing_table.hasNode(deviceId);
     132              : }
     133              : 
     134              : void
     135          628 : SwarmManager::shutdown()
     136              : {
     137          628 :     if (isShutdown_) {
     138           13 :         return;
     139              :     }
     140          615 :     isShutdown_ = true;
     141          615 :     std::lock_guard lock(mutex);
     142          615 :     routing_table.shutdownAllNodes();
     143          615 : }
     144              : 
     145              : void
     146           22 : SwarmManager::restart()
     147              : {
     148           22 :     isShutdown_ = false;
     149           22 : }
     150              : 
     151              : bool
     152         3417 : SwarmManager::addKnownNode(const NodeId& nodeId)
     153              : {
     154         3417 :     return routing_table.addKnownNode(nodeId);
     155              : }
     156              : 
     157              : void
     158           16 : SwarmManager::addMobileNodes(const NodeId& nodeId)
     159              : {
     160           16 :     if (id_ != nodeId) {
     161           15 :         routing_table.addMobileNode(nodeId);
     162              :     }
     163           16 : }
     164              : 
     165              : void
     166         1089 : SwarmManager::maintainBuckets(const std::set<NodeId>& toConnect)
     167              : {
     168         1089 :     std::set<NodeId> nodes = toConnect;
     169         1087 :     std::unique_lock lock(mutex);
     170         1091 :     auto& buckets = routing_table.getBuckets();
     171         3976 :     for (auto it = buckets.begin(); it != buckets.end(); ++it) {
     172         2885 :         auto& bucket = *it;
     173         2889 :         bool myBucket = routing_table.contains(it, id_);
     174         4690 :         auto connecting_nodes = myBucket ? bucket.getConnectingNodesSize()
     175         1802 :                                          : bucket.getConnectingNodesSize() + bucket.getNodesSize();
     176         2887 :         if (connecting_nodes < Bucket::BUCKET_MAX_SIZE) {
     177         1435 :             auto nodesToTry = bucket.getKnownNodesRandom(Bucket::BUCKET_MAX_SIZE - connecting_nodes, rd);
     178         2215 :             for (auto& node : nodesToTry)
     179          787 :                 routing_table.addConnectingNode(node);
     180              : 
     181         1429 :             nodes.insert(nodesToTry.begin(), nodesToTry.end());
     182         1434 :         }
     183              :     }
     184         1089 :     lock.unlock();
     185         1896 :     for (const auto& node : nodes)
     186          805 :         tryConnect(node);
     187         1087 : }
     188              : 
     189              : void
     190         1336 : SwarmManager::sendRequest(const std::shared_ptr<dhtnet::ChannelSocketInterface>& socket,
     191              :                           const NodeId& nodeId,
     192              :                           Query q,
     193              :                           int numberNodes)
     194              : {
     195         1336 :     dht::ThreadPool::io().run([socket, isMobile = isMobile_, nodeId, q, numberNodes] {
     196         1336 :         msgpack::sbuffer buffer;
     197         1336 :         msgpack::packer<msgpack::sbuffer> pk(&buffer);
     198         1336 :         Message msg;
     199         1336 :         msg.is_mobile = isMobile;
     200         1336 :         msg.request = Request {q, numberNodes, nodeId};
     201         1336 :         pk.pack(msg);
     202              : 
     203         1334 :         std::error_code ec;
     204         1334 :         socket->write(reinterpret_cast<const unsigned char*>(buffer.data()), buffer.size(), ec);
     205         1336 :         if (ec) {
     206           12 :             JAMI_ERROR("{}", ec.message());
     207              :         }
     208         1336 :     });
     209         1336 : }
     210              : 
     211              : void
     212         1301 : SwarmManager::sendAnswer(const std::shared_ptr<dhtnet::ChannelSocketInterface>& socket, const Message& msg_)
     213              : {
     214         1301 :     std::lock_guard lock(mutex);
     215              : 
     216         1302 :     if (msg_.request->q == Query::FIND) {
     217         1298 :         auto nodes = routing_table.closestNodes(msg_.request->nodeId, msg_.request->num);
     218         1307 :         auto bucket = routing_table.findBucket(msg_.request->nodeId);
     219         1308 :         const auto& m_nodes = bucket->getMobileNodes();
     220         2613 :         Response toResponse {Query::FOUND, nodes, {m_nodes.begin(), m_nodes.end()}};
     221              : 
     222         1303 :         Message msg;
     223         1303 :         msg.is_mobile = isMobile_;
     224         1303 :         msg.response = std::move(toResponse);
     225              : 
     226         1304 :         msgpack::sbuffer buffer((size_t) 60000);
     227         1308 :         msgpack::packer<msgpack::sbuffer> pk(&buffer);
     228         1307 :         pk.pack(msg);
     229              : 
     230         1305 :         std::error_code ec;
     231         1304 :         socket->write(reinterpret_cast<const unsigned char*>(buffer.data()), buffer.size(), ec);
     232         1308 :         if (ec) {
     233           12 :             JAMI_ERROR("{}", ec.message());
     234            3 :             return;
     235              :         }
     236         1317 :     }
     237         1308 : }
     238              : 
     239              : void
     240         2310 : SwarmManager::receiveMessage(const std::shared_ptr<dhtnet::ChannelSocketInterface>& socket)
     241              : {
     242         4620 :     socket->setOnRecv(dhtnet::buildMsgpackReader<Message>(
     243         4620 :         [w = weak(), wsocket = std::weak_ptr<dhtnet::ChannelSocketInterface>(socket)](Message&& msg) {
     244         2597 :             auto shared = w.lock();
     245         2594 :             auto socket = wsocket.lock();
     246         2589 :             if (!shared || !socket)
     247            0 :                 return std::make_error_code(std::errc::operation_canceled);
     248              : 
     249         2584 :             if (msg.is_mobile)
     250          228 :                 shared->changeMobility(socket->deviceId(), msg.is_mobile);
     251              : 
     252         2584 :             if (msg.request) {
     253         1301 :                 shared->sendAnswer(socket, msg);
     254              : 
     255         1297 :             } else if (msg.response) {
     256         1304 :                 shared->setKnownNodes(msg.response->nodes);
     257         1300 :                 shared->setMobileNodes(msg.response->mobile_nodes);
     258              :             }
     259         2608 :             return std::error_code();
     260         2606 :         }));
     261              : 
     262         2309 :     socket->onShutdown([w = weak(), deviceId = socket->deviceId()](const std::error_code&) {
     263         1191 :         dht::ThreadPool::io().run([w, deviceId] {
     264         1189 :             auto shared = w.lock();
     265         1190 :             if (shared && !shared->isShutdown_) {
     266          601 :                 shared->removeNode(deviceId);
     267              :             }
     268         1191 :         });
     269         1194 :     });
     270         2308 : }
     271              : 
     272              : void
     273         1336 : SwarmManager::resetNodeExpiry(const asio::error_code& ec,
     274              :                               const std::shared_ptr<dhtnet::ChannelSocketInterface>& socket,
     275              :                               NodeId node)
     276              : {
     277         1336 :     NodeId idToFind;
     278         1336 :     std::list<Bucket>::iterator bucket;
     279              : 
     280         1335 :     if (ec == asio::error::operation_aborted)
     281            0 :         return;
     282              : 
     283         1336 :     if (!node) {
     284            0 :         bucket = routing_table.findBucket(socket->deviceId());
     285            0 :         idToFind = bucket->randomId(rd);
     286              :     } else {
     287         1336 :         bucket = routing_table.findBucket(node);
     288         1336 :         idToFind = node;
     289              :     }
     290              : 
     291         1336 :     sendRequest(socket, idToFind, Query::FIND, Bucket::BUCKET_MAX_SIZE);
     292              : 
     293         1336 :     if (!node) {
     294            0 :         auto& nodeTimer = bucket->getNodeTimer(socket);
     295            0 :         nodeTimer.expires_after(FIND_PERIOD);
     296            0 :         nodeTimer.async_wait(std::bind(&jami::SwarmManager::resetNodeExpiry,
     297            0 :                                        shared_from_this(),
     298              :                                        std::placeholders::_1,
     299              :                                        socket,
     300            0 :                                        NodeId {}));
     301              :     }
     302              : }
     303              : 
     304              : void
     305         1405 : SwarmManager::tryConnect(const NodeId& nodeId, bool noNewSocket)
     306              : {
     307         1405 :     if (needSocketCb_)
     308         1404 :         needSocketCb_(
     309         2807 :             nodeId.toString(),
     310         2803 :             [w = weak(), nodeId](const std::shared_ptr<dhtnet::ChannelSocketInterface>& socket) {
     311         1303 :                 auto shared = w.lock();
     312         1303 :                 if (!shared || shared->isShutdown_)
     313          189 :                     return true;
     314         1114 :                 if (socket) {
     315         1035 :                     shared->addChannel(socket);
     316         1035 :                     return true;
     317              :                 }
     318           79 :                 std::unique_lock lk(shared->mutex);
     319           79 :                 auto bucket = shared->routing_table.findBucket(nodeId);
     320           79 :                 bucket->removeConnectingNode(nodeId);
     321           79 :                 bucket->addKnownNode(nodeId);
     322           79 :                 bucket = shared->routing_table.findBucket(shared->getId());
     323           79 :                 if (bucket->getConnectingNodesSize() == 0 && bucket->isEmpty() && shared->onConnectionChanged_) {
     324           41 :                     lk.unlock();
     325          164 :                     JAMI_LOG("[SwarmManager {:p}] Bootstrap: all connections failed", fmt::ptr(shared.get()));
     326           41 :                     shared->onConnectionChanged_(false);
     327              :                 }
     328           79 :                 return true;
     329         1303 :             },
     330              :             noNewSocket);
     331         1407 : }
     332              : 
     333              : void
     334          496 : SwarmManager::removeNodeInternal(const NodeId& nodeId)
     335              : {
     336          496 :     routing_table.removeNode(nodeId);
     337          494 : }
     338              : 
     339              : void
     340          607 : SwarmManager::connectNode(const NodeId& nodeId)
     341              : {
     342              :     {
     343          607 :         std::lock_guard lock(mutex);
     344          607 :         if (isShutdown_)
     345            1 :             return;
     346          606 :         if (isConnectedWith(nodeId))
     347            5 :             return;
     348          601 :         addKnownNode(nodeId);
     349          601 :         if (!routing_table.addConnectingNode(nodeId))
     350            0 :             return;
     351          607 :     }
     352          601 :     tryConnect(nodeId, true);
     353              : }
     354              : 
     355              : std::vector<NodeId>
     356           16 : SwarmManager::getAllNodes() const
     357              : {
     358           16 :     std::lock_guard lock(mutex);
     359           32 :     return routing_table.getAllNodes();
     360           16 : }
     361              : 
     362              : std::vector<NodeId>
     363         1769 : SwarmManager::getConnectedNodes() const
     364              : {
     365         1769 :     std::lock_guard lock(mutex);
     366         3538 :     return routing_table.getConnectedNodes();
     367         1768 : }
     368              : 
     369              : std::vector<std::map<std::string, std::string>>
     370            0 : SwarmManager::getRoutingTableInfo() const
     371              : {
     372            0 :     std::lock_guard lock(mutex);
     373            0 :     auto stats = routing_table.getRoutingTableStats();
     374            0 :     std::vector<std::map<std::string, std::string>> result;
     375            0 :     result.reserve(stats.size());
     376            0 :     for (const auto& stat : stats) {
     377            0 :         result.push_back({{"id", stat.id},
     378            0 :                           {"device", stat.id},
     379            0 :                           {"status", stat.status},
     380            0 :                           {"remoteAddress", stat.remoteAddress},
     381            0 :                           {"mobile", stat.isMobile ? "true" : "false"}});
     382            0 :         if (stat.connectionTime != std::chrono::system_clock::time_point::min()) {
     383            0 :             auto tt = std::chrono::system_clock::to_time_t(stat.connectionTime);
     384            0 :             result.back().emplace("connectionTime", std::to_string(tt));
     385              :         }
     386              :     }
     387            0 :     return result;
     388            0 : }
     389              : 
     390              : bool
     391         2690 : SwarmManager::isConnected() const
     392              : {
     393         2690 :     std::lock_guard lock(mutex);
     394         5380 :     return !routing_table.isEmpty();
     395         2690 : }
     396              : 
     397              : void
     398           10 : SwarmManager::deleteNode(const std::vector<NodeId>& nodes)
     399              : {
     400              :     {
     401           10 :         std::lock_guard lock(mutex);
     402           20 :         for (const auto& node : nodes) {
     403           10 :             routing_table.deleteNode(node);
     404              :         }
     405           10 :     }
     406           10 :     maintainBuckets();
     407           10 : }
     408              : 
     409              : } // namespace jami
        

Generated by: LCOV version 2.0-1