changes
[oweals/gnunet.git] / src / peerinfo / gnunet-service-peerinfo.c
1 /*
2      This file is part of GNUnet.
3      (C) 2001, 2002, 2004, 2005, 2007, 2009, 2010, 2012 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 peerinfo/gnunet-service-peerinfo.c
23  * @brief maintains list of known peers
24  *
25  * Code to maintain the list of currently known hosts (in memory
26  * structure of data/hosts/).
27  *
28  * @author Christian Grothoff
29  *
30  * TODO:
31  * - notify clients when addresses in HELLO expire (#1933)
32  */
33
34 #include "platform.h"
35 #include "gnunet_util_lib.h"
36 #include "gnunet_hello_lib.h"
37 #include "gnunet_protocols.h"
38 #include "gnunet_statistics_service.h"
39 #include "peerinfo.h"
40
41 /**
42  * How often do we scan the HOST_DIR for new entries?
43  */
44 #define DATA_HOST_FREQ GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES, 15)
45
46 /**
47  * How often do we discard old entries in data/hosts/?
48  */
49 #define DATA_HOST_CLEAN_FREQ GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES, 60)
50
51
52 /**
53  * In-memory cache of known hosts.
54  */
55 struct HostEntry
56 {
57
58   /**
59    * Identity of the peer.
60    */
61   struct GNUNET_PeerIdentity identity;
62
63   /**
64    * Hello for the peer (can be NULL)
65    */
66   struct GNUNET_HELLO_Message *hello;
67
68   /**
69    * Friend only hello for the peer (can be NULL)
70    */
71   struct GNUNET_HELLO_Message *friend_only_hello;
72
73 };
74
75 /**
76  * Transmit context for GET requests
77  */
78 struct TransmitContext
79 {
80   /**
81    * Server transmit context
82    */
83   struct GNUNET_SERVER_TransmitContext *tc;
84   
85   /**
86    * Include friend only HELLOs GNUNET_YES or _NO
87    */
88   int friend_only;
89 };
90
91 /**
92  * Result of reading a file
93  */
94 struct ReadHostFileContext
95 {
96   /**
97    * Hello for the peer (can be NULL)
98    */
99   struct GNUNET_HELLO_Message *hello;
100
101   /**
102    * Friend only hello for the peer (can be NULL)
103    */
104   struct GNUNET_HELLO_Message *friend_only_hello;
105 };
106
107
108 /**
109  * Client notification context
110  */
111 struct NotificationContext
112 {
113         /**
114          * Next in DLL
115          */
116         struct NotificationContext *prev;
117
118         /**
119          * Previous in DLL
120          */
121         struct NotificationContext *next;
122
123         /**
124          * Server client
125          */
126         struct GNUNET_SERVER_Client *client;
127
128         /**
129          * Interested in friend only HELLO?
130          */
131         int include_friend_only;
132 };
133
134
135 /**
136  * The in-memory list of known hosts, mapping of
137  * host IDs to 'struct HostEntry*' values.
138  */
139 static struct GNUNET_CONTAINER_MultiHashMap *hostmap;
140
141 /**
142  * Clients to immediately notify about all changes.
143  */
144 static struct GNUNET_SERVER_NotificationContext *notify_list;
145
146 /**
147  * Directory where the hellos are stored in (peerinfo/)
148  */
149 static char *networkIdDirectory;
150
151 /**
152  * Handle for reporting statistics.
153  */
154 static struct GNUNET_STATISTICS_Handle *stats;
155
156 /**
157  * DLL of notification contexts: head
158  */
159 static struct NotificationContext *nc_head;
160
161 /**
162  * DLL of notification contexts: tail
163  */
164 static struct NotificationContext *nc_tail;
165
166
167 /**
168  * Notify all clients in the notify list about the
169  * given host entry changing.
170  *
171  * @param he entry of the host for which we generate a notification
172  * @param include_friend_only create public of friend-only message
173  * @return generated notification message
174  */
175 static struct InfoMessage *
176 make_info_message (const struct HostEntry *he, int include_friend_only)
177 {
178   struct InfoMessage *im;
179   struct GNUNET_HELLO_Message *src;
180   size_t hs;
181
182   if (GNUNET_YES == include_friend_only)
183         src = he->friend_only_hello;
184   else
185         src = he->hello;
186
187   hs = (NULL == src) ? 0 : GNUNET_HELLO_size (src);
188   im = GNUNET_malloc (sizeof (struct InfoMessage) + hs);
189   im->header.size = htons (hs + sizeof (struct InfoMessage));
190   im->header.type = htons (GNUNET_MESSAGE_TYPE_PEERINFO_INFO);
191   im->peer = he->identity;
192   if (NULL != src)
193     memcpy (&im[1], src, hs);
194   return im;
195 }
196
197
198 /**
199  * Address iterator that causes expired entries to be discarded.
200  *
201  * @param cls pointer to the current time
202  * @param address the address
203  * @param expiration expiration time for the address
204  * @return GNUNET_NO if expiration smaller than the current time
205  */
206 static int
207 discard_expired (void *cls, const struct GNUNET_HELLO_Address *address,
208                  struct GNUNET_TIME_Absolute expiration)
209 {
210   const struct GNUNET_TIME_Absolute *now = cls;
211
212   if (now->abs_value > expiration.abs_value)
213   {
214     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
215                 _("Removing expired address of transport `%s'\n"),
216                 address->transport_name);
217     return GNUNET_NO;
218   }
219   return GNUNET_OK;
220 }
221
222
223 /**
224  * Address iterator that counts the remaining addresses.
225  *
226  * @param cls pointer to the counter
227  * @param address the address
228  * @param expiration expiration time for the address
229  * @return GNUNET_OK (always)
230  */
231 static int
232 count_addresses (void *cls, const struct GNUNET_HELLO_Address *address,
233                  struct GNUNET_TIME_Absolute expiration)
234 {
235   unsigned int *cnt = cls;
236
237   (*cnt)++;
238   return GNUNET_OK;
239 }
240
241
242 /**
243  * Get the filename under which we would store the GNUNET_HELLO_Message
244  * for the given host and protocol.
245  *
246  * @param id peer for which we need the filename for the HELLO
247  * @return filename of the form DIRECTORY/HOSTID
248  */
249 static char *
250 get_host_filename (const struct GNUNET_PeerIdentity *id)
251 {
252   struct GNUNET_CRYPTO_HashAsciiEncoded fil;
253   char *fn;
254
255   if (NULL == networkIdDirectory)
256     return NULL;
257   GNUNET_CRYPTO_hash_to_enc (&id->hashPubKey, &fil);
258   GNUNET_asprintf (&fn, "%s%s%s", networkIdDirectory, DIR_SEPARATOR_STR, &fil);
259   return fn;
260 }
261
262
263 /**
264  * Broadcast information about the given entry to all
265  * clients that care.
266  *
267  * @param entry entry to broadcast about
268  */
269 static void
270 notify_all (struct HostEntry *entry)
271 {
272   struct InfoMessage *msg_pub;
273   struct InfoMessage *msg_friend;
274   struct NotificationContext *cur;
275
276   msg_pub = make_info_message (entry, GNUNET_NO);
277   msg_friend = make_info_message (entry, GNUNET_YES);
278   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
279               "Notifying all clients about peer `%s'\n",
280               GNUNET_i2s(&entry->identity));
281   for (cur = nc_head; NULL != cur; cur = cur->next)
282   {
283     if (GNUNET_NO == cur->include_friend_only)
284       {
285         GNUNET_SERVER_notification_context_unicast (notify_list,
286                                                     cur->client,
287                                                     &msg_pub->header,
288                                                     GNUNET_NO);
289       }
290     if (GNUNET_YES == cur->include_friend_only)
291     {
292       GNUNET_SERVER_notification_context_unicast (notify_list,
293                                                   cur->client,
294                                                   &msg_friend->header,
295                                                   GNUNET_NO);
296     }
297   }
298   GNUNET_free (msg_pub);
299   GNUNET_free (msg_friend);
300 }
301
302
303 /**
304  * Bind a host address (hello) to a hostId.
305  *
306  * @param peer the peer for which this is a hello
307  * @param hello the verified (!) hello message
308  */
309 static void
310 update_hello (const struct GNUNET_PeerIdentity *peer,
311               const struct GNUNET_HELLO_Message *hello);
312
313
314 /**
315  * Try to read the HELLOs in the given filename and discard expired
316  * addresses.  Removes the file if one the HELLO is mal-formed.  If all
317  * addresses are expired, the HELLO is also removed (but the HELLO
318  * with the public key is still returned if it was found and valid).
319  * 
320  * The file can contain multiple HELLO messages, but onlu a public and a friend only
321  * HELLO should be included
322  *
323  * @param fn name of the file
324  * @param unlink_garbage if GNUNET_YES, try to remove useless files
325  * @param r ReadHostFileContext to store the resutl
326  */
327 static void
328 read_host_file (const char *fn, int unlink_garbage, struct ReadHostFileContext *r)
329 {
330   char buffer[GNUNET_SERVER_MAX_MESSAGE_SIZE - 1] GNUNET_ALIGN;
331
332   int size_total;
333   struct GNUNET_TIME_Absolute now;
334   unsigned int left;
335
336   const struct GNUNET_HELLO_Message *hello;
337   struct GNUNET_HELLO_Message *hello_clean;
338   unsigned read_pos;
339   int size_hello;
340
341   size_total = 0;
342   r->friend_only_hello = NULL;
343   r->hello = NULL;
344
345   if (GNUNET_YES != GNUNET_DISK_file_test (fn))
346   {
347     return;
348   }
349
350   size_total = GNUNET_DISK_fn_read (fn, buffer, sizeof (buffer));
351   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Read %u bytes from `%s'\n", size_total, fn);
352   if (size_total < sizeof (struct GNUNET_MessageHeader))
353   {
354     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
355                 _("Failed to parse HELLO in file `%s': %s\n"),
356                 fn, "Fail has invalid size");
357     if ( (GNUNET_YES == unlink_garbage) && 
358          (0 != UNLINK (fn)) &&
359          (ENOENT != errno) )
360       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "unlink", fn);
361     return;
362   }
363
364   read_pos = 0;
365   while (read_pos < size_total)
366   {
367     hello = (const struct GNUNET_HELLO_Message *) &buffer[read_pos];
368     size_hello = GNUNET_HELLO_size (hello);
369     if (0 == size_hello)
370       {
371         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
372                     _("Failed to parse HELLO in file `%s': %s %u \n"),
373                     fn, "HELLO is invalid and has size of ", size_hello);
374         if ((GNUNET_YES == unlink_garbage) && 
375             (0 != UNLINK (fn)) &&
376             (ENOENT != errno) )
377           GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "unlink", fn);
378         return;
379       }
380     
381     now = GNUNET_TIME_absolute_get ();
382     hello_clean = GNUNET_HELLO_iterate_addresses (hello, GNUNET_YES,
383                                                   &discard_expired, &now);
384     left = 0;
385     (void) GNUNET_HELLO_iterate_addresses (hello_clean, GNUNET_NO,
386                                            &count_addresses, &left);
387     
388     if (0 == left)
389     {
390       GNUNET_free (hello_clean);
391       break;
392     }
393     
394     if (GNUNET_NO == GNUNET_HELLO_is_friend_only (hello_clean))
395     {
396       if (NULL == r->hello)
397         r->hello = hello_clean;
398       else
399       {
400         GNUNET_break (0);
401         GNUNET_free (r->hello);
402         r->hello = hello_clean;
403       }
404     }
405     else
406     {
407       if (NULL == r->friend_only_hello)
408         r->friend_only_hello = hello_clean;
409       else
410       {
411         GNUNET_break (0);
412         GNUNET_free (r->friend_only_hello);
413         r->friend_only_hello = hello_clean;
414       }
415     }
416     read_pos += size_hello;
417   }
418
419   if (0 == left)
420   {
421     /* no addresses left, remove from disk */
422     if ((GNUNET_YES == unlink_garbage) && (0 != UNLINK (fn)))
423       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "unlink", fn);
424   }
425
426   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
427               "Found `%s' and `%s' HELLO message in file\n",
428               (NULL != r->hello) ? "public" : "NO public",
429               (NULL != r->friend_only_hello) ? "friend only" : "NO friend only");
430 }
431
432
433 /**
434  * Add a host to the list and notify clients about this event
435  *
436  * @param identity the identity of the host
437  * @return the HostEntry
438  */
439 static struct HostEntry *
440 add_host_to_known_hosts (const struct GNUNET_PeerIdentity *identity)
441 {
442   struct HostEntry *entry;
443   struct ReadHostFileContext r;
444   char *fn;
445
446   entry = GNUNET_CONTAINER_multihashmap_get (hostmap, &identity->hashPubKey);
447   if (NULL == entry)
448   {
449     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Adding new peer `%s'\n", GNUNET_i2s (identity));
450     GNUNET_STATISTICS_update (stats, gettext_noop ("# peers known"), 1,
451                               GNUNET_NO);
452     entry = GNUNET_malloc (sizeof (struct HostEntry));
453     entry->identity = *identity;
454     GNUNET_CONTAINER_multihashmap_put (hostmap, &entry->identity.hashPubKey, entry,
455                                        GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY);
456     notify_all (entry);
457     fn = get_host_filename (identity);
458     if (NULL != fn)
459     {
460       read_host_file (fn, GNUNET_YES, &r);
461       if (NULL != r.hello)
462         update_hello (identity, r.hello);
463       if (NULL != r.friend_only_hello)
464         update_hello (identity, r.friend_only_hello);
465       GNUNET_free_non_null (r.hello);
466       GNUNET_free_non_null (r.friend_only_hello);
467       GNUNET_free (fn);
468     }
469   }
470   return entry;
471 }
472
473
474 /**
475  * Remove a file that should not be there.  LOG
476  * success or failure.
477  *
478  * @param fullname name of the file to remove
479  */
480 static void
481 remove_garbage (const char *fullname)
482 {
483   if (0 == UNLINK (fullname))
484     GNUNET_log (GNUNET_ERROR_TYPE_WARNING | GNUNET_ERROR_TYPE_BULK,
485                 _
486                 ("File `%s' in directory `%s' does not match naming convention. "
487                  "Removed.\n"), fullname, networkIdDirectory);
488   else
489     GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
490                               "unlink", fullname);
491 }
492
493
494 /**
495  * Closure for 'hosts_directory_scan_callback'.
496  */
497 struct DirScanContext
498 {
499   /**
500    * GNUNET_YES if we should remove files that are broken,
501    * GNUNET_NO if the directory we are iterating over should
502    * be treated as read-only by us.
503    */ 
504   int remove_files;
505
506   /**
507    * Counter for the number of (valid) entries found, incremented
508    * by one for each match.
509    */
510   unsigned int matched;
511 };
512
513
514 /**
515  * Function that is called on each HELLO file in a particular directory.
516  * Try to parse the file and add the HELLO to our list.
517  *
518  * @param cls pointer to 'unsigned int' to increment for each file, or NULL
519  *            if the file is from a read-only, read-once resource directory
520  * @param fullname name of the file to parse
521  * @return GNUNET_OK (continue iteration)
522  */
523 static int
524 hosts_directory_scan_callback (void *cls, const char *fullname)
525 {
526   struct DirScanContext *dsc = cls;
527   struct GNUNET_PeerIdentity identity;
528   struct ReadHostFileContext r;
529   const char *filename;
530   struct GNUNET_PeerIdentity id_public;
531   struct GNUNET_PeerIdentity id_friend;
532   struct GNUNET_PeerIdentity id;
533
534   if (GNUNET_YES != GNUNET_DISK_file_test (fullname))
535     return GNUNET_OK;           /* ignore non-files */
536
537   filename = strrchr (fullname, DIR_SEPARATOR);
538   if ((NULL == filename) || (1 > strlen (filename)))
539         filename = fullname;
540   else
541     filename ++;
542
543   read_host_file (fullname, dsc->remove_files, &r);
544   if ( (NULL == r.hello) && (NULL == r.friend_only_hello))
545   {
546     if (GNUNET_YES == dsc->remove_files)
547       remove_garbage (fullname);
548     return GNUNET_OK;
549   }
550
551   if (NULL != r.friend_only_hello)
552   {
553     if (GNUNET_OK != GNUNET_HELLO_get_id (r.friend_only_hello, &id_friend))
554       if (GNUNET_YES == dsc->remove_files)
555       {
556         remove_garbage (fullname);
557         return GNUNET_OK;
558       }
559     id = id_friend;
560   }
561   if (NULL != r.hello)
562   {
563     if (GNUNET_OK != GNUNET_HELLO_get_id (r.hello, &id_public))
564       if (GNUNET_YES == dsc->remove_files)
565       {
566         remove_garbage (fullname);
567         return GNUNET_OK;
568       }
569     id = id_public;
570   }
571   
572   if ( (NULL != r.hello) && (NULL != r.friend_only_hello) &&
573        (0 != memcmp (&id_friend, &id_public, sizeof (id_friend))) )
574   {
575     /* HELLOs are not for the same peer */
576     GNUNET_break (0);
577     if (GNUNET_YES == dsc->remove_files)
578       remove_garbage (fullname);
579     return GNUNET_OK;
580   }
581   if (GNUNET_OK == GNUNET_CRYPTO_hash_from_string (filename, &identity.hashPubKey))
582   {
583     if (0 != memcmp (&id, &identity, sizeof (id_friend)))
584     {
585       /* HELLOs are not for the same peer */
586       GNUNET_break (0);
587       if (GNUNET_YES == dsc->remove_files)
588         remove_garbage (fullname);
589       return GNUNET_OK;
590     }
591   }
592
593   /* ok, found something valid, remember HELLO */
594   add_host_to_known_hosts (&id);
595   if (NULL != r.hello)
596   {
597     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Updating peer `%s' public HELLO \n",
598                 GNUNET_i2s (&id));
599     update_hello (&id, r.hello);
600     GNUNET_free (r.hello);
601   }
602   if (NULL != r.friend_only_hello)
603   {
604     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Updating peer `%s' friend only HELLO \n",
605                 GNUNET_i2s (&id));
606     update_hello (&id, r.friend_only_hello);
607     GNUNET_free (r.friend_only_hello);
608   }
609   dsc->matched++;
610   return GNUNET_OK;
611 }
612
613
614 /**
615  * Call this method periodically to scan data/hosts for new hosts.
616  *
617  * @param cls unused
618  * @param tc scheduler context, aborted if reason is shutdown
619  */
620 static void
621 cron_scan_directory_data_hosts (void *cls,
622                                 const struct GNUNET_SCHEDULER_TaskContext *tc)
623 {
624   static unsigned int retries;
625   struct DirScanContext dsc;
626
627   if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN))
628     return;
629   if (GNUNET_SYSERR == GNUNET_DISK_directory_create (networkIdDirectory))
630   {
631     GNUNET_SCHEDULER_add_delayed_with_priority (DATA_HOST_FREQ,
632                                                 GNUNET_SCHEDULER_PRIORITY_IDLE,
633                                                 &cron_scan_directory_data_hosts, NULL);
634     return;
635   }
636   dsc.matched = 0;
637   dsc.remove_files = GNUNET_YES;
638   GNUNET_log (GNUNET_ERROR_TYPE_INFO | GNUNET_ERROR_TYPE_BULK,
639               _("Scanning directory `%s'\n"), networkIdDirectory);
640   GNUNET_DISK_directory_scan (networkIdDirectory,
641                               &hosts_directory_scan_callback, &dsc);
642   if ((0 == dsc.matched) && (0 == (++retries & 31)))
643     GNUNET_log (GNUNET_ERROR_TYPE_WARNING | GNUNET_ERROR_TYPE_BULK,
644                 _("Still no peers found in `%s'!\n"), networkIdDirectory);
645   GNUNET_SCHEDULER_add_delayed_with_priority (DATA_HOST_FREQ, 
646                                               GNUNET_SCHEDULER_PRIORITY_IDLE,
647                                               &cron_scan_directory_data_hosts,
648                                               NULL);
649 }
650
651
652 static struct GNUNET_HELLO_Message *
653 update_friend_hello (const struct GNUNET_HELLO_Message *hello,
654                      const struct GNUNET_HELLO_Message *friend_hello)
655 {
656   struct GNUNET_HELLO_Message * res;
657   struct GNUNET_HELLO_Message * tmp;
658   struct GNUNET_CRYPTO_EccPublicKeyBinaryEncoded pk;
659   
660   if (NULL != friend_hello)
661   {
662     res = GNUNET_HELLO_merge (hello, friend_hello);
663     GNUNET_assert (GNUNET_YES == GNUNET_HELLO_is_friend_only (res));
664     return res;
665   }
666   
667   GNUNET_HELLO_get_key (hello, &pk);
668   tmp = GNUNET_HELLO_create (&pk, NULL, NULL, GNUNET_YES);
669   res = GNUNET_HELLO_merge (hello, tmp);
670   GNUNET_free (tmp);
671   GNUNET_assert (GNUNET_YES == GNUNET_HELLO_is_friend_only (res));
672   return res;
673 }
674
675
676 /**
677  * Bind a host address (hello) to a hostId.
678  *
679  * @param peer the peer for which this is a hello
680  * @param hello the verified (!) hello message
681  */
682 static void
683 update_hello (const struct GNUNET_PeerIdentity *peer,
684               const struct GNUNET_HELLO_Message *hello)
685 {
686   char *fn;
687   struct HostEntry *host;
688   struct GNUNET_HELLO_Message *mrg;
689   struct GNUNET_HELLO_Message **dest;
690   struct GNUNET_TIME_Absolute delta;
691   unsigned int cnt;
692   unsigned int size;
693   int friend_hello_type;
694   int store_hello;
695   int store_friend_hello;
696   int pos;
697   char *buffer;
698
699   host = GNUNET_CONTAINER_multihashmap_get (hostmap, &peer->hashPubKey);
700   GNUNET_assert (NULL != host);
701
702   friend_hello_type = GNUNET_HELLO_is_friend_only (hello);
703         GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Updating %s HELLO for `%s'\n",
704                         (GNUNET_YES == friend_hello_type) ? "friend-only" : "public",
705                         GNUNET_i2s (peer));
706
707   dest = NULL;
708   if (GNUNET_YES == friend_hello_type)
709   {
710     dest = &host->friend_only_hello;
711   }
712   else
713   {
714     dest = &host->hello;
715   }
716
717   if (NULL == (*dest))
718   {
719     (*dest) = GNUNET_malloc (GNUNET_HELLO_size (hello));
720     memcpy ((*dest), hello, GNUNET_HELLO_size (hello));
721   }
722   else
723   {
724     mrg = GNUNET_HELLO_merge ((*dest), hello);
725     delta = GNUNET_HELLO_equals (mrg, (*dest), GNUNET_TIME_absolute_get ());
726     if (delta.abs_value == GNUNET_TIME_UNIT_FOREVER_ABS.abs_value)
727     {
728       /* no differences, just ignore the update */
729         GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "No change in %s HELLO for `%s'\n",
730                         (GNUNET_YES == friend_hello_type) ? "friend-only" : "public",
731                         GNUNET_i2s (peer));
732       GNUNET_free (mrg);
733       return;
734     }
735     GNUNET_free ((*dest));
736     (*dest) = mrg;
737   }
738
739   if ((NULL != (host->hello)) && (GNUNET_NO == friend_hello_type))
740   {
741     /* Update friend only hello */
742     mrg = update_friend_hello (host->hello, host->friend_only_hello);
743     if (NULL != host->friend_only_hello)
744       GNUNET_free (host->friend_only_hello);
745     host->friend_only_hello = mrg;
746   }
747
748   if (NULL != host->hello)
749     GNUNET_assert ((GNUNET_NO == GNUNET_HELLO_is_friend_only (host->hello)));
750   if (NULL != host->friend_only_hello)
751     GNUNET_assert ((GNUNET_YES == GNUNET_HELLO_is_friend_only(host->friend_only_hello)));
752
753   store_hello = GNUNET_NO;
754   store_friend_hello = GNUNET_NO;
755   fn = get_host_filename (peer);
756   if ( (NULL != fn) &&
757        (GNUNET_OK == GNUNET_DISK_directory_create_for_file (fn)) )
758   {
759     store_hello = GNUNET_NO;
760     size = 0;
761     cnt = 0;
762     if (NULL != host->hello)
763       (void) GNUNET_HELLO_iterate_addresses (host->hello,
764                                              GNUNET_NO, &count_addresses, &cnt);
765     if (cnt > 0)
766     {
767       store_hello = GNUNET_YES;
768       size += GNUNET_HELLO_size (host->hello);
769     }
770     cnt = 0;
771     if (NULL != host->friend_only_hello)
772       (void) GNUNET_HELLO_iterate_addresses (host->friend_only_hello, GNUNET_NO,
773                                              &count_addresses, &cnt);
774     if (0 < cnt)
775     {
776       store_friend_hello = GNUNET_YES;
777       size += GNUNET_HELLO_size (host->friend_only_hello);
778     }
779     
780     if ((GNUNET_NO == store_hello) && (GNUNET_NO == store_friend_hello))
781     {
782       /* no valid addresses, don't put HELLO on disk; in fact,
783          if one exists on disk, remove it */
784       (void) UNLINK (fn);
785     }
786     else
787     {
788       buffer = GNUNET_malloc (size);
789       pos = 0;
790       
791       if (GNUNET_YES == store_hello)
792       {
793         memcpy (buffer, host->hello, GNUNET_HELLO_size (host->hello));
794         pos += GNUNET_HELLO_size (host->hello);
795       }
796       if (GNUNET_YES == store_friend_hello)
797       {
798         memcpy (&buffer[pos], host->friend_only_hello, GNUNET_HELLO_size (host->friend_only_hello));
799         pos += GNUNET_HELLO_size (host->friend_only_hello);
800       }
801       GNUNET_assert (pos == size);
802       
803       if (GNUNET_SYSERR == GNUNET_DISK_fn_write (fn, buffer, size,
804                                                  GNUNET_DISK_PERM_USER_READ |
805                                                  GNUNET_DISK_PERM_USER_WRITE |
806                                                  GNUNET_DISK_PERM_GROUP_READ |
807                                                  GNUNET_DISK_PERM_OTHER_READ))
808         GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "write", fn);
809       else
810         GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Stored %s %s HELLO in %s  with total size %u\n",
811                     (GNUNET_YES == store_friend_hello) ? "friend-only": "",
812                     (GNUNET_YES == store_hello) ? "public": "",
813                     fn, size);
814       GNUNET_free (buffer);
815     }
816   }
817   GNUNET_free_non_null (fn);
818   notify_all (host);
819 }
820
821
822 /**
823  * Do transmit info about peer to given host.
824  *
825  * @param cls NULL to hit all hosts, otherwise specifies a particular target
826  * @param key hostID
827  * @param value information to transmit
828  * @return GNUNET_YES (continue to iterate)
829  */
830 static int
831 add_to_tc (void *cls, const struct GNUNET_HashCode * key, void *value)
832 {
833   struct TransmitContext *tc = cls;
834   struct HostEntry *pos = value;
835   struct InfoMessage *im;
836   uint16_t hs;
837   char buf[GNUNET_SERVER_MAX_MESSAGE_SIZE - 1] GNUNET_ALIGN;
838
839   hs = 0;
840   im = (struct InfoMessage *) buf;
841
842   if ((pos->hello != NULL) && (GNUNET_NO == tc->friend_only))
843   {
844         /* Copy public HELLO */
845     hs = GNUNET_HELLO_size (pos->hello);
846     GNUNET_assert (hs < GNUNET_SERVER_MAX_MESSAGE_SIZE -
847                    sizeof (struct InfoMessage));
848     memcpy (&im[1], pos->hello, hs);
849     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Sending public HELLO with size %u for peer `%4s'\n",
850                 hs, GNUNET_h2s (key));
851   }
852   else if ((pos->friend_only_hello != NULL) && (GNUNET_YES == tc->friend_only))
853   {
854         /* Copy friend only HELLO */
855     hs = GNUNET_HELLO_size (pos->friend_only_hello);
856     GNUNET_assert (hs < GNUNET_SERVER_MAX_MESSAGE_SIZE -
857                    sizeof (struct InfoMessage));
858     memcpy (&im[1], pos->friend_only_hello, hs);
859     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Sending friend-only HELLO with size %u for peer `%4s'\n",
860                 hs, GNUNET_h2s (key));
861   }
862   else
863   {
864       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Adding no HELLO for peer `%s'\n",
865                  GNUNET_h2s (key));
866   }
867
868   im->header.type = htons (GNUNET_MESSAGE_TYPE_PEERINFO_INFO);
869   im->header.size = htons (sizeof (struct InfoMessage) + hs);
870   im->reserved = htonl (0);
871   im->peer = pos->identity;
872   GNUNET_SERVER_transmit_context_append_message (tc->tc, &im->header);
873   return GNUNET_YES;
874 }
875
876
877 /**
878  * @brief delete expired HELLO entries in directory
879  *
880  * @param cls pointer to current time (struct GNUNET_TIME_Absolute)
881  * @param fn filename to test to see if the HELLO expired
882  * @return GNUNET_OK (continue iteration)
883  */
884 static int
885 discard_hosts_helper (void *cls, const char *fn)
886 {
887   struct GNUNET_TIME_Absolute *now = cls;
888   char buffer[GNUNET_SERVER_MAX_MESSAGE_SIZE - 1] GNUNET_ALIGN;
889   const struct GNUNET_HELLO_Message *hello;
890   struct GNUNET_HELLO_Message *new_hello;
891   int read_size;
892   int cur_hello_size;
893   int new_hello_size;
894   int read_pos;
895   int write_pos;
896   unsigned int cnt;
897   char *writebuffer;
898
899
900   read_size = GNUNET_DISK_fn_read (fn, buffer, sizeof (buffer));
901   if (read_size < sizeof (struct GNUNET_MessageHeader))
902   {
903     if (0 != UNLINK (fn))
904       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING |
905                                 GNUNET_ERROR_TYPE_BULK, "unlink", fn);
906     return GNUNET_OK;
907   }
908
909   writebuffer = GNUNET_malloc (read_size);
910   read_pos = 0;
911   write_pos = 0;
912   while (read_pos < read_size)
913   {
914     /* Check each HELLO */
915     hello = (const struct GNUNET_HELLO_Message *) &buffer[read_pos];
916     cur_hello_size = GNUNET_HELLO_size (hello);
917     new_hello_size = 0;
918     if (0 == cur_hello_size)
919     {
920       /* Invalid data, discard */
921       if (0 != UNLINK (fn))
922         GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING |
923                                   GNUNET_ERROR_TYPE_BULK, "unlink", fn);
924       return GNUNET_OK;
925     }
926     new_hello = GNUNET_HELLO_iterate_addresses (hello, GNUNET_YES, &discard_expired, now);
927     cnt = 0;
928     if (NULL != new_hello)
929       (void) GNUNET_HELLO_iterate_addresses (hello, GNUNET_NO, &count_addresses, &cnt);
930     if ( (NULL != new_hello) && (0 < cnt) )
931     {
932       /* Store new HELLO to write it when done */
933       new_hello_size = GNUNET_HELLO_size(new_hello);
934       memcpy (&writebuffer[write_pos], new_hello, new_hello_size);
935       write_pos += new_hello_size;
936     }
937     read_pos += cur_hello_size;
938     GNUNET_free_non_null (new_hello);
939   }
940
941   if (0 < write_pos)
942   {
943       GNUNET_DISK_fn_write (fn, writebuffer,write_pos,
944                             GNUNET_DISK_PERM_USER_READ |
945                             GNUNET_DISK_PERM_USER_WRITE |
946                             GNUNET_DISK_PERM_GROUP_READ |
947                             GNUNET_DISK_PERM_OTHER_READ);
948   }
949   else if (0 != UNLINK (fn))
950     GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING |
951                               GNUNET_ERROR_TYPE_BULK, "unlink", fn);
952
953   GNUNET_free (writebuffer);
954   return GNUNET_OK;
955 }
956
957
958 /**
959  * Call this method periodically to scan peerinfo/ for ancient
960  * HELLOs to expire.
961  *
962  * @param cls unused
963  * @param tc scheduler context, aborted if reason is shutdown
964  */
965 static void
966 cron_clean_data_hosts (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
967 {
968   struct GNUNET_TIME_Absolute now;
969
970   if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN))
971     return;
972   now = GNUNET_TIME_absolute_get ();
973   GNUNET_log (GNUNET_ERROR_TYPE_INFO | GNUNET_ERROR_TYPE_BULK,
974               _("Cleaning up directory `%s'\n"), networkIdDirectory);
975   GNUNET_DISK_directory_scan (networkIdDirectory, &discard_hosts_helper, &now);
976   GNUNET_SCHEDULER_add_delayed (DATA_HOST_CLEAN_FREQ, &cron_clean_data_hosts,
977                                 NULL);
978 }
979
980
981 /**
982  * Handle HELLO-message.
983  *
984  * @param cls closure
985  * @param client identification of the client
986  * @param message the actual message
987  */
988 static void
989 handle_hello (void *cls, struct GNUNET_SERVER_Client *client,
990               const struct GNUNET_MessageHeader *message)
991 {
992   const struct GNUNET_HELLO_Message *hello;
993   struct GNUNET_PeerIdentity pid;
994
995   hello = (const struct GNUNET_HELLO_Message *) message;
996   if (GNUNET_OK != GNUNET_HELLO_get_id (hello, &pid))
997   {
998     GNUNET_break (0);
999     GNUNET_SERVER_receive_done (client, GNUNET_SYSERR);
1000     return;
1001   }
1002   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "`%s' message received for peer `%4s'\n",
1003               "HELLO", GNUNET_i2s (&pid));
1004   add_host_to_known_hosts (&pid);
1005   update_hello (&pid, hello);
1006   GNUNET_SERVER_receive_done (client, GNUNET_OK);
1007 }
1008
1009
1010 /**
1011  * Handle GET-message.
1012  *
1013  * @param cls closure
1014  * @param client identification of the client
1015  * @param message the actual message
1016  */
1017 static void
1018 handle_get (void *cls, struct GNUNET_SERVER_Client *client,
1019             const struct GNUNET_MessageHeader *message)
1020 {
1021   const struct ListPeerMessage *lpm;
1022   struct TransmitContext tcx;
1023
1024   lpm = (const struct ListPeerMessage *) message;
1025   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "`%s' message received for peer `%4s'\n",
1026               "GET", GNUNET_i2s (&lpm->peer));
1027   tcx.friend_only = ntohl (lpm->include_friend_only);
1028   tcx.tc = GNUNET_SERVER_transmit_context_create (client);
1029   GNUNET_CONTAINER_multihashmap_get_multiple (hostmap, &lpm->peer.hashPubKey,
1030                                               &add_to_tc, &tcx);
1031   GNUNET_SERVER_transmit_context_append_data (tcx.tc, NULL, 0,
1032                                               GNUNET_MESSAGE_TYPE_PEERINFO_INFO_END);
1033   GNUNET_SERVER_transmit_context_run (tcx.tc, GNUNET_TIME_UNIT_FOREVER_REL);
1034 }
1035
1036
1037 /**
1038  * Handle GET-ALL-message.
1039  *
1040  * @param cls closure
1041  * @param client identification of the client
1042  * @param message the actual message
1043  */
1044 static void
1045 handle_get_all (void *cls, struct GNUNET_SERVER_Client *client,
1046                 const struct GNUNET_MessageHeader *message)
1047 {
1048   const struct ListAllPeersMessage *lapm;
1049   struct TransmitContext tcx;
1050
1051   lapm = (const struct ListAllPeersMessage *) message;
1052   tcx.friend_only = ntohl (lapm->include_friend_only);
1053   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "`%s' message received\n", "GET_ALL");
1054   tcx.tc = GNUNET_SERVER_transmit_context_create (client);
1055   GNUNET_CONTAINER_multihashmap_iterate (hostmap, &add_to_tc, &tcx);
1056   GNUNET_SERVER_transmit_context_append_data (tcx.tc, NULL, 0,
1057                                               GNUNET_MESSAGE_TYPE_PEERINFO_INFO_END);
1058   GNUNET_SERVER_transmit_context_run (tcx.tc, GNUNET_TIME_UNIT_FOREVER_REL);
1059 }
1060
1061
1062
1063 /**
1064  * Pass the given client the information we have in the respective
1065  * host entry; the client is already in the notification context.
1066  *
1067  * @param cls the 'struct GNUNET_SERVER_Client' to notify
1068  * @param key key for the value (unused)
1069  * @param value the 'struct HostEntry' to notify the client about
1070  * @return GNUNET_YES (always, continue to iterate)
1071  */
1072 static int
1073 do_notify_entry (void *cls, const struct GNUNET_HashCode * key, void *value)
1074 {
1075   struct NotificationContext *nc = cls;
1076   struct HostEntry *he = value;
1077   struct InfoMessage *msg;
1078
1079   if ((NULL == he->hello) && (GNUNET_NO == nc->include_friend_only))
1080   {
1081     /* We have no public hello  */
1082     return GNUNET_YES;
1083   }
1084   
1085   if ( (NULL == he->friend_only_hello) && 
1086        (GNUNET_YES == nc->include_friend_only) )
1087   {
1088     /* We have no friend hello */
1089     return GNUNET_YES;
1090   }
1091   
1092   msg = make_info_message (he, nc->include_friend_only);
1093   GNUNET_SERVER_notification_context_unicast (notify_list,
1094                                               nc->client,
1095                                               &msg->header,
1096                                               GNUNET_NO);
1097   GNUNET_free (msg);
1098   return GNUNET_YES;
1099 }
1100
1101
1102 /**
1103  * Handle NOTIFY-message.
1104  *
1105  * @param cls closure
1106  * @param client identification of the client
1107  * @param message the actual message
1108  */
1109 static void
1110 handle_notify (void *cls, struct GNUNET_SERVER_Client *client,
1111                const struct GNUNET_MessageHeader *message)
1112 {
1113   struct NotifyMessage *nm = (struct NotifyMessage *) message;
1114   struct NotificationContext *nc;
1115
1116   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 
1117               "`%s' message received\n", 
1118               "NOTIFY");
1119   nc = GNUNET_malloc (sizeof (struct NotificationContext));
1120   nc->client = client;
1121   nc->include_friend_only = ntohl (nm->include_friend_only);
1122   
1123   GNUNET_CONTAINER_DLL_insert (nc_head, nc_tail, nc);
1124   GNUNET_SERVER_client_mark_monitor (client);
1125         GNUNET_SERVER_notification_context_add (notify_list, client);
1126   GNUNET_CONTAINER_multihashmap_iterate (hostmap, &do_notify_entry, nc);
1127   GNUNET_SERVER_receive_done (client, GNUNET_OK);
1128 }
1129
1130
1131 /**
1132  * Client disconnect callback
1133  *
1134  * @param cls unused
1135  * @param client server client
1136  */
1137 static void 
1138 disconnect_cb (void *cls,struct GNUNET_SERVER_Client *client)
1139 {
1140   struct NotificationContext *cur;
1141
1142   for (cur = nc_head; NULL != cur; cur = cur->next)
1143     if (cur->client == client)
1144       break;
1145   if (NULL == cur)
1146     return;
1147   GNUNET_CONTAINER_DLL_remove (nc_head, nc_tail, cur);
1148   GNUNET_free (cur);
1149 }
1150
1151
1152 /**
1153  * Release memory taken by a host entry.
1154  *
1155  * @param cls NULL
1156  * @param key key of the host entry
1157  * @param value the 'struct HostEntry' to free
1158  * @return GNUNET_YES (continue to iterate)
1159  */
1160 static int
1161 free_host_entry (void *cls, const struct GNUNET_HashCode * key, void *value)
1162 {
1163   struct HostEntry *he = value;
1164
1165   GNUNET_free_non_null (he->hello);
1166   GNUNET_free_non_null (he->friend_only_hello);
1167   GNUNET_free (he);
1168   return GNUNET_YES;
1169 }
1170
1171
1172 /**
1173  * Clean up our state.  Called during shutdown.
1174  *
1175  * @param cls unused
1176  * @param tc scheduler task context, unused
1177  */
1178 static void
1179 shutdown_task (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
1180 {
1181         struct NotificationContext *cur;
1182         struct NotificationContext *next;
1183         GNUNET_SERVER_notification_context_destroy (notify_list);
1184   notify_list = NULL;
1185
1186         for (cur = nc_head; NULL != cur; cur = next)
1187         {
1188                         next = cur->next;
1189                         GNUNET_CONTAINER_DLL_remove (nc_head, nc_tail, cur);
1190                         GNUNET_free (cur);
1191         }
1192
1193   GNUNET_CONTAINER_multihashmap_iterate (hostmap, &free_host_entry, NULL);
1194   GNUNET_CONTAINER_multihashmap_destroy (hostmap);
1195   if (NULL != stats)
1196   {
1197     GNUNET_STATISTICS_destroy (stats, GNUNET_NO);
1198     stats = NULL;
1199   }
1200 }
1201
1202
1203 /**
1204  * Start up peerinfo service.
1205  *
1206  * @param cls closure
1207  * @param server the initialized server
1208  * @param cfg configuration to use
1209  */
1210 static void
1211 run (void *cls, struct GNUNET_SERVER_Handle *server,
1212      const struct GNUNET_CONFIGURATION_Handle *cfg)
1213 {
1214   static const struct GNUNET_SERVER_MessageHandler handlers[] = {
1215     {&handle_hello, NULL, GNUNET_MESSAGE_TYPE_HELLO, 0},
1216     {&handle_get, NULL, GNUNET_MESSAGE_TYPE_PEERINFO_GET,
1217      sizeof (struct ListPeerMessage)},
1218     {&handle_get_all, NULL, GNUNET_MESSAGE_TYPE_PEERINFO_GET_ALL,
1219      sizeof (struct ListAllPeersMessage)},
1220     {&handle_notify, NULL, GNUNET_MESSAGE_TYPE_PEERINFO_NOTIFY,
1221      sizeof (struct NotifyMessage)},
1222     {NULL, NULL, 0, 0}
1223   };
1224   char *peerdir;
1225   char *ip;
1226   struct DirScanContext dsc;
1227   int noio;
1228
1229   hostmap = GNUNET_CONTAINER_multihashmap_create (1024, GNUNET_YES);
1230   stats = GNUNET_STATISTICS_create ("peerinfo", cfg);
1231   notify_list = GNUNET_SERVER_notification_context_create (server, 0);
1232   noio = GNUNET_CONFIGURATION_get_value_yesno (cfg, "peerinfo", "NO_IO");
1233   if (GNUNET_YES != noio)
1234   {
1235     GNUNET_assert (GNUNET_OK ==
1236                    GNUNET_CONFIGURATION_get_value_filename (cfg, "peerinfo",
1237                                                             "HOSTS",
1238                                                             &networkIdDirectory));
1239     GNUNET_DISK_directory_create (networkIdDirectory);
1240
1241     GNUNET_SCHEDULER_add_with_priority (GNUNET_SCHEDULER_PRIORITY_IDLE,
1242                                         &cron_scan_directory_data_hosts, NULL);
1243
1244     GNUNET_SCHEDULER_add_with_priority (GNUNET_SCHEDULER_PRIORITY_IDLE,
1245                                         &cron_clean_data_hosts, NULL);
1246
1247     ip = GNUNET_OS_installation_get_path (GNUNET_OS_IPK_DATADIR);
1248     GNUNET_asprintf (&peerdir,
1249                      "%shellos",
1250                      ip);
1251     GNUNET_free (ip);
1252
1253     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1254                 _("Importing HELLOs from `%s'\n"),
1255                 peerdir);
1256     dsc.matched = 0;
1257     dsc.remove_files = GNUNET_NO;
1258
1259     GNUNET_DISK_directory_scan (peerdir,
1260                                 &hosts_directory_scan_callback, &dsc);
1261
1262     GNUNET_free (peerdir);
1263   }
1264   GNUNET_SERVER_add_handlers (server, handlers);
1265   GNUNET_SERVER_disconnect_notify (server, &disconnect_cb, NULL) ;
1266   GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_FOREVER_REL, &shutdown_task,
1267                                 NULL);
1268
1269 }
1270
1271
1272 /**
1273  * The main function for the peerinfo service.
1274  *
1275  * @param argc number of arguments from the command line
1276  * @param argv command line arguments
1277  * @return 0 ok, 1 on error
1278  */
1279 int
1280 main (int argc, char *const *argv)
1281 {
1282   int ret;
1283
1284   ret =
1285       (GNUNET_OK ==
1286        GNUNET_SERVICE_run (argc, argv, "peerinfo", GNUNET_SERVICE_OPTION_NONE,
1287                            &run, NULL)) ? 0 : 1;
1288   GNUNET_free_non_null (networkIdDirectory);
1289   return ret;
1290 }
1291
1292
1293 /* end of gnunet-service-peerinfo.c */