Ring Daemon 16.0.0
Loading...
Searching...
No Matches
routing_table.cpp
Go to the documentation of this file.
1/*
2 * Copyright (C) 2004-2025 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 "routing_table.h"
19
20#include <dhtnet/multiplexed_socket.h>
21#include <opendht/infohash.h>
22
23#include <math.h>
24#include <stdio.h>
25#include <iostream>
26#include <iterator>
27#include <stdlib.h>
28#include <time.h>
29
30constexpr const std::chrono::minutes FIND_PERIOD {10};
31using namespace std::placeholders;
32
33namespace jami {
34
35using namespace dht;
36
38 : lowerLimit_(id)
39{}
40
41bool
42Bucket::addNode(const std::shared_ptr<dhtnet::ChannelSocketInterface>& socket)
43{
44 return addNode(NodeInfo(socket));
45}
46
47bool
49{
50 auto nodeId = info.socket->deviceId();
51 if (nodes.try_emplace(nodeId, std::move(info)).second) {
52 connecting_nodes.erase(nodeId);
53 known_nodes.erase(nodeId);
54 mobile_nodes.erase(nodeId);
55 return true;
56 }
57 return false;
58}
59
60bool
62{
63 auto node = nodes.find(nodeId);
64 auto isMobile = node->second.isMobile_;
65 if (node == nodes.end())
66 return false;
67 nodes.erase(nodeId);
68 if (isMobile) {
69 addMobileNode(nodeId);
70 } else {
71 addKnownNode(nodeId);
72 }
73
74 return true;
75}
76
77std::set<NodeId>
79{
80 std::set<NodeId> nodesId;
81 for (auto const& key : nodes)
82 nodesId.insert(key.first);
83 return nodesId;
84}
85
86bool
87Bucket::hasNode(const NodeId& nodeId) const
88{
89 return nodes.find(nodeId) != nodes.end();
90}
91
92bool
94{
95 if (!hasNode(nodeId)) {
96 if (known_nodes.emplace(nodeId).second) {
97 return true;
98 }
99 }
100 return false;
101}
102
103NodeId
104Bucket::getKnownNode(unsigned index) const
105{
106 if (index > known_nodes.size()) {
107 throw std::out_of_range("End of table for get known Node Id " + std::to_string(index));
108 }
109 auto it = known_nodes.begin();
110 std::advance(it, index);
111
112 return *it;
113}
114
115bool
117{
118 if (!hasNode(nodeId)) {
119 if (mobile_nodes.emplace(nodeId).second) {
120 known_nodes.erase(nodeId);
121 return true;
122 }
123 }
124 return false;
125}
126
127bool
129{
130 if (!hasNode(nodeId)) {
131 if (connecting_nodes.emplace(nodeId).second) {
132 known_nodes.erase(nodeId);
133 mobile_nodes.erase(nodeId);
134 return true;
135 }
136 }
137 return false;
138}
139
140std::set<NodeId>
141Bucket::getKnownNodesRandom(unsigned numberNodes, std::mt19937_64& rd) const
142{
143 std::set<NodeId> nodesToReturn;
144
146 return getKnownNodes();
147
148 std::uniform_int_distribution<unsigned> distrib(0, getKnownNodesSize() - 1);
149
150 while (nodesToReturn.size() < numberNodes) {
151 nodesToReturn.emplace(getKnownNode(distrib(rd)));
152 }
153
154 return nodesToReturn;
155}
156
157asio::steady_timer&
158Bucket::getNodeTimer(const std::shared_ptr<dhtnet::ChannelSocketInterface>& socket)
159{
160 auto node = nodes.find(socket->deviceId());
161 if (node == nodes.end()) {
162 throw std::range_error("Unable to find timer " + socket->deviceId().toString());
163 }
164 return node->second.refresh_timer;
165}
166
167bool
169{
170 auto node = nodes.find(nodeId);
171
172 if (node != nodes.end()) {
173 auto socket = node->second.socket;
174 auto node = socket->deviceId();
175 socket->shutdown();
176 removeNode(node);
177 return true;
178 }
179 return false;
180}
181
182void
184{
185 while (not nodes.empty()) {
186 auto it = nodes.begin();
187 auto socket = it->second.socket;
188 auto nodeId = socket->deviceId();
189 socket->shutdown();
190 removeNode(nodeId);
191 }
192}
193
194void
196{
197 JAMI_ERROR("BUCKET Number: {:d}", number);
198
199 unsigned nodeNum = 1;
200 for (auto it = nodes.begin(); it != nodes.end(); ++it) {
201 JAMI_DEBUG("Node {:s} Id: {:s} isMobile: {:s}", std::to_string(nodeNum), it->first.toString(), std::to_string(it->second.isMobile_));
202 nodeNum++;
203 }
204 JAMI_ERROR("Mobile Nodes");
205 nodeNum = 0;
206 for (auto it = mobile_nodes.begin(); it != mobile_nodes.end(); ++it) {
207 JAMI_DEBUG("Node {:s} Id: {:s}", std::to_string(nodeNum), (*it).toString());
208 nodeNum++;
209 }
210
211 JAMI_ERROR("Known Nodes");
212 nodeNum = 0;
213 for (auto it = known_nodes.begin(); it != known_nodes.end(); ++it) {
214 JAMI_DEBUG("Node {:s} Id: {:s}", std::to_string(nodeNum), (*it).toString());
215 nodeNum++;
216 }
217 JAMI_ERROR("Connecting_nodes");
218 nodeNum = 0;
219 for (auto it = connecting_nodes.begin(); it != connecting_nodes.end(); ++it) {
220 JAMI_DEBUG("Node {:s} Id: {:s}", std::to_string(nodeNum), (*it).toString());
221 nodeNum++;
222 }
223};
224
225void
226Bucket::changeMobility(const NodeId& nodeId, bool isMobile)
227{
228 auto itn = nodes.find(nodeId);
229 if (itn != nodes.end()) {
230 itn->second.isMobile_ = isMobile;
231 }
232}
233
234// For tests
235
236std::set<std::shared_ptr<dhtnet::ChannelSocketInterface>>
238{
239 std::set<std::shared_ptr<dhtnet::ChannelSocketInterface>> sockets;
240 for (auto const& info : nodes)
241 sockets.insert(info.second.socket);
242 return sockets;
243}
244
245// ####################################################################################################
246
248{
249 buckets.emplace_back(NodeId::zero());
250}
251
252bool
253RoutingTable::addNode(const std::shared_ptr<dhtnet::ChannelSocketInterface>& socket)
254{
255 auto bucket = findBucket(socket->deviceId());
256 return addNode(socket, bucket);
257}
258
259bool
260RoutingTable::addNode(const std::shared_ptr<dhtnet::ChannelSocketInterface>& channel,
261 std::list<Bucket>::iterator& bucket)
262{
263 NodeId nodeId = channel->deviceId();
264
265 if (bucket->hasNode(nodeId) || id_ == nodeId) {
266 return false;
267 }
268
269 while (bucket->isFull()) {
270 if (contains(bucket, id_)) {
271 split(bucket);
272 bucket = findBucket(nodeId);
273
274 } else {
275 return bucket->addNode(std::move(channel));
276 }
277 }
278 return bucket->addNode(std::move(channel));
279}
280
281bool
283{
284 return findBucket(nodeId)->removeNode(nodeId);
285}
286
287bool
289{
290 return findBucket(nodeId)->hasNode(nodeId);
291}
292
293bool
295{
296 if (id_ == nodeId)
297 return false;
298
299 auto bucket = findBucket(nodeId);
300 if (bucket == buckets.end())
301 return false;
302
303 return bucket->addKnownNode(nodeId);
304}
305
306bool
308{
309 if (id_ == nodeId)
310 return false;
311
312 auto bucket = findBucket(nodeId);
313
314 if (bucket == buckets.end())
315 return 0;
316
317 bucket->addMobileNode(nodeId);
318 return 1;
319}
320
321void
323{
324 return findBucket(nodeId)->removeMobileNode(nodeId);
325}
326
327bool
329{
330 return findBucket(nodeId)->hasMobileNode(nodeId);
331};
332
333bool
335{
336 if (id_ == nodeId)
337 return false;
338
339 auto bucket = findBucket(nodeId);
340
341 if (bucket == buckets.end())
342 return 0;
343
344 bucket->addConnectingNode(nodeId);
345 return 1;
346}
347
348void
350{
351 findBucket(nodeId)->removeConnectingNode(nodeId);
352}
353
354std::list<Bucket>::iterator
356{
357 if (buckets.empty())
358 throw std::runtime_error("No bucket");
359
360 auto b = buckets.begin();
361
362 while (true) {
363 auto next = std::next(b);
364 if (next == buckets.end())
365 return b;
366 if (std::memcmp(nodeId.data(), next->getLowerLimit().data(), nodeId.size()) < 0)
367 return b;
368 b = next;
369 }
370}
371
372std::vector<NodeId>
373RoutingTable::closestNodes(const NodeId& nodeId, unsigned count)
374{
375 std::vector<NodeId> closestNodes;
376 auto bucket = findBucket(nodeId);
377 auto sortedBucketInsert = [&](const std::list<Bucket>::iterator& b) {
378 auto nodes = b->getNodeIds();
379 for (auto n : nodes) {
380 if (n != nodeId) {
381 auto here = std::find_if(closestNodes.begin(),
382 closestNodes.end(),
383 [&nodeId, &n](NodeId& NodeId) {
384 return nodeId.xorCmp(n, NodeId) < 0;
385 });
386
387 closestNodes.insert(here, n);
388 }
389 }
390 };
391
392 auto itn = bucket;
393 auto itp = (bucket == buckets.begin()) ? buckets.end() : std::prev(bucket);
394 while (itn != buckets.end() || itp != buckets.end()) {
395 if (itn != buckets.end()) {
397 itn = std::next(itn);
398 }
399 if (itp != buckets.end()) {
401 itp = (itp == buckets.begin()) ? buckets.end() : std::prev(itp);
402 }
403 }
404
405 if (closestNodes.size() > count) {
406 closestNodes.resize(count);
407 }
408
409 return closestNodes;
410}
411
412void
414{
415 int counter = 1;
416 JAMI_DEBUG("SWARM: {:s} ", id_.toString());
417 for (auto it = buckets.begin(); it != buckets.end(); ++it) {
418 it->printBucket(counter);
419 counter++;
420 }
421 JAMI_DEBUG("_____________________________________________________________________________");
422}
423
424void
426{
427 findBucket(nodeId)->shutdownNode(nodeId);
428}
429
430std::vector<NodeId>
432{
433 std::lock_guard lock(mutex_);
434 std::vector<NodeId> ret;
435 for (const auto& b : buckets) {
436 const auto& nodes = b.getNodeIds();
437 ret.insert(ret.end(), nodes.begin(), nodes.end());
438 }
439 return ret;
440}
441
442std::vector<NodeId>
444{
445 std::vector<NodeId> ret;
446 for (const auto& b : buckets) {
447 const auto& nodes = b.getKnownNodes();
448 ret.insert(ret.end(), nodes.begin(), nodes.end());
449 }
450 return ret;
451}
452
453std::vector<NodeId>
455{
456 std::vector<NodeId> ret;
457 for (const auto& b : buckets) {
458 const auto& nodes = b.getMobileNodes();
459 ret.insert(ret.end(), nodes.begin(), nodes.end());
460 }
461 return ret;
462}
463
464std::vector<NodeId>
466{
467 std::vector<NodeId> ret;
468 for (const auto& b : buckets) {
469 const auto& nodes = b.getConnectingNodes();
470 ret.insert(ret.end(), nodes.begin(), nodes.end());
471 }
472 return ret;
473}
474
475std::vector<NodeId>
477{
478 std::vector<NodeId> ret;
479 auto bucket = findBucket(id_);
480 const auto& nodes = bucket->getMobileNodes();
481 ret.insert(ret.end(), nodes.begin(), nodes.end());
482
483 return ret;
484}
485
486bool
487RoutingTable::contains(const std::list<Bucket>::iterator& bucket, const NodeId& nodeId) const
488{
489 return NodeId::cmp(bucket->getLowerLimit(), nodeId) <= 0
490 && (std::next(bucket) == buckets.end()
491 || NodeId::cmp(nodeId, std::next(bucket)->getLowerLimit()) < 0);
492}
493
494std::vector<NodeId>
496{
497 std::vector<NodeId> ret;
498 for (const auto& b : buckets) {
499 const auto& nodes = b.getNodeIds();
500 const auto& knownNodes = b.getKnownNodes();
501 const auto& mobileNodes = b.getMobileNodes();
502 const auto& connectingNodes = b.getConnectingNodes();
503 ret.reserve(nodes.size() + knownNodes.size() + mobileNodes.size() + connectingNodes.size());
504 ret.insert(ret.end(), nodes.begin(), nodes.end());
505 ret.insert(ret.end(), knownNodes.begin(), knownNodes.end());
506 ret.insert(ret.end(), mobileNodes.begin(), mobileNodes.end());
507 ret.insert(ret.end(), connectingNodes.begin(), connectingNodes.end());
508 }
509 return ret;
510}
511
512void
514{
515 auto bucket = findBucket(nodeId);
516 bucket->shutdownNode(nodeId);
517 bucket->removeConnectingNode(nodeId);
518 bucket->removeKnownNode(nodeId);
519 bucket->removeMobileNode(nodeId);
520}
521
522NodeId
523RoutingTable::middle(std::list<Bucket>::iterator& it) const
524{
525 unsigned bit = depth(it);
526 if (bit >= 8 * HASH_LEN)
527 throw std::out_of_range("End of table");
528
529 NodeId id = it->getLowerLimit();
530 id.setBit(bit, true);
531 return id;
532}
533
534unsigned
535RoutingTable::depth(std::list<Bucket>::iterator& bucket) const
536{
537 int bit1 = bucket->getLowerLimit().lowbit();
538 int bit2 = std::next(bucket) != buckets.end() ? std::next(bucket)->getLowerLimit().lowbit()
539 : -1;
540 return std::max(bit1, bit2) + 1;
541}
542
543bool
544RoutingTable::split(std::list<Bucket>::iterator& bucket)
545{
546 NodeId id = middle(bucket);
547 auto newBucketIt = buckets.emplace(std::next(bucket), id);
548 // Re-assign nodes
549 auto& nodeSwap = bucket->getNodes();
550
551 for (auto it = nodeSwap.begin(); it != nodeSwap.end();) {
552 auto& node = *it;
553
554 auto nodeId = it->first;
555
556 if (!contains(bucket, nodeId)) {
557 newBucketIt->addNode(std::move(node.second));
558 it = nodeSwap.erase(it);
559 } else {
560 ++it;
561 }
562 }
563
564 auto connectingSwap = bucket->getConnectingNodes();
565 for (auto it = connectingSwap.begin(); it != connectingSwap.end();) {
566 auto nodeId = *it;
567
568 if (!contains(bucket, nodeId)) {
569 newBucketIt->addConnectingNode(nodeId);
570 it = connectingSwap.erase(it);
571 bucket->removeConnectingNode(nodeId);
572 } else {
573 ++it;
574 }
575 }
576
577 auto knownSwap = bucket->getKnownNodes();
578 for (auto it = knownSwap.begin(); it != knownSwap.end();) {
579 auto nodeId = *it;
580
581 if (!contains(bucket, nodeId)) {
582 newBucketIt->addKnownNode(nodeId);
583 it = knownSwap.erase(it);
584 bucket->removeKnownNode(nodeId);
585 } else {
586 ++it;
587 }
588 }
589
590 auto mobileSwap = bucket->getMobileNodes();
591 for (auto it = mobileSwap.begin(); it != mobileSwap.end();) {
592 auto nodeId = *it;
593
594 if (!contains(bucket, nodeId)) {
595 newBucketIt->addMobileNode(nodeId);
596 it = mobileSwap.erase(it);
597 bucket->removeMobileNode(nodeId);
598 } else {
599 ++it;
600 }
601 }
602
603 return true;
604}
605
606} // namespace jami
void shutdownAllNodes()
Shutdowns all sockets in nodes through shutdownNode.
bool addNode(const std::shared_ptr< dhtnet::ChannelSocketInterface > &socket)
Add Node socket to bucket.
bool removeNode(const NodeId &nodeId)
Remove NodeId socket from bucket and insert it in known_nodes or mobile_nodes depending on its type.
Bucket()=delete
bool shutdownNode(const NodeId &nodeId)
Shutdowns socket and removes it from nodes.
unsigned getKnownNodesSize() const
Returns number of knwon_nodes in bucket.
bool addMobileNode(const NodeId &nodeId)
Add NodeId to mobile_nodes if it doesn't exist in nodes.
bool hasNode(const NodeId &nodeId) const
Test if socket exists in nodes.
std::set< NodeId > getNodeIds() const
Get NodeIds from bucket.
bool addConnectingNode(const NodeId &nodeId)
Add NodeId to connecting_nodes if it doesn't exist in nodes.
NodeId getKnownNode(unsigned index) const
Returns NodeId from known_nodes at index.
void printBucket(unsigned number) const
Prints bucket and bucket's number.
bool addKnownNode(const NodeId &nodeId)
Add NodeId to known_nodes if it doesn't exist in nodes.
std::set< std::shared_ptr< dhtnet::ChannelSocketInterface > > getNodeSockets() const
Get sockets from bucket.
asio::steady_timer & getNodeTimer(const std::shared_ptr< dhtnet::ChannelSocketInterface > &socket)
Returns socket's timer.
const std::set< NodeId > & getKnownNodes() const
Get NodeIds from known_nodes.
std::set< NodeId > getKnownNodesRandom(unsigned numberNodes, std::mt19937_64 &rd) const
Returns random numberNodes NodeId from known_nodes.
void changeMobility(const NodeId &nodeId, bool isMobile)
Change mobility of specific node, mobile or not.
bool hasMobileNode(const NodeId &nodeId)
Check if mobile node exists in routing table.
std::vector< NodeId > getAllNodes() const
Return every node from each bucket.
std::vector< NodeId > getConnectingNodes() const
Returns all routing table's connecting nodes.
void deleteNode(const NodeId &nodeId)
Delete node from every table in bucket.
bool addKnownNode(const NodeId &nodeId)
Add known node to routing table.
std::vector< NodeId > closestNodes(const NodeId &nodeId, unsigned count)
Returns the count closest nodes to a specific nodeId.
std::vector< NodeId > getBucketMobileNodes() const
Returns mobile nodes corresponding to the swarm's id.
void shutdownNode(const NodeId &nodeId)
Shutdowns a node.
bool addMobileNode(const NodeId &nodeId)
Add mobile node to routing table.
std::list< Bucket >::iterator findBucket(const NodeId &nodeId)
Returns bucket iterator containing nodeId.
std::vector< NodeId > getMobileNodes() const
Returns all routing table's mobile nodes.
bool removeNode(const NodeId &nodeId)
Removes node from routing table Adds it to known_nodes or mobile_nodes depending on mobility.
void removeConnectingNode(const NodeId &nodeId)
Remove connecting connecting node to routing table.
bool hasNode(const NodeId &nodeId)
Check if connected node exsits in routing table.
std::vector< NodeId > getKnownNodes() const
Returns all routing table's known nodes.
std::vector< NodeId > getNodes() const
Returns all routing table's connected nodes.
bool contains(const std::list< Bucket >::iterator &it, const NodeId &nodeId) const
Test if connected nodeId is in specific bucket.
void printRoutingTable() const
Prints routing table.
bool addConnectingNode(const NodeId &nodeId)
Add connecting node to routing table.
void removeMobileNode(const NodeId &nodeId)
Remove mobile node to routing table.
bool addNode(const std::shared_ptr< dhtnet::ChannelSocketInterface > &socket)
Add socket to bucket.
#define JAMI_ERROR(formatstr,...)
Definition logger.h:228
#define JAMI_DEBUG(formatstr,...)
Definition logger.h:226
Definition account.h:55
dht::h256 NodeId
void emitSignal(Args... args)
Definition ring_signal.h:64
constexpr const std::chrono::minutes FIND_PERIOD
dht::PkId NodeId