scheduling
[oweals/gnunet.git] / src / experimentation / gnunet-daemon-experimentation_nodes.c
1 /*
2      This file is part of GNUnet.
3      (C) 2009 Christian Grothoff (and other contributing authors)
4
5      GNUnet is free software; you can redistribute it and/or modify
6      it under the terms of the GNU General Public License as published
7      by the Free Software Foundation; either version 3, or (at your
8      option) any later version.
9
10      GNUnet is distributed in the hope that it will be useful, but
11      WITHOUT ANY WARRANTY; without even the implied warranty of
12      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13      General Public License for more details.
14
15      You should have received a copy of the GNU General Public License
16      along with GNUnet; see the file COPYING.  If not, write to the
17      Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18      Boston, MA 02111-1307, USA.
19 */
20
21 /**
22  * @file experimentation/gnunet-daemon-experimentation_nodes.c
23  * @brief experimentation daemon: node management
24  * @author Christian Grothoff
25  * @author Matthias Wachs
26  */
27 #include "platform.h"
28 #include "gnunet_getopt_lib.h"
29 #include "gnunet_util_lib.h"
30 #include "gnunet_core_service.h"
31 #include "gnunet_statistics_service.h"
32 #include "gnunet-daemon-experimentation.h"
33
34
35 /**
36  * Core handle
37  */
38 static struct GNUNET_CORE_Handle *ch;
39
40
41 /**
42  * Peer's own identity
43  */
44 static struct GNUNET_PeerIdentity me;
45
46
47 /**
48  * Nodes with a pending request
49  */
50 struct GNUNET_CONTAINER_MultiHashMap *nodes_requested;
51
52
53 /**
54  * Active experimentation nodes
55  */
56 struct GNUNET_CONTAINER_MultiHashMap *nodes_active;
57
58
59 /**
60  * Inactive experimentation nodes
61  * To be excluded from future requests
62  */
63 struct GNUNET_CONTAINER_MultiHashMap *nodes_inactive;
64
65
66 /**
67  * Update statistics
68  *
69  * @param m hashmap to update values from
70  */
71 static void update_stats (struct GNUNET_CONTAINER_MultiHashMap *m)
72 {
73         GNUNET_assert (NULL != m);
74         GNUNET_assert (NULL != GSE_stats);
75
76         if (m == nodes_active)
77         {
78                         GNUNET_STATISTICS_set (GSE_stats, "# nodes active",
79                                         GNUNET_CONTAINER_multihashmap_size(m), GNUNET_NO);
80         }
81         else if (m == nodes_inactive)
82         {
83                         GNUNET_STATISTICS_set (GSE_stats, "# nodes inactive",
84                                         GNUNET_CONTAINER_multihashmap_size(m), GNUNET_NO);
85         }
86         else if (m == nodes_requested)
87         {
88                         GNUNET_STATISTICS_set (GSE_stats, "# nodes requested",
89                                         GNUNET_CONTAINER_multihashmap_size(m), GNUNET_NO);
90         }
91         else
92                 GNUNET_break (0);
93
94 }
95
96
97 /**
98  * Clean up nodes
99  *
100  * @param cls the hashmap to clean up
101  * @param key key of the current node
102  * @param value related node object
103  * @return always GNUNET_OK
104  */
105 static int
106 cleanup_nodes (void *cls,
107                                                          const struct GNUNET_HashCode * key,
108                                                          void *value)
109 {
110         struct Node *n;
111         struct GNUNET_CONTAINER_MultiHashMap *cur = cls;
112
113         n = value;
114         if (GNUNET_SCHEDULER_NO_TASK != n->timeout_task)
115         {
116                 GNUNET_SCHEDULER_cancel (n->timeout_task);
117                 n->timeout_task = GNUNET_SCHEDULER_NO_TASK;
118         }
119         if (NULL != n->cth)
120         {
121                 GNUNET_CORE_notify_transmit_ready_cancel (n->cth);
122                 n->cth = NULL;
123         }
124
125
126         GNUNET_CONTAINER_multihashmap_remove (cur, key, value);
127         GNUNET_free (value);
128         return GNUNET_OK;
129 }
130
131
132 /**
133  * Check if id passed is my id
134  *
135  * @param id the id to check
136  * @return GNUNET_YES or GNUNET_NO
137  */
138 static int is_me (const struct GNUNET_PeerIdentity *id)
139 {
140         if (0 == memcmp (&me, id, sizeof (me)))
141                 return GNUNET_YES;
142         else
143                 return GNUNET_NO;
144 }
145
146 /**
147  * Core startup callback
148  *
149  * @param cls unused
150  * @param server core service's server handle
151  * @param my_identity my id
152  */
153 static void
154 core_startup_handler (void *cls,
155                                                                                         struct GNUNET_CORE_Handle *server,
156                       const struct GNUNET_PeerIdentity *my_identity)
157 {
158         me = *my_identity;
159 }
160
161
162 /**
163  * Remove experimentation request due to timeout
164  *
165  * @param cls the related node
166  * @param tc scheduler's task context
167  */
168 static void
169 remove_request (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
170 {
171         struct Node *n = cls;
172
173         GNUNET_log (GNUNET_ERROR_TYPE_INFO, _("Removing request for peer %s due to timeout\n"),
174                         GNUNET_i2s (&n->id));
175
176         if (GNUNET_YES == GNUNET_CONTAINER_multihashmap_contains (nodes_requested, &n->id.hashPubKey))
177         {
178                         GNUNET_CONTAINER_multihashmap_remove (nodes_requested, &n->id.hashPubKey, n);
179                         update_stats (nodes_requested);
180                         GNUNET_CONTAINER_multihashmap_put (nodes_inactive, &n->id.hashPubKey, n,
181                                         GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_FAST);
182                         update_stats (nodes_inactive);
183         }
184
185         n->timeout_task = GNUNET_SCHEDULER_NO_TASK;
186         if (NULL != n->cth)
187         {
188                 GNUNET_CORE_notify_transmit_ready_cancel (n->cth);
189                 n->cth = NULL;
190         }
191 }
192
193
194 /**
195  * Core's transmit notify callback to send request
196  *
197  * @param cls the related node
198  * @param bufsize buffer size
199  * @param buf the buffer to copy to
200  * @return bytes passed
201  */
202 size_t send_request_cb (void *cls, size_t bufsize, void *buf)
203 {
204         struct Node *n = cls;
205         struct Experimentation_Request msg;
206         size_t size = sizeof (msg);
207
208         n->cth = NULL;
209   if (buf == NULL)
210   {
211     /* client disconnected */
212     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Client disconnected\n");
213     if (GNUNET_SCHEDULER_NO_TASK != n->timeout_task)
214                 GNUNET_SCHEDULER_cancel (n->timeout_task);
215     GNUNET_SCHEDULER_add_now (&remove_request, n);
216     return 0;
217   }
218   GNUNET_assert (bufsize >= size);
219
220         msg.msg.size = htons (size);
221         msg.msg.type = htons (GNUNET_MESSAGE_TYPE_EXPERIMENTATION_REQUEST);
222         msg.capabilities = htonl (GSE_node_capabilities);
223         memcpy (buf, &msg, size);
224
225         GNUNET_log (GNUNET_ERROR_TYPE_INFO, _("Sending request to peer %s\n"),
226                         GNUNET_i2s (&n->id));
227         return size;
228 }
229
230
231 /**
232  * Send request
233  *
234  * @param peer the peer to send to
235  */
236 static void send_request (const struct GNUNET_PeerIdentity *peer)
237 {
238         struct Node *n;
239         size_t size;
240
241         size = sizeof (struct Experimentation_Request);
242         n = GNUNET_malloc (sizeof (struct Node));
243         n->id = *peer;
244         n->timeout_task = GNUNET_SCHEDULER_add_delayed (EXP_RESPONSE_TIMEOUT, &remove_request, n);
245         n->cth = GNUNET_CORE_notify_transmit_ready(ch, GNUNET_NO, 0,
246                                                                 GNUNET_TIME_relative_get_forever_(),
247                                                                 peer, size, send_request_cb, n);
248         n->capabilities = NONE;
249
250         GNUNET_assert (GNUNET_OK == GNUNET_CONTAINER_multihashmap_put (nodes_requested,
251                         &peer->hashPubKey, n, GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_FAST));
252
253         update_stats (nodes_requested);
254 }
255
256
257 /**
258  * Core's transmit notify callback to send response
259  *
260  * @param cls the related node
261  * @param bufsize buffer size
262  * @param buf the buffer to copy to
263  * @return bytes passed
264  */
265 size_t send_response_cb (void *cls, size_t bufsize, void *buf)
266 {
267         struct Node *n = cls;
268         struct Experimentation_Response msg;
269         size_t size = sizeof (msg);
270
271         n->cth = NULL;
272   if (buf == NULL)
273   {
274     /* client disconnected */
275     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Client disconnected\n");
276     return 0;
277   }
278   GNUNET_assert (bufsize >= size);
279
280         msg.msg.size = htons (size);
281         msg.msg.type = htons (GNUNET_MESSAGE_TYPE_EXPERIMENTATION_RESPONSE);
282         msg.capabilities = htonl (GSE_node_capabilities);
283         memcpy (buf, &msg, size);
284
285         GNUNET_log (GNUNET_ERROR_TYPE_INFO, _("Sending response to peer %s\n"),
286                         GNUNET_i2s (&n->id));
287         return size;
288 }
289
290
291 /**
292  * Set a specific node as active
293  *
294  * @param n the node
295  */
296 static void node_make_active (struct Node *n)
297 {
298   GNUNET_CONTAINER_multihashmap_put (nodes_active,
299                         &n->id.hashPubKey, n, GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_FAST);
300         update_stats (nodes_active);
301         GNUNET_log (GNUNET_ERROR_TYPE_INFO, _("Added peer `%s' as active node\n"),
302                         GNUNET_i2s (&n->id));
303 }
304
305
306 /**
307  * Handle a request and send a response
308  *
309  * @param peer the source
310  * @param message the message
311  */
312 static void handle_request (const struct GNUNET_PeerIdentity *peer,
313                                                                                                                 const struct GNUNET_MessageHeader *message)
314 {
315         struct Node *n;
316         struct Experimentation_Request *rm = (struct Experimentation_Request *) message;
317
318         if (NULL != (n = GNUNET_CONTAINER_multihashmap_get (nodes_active, &peer->hashPubKey)))
319         {
320                         GNUNET_log (GNUNET_ERROR_TYPE_INFO, _("Received %s from %s peer `%s'\n"),
321                                         "REQUEST", "active", GNUNET_i2s (peer));
322                         n->capabilities = ntohl (rm->capabilities);
323         }
324         else if (NULL != (n = GNUNET_CONTAINER_multihashmap_get (nodes_requested, &peer->hashPubKey)))
325         {
326                         GNUNET_log (GNUNET_ERROR_TYPE_INFO, _("Received %s from %s peer `%s'\n"),
327                                         "REQUEST", "requested", GNUNET_i2s (peer));
328                         n->capabilities = ntohl (rm->capabilities);
329                         GNUNET_CONTAINER_multihashmap_remove (nodes_requested, &peer->hashPubKey, n);
330                         if (GNUNET_SCHEDULER_NO_TASK != n->timeout_task)
331                         {
332                                 GNUNET_SCHEDULER_cancel (n->timeout_task);
333                                 n->timeout_task = GNUNET_SCHEDULER_NO_TASK;
334                         }
335                         if (NULL != n->cth)
336                         {
337                                 GNUNET_CORE_notify_transmit_ready_cancel (n->cth);
338                                 n->cth = NULL;
339                         }
340                         update_stats (nodes_requested);
341                         node_make_active (n);
342         }
343         else if (NULL != (n = GNUNET_CONTAINER_multihashmap_get (nodes_inactive, &peer->hashPubKey)))
344         {
345                         GNUNET_log (GNUNET_ERROR_TYPE_INFO, _("Received %s from %s peer `%s'\n"),
346                                         "REQUEST", "inactive", GNUNET_i2s (peer));
347                         n->capabilities = ntohl (rm->capabilities);
348                         GNUNET_CONTAINER_multihashmap_remove (nodes_inactive, &peer->hashPubKey, n);
349                         update_stats (nodes_inactive);
350                         node_make_active (n);
351         }
352         else
353         {
354                         /* Create new node */
355                         n = GNUNET_malloc (sizeof (struct Node));
356                         n->id = *peer;
357                         n->capabilities = NONE;
358                         n->capabilities = ntohl (rm->capabilities);
359                         GNUNET_log (GNUNET_ERROR_TYPE_INFO, _("Received %s from %s peer `%s'\n"),
360                                         "REQUEST", "new", GNUNET_i2s (peer));
361                         node_make_active (n);
362         }
363         n->cth = GNUNET_CORE_notify_transmit_ready (ch, GNUNET_NO, 0,
364                                                                 GNUNET_TIME_relative_get_forever_(),
365                                                                 peer, sizeof (struct Experimentation_Response),
366                                                                 send_response_cb, n);
367 }
368
369
370 /**
371  * Handle a response
372  *
373  * @param peer the source
374  * @param message the message
375  */
376 static void handle_response (const struct GNUNET_PeerIdentity *peer,
377                                                                                                                  const struct GNUNET_MessageHeader *message)
378 {
379         struct Node *n;
380         struct Experimentation_Request *rm = (struct Experimentation_Request *) message;
381
382         if (NULL != (n = GNUNET_CONTAINER_multihashmap_get (nodes_active, &peer->hashPubKey)))
383         {
384                         GNUNET_log (GNUNET_ERROR_TYPE_INFO, _("Received %s from %s peer `%s'\n"),
385                                         "RESPONSE", "active", GNUNET_i2s (peer));
386                         n->capabilities = ntohl (rm->capabilities);
387         }
388         else if (NULL != (n = GNUNET_CONTAINER_multihashmap_get (nodes_requested, &peer->hashPubKey)))
389         {
390                         GNUNET_log (GNUNET_ERROR_TYPE_INFO, _("Received %s from %s peer `%s'\n"),
391                                         "RESPONSE", "requested", GNUNET_i2s (peer));
392                         n->capabilities = ntohl (rm->capabilities);
393                         GNUNET_CONTAINER_multihashmap_remove (nodes_requested, &peer->hashPubKey, n);
394                         if (GNUNET_SCHEDULER_NO_TASK != n->timeout_task)
395                         {
396                                 GNUNET_SCHEDULER_cancel (n->timeout_task);
397                                 n->timeout_task = GNUNET_SCHEDULER_NO_TASK;
398                         }
399                         if (NULL != n->cth)
400                         {
401                                 GNUNET_CORE_notify_transmit_ready_cancel (n->cth);
402                                 n->cth = NULL;
403                         }
404                         update_stats (nodes_requested);
405                         node_make_active (n);
406         }
407         else if (NULL != (n = GNUNET_CONTAINER_multihashmap_get (nodes_inactive, &peer->hashPubKey)))
408         {
409                         GNUNET_log (GNUNET_ERROR_TYPE_INFO, _("Received %s from peer `%s'\n"),
410                                         "RESPONSE", "inactive", GNUNET_i2s (peer));
411                         n->capabilities = ntohl (rm->capabilities);
412                         GNUNET_CONTAINER_multihashmap_remove (nodes_inactive, &peer->hashPubKey, n);
413                         update_stats (nodes_inactive);
414                         node_make_active (n);
415         }
416         else
417         {
418                         GNUNET_log (GNUNET_ERROR_TYPE_INFO, _("Received %s from %s peer `%s'\n"),
419                                         "RESPONSE", "unknown", GNUNET_i2s (peer));
420                         return;
421         }
422 }
423
424 /**
425  * Method called whenever a given peer connects.
426  *
427  * @param cls closure
428  * @param peer peer identity this notification is about
429  */
430 void core_connect_handler (void *cls,
431                            const struct GNUNET_PeerIdentity *peer)
432 {
433         if (GNUNET_YES == is_me(peer))
434                 return;
435
436         GNUNET_log (GNUNET_ERROR_TYPE_INFO, _("Connected to peer %s\n"),
437                         GNUNET_i2s (peer));
438
439         if (GNUNET_YES == GNUNET_CONTAINER_multihashmap_contains (nodes_requested, &peer->hashPubKey))
440                 return; /* We already sent a request */
441
442         if (GNUNET_YES == GNUNET_CONTAINER_multihashmap_contains (nodes_active, &peer->hashPubKey))
443                 return; /* This peer is known as active  */
444
445         if (GNUNET_YES == GNUNET_CONTAINER_multihashmap_contains (nodes_inactive, &peer->hashPubKey))
446                 return; /* This peer is known as inactive  */
447
448         send_request (peer);
449
450 }
451
452
453 /**
454  * Method called whenever a given peer disconnects.
455  *
456  * @param cls closure
457  * @param peer peer identity this notification is about
458  */
459 void core_disconnect_handler (void *cls,
460                            const struct GNUNET_PeerIdentity * peer)
461 {
462         if (GNUNET_YES == is_me(peer))
463                 return;
464
465         GNUNET_log (GNUNET_ERROR_TYPE_INFO, _("Disconnected from peer %s\n"),
466                         GNUNET_i2s (peer));
467
468 }
469
470
471 /**
472  * Handle a request and send a response
473  *
474  * @param cls unused
475  * @param other the sender
476  * @param message the message
477  * @return GNUNET_OK to keep connection, GNUNET_SYSERR on error
478  */
479 static int
480 core_receive_handler (void *cls,
481                                                                                         const struct GNUNET_PeerIdentity *other,
482                                                                                         const struct GNUNET_MessageHeader *message)
483 {
484         if (ntohs (message->size) < sizeof (struct GNUNET_MessageHeader))
485         {
486                         GNUNET_break (0);
487                         return GNUNET_SYSERR;
488         }
489
490         switch (ntohs (message->type)) {
491                 case GNUNET_MESSAGE_TYPE_EXPERIMENTATION_REQUEST:
492                         handle_request (other, message);
493                         break;
494                 case GNUNET_MESSAGE_TYPE_EXPERIMENTATION_RESPONSE:
495                         handle_response (other, message);
496                         break;
497                 default:
498                         break;
499         }
500
501         return GNUNET_OK;
502 }
503
504
505
506 /**
507  * Start the nodes management
508  */
509 void
510 GNUNET_EXPERIMENTATION_nodes_start ()
511 {
512         /* Connecting to core service to find partners */
513         ch = GNUNET_CORE_connect (GSE_cfg, NULL,
514                                                                                                                 &core_startup_handler,
515                                                                                                                 &core_connect_handler,
516                                                                                                                 &core_disconnect_handler,
517                                                                                                                 &core_receive_handler,
518                                                                                                                 GNUNET_NO, NULL, GNUNET_NO, NULL);
519         if (NULL == ch)
520         {
521                         GNUNET_log (GNUNET_ERROR_TYPE_INFO, _("Failed to connect to CORE service!\n"));
522                         return;
523         }
524
525         nodes_requested = GNUNET_CONTAINER_multihashmap_create (10, GNUNET_NO);
526         nodes_active = GNUNET_CONTAINER_multihashmap_create (10, GNUNET_NO);
527         nodes_inactive = GNUNET_CONTAINER_multihashmap_create (10, GNUNET_NO);
528 }
529
530
531 /**
532  * Stop the nodes management
533  */
534 void
535 GNUNET_EXPERIMENTATION_nodes_stop ()
536 {
537   if (NULL != ch)
538   {
539                 GNUNET_CORE_disconnect (ch);
540                 ch = NULL;
541   }
542
543   if (NULL != nodes_requested)
544   {
545                 GNUNET_CONTAINER_multihashmap_iterate (nodes_requested,
546                                                                                                                                                                          &cleanup_nodes,
547                                                                                                                                                                          nodes_requested);
548                 update_stats (nodes_requested);
549                 GNUNET_CONTAINER_multihashmap_destroy (nodes_requested);
550                 nodes_requested = NULL;
551   }
552
553   if (NULL != nodes_active)
554   {
555                 GNUNET_CONTAINER_multihashmap_iterate (nodes_active,
556                                                                                                                                                                          &cleanup_nodes,
557                                                                                                                                                                          nodes_active);
558                 update_stats (nodes_active);
559                 GNUNET_CONTAINER_multihashmap_destroy (nodes_active);
560                 nodes_active = NULL;
561   }
562
563   if (NULL != nodes_inactive)
564   {
565                 GNUNET_CONTAINER_multihashmap_iterate (nodes_inactive,
566                                                                                                                                                                          &cleanup_nodes,
567                                                                                                                                                                          nodes_inactive);
568                 update_stats (nodes_inactive);
569                 GNUNET_CONTAINER_multihashmap_destroy (nodes_inactive);
570                 nodes_inactive = NULL;
571   }
572 }
573
574 /* end of gnunet-daemon-experimentation_nodes.c */