Logo AND Algorithmique Numérique Distribuée

Public GIT Repository
[sonar] Replace redundant type with "auto" (examples/).
[simgrid.git] / examples / s4u / dht-kademlia / node.cpp
1 /* Copyright (c) 2010-2020. The SimGrid Team. All rights reserved.          */
2
3 /* This program is free software; you can redistribute it and/or modify it
4  * under the terms of the license (GNU LGPL) which comes with this package. */
5
6 #include "node.hpp"
7 #include "routing_table.hpp"
8
9 XBT_LOG_NEW_DEFAULT_CATEGORY(kademlia_node, "Messages specific for this example");
10
11 namespace kademlia {
12 static void destroy(void* message)
13 {
14   const auto* msg = static_cast<Message*>(message);
15   delete msg->answer_;
16   delete msg;
17 }
18
19 /**
20   * @brief Tries to join the network
21   * @param known_id id of the node I know in the network.
22   */
23 bool Node::join(unsigned int known_id)
24 {
25   const Answer* node_list;
26   bool got_answer = false;
27
28   /* Add the guy we know to our routing table and ourselves. */
29   routingTableUpdate(id_);
30   routingTableUpdate(known_id);
31
32   /* First step: Send a "FIND_NODE" request to the node we know */
33   sendFindNode(known_id, id_);
34
35   simgrid::s4u::Mailbox* mailbox = simgrid::s4u::Mailbox::by_name(std::to_string(id_));
36   do {
37     if (receive_comm == nullptr)
38       receive_comm = mailbox->get_async(&received_msg);
39     if (receive_comm->test()) {
40       XBT_DEBUG("Received an answer from the node I know.");
41       got_answer = true;
42       // retrieve the node list and ping them.
43       const auto* msg = static_cast<Message*>(received_msg);
44       node_list    = msg->answer_;
45       if (node_list) {
46         for (auto const& contact : node_list->getNodes())
47           routingTableUpdate(contact.first);
48       } else {
49         handleFindNode(msg);
50       }
51       delete msg->answer_;
52       delete msg;
53       receive_comm = nullptr;
54     } else
55       simgrid::s4u::this_actor::sleep_for(1);
56   } while (not got_answer);
57
58   /* Second step: Send a FIND_NODE to a a random node in buckets */
59   unsigned int bucket_id = table.findBucket(known_id)->getId();
60   xbt_assert(bucket_id <= IDENTIFIER_SIZE);
61   for (unsigned int i = 0; ((bucket_id > i) || (bucket_id + i) <= IDENTIFIER_SIZE) && i < JOIN_BUCKETS_QUERIES; i++) {
62     if (bucket_id > i) {
63       unsigned int id_in_bucket = get_id_in_prefix(id_, bucket_id - i);
64       findNode(id_in_bucket, false);
65     }
66     if (bucket_id + i <= IDENTIFIER_SIZE) {
67       unsigned int id_in_bucket = get_id_in_prefix(id_, bucket_id + i);
68       findNode(id_in_bucket, false);
69     }
70   }
71   return got_answer;
72 }
73
74 /** @brief Send a "FIND_NODE" to a node
75   * @param id node we are querying
76   * @param destination node we are trying to find.
77   */
78 void Node::sendFindNode(unsigned int id, unsigned int destination) const
79 {
80   /* Gets the mailbox to send to */
81   simgrid::s4u::Mailbox* mailbox = simgrid::s4u::Mailbox::by_name(std::to_string(id));
82   /* Build the task */
83
84   auto* msg = new Message(id_, destination, simgrid::s4u::Mailbox::by_name(std::to_string(id_)),
85                           simgrid::s4u::Host::current()->get_cname());
86
87   /* Send the task */
88   mailbox->put_init(msg, 1)->detach(kademlia::destroy);
89   XBT_VERB("Asking %u for its closest nodes", id);
90 }
91
92 /**
93   * Sends to the best "KADEMLIA_ALPHA" nodes in the "node_list" array a "FIND_NODE" request, to ask them for their best
94   * nodes
95   */
96 unsigned int Node::sendFindNodeToBest(const Answer* node_list) const
97 {
98   unsigned int i           = 0;
99   unsigned int j           = 0;
100   unsigned int destination = node_list->getDestinationId();
101   for (auto const& node_to_query : node_list->getNodes()) {
102     /* We need to have at most "KADEMLIA_ALPHA" requests each time, according to the protocol */
103     /* Gets the node we want to send the query to */
104     if (node_to_query.first != id_) { /* No need to query ourselves */
105       sendFindNode(node_to_query.first, destination);
106       j++;
107     }
108     i++;
109     if (j == KADEMLIA_ALPHA)
110       break;
111   }
112   return i;
113 }
114
115 /** @brief Updates/Puts the node id unsigned into our routing table
116   * @param id The id of the node we need to add unsigned into our routing table
117   */
118 void Node::routingTableUpdate(unsigned int id)
119 {
120   // retrieval of the bucket in which the should be
121   Bucket* bucket = table.findBucket(id);
122
123   // check if the id is already in the bucket.
124   auto id_pos = std::find(bucket->nodes_.begin(), bucket->nodes_.end(), id);
125
126   if (id_pos == bucket->nodes_.end()) {
127     /* We check if the bucket is full or not. If it is, we evict an old element */
128     if (bucket->nodes_.size() >= BUCKET_SIZE) {
129       bucket->nodes_.pop_back();
130     }
131     bucket->nodes_.push_front(id);
132     XBT_VERB("I'm adding to my routing table %08x", id);
133   } else {
134     // We push the element to the front
135     bucket->nodes_.erase(id_pos);
136     bucket->nodes_.push_front(id);
137     XBT_VERB("I'm updating %08x", id);
138   }
139 }
140
141 /** @brief Finds the closest nodes to the node given.
142   * @param node : our node
143   * @param destination_id : the id of the guy we are trying to find
144   */
145 Answer* Node::findClosest(unsigned int destination_id)
146 {
147   auto* answer = new Answer(destination_id);
148   /* We find the corresponding bucket for the id */
149   const Bucket* bucket = table.findBucket(destination_id);
150   int bucket_id  = bucket->getId();
151   xbt_assert((bucket_id <= IDENTIFIER_SIZE), "Bucket found has a wrong identifier");
152   /* So, we copy the contents of the bucket unsigned into our answer */
153   answer->addBucket(bucket);
154
155   /* However, if we don't have enough elements in our bucket, we NEED to include at least "BUCKET_SIZE" elements
156    * (if, of course, we know at least "BUCKET_SIZE" elements. So we're going to look unsigned into the other buckets.
157    */
158   for (int i = 1; answer->getSize() < BUCKET_SIZE && ((bucket_id - i > 0) || (bucket_id + i < IDENTIFIER_SIZE)); i++) {
159     /* We check the previous buckets */
160     if (bucket_id - i >= 0) {
161       const Bucket* bucket_p = &table.getBucketAt(bucket_id - i);
162       answer->addBucket(bucket_p);
163     }
164     /* We check the next buckets */
165     if (bucket_id + i <= IDENTIFIER_SIZE) {
166       const Bucket* bucket_n = &table.getBucketAt(bucket_id + i);
167       answer->addBucket(bucket_n);
168     }
169   }
170   /* We trim the array to have only BUCKET_SIZE or less elements */
171   answer->trim();
172
173   return answer;
174 }
175
176 /** @brief Send a request to find a node in the node routing table.
177   * @param id_to_find the id of the node we are trying to find
178   */
179 bool Node::findNode(unsigned int id_to_find, bool count_in_stats)
180 {
181   unsigned int queries;
182   unsigned int answers;
183   bool destination_found   = false;
184   unsigned int nodes_added = 0;
185   double global_timeout    = simgrid::s4u::Engine::get_clock() + FIND_NODE_GLOBAL_TIMEOUT;
186   unsigned int steps       = 0;
187
188   /* First we build a list of who we already know */
189   Answer* node_list = findClosest(id_to_find);
190   xbt_assert((node_list != nullptr), "node_list incorrect");
191   XBT_DEBUG("Doing a FIND_NODE on %08x", id_to_find);
192
193   /* Ask the nodes on our list if they have information about the node we are trying to find */
194   do {
195     answers        = 0;
196     queries        = sendFindNodeToBest(node_list);
197     nodes_added    = 0;
198     double timeout = simgrid::s4u::Engine::get_clock() + FIND_NODE_TIMEOUT;
199     steps++;
200     double time_beginreceive = simgrid::s4u::Engine::get_clock();
201
202     simgrid::s4u::Mailbox* mailbox = simgrid::s4u::Mailbox::by_name(std::to_string(id_));
203     do {
204       if (receive_comm == nullptr)
205         receive_comm = mailbox->get_async(&received_msg);
206
207       if (receive_comm->test()) {
208         const auto* msg = static_cast<Message*>(received_msg);
209         // Check if what we have received is what we are looking for.
210         if (msg->answer_ && msg->answer_->getDestinationId() == id_to_find) {
211           routingTableUpdate(msg->sender_id_);
212           // Handle the answer
213           for (auto const& contact : node_list->getNodes())
214             routingTableUpdate(contact.first);
215           answers++;
216
217           nodes_added = node_list->merge(msg->answer_);
218           XBT_DEBUG("Received an answer from %s (%s) with %zu nodes on it", msg->answer_to_->get_cname(),
219                     msg->issuer_host_name_.c_str(), msg->answer_->getSize());
220         } else {
221           if (msg->answer_) {
222             routingTableUpdate(msg->sender_id_);
223             XBT_DEBUG("Received a wrong answer for a FIND_NODE");
224           } else {
225             handleFindNode(msg);
226           }
227           // Update the timeout if we didn't have our answer
228           timeout += simgrid::s4u::Engine::get_clock() - time_beginreceive;
229           time_beginreceive = simgrid::s4u::Engine::get_clock();
230         }
231         delete msg->answer_;
232         delete msg;
233         receive_comm = nullptr;
234       } else {
235         simgrid::s4u::this_actor::sleep_for(1);
236       }
237     } while (simgrid::s4u::Engine::get_clock() < timeout && answers < queries);
238     destination_found = node_list->destinationFound();
239   } while (not destination_found && (nodes_added > 0 || answers == 0) &&
240            simgrid::s4u::Engine::get_clock() < global_timeout && steps < MAX_STEPS);
241
242   if (destination_found) {
243     if (count_in_stats)
244       find_node_success++;
245     if (queries > 4)
246       XBT_VERB("FIND_NODE on %08x success in %u steps", id_to_find, steps);
247     routingTableUpdate(id_to_find);
248   } else {
249     if (count_in_stats) {
250       find_node_failed++;
251       XBT_VERB("%08x not found in %u steps", id_to_find, steps);
252     }
253   }
254   delete node_list;
255   return destination_found;
256 }
257
258 /** @brief Does a pseudo-random lookup for someone in the system
259   * @param node caller node data
260   */
261 void Node::randomLookup()
262 {
263   unsigned int id_to_look = RANDOM_LOOKUP_NODE; // Totally random.
264   /* TODO: Use some pseudo-random generator. */
265   XBT_DEBUG("I'm doing a random lookup");
266   findNode(id_to_look, true);
267 }
268
269 /** @brief Handles the answer to an incoming "find_node" task */
270 void Node::handleFindNode(const Message* msg)
271 {
272   routingTableUpdate(msg->sender_id_);
273   XBT_VERB("Received a FIND_NODE from %s (%s), he's trying to find %08x", msg->answer_to_->get_cname(),
274            msg->issuer_host_name_.c_str(), msg->destination_id_);
275   // Building the answer to the request
276   auto* answer =
277       new Message(id_, msg->destination_id_, findClosest(msg->destination_id_),
278                   simgrid::s4u::Mailbox::by_name(std::to_string(id_)), simgrid::s4u::Host::current()->get_cname());
279   // Sending the answer
280   msg->answer_to_->put_init(answer, 1)->detach(kademlia::destroy);
281 }
282
283 void Node::displaySuccessRate() const
284 {
285   XBT_INFO("%u/%u FIND_NODE have succeeded", find_node_success, find_node_success + find_node_failed);
286 }
287 } // namespace kademlia
288 /**@brief Returns an identifier which is in a specific bucket of a routing table
289  * @param id id of the routing table owner
290  * @param prefix id of the bucket where we want that identifier to be
291  */
292 unsigned int get_id_in_prefix(unsigned int id, unsigned int prefix)
293 {
294   if (prefix == 0) {
295     return 0;
296   } else {
297     return (1U << (prefix - 1)) ^ id;
298   }
299 }
300
301 /** @brief Returns the prefix of an identifier.
302   * The prefix is the id of the bucket in which the remote identifier xor our identifier should be stored.
303   * @param id : big unsigned int id to test
304   * @param nb_bits : key size
305   */
306 unsigned int get_node_prefix(unsigned int id, unsigned int nb_bits)
307 {
308   unsigned int size = sizeof(unsigned int) * 8;
309   for (unsigned int j = 0; j < size; j++) {
310     if (((id >> (size - 1 - j)) & 0x1) != 0) {
311       return nb_bits - (j);
312     }
313   }
314   return 0;
315 }