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
|