modifications
[oweals/gnunet.git] / src / experimentation / gnunet-daemon-experimentation.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.c
23  * @brief experimentation daemon
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
33 #define EXP_RESPONSE_TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 2)
34
35 static struct GNUNET_CORE_Handle *ch;
36
37 static struct GNUNET_PeerIdentity me;
38
39 static struct GNUNET_STATISTICS_Handle *stats;
40
41 /**
42  * A experimentation node
43  */
44 struct Node
45 {
46         struct GNUNET_PeerIdentity id;
47
48         GNUNET_SCHEDULER_TaskIdentifier timeout_task;
49
50         struct GNUNET_CORE_TransmitHandle *cth;
51 };
52
53
54 struct Experimentation_Request
55 {
56         struct GNUNET_MessageHeader msg;
57 };
58
59 struct Experimentation_Response
60 {
61         struct GNUNET_MessageHeader msg;
62 };
63
64
65 /**
66  * Nodes with a pending request
67  */
68
69 struct GNUNET_CONTAINER_MultiHashMap *nodes_requested;
70
71 /**
72  * Active experimentation nodes
73  */
74 struct GNUNET_CONTAINER_MultiHashMap *nodes_active;
75
76 /**
77  * Inactive experimentation nodes
78  * To be excluded from future requests
79  */
80 struct GNUNET_CONTAINER_MultiHashMap *nodes_inactive;
81
82
83 static void update_stats (struct GNUNET_CONTAINER_MultiHashMap *m)
84 {
85         GNUNET_assert (NULL != m);
86         GNUNET_assert (NULL != stats);
87
88         if (m == nodes_active)
89         {
90                         GNUNET_STATISTICS_set (stats, "# nodes active",
91                                         GNUNET_CONTAINER_multihashmap_size(m), GNUNET_NO);
92         }
93         else if (m == nodes_inactive)
94         {
95                         GNUNET_STATISTICS_set (stats, "# nodes inactive",
96                                         GNUNET_CONTAINER_multihashmap_size(m), GNUNET_NO);
97         }
98         else if (m == nodes_requested)
99         {
100                         GNUNET_STATISTICS_set (stats, "# nodes requested",
101                                         GNUNET_CONTAINER_multihashmap_size(m), GNUNET_NO);
102         }
103         else
104                 GNUNET_break (0);
105
106 }
107
108 static int
109 cleanup_nodes (void *cls,
110                                                          const struct GNUNET_HashCode * key,
111                                                          void *value)
112 {
113         struct Node *n;
114         struct GNUNET_CONTAINER_MultiHashMap *cur = cls;
115
116         n = value;
117         if (GNUNET_SCHEDULER_NO_TASK != n->timeout_task)
118         {
119                 GNUNET_SCHEDULER_cancel (n->timeout_task);
120                 n->timeout_task = GNUNET_SCHEDULER_NO_TASK;
121         }
122         if (NULL != n->cth)
123         {
124                 GNUNET_CORE_notify_transmit_ready_cancel (n->cth);
125                 n->cth = NULL;
126         }
127
128
129         GNUNET_CONTAINER_multihashmap_remove (cur, key, value);
130         GNUNET_free (value);
131         return GNUNET_OK;
132 }
133
134 /**
135  * Task run during shutdown.
136  *
137  * @param cls unused
138  * @param tc unused
139  */
140 static void
141 cleanup_task (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
142 {
143   GNUNET_log (GNUNET_ERROR_TYPE_INFO, _("Experimentation daemon shutting down ...\n"));
144   if (NULL != ch)
145   {
146                 GNUNET_CORE_disconnect (ch);
147                 ch = NULL;
148   }
149
150   if (NULL != nodes_requested)
151   {
152                 GNUNET_CONTAINER_multihashmap_iterate (nodes_requested,
153                                                                                                                                                                          &cleanup_nodes,
154                                                                                                                                                                          nodes_requested);
155                 update_stats (nodes_requested);
156                 GNUNET_CONTAINER_multihashmap_destroy (nodes_requested);
157                 nodes_requested = NULL;
158   }
159
160   if (NULL != nodes_active)
161   {
162                 GNUNET_CONTAINER_multihashmap_iterate (nodes_active,
163                                                                                                                                                                          &cleanup_nodes,
164                                                                                                                                                                          nodes_active);
165                 update_stats (nodes_active);
166                 GNUNET_CONTAINER_multihashmap_destroy (nodes_active);
167                 nodes_active = NULL;
168   }
169
170   if (NULL != nodes_inactive)
171   {
172                 GNUNET_CONTAINER_multihashmap_iterate (nodes_inactive,
173                                                                                                                                                                          &cleanup_nodes,
174                                                                                                                                                                          nodes_inactive);
175                 update_stats (nodes_inactive);
176                 GNUNET_CONTAINER_multihashmap_destroy (nodes_inactive);
177                 nodes_inactive = NULL;
178   }
179
180   if (NULL != stats)
181   {
182                 GNUNET_STATISTICS_destroy (stats, GNUNET_NO);
183                 stats = NULL;
184   }
185 }
186
187 static int is_me (const struct GNUNET_PeerIdentity *id)
188 {
189         if (0 == memcmp (&me, id, sizeof (me)))
190                 return GNUNET_YES;
191         else
192                 return GNUNET_NO;
193 }
194
195 static void
196 core_startup_handler (void *cls,
197                                                                                         struct GNUNET_CORE_Handle * server,
198                       const struct GNUNET_PeerIdentity *my_identity)
199 {
200         me = *my_identity;
201 }
202
203 static void
204 remove_request (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
205 {
206         struct Node *n = cls;
207
208         GNUNET_log (GNUNET_ERROR_TYPE_INFO, _("Removing request for peer %s due to timeout\n"),
209                         GNUNET_i2s (&n->id));
210
211         if (GNUNET_NO == GNUNET_CONTAINER_multihashmap_contains (nodes_requested, &n->id.hashPubKey))
212                         GNUNET_break (0);
213         else
214         {
215                         GNUNET_CONTAINER_multihashmap_remove (nodes_requested, &n->id.hashPubKey, n);
216                         update_stats (nodes_requested);
217                         GNUNET_CONTAINER_multihashmap_put (nodes_inactive, &n->id.hashPubKey, n,
218                                         GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_FAST);
219                         update_stats (nodes_inactive);
220                         n->timeout_task = GNUNET_SCHEDULER_NO_TASK;
221                         if (NULL != n->cth)
222                         {
223                                 GNUNET_CORE_notify_transmit_ready_cancel (n->cth);
224                                 n->cth = NULL;
225                         }
226         }
227 }
228
229 size_t send_request_cb (void *cls, size_t bufsize, void *buf)
230 {
231         struct Node *n = cls;
232         struct Experimentation_Request msg;
233         size_t size = sizeof (msg);
234
235         n->cth = NULL;
236   if (buf == NULL)
237   {
238     /* client disconnected */
239     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Client disconnected\n");
240     if (GNUNET_SCHEDULER_NO_TASK != n->timeout_task)
241                 GNUNET_SCHEDULER_cancel (n->timeout_task);
242     GNUNET_SCHEDULER_add_now (&remove_request, n);
243     return 0;
244   }
245   GNUNET_assert (bufsize >= size);
246
247         msg.msg.size = htons (size);
248         msg.msg.type = htons (GNUNET_MESSAGE_TYPE_EXPERIMENTATION_REQUEST);
249         memcpy (buf, &msg, size);
250
251         GNUNET_log (GNUNET_ERROR_TYPE_INFO, _("Sending request to peer %s\n"),
252                         GNUNET_i2s (&n->id));
253         return size;
254 }
255
256 static void send_request (const struct GNUNET_PeerIdentity *peer)
257 {
258         struct Node *n;
259         size_t size;
260
261         size = sizeof (struct Experimentation_Request);
262         n = GNUNET_malloc (sizeof (struct Node));
263         n->id = *peer;
264         n->timeout_task = GNUNET_SCHEDULER_add_delayed (EXP_RESPONSE_TIMEOUT, &remove_request, n);
265         n->cth = GNUNET_CORE_notify_transmit_ready(ch, GNUNET_NO, 0,
266                                                                 GNUNET_TIME_relative_get_forever_(),
267                                                                 peer, size, send_request_cb, n);
268
269         GNUNET_assert (GNUNET_OK == GNUNET_CONTAINER_multihashmap_put (nodes_requested,
270                         &peer->hashPubKey, n, GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_FAST));
271
272         update_stats (nodes_requested);
273 }
274
275 size_t send_response_cb (void *cls, size_t bufsize, void *buf)
276 {
277         struct Node *n = cls;
278         struct Experimentation_Response msg;
279         size_t size = sizeof (msg);
280
281         n->cth = NULL;
282   if (buf == NULL)
283   {
284     /* client disconnected */
285     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Client disconnected\n");
286     return 0;
287   }
288   GNUNET_assert (bufsize >= size);
289
290         msg.msg.size = htons (size);
291         msg.msg.type = htons (GNUNET_MESSAGE_TYPE_EXPERIMENTATION_RESPONSE);
292         memcpy (buf, &msg, size);
293
294         GNUNET_log (GNUNET_ERROR_TYPE_INFO, _("Sending response to peer %s\n"),
295                         GNUNET_i2s (&n->id));
296         return size;
297 }
298
299
300 static void handle_request (const struct GNUNET_PeerIdentity *peer)
301 {
302         struct Node *n;
303
304         if (NULL != (n = GNUNET_CONTAINER_multihashmap_get (nodes_active, &peer->hashPubKey)))
305         {
306                         GNUNET_log (GNUNET_ERROR_TYPE_INFO, _("Received %s from %s peer `%s'\n"),
307                                         "REQUEST", "active", GNUNET_i2s (peer));
308         }
309         else if (NULL != (n = GNUNET_CONTAINER_multihashmap_get (nodes_requested, &peer->hashPubKey)))
310         {
311                         GNUNET_log (GNUNET_ERROR_TYPE_INFO, _("Received %s from %s peer `%s'\n"),
312                                         "REQUEST", "requested", GNUNET_i2s (peer));
313                         GNUNET_CONTAINER_multihashmap_remove (nodes_requested, &peer->hashPubKey, n);
314                         if (GNUNET_SCHEDULER_NO_TASK != n->timeout_task)
315                         {
316                                 GNUNET_SCHEDULER_cancel (n->timeout_task);
317                                 n->timeout_task = GNUNET_SCHEDULER_NO_TASK;
318                         }
319                         if (NULL != n->cth)
320                         {
321                                 GNUNET_CORE_notify_transmit_ready_cancel (n->cth);
322                                 n->cth = NULL;
323                         }
324                         update_stats (nodes_requested);
325                   GNUNET_CONTAINER_multihashmap_put (nodes_active,
326                                         &peer->hashPubKey, n, GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_FAST);
327                         update_stats (nodes_active);
328                         GNUNET_log (GNUNET_ERROR_TYPE_INFO, _("Added peer `%s' active node \n"),
329                                         GNUNET_i2s (peer));
330         }
331         else if (NULL != (n = GNUNET_CONTAINER_multihashmap_get (nodes_inactive, &peer->hashPubKey)))
332         {
333                         GNUNET_log (GNUNET_ERROR_TYPE_INFO, _("Received %s from %s peer `%s'\n"),
334                                         "REQUEST", "inactive", GNUNET_i2s (peer));
335                         GNUNET_CONTAINER_multihashmap_remove (nodes_inactive, &peer->hashPubKey, n);
336                         update_stats (nodes_inactive);
337                   GNUNET_CONTAINER_multihashmap_put (nodes_active,
338                                         &peer->hashPubKey, n, GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_FAST);
339                         update_stats (nodes_active);
340                         GNUNET_log (GNUNET_ERROR_TYPE_INFO, _("Added peer `%s' active node \n"),
341                                         GNUNET_i2s (peer));
342         }
343         else
344         {
345                         /* Create new node */
346                         n = GNUNET_malloc (sizeof (struct Node));
347                         n->id = *peer;
348                         GNUNET_log (GNUNET_ERROR_TYPE_INFO, _("Received %s from %s peer `%s'\n"),
349                                         "REQUEST", "new", GNUNET_i2s (peer));
350                   GNUNET_CONTAINER_multihashmap_put (nodes_active,
351                                         &peer->hashPubKey, n, GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_FAST);
352                         update_stats (nodes_active);
353                         GNUNET_log (GNUNET_ERROR_TYPE_INFO, _("Added peer `%s' active node \n"),
354                                         GNUNET_i2s (peer));
355         }
356
357         n->cth = GNUNET_CORE_notify_transmit_ready(ch, GNUNET_NO, 0,
358                                                                 GNUNET_TIME_relative_get_forever_(),
359                                                                 peer, sizeof (struct Experimentation_Response),
360                                                                 send_response_cb, n);
361 }
362
363 static void handle_response (const struct GNUNET_PeerIdentity *peer)
364 {
365         struct Node *n;
366
367         if (NULL != (n = GNUNET_CONTAINER_multihashmap_get (nodes_active, &peer->hashPubKey)))
368         {
369                         GNUNET_log (GNUNET_ERROR_TYPE_INFO, _("Received %s from %s peer `%s'\n"),
370                                         "RESPONSE", "active", GNUNET_i2s (peer));
371         }
372         else if (NULL != (n = GNUNET_CONTAINER_multihashmap_get (nodes_requested, &peer->hashPubKey)))
373         {
374                         GNUNET_log (GNUNET_ERROR_TYPE_INFO, _("Received %s from %s peer `%s'\n"),
375                                         "RESPONSE", "requested", GNUNET_i2s (peer));
376                         GNUNET_CONTAINER_multihashmap_remove (nodes_requested, &peer->hashPubKey, n);
377                         if (GNUNET_SCHEDULER_NO_TASK != n->timeout_task)
378                         {
379                                 GNUNET_SCHEDULER_cancel (n->timeout_task);
380                                 n->timeout_task = GNUNET_SCHEDULER_NO_TASK;
381                         }
382                         if (NULL != n->cth)
383                         {
384                                 GNUNET_CORE_notify_transmit_ready_cancel (n->cth);
385                                 n->cth = NULL;
386                         }
387                         update_stats (nodes_requested);
388                   GNUNET_CONTAINER_multihashmap_put (nodes_active,
389                                         &peer->hashPubKey, n, GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_FAST);
390                         update_stats (nodes_active);
391                         GNUNET_log (GNUNET_ERROR_TYPE_INFO, _("Added peer `%s' active node \n"),
392                                         GNUNET_i2s (peer));
393         }
394         else if (NULL != (n = GNUNET_CONTAINER_multihashmap_get (nodes_inactive, &peer->hashPubKey)))
395         {
396                         GNUNET_log (GNUNET_ERROR_TYPE_INFO, _("Received %s from peer `%s'\n"),
397                                         "RESPONSE", "inactive", GNUNET_i2s (peer));
398                         GNUNET_CONTAINER_multihashmap_remove (nodes_inactive, &peer->hashPubKey, n);
399                         update_stats (nodes_inactive);
400                   GNUNET_CONTAINER_multihashmap_put (nodes_active,
401                                         &peer->hashPubKey, n, GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_FAST);
402                         update_stats (nodes_active);
403                         GNUNET_log (GNUNET_ERROR_TYPE_INFO, _("Added peer `%s' active node \n"),
404                                         GNUNET_i2s (peer));
405         }
406         else
407         {
408                         GNUNET_log (GNUNET_ERROR_TYPE_INFO, _("Received %s from %s peer `%s'\n"),
409                                         "RESPONSE", "unknown", GNUNET_i2s (peer));
410         }
411 }
412
413 /**
414  * Method called whenever a given peer connects.
415  *
416  * @param cls closure
417  * @param peer peer identity this notification is about
418  */
419 void core_connect_handler (void *cls,
420                            const struct GNUNET_PeerIdentity *peer)
421 {
422         if (GNUNET_YES == is_me(peer))
423                 return;
424
425         GNUNET_log (GNUNET_ERROR_TYPE_INFO, _("Connected to peer %s\n"),
426                         GNUNET_i2s (peer));
427
428         if (GNUNET_YES == GNUNET_CONTAINER_multihashmap_contains (nodes_requested, &peer->hashPubKey))
429                 return; /* We already sent a request */
430
431         if (GNUNET_YES == GNUNET_CONTAINER_multihashmap_contains (nodes_active, &peer->hashPubKey))
432                 return; /* This peer is known as active  */
433
434         if (GNUNET_YES == GNUNET_CONTAINER_multihashmap_contains (nodes_inactive, &peer->hashPubKey))
435                 return; /* This peer is known as inactive  */
436
437         send_request (peer);
438
439 }
440
441
442 /**
443  * Method called whenever a given peer disconnects.
444  *
445  * @param cls closure
446  * @param peer peer identity this notification is about
447  */
448 void core_disconnect_handler (void *cls,
449                            const struct GNUNET_PeerIdentity * peer)
450 {
451         if (GNUNET_YES == is_me(peer))
452                 return;
453
454         GNUNET_log (GNUNET_ERROR_TYPE_INFO, _("Disconnected from peer %s\n"),
455                         GNUNET_i2s (peer));
456
457 }
458
459
460 static int
461 core_receive_handler (void *cls,
462                                                                                         const struct GNUNET_PeerIdentity *other,
463                                                                                         const struct GNUNET_MessageHeader *message)
464 {
465         if (ntohs (message->size) < sizeof (struct GNUNET_MessageHeader))
466         {
467                         GNUNET_break (0);
468                         return GNUNET_SYSERR;
469         }
470
471         switch (ntohs (message->type)) {
472                 case GNUNET_MESSAGE_TYPE_EXPERIMENTATION_REQUEST:
473                         handle_request (other);
474                         break;
475                 case GNUNET_MESSAGE_TYPE_EXPERIMENTATION_RESPONSE:
476                         handle_response (other);
477                         break;
478                 default:
479                         break;
480         }
481
482         return GNUNET_OK;
483 }
484
485
486
487 /**
488  * The main function for the experimentation daemon.
489  *
490  * @param argc number of arguments from the command line
491  * @param argv command line arguments
492  */
493 static void
494 run (void *cls, char *const *args, const char *cfgfile,
495      const struct GNUNET_CONFIGURATION_Handle *cfg)
496 {
497         GNUNET_log (GNUNET_ERROR_TYPE_INFO, _("Experimentation daemon starting ...\n"));
498
499         stats = GNUNET_STATISTICS_create ("experimentation", cfg);
500         if (NULL == stats)
501         {
502                 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, _("Failed to create statistics!\n"));
503                 return;
504         }
505
506         /* Connecting to core service to find partners */
507         ch = GNUNET_CORE_connect (cfg, NULL,
508                                                                                                                 &core_startup_handler,
509                                                                                                                 &core_connect_handler,
510                                                                                                                 &core_disconnect_handler,
511                                                                                                                 &core_receive_handler,
512                                                                                                                 GNUNET_NO, NULL, GNUNET_NO, NULL);
513         if (NULL == ch)
514         {
515                         GNUNET_log (GNUNET_ERROR_TYPE_INFO, _("Failed to connect to CORE service!\n"));
516                         return;
517         }
518
519         nodes_requested = GNUNET_CONTAINER_multihashmap_create (10, GNUNET_NO);
520         nodes_active = GNUNET_CONTAINER_multihashmap_create (10, GNUNET_NO);
521         nodes_inactive = GNUNET_CONTAINER_multihashmap_create (10, GNUNET_NO);
522
523   GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_FOREVER_REL, &cleanup_task,
524                                 NULL);
525
526 }
527
528
529 /**
530  * The main function for the experimentation daemon.
531  *
532  * @param argc number of arguments from the command line
533  * @param argv command line arguments
534  * @return 0 ok, 1 on error
535  */
536 int
537 main (int argc, char *const *argv)
538 {
539   static const struct GNUNET_GETOPT_CommandLineOption options[] = {
540     GNUNET_GETOPT_OPTION_END
541   };
542
543   return (GNUNET_OK ==
544           GNUNET_PROGRAM_run (argc, argv, "experimentation",
545                                                                                         _("GNUnet hostlist server and client"), options,
546                               &run, NULL)) ? 0 : 1;
547 }
548
549 /* end of gnunet-daemon-experimentation.c */