LCOV - code coverage report
Current view: top level - src/jamidht/swarm - swarm_manager.h (source / functions) Coverage Total Hit
Test: jami-coverage-filtered.info Lines: 100.0 % 16 16
Test Date: 2026-08-23 08:52:56 Functions: 100.0 % 10 10

            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              : #pragma once
      18              : 
      19              : #include "routing_table.h"
      20              : #include "swarm_protocol.h"
      21              : 
      22              : #include <memory>
      23              : 
      24              : namespace jami {
      25              : 
      26              : using namespace swarm_protocol;
      27              : 
      28              : class SwarmManager : public std::enable_shared_from_this<SwarmManager>
      29              : {
      30              :     using ChannelCb = std::function<bool(const std::shared_ptr<dhtnet::ChannelSocketInterface>&)>;
      31              :     using NeedSocketCb = std::function<void(const std::string&, ChannelCb&&, bool noNewSocket)>;
      32              :     using ToConnectCb = std::function<bool(const NodeId&)>;
      33              :     using OnConnectionChanged = std::function<void(bool ok)>;
      34              :     using OnMobileNodesChanged = std::function<void(const std::vector<NodeId>&)>;
      35              :     using OnMobileNodeInfosChanged = std::function<void(const std::vector<MobileNodeInfo>&)>;
      36              :     using MobileLeaseProvider = std::function<std::optional<MobileNodeInfo>()>;
      37              :     using MobileLeaseIssuerValidator = std::function<bool(const dht::InfoHash&)>;
      38              :     /** Synchronous lookup of an already known device certificate (account certificate store). */
      39              :     using CertificateProvider = std::function<std::shared_ptr<dht::crypto::Certificate>(const NodeId&)>;
      40              :     /** Asynchronous last-resort lookup (DHT). The implementation is expected to pin on success. */
      41              :     using CertificateFetcher
      42              :         = std::function<void(const NodeId&, std::function<void(const std::shared_ptr<dht::crypto::Certificate>&)>&&)>;
      43              : 
      44              : public:
      45              :     explicit SwarmManager(const NodeId& nodeId,
      46              :                           bool isMobile,
      47              :                           const std::mt19937_64& rand,
      48              :                           ToConnectCb&& toConnectCb,
      49              :                           std::string conversationId = {},
      50              :                           MobileLeaseProvider mobileLeaseProvider = {},
      51              :                           MobileLeaseIssuerValidator mobileLeaseIssuerValidator = {},
      52              :                           CertificateProvider certificateProvider = {},
      53              :                           CertificateFetcher certificateFetcher = {});
      54              :     ~SwarmManager();
      55              : 
      56              :     NeedSocketCb needSocketCb_;
      57              : 
      58         7504 :     std::weak_ptr<SwarmManager> weak() { return weak_from_this(); }
      59              : 
      60              :     /**
      61              :      * Get swarm manager id
      62              :      * @return NodeId
      63              :      */
      64          475 :     const NodeId& getId() const { return id_; }
      65              : 
      66              :     /**
      67              :      * Set list of nodes to the routing table known_nodes
      68              :      * @param known_nodes
      69              :      * @return if some are new
      70              :      */
      71              :     bool setKnownNodes(const std::vector<NodeId>& known_nodes);
      72              : 
      73              :     /**
      74              :      * Set list of nodes to the routing table mobile_nodes
      75              :      * @param mobile_nodes
      76              :      */
      77              :     void setMobileNodes(const std::vector<NodeId>& mobile_nodes);
      78              : 
      79              :     /**
      80              :      * Merge lease-bearing mobile node records received from peers.
      81              :      * Records whose lease cannot be verified yet (the device certificate is
      82              :      * unknown locally) are kept aside and resolved asynchronously; they only
      83              :      * become visible once verified.
      84              :      * @param mobile_nodes
      85              :      */
      86              :     void setMobileNodes(const std::vector<MobileNodeInfo>& mobile_nodes, bool requireLease = false);
      87              : 
      88              :     /**
      89              :      * Add channel to routing table
      90              :      * @param channel
      91              :      */
      92              :     void addChannel(const std::shared_ptr<dhtnet::ChannelSocketInterface>& channel);
      93              : 
      94              :     /**
      95              :      * Remove channel from routing table
      96              :      * @param channel
      97              :      */
      98              :     void removeNode(const NodeId& nodeId);
      99              : 
     100              :     /**
     101              :      * Change mobility of specific node
     102              :      * @param nodeId
     103              :      * @param isMobile
     104              :      */
     105              :     void changeMobility(const NodeId& nodeId, bool isMobile);
     106              : 
     107              :     /**
     108              :      * Check if swarm manager is connected
     109              :      * @return true if the swarm has at least one connected node, false if not
     110              :      */
     111              :     bool isConnected() const;
     112              : 
     113              :     /**
     114              :      * get all nodes from the different tables in bucket
     115              :      */
     116              :     std::vector<NodeId> getAllNodes() const;
     117              : 
     118              :     std::vector<NodeId> getConnectedNodes() const;
     119              : 
     120              :     /**
     121              :      * Get the mobile nodes this device is responsible for waking up.
     122              :      * The closest redundant nodes in the local Kademlia view wake it.
     123              :      */
     124              :     std::vector<NodeId> getMobileNodesToNotify();
     125              : 
     126              :     /**
     127              :      * Get every node known to be mobile (connected or not).
     128              :      * Used to persist mobility knowledge across restarts.
     129              :      */
     130              :     std::vector<NodeId> getKnownMobileNodes() const;
     131              : 
     132              :     /**
     133              :      * Get known mobile nodes with any validated lease metadata.
     134              :      */
     135              :     std::vector<MobileNodeInfo> getKnownMobileNodeInfos() const;
     136              : 
     137              :     /**
     138              :      * Get wake-up targets with any validated lease metadata.
     139              :      */
     140              :     std::vector<MobileNodeInfo> getMobileNodeInfosToNotify();
     141              : 
     142              :     /**
     143              :      * Callback invoked when the set of known mobile nodes changes,
     144              :      * with the updated set.
     145              :      * @param cb
     146              :      */
     147           12 :     void onMobileNodesChanged(OnMobileNodesChanged cb)
     148              :     {
     149           12 :         std::lock_guard lock(onMobileNodesChangedMtx_);
     150           12 :         onMobileNodesChanged_ = std::move(cb);
     151           12 :     }
     152              : 
     153          430 :     void onMobileNodeInfosChanged(OnMobileNodeInfosChanged cb)
     154              :     {
     155          430 :         std::lock_guard lock(onMobileNodesChangedMtx_);
     156          430 :         onMobileNodeInfosChanged_ = std::move(cb);
     157          430 :     }
     158              : 
     159              :     std::vector<std::map<std::string, std::string>> getRoutingTableInfo() const;
     160              : 
     161          491 :     unsigned getActiveNodesCount() const { return routing_table.getActiveNodesCount(); }
     162              : 
     163              :     /**
     164              :      * Delete nodes from the different tables in bucket
     165              :      */
     166              :     void deleteNode(const std::vector<NodeId>& nodes);
     167              : 
     168              :     // For tests
     169              : 
     170              :     /**
     171              :      * Get routing table
     172              :      * @return RoutingTable
     173              :      */
     174          279 :     RoutingTable& getRoutingTable() { return routing_table; };
     175              : 
     176            3 :     std::optional<MobileNodeInfo> getLocalMobileNodeInfo() { return localMobileNodeInfo(); }
     177              : 
     178              :     /**
     179              :      * Get buckets of routing table
     180              :      * @return buckets list
     181              :      */
     182              :     std::list<Bucket>& getBuckets() { return routing_table.getBuckets(); };
     183              : 
     184              :     /**
     185              :      * Shutdown swarm manager
     186              :      */
     187              :     void shutdown();
     188              : 
     189              :     /**
     190              :      * Restart the swarm manager.
     191              :      *
     192              :      * This function must be called in situations where we want
     193              :      * to use a swarm manager that was previously shut down.
     194              :      */
     195              :     void restart();
     196              : 
     197              :     /**
     198              :      * Display swarm manager info
     199              :      */
     200              :     void display()
     201              :     {
     202              :         JAMI_DEBUG("SwarmManager {:s} has {:d} nodes in table [P = {}]",
     203              :                    getId().to_c_str(),
     204              :                    routing_table.getNodeCount(),
     205              :                    isMobile_);
     206              :         // print nodes of routingtable
     207              :         for (auto& bucket : routing_table.getBuckets()) {
     208              :             for (auto& node : bucket.getNodes()) {
     209              :                 JAMI_DEBUG("Node {:s}", node.first.toString());
     210              :             }
     211              :         }
     212              :     }
     213              : 
     214              :     /*
     215              :      * Callback for connection changed
     216              :      * @param cb
     217              :      */
     218          525 :     void onConnectionChanged(OnConnectionChanged cb) { onConnectionChanged_ = std::move(cb); }
     219              : 
     220              :     /**
     221              :      * Get mobility of swarm manager
     222              :      * @return true if mobile, false if not
     223              :      */
     224            9 :     bool isMobile() const { return isMobile_; }
     225              : 
     226              :     /**
     227              :      * Maintain/Update buckets
     228              :      * @param toConnect         Nodes to connect
     229              :      */
     230              :     void maintainBuckets(const std::set<NodeId>& toConnect = {});
     231              : 
     232              :     /**
     233              :      * Proactively connect to a node, bypassing bucket capacity checks.
     234              :      * The node is registered as known and a connection is attempted with
     235              :      * noNewSocket=true (reuses an existing transport).
     236              :      * @param nodeId
     237              :      */
     238              :     void connectNode(const NodeId& nodeId);
     239              : 
     240              :     /**
     241              :      * Check if swarm manager is shutdown
     242              :      * @return true if shutdown, false if not
     243              :      */
     244         1354 :     bool isShutdown() { return isShutdown_; };
     245              : 
     246              : private:
     247              :     /**
     248              :      * Check if we're connected with a specific device
     249              :      * @param deviceId
     250              :      * @return true if connected, false if not
     251              :      */
     252              :     bool isConnectedWith(const NodeId& deviceId);
     253              : 
     254              :     /**
     255              :      * Add node to the known_nodes list
     256              :      * @param nodeId
     257              :      * @return if node inserted
     258              :      */
     259              :     bool addKnownNode(const NodeId& nodeId);
     260              : 
     261              :     /**
     262              :      * Add node to the mobile_Nodes list
     263              :      * @param nodeId
     264              :      * @return if node inserted
     265              :      */
     266              :     bool addMobileNodes(const NodeId& nodeId);
     267              : 
     268              :     bool setMobileNodeInfo(const MobileNodeInfo& mobile,
     269              :                            bool requireLease = false,
     270              :                            const std::shared_ptr<dhtnet::ChannelSocketInterface>& source = {});
     271              : 
     272              :     /**
     273              :      * Certificate-free validation of a lease. Cheap: no cryptography, no I/O.
     274              :      * Gates every expensive certificate resolution, in particular by requiring
     275              :      * the issuer to be a member of the conversation.
     276              :      */
     277              :     bool precheckLease(const MobileLease& lease) const;
     278              : 
     279              :     /**
     280              :      * Full cryptographic validation of a lease against its device certificate.
     281              :      */
     282              :     bool verifyLease(const dht::crypto::Certificate& certificate, const MobileLease& lease) const;
     283              : 
     284              :     /**
     285              :      * Record a verified lease. Must be called with mutex held.
     286              :      * @return true if the known mobile node set or leases changed
     287              :      */
     288              :     bool commitLeaseInternal(const MobileLease& lease);
     289              : 
     290              :     /**
     291              :      * Keep an unverified lease aside and start resolving its certificate.
     292              :      * Must be called with mutex held.
     293              :      */
     294              :     void enqueuePendingLeaseInternal(const MobileLease& lease,
     295              :                                      const std::shared_ptr<dhtnet::ChannelSocketInterface>& source);
     296              : 
     297              :     /** Ask a peer for device certificates over the swarm channel. */
     298              :     void requestCertificates(const std::shared_ptr<dhtnet::ChannelSocketInterface>& socket,
     299              :                              const std::vector<NodeId>& ids);
     300              : 
     301              :     void onCertRequest(const std::shared_ptr<dhtnet::ChannelSocketInterface>& socket, const CertRequest& request);
     302              : 
     303              :     void onCertResponse(const std::shared_ptr<dhtnet::ChannelSocketInterface>& socket, const CertResponse& response);
     304              : 
     305              :     /** Try to promote pending leases now that `certificate` is known. */
     306              :     void onCertificateResolved(const NodeId& nodeId, const std::shared_ptr<dht::crypto::Certificate>& certificate);
     307              : 
     308              :     /** Last-resort DHT lookup for a pending lease's certificate. */
     309              :     void fetchCertificateFromDht(const NodeId& nodeId);
     310              : 
     311              :     /**
     312              :      * Forget an unverifiable lease: the node is simply ignored until it is
     313              :      * announced again. Must be called with mutex held.
     314              :      */
     315              :     void abandonLeaseInternal(const NodeId& nodeId);
     316              : 
     317              :     bool isMobileNodeCurrentInternal(const NodeId& nodeId, int64_t now) const;
     318              : 
     319              :     std::optional<MobileNodeInfo> localMobileNodeInfo();
     320              : 
     321              :     void scheduleMobileLeaseExpiryInternal();
     322              : 
     323              :     void expireMobileLeases(const asio::error_code& ec);
     324              : 
     325              :     /**
     326              :      * Notify the onMobileNodesChanged_ callback with the current set of
     327              :      * known mobile nodes. Must be called without the mutex held.
     328              :      */
     329              :     void emitMobileNodesChanged();
     330              : 
     331              :     /**
     332              :      * Send nodes request to fill known_nodes list
     333              :      * @param socket
     334              :      * @param nodeId
     335              :      * @param q
     336              :      * @param numberNodes
     337              :      */
     338              :     void sendRequest(const std::shared_ptr<dhtnet::ChannelSocketInterface>& socket,
     339              :                      const NodeId& nodeId,
     340              :                      Query q,
     341              :                      int numberNodes = Bucket::BUCKET_MAX_SIZE);
     342              : 
     343              :     /**
     344              :      * Send answer to request
     345              :      * @param socket
     346              :      * @param msg
     347              :      */
     348              :     void sendAnswer(const std::shared_ptr<dhtnet::ChannelSocketInterface>& socket, const Message& msg_);
     349              : 
     350              :     /**
     351              :      * Interpret received message
     352              :      * @param socket
     353              :      */
     354              :     void receiveMessage(const std::shared_ptr<dhtnet::ChannelSocketInterface>& socket);
     355              : 
     356              :     /**
     357              :      * Reset node's timer expiry
     358              :      * @param ec
     359              :      * @param socket
     360              :      * @param node
     361              :      */
     362              :     void resetNodeExpiry(const asio::error_code& ec,
     363              :                          const std::shared_ptr<dhtnet::ChannelSocketInterface>& socket,
     364              :                          NodeId node = {});
     365              : 
     366              :     /**
     367              :      * Try to establish connection with specific node
     368              :      * @param nodeId
     369              :      * @param noNewSocket  If true, reuse an existing transport (no new ICE negotiation)
     370              :      */
     371              :     void tryConnect(const NodeId& nodeId, bool noNewSocket = false);
     372              : 
     373              :     /**
     374              :      * Remove node from routing table
     375              :      * @param nodeId
     376              :      */
     377              :     void removeNodeInternal(const NodeId& nodeId);
     378              : 
     379              :     const NodeId id_;
     380              :     bool isMobile_ {false};
     381              :     const std::string conversationId_;
     382              :     std::mt19937_64 rd;
     383              :     mutable std::mutex mutex;
     384              :     RoutingTable routing_table;
     385              :     std::map<NodeId, MobileLease> mobileNodeLeases_;
     386              :     std::map<NodeId, int64_t> legacyMobileNodeExpiries_;
     387              : 
     388              :     /** Lease accepted by precheckLease() but not yet verified: certificate unknown. */
     389              :     struct PendingLease
     390              :     {
     391              :         MobileLease lease;
     392              :         std::weak_ptr<dhtnet::ChannelSocketInterface> source;
     393              :     };
     394              :     std::map<NodeId, PendingLease> pendingMobileLeases_;
     395              : 
     396              :     /** Nodes whose certificate resolution is already under way: avoids duplicate lookups. */
     397              :     std::set<NodeId> certFetchInFlight_;
     398              : 
     399              :     /** At most one outstanding CERT_REQ per peer, keyed by the peer's device id. */
     400              :     struct CertRequestState
     401              :     {
     402              :         std::set<NodeId> ids;
     403              :         std::shared_ptr<asio::steady_timer> timer;
     404              :     };
     405              :     std::map<NodeId, CertRequestState> outstandingCertRequests_;
     406              : 
     407              :     MobileLeaseProvider mobileLeaseProvider_;
     408              :     MobileLeaseIssuerValidator mobileLeaseIssuerValidator_;
     409              :     CertificateProvider certificateProvider_;
     410              :     CertificateFetcher certificateFetcher_;
     411              :     std::mutex mobileLeaseRenewalMtx_;
     412              :     std::optional<MobileNodeInfo> localMobileNodeInfo_;
     413              :     asio::steady_timer mobileLeaseExpiryTimer_ {*Manager::instance().ioContext()};
     414              : 
     415              :     std::atomic_bool isShutdown_ {false};
     416              : 
     417              :     OnConnectionChanged onConnectionChanged_ {};
     418              :     mutable std::mutex onMobileNodesChangedMtx_;
     419              :     std::recursive_mutex mobileNodesEmissionMtx_;
     420              :     OnMobileNodesChanged onMobileNodesChanged_ {};
     421              :     OnMobileNodeInfosChanged onMobileNodeInfosChanged_ {};
     422              : 
     423              :     ToConnectCb toConnectCb_;
     424              : };
     425              : 
     426              : } // namespace jami
        

Generated by: LCOV version 2.0-1