- debug info
[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_us > expiration.abs_value_us)
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   unsigned int size_total;
332   struct GNUNET_TIME_Absolute now;
333   unsigned int left;
334
335   const struct GNUNET_HELLO_Message *hello;
336   struct GNUNET_HELLO_Message *hello_clean;
337   unsigned read_pos;
338   int size_hello;
339
340   r->friend_only_hello = NULL;
341   r->hello = NULL;
342
343   if (GNUNET_YES != GNUNET_DISK_file_test (fn))
344   {
345     return;
346   }
347
348   size_total = GNUNET_DISK_fn_read (fn, buffer, sizeof (buffer));
349   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Read %u bytes from `%s'\n", size_total, fn);
350   if (size_total < sizeof (struct GNUNET_MessageHeader))
351   {
352     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
353                 _("Failed to parse HELLO in file `%s': %s\n"),
354                 fn, "Fail has invalid size");
355     if ( (GNUNET_YES == unlink_garbage) && 
356          (0 != UNLINK (fn)) &&
357          (ENOENT != errno) )
358       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "unlink", fn);
359     return;
360   }
361
362   read_pos = 0;
363   while (read_pos < size_total)
364   {
365     hello = (const struct GNUNET_HELLO_Message *) &buffer[read_pos];
366     size_hello = GNUNET_HELLO_size (hello);
367     if (0 == size_hello)
368       {
369         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
370                     _("Failed to parse HELLO in file `%s': %s %u \n"),
371                     fn, "HELLO is invalid and has size of ", size_hello);
372         if ((GNUNET_YES == unlink_garbage) && 
373             (0 != UNLINK (fn)) &&
374             (ENOENT != errno) )
375           GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "unlink", fn);
376         return;
377       }
378     
379     now = GNUNET_TIME_absolute_get ();
380     hello_clean = GNUNET_HELLO_iterate_addresses (hello, GNUNET_YES,
381                                                   &discard_expired, &now);
382     left = 0;
383     (void) GNUNET_HELLO_iterate_addresses (hello_clean, GNUNET_NO,
384                                            &count_addresses, &left);
385     
386     if (0 == left)
387     {
388       GNUNET_free (hello_clean);
389       break;
390     }
391     
392     if (GNUNET_NO == GNUNET_HELLO_is_friend_only (hello_clean))
393     {
394       if (NULL == r->hello)
395         r->hello = hello_clean;
396       else
397       {
398         GNUNET_break (0);
399         GNUNET_free (r->hello);
400         r->hello = hello_clean;
401       }
402     }
403     else
404     {
405       if (NULL == r->friend_only_hello)
406         r->friend_only_hello = hello_clean;
407       else
408       {
409         GNUNET_break (0);
410         GNUNET_free (r->friend_only_hello);
411         r->friend_only_hello = hello_clean;
412       }
413     }
414     read_pos += size_hello;
415   }
416
417   if (0 == left)
418   {
419     /* no addresses left, remove from disk */
420     if ((GNUNET_YES == unlink_garbage) && (0 != UNLINK (fn)))
421       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "unlink", fn);
422   }
423
424   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
425               "Found `%s' and `%s' HELLO message in file\n",
426               (NULL != r->hello) ? "public" : "NO public",
427               (NULL != r->friend_only_hello) ? "friend only" : "NO friend only");
428 }
429
430
431 /**
432  * Add a host to the list and notify clients about this event
433  *
434  * @param identity the identity of the host
435  * @return the HostEntry
436  */
437 static struct HostEntry *
438 add_host_to_known_hosts (const struct GNUNET_PeerIdentity *identity)
439 {
440   struct HostEntry *entry;
441   struct ReadHostFileContext r;
442   char *fn;
443
444   entry = GNUNET_CONTAINER_multihashmap_get (hostmap, &identity->hashPubKey);
445   if (NULL == entry)
446   {
447     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Adding new peer `%s'\n", GNUNET_i2s (identity));
448     GNUNET_STATISTICS_update (stats, gettext_noop ("# peers known"), 1,
449                               GNUNET_NO);
450     entry = GNUNET_malloc (sizeof (struct HostEntry));
451     entry->identity = *identity;
452     GNUNET_CONTAINER_multihashmap_put (hostmap, &entry->identity.hashPubKey, entry,
453                                        GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY);
454     notify_all (entry);
455     fn = get_host_filename (identity);
456     if (NULL != fn)
457     {
458       read_host_file (fn, GNUNET_YES, &r);
459       if (NULL != r.hello)
460         update_hello (identity, r.hello);
461       if (NULL != r.friend_only_hello)
462         update_hello (identity, r.friend_only_hello);
463       GNUNET_free_non_null (r.hello);
464       GNUNET_free_non_null (r.friend_only_hello);
465       GNUNET_free (fn);
466     }
467   }
468   return entry;
469 }
470
471
472 /**
473  * Remove a file that should not be there.  LOG
474  * success or failure.
475  *
476  * @param fullname name of the file to remove
477  */
478 static void
479 remove_garbage (const char *fullname)
480 {
481   if (0 == UNLINK (fullname))
482     GNUNET_log (GNUNET_ERROR_TYPE_WARNING | GNUNET_ERROR_TYPE_BULK,
483                 _
484                 ("File `%s' in directory `%s' does not match naming convention. "
485                  "Removed.\n"), fullname, networkIdDirectory);
486   else
487     GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
488                               "unlink", fullname);
489 }
490
491
492 /**
493  * Closure for 'hosts_directory_scan_callback'.
494  */
495 struct DirScanContext
496 {
497   /**
498    * GNUNET_YES if we should remove files that are broken,
499    * GNUNET_NO if the directory we are iterating over should
500    * be treated as read-only by us.
501    */ 
502   int remove_files;
503
504   /**
505    * Counter for the number of (valid) entries found, incremented
506    * by one for each match.
507    */
508   unsigned int matched;
509 };
510
511
512 /**
513  * Function that is called on each HELLO file in a particular directory.
514  * Try to parse the file and add the HELLO to our list.
515  *
516  * @param cls pointer to 'unsigned int' to increment for each file, or NULL
517  *            if the file is from a read-only, read-once resource directory
518  * @param fullname name of the file to parse
519  * @return #GNUNET_OK (continue iteration)
520  */
521 static int
522 hosts_directory_scan_callback (void *cls, const char *fullname)
523 {
524   struct DirScanContext *dsc = cls;
525   struct GNUNET_PeerIdentity identity;
526   struct ReadHostFileContext r;
527   const char *filename;
528   struct GNUNET_PeerIdentity id_public;
529   struct GNUNET_PeerIdentity id_friend;
530   struct GNUNET_PeerIdentity id;
531
532   if (GNUNET_YES != GNUNET_DISK_file_test (fullname))
533     return GNUNET_OK;           /* ignore non-files */
534
535   filename = strrchr (fullname, DIR_SEPARATOR);
536   if ((NULL == filename) || (1 > strlen (filename)))
537         filename = fullname;
538   else
539     filename ++;
540
541   read_host_file (fullname, dsc->remove_files, &r);
542   if ( (NULL == r.hello) && (NULL == r.friend_only_hello))
543   {
544     if (GNUNET_YES == dsc->remove_files)
545       remove_garbage (fullname);
546     return GNUNET_OK;
547   }
548
549   if (NULL != r.friend_only_hello)
550   {
551     if (GNUNET_OK != GNUNET_HELLO_get_id (r.friend_only_hello, &id_friend))
552       if (GNUNET_YES == dsc->remove_files)
553       {
554         remove_garbage (fullname);
555         return GNUNET_OK;
556       }
557     id = id_friend;
558   }
559   if (NULL != r.hello)
560   {
561     if (GNUNET_OK != GNUNET_HELLO_get_id (r.hello, &id_public))
562       if (GNUNET_YES == dsc->remove_files)
563       {
564         remove_garbage (fullname);
565         return GNUNET_OK;
566       }
567     id = id_public;
568   }
569   
570   if ( (NULL != r.hello) && (NULL != r.friend_only_hello) &&
571        (0 != memcmp (&id_friend, &id_public, sizeof (id_friend))) )
572   {
573     /* HELLOs are not for the same peer */
574     GNUNET_break (0);
575     if (GNUNET_YES == dsc->remove_files)
576       remove_garbage (fullname);
577     return GNUNET_OK;
578   }
579   if (GNUNET_OK == GNUNET_CRYPTO_hash_from_string (filename, &identity.hashPubKey))
580   {
581     if (0 != memcmp (&id, &identity, sizeof (id_friend)))
582     {
583       /* HELLOs are not for the same peer */
584       GNUNET_break (0);
585       if (GNUNET_YES == dsc->remove_files)
586         remove_garbage (fullname);
587       return GNUNET_OK;
588     }
589   }
590
591   /* ok, found something valid, remember HELLO */
592   add_host_to_known_hosts (&id);
593   if (NULL != r.hello)
594   {
595     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Updating peer `%s' public HELLO \n",
596                 GNUNET_i2s (&id));
597     update_hello (&id, r.hello);
598     GNUNET_free (r.hello);
599   }
600   if (NULL != r.friend_only_hello)
601   {
602     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Updating peer `%s' friend only HELLO \n",
603                 GNUNET_i2s (&id));
604     update_hello (&id, r.friend_only_hello);
605     GNUNET_free (r.friend_only_hello);
606   }
607   dsc->matched++;
608   return GNUNET_OK;
609 }
610
611
612 /**
613  * Call this method periodically to scan data/hosts for new hosts.
614  *
615  * @param cls unused
616  * @param tc scheduler context, aborted if reason is shutdown
617  */
618 static void
619 cron_scan_directory_data_hosts (void *cls,
620                                 const struct GNUNET_SCHEDULER_TaskContext *tc)
621 {
622   static unsigned int retries;
623   struct DirScanContext dsc;
624
625   if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN))
626     return;
627   if (GNUNET_SYSERR == GNUNET_DISK_directory_create (networkIdDirectory))
628   {
629     GNUNET_SCHEDULER_add_delayed_with_priority (DATA_HOST_FREQ,
630                                                 GNUNET_SCHEDULER_PRIORITY_IDLE,
631                                                 &cron_scan_directory_data_hosts, NULL);
632     return;
633   }
634   dsc.matched = 0;
635   dsc.remove_files = GNUNET_YES;
636   GNUNET_log (GNUNET_ERROR_TYPE_INFO | GNUNET_ERROR_TYPE_BULK,
637               _("Scanning directory `%s'\n"), networkIdDirectory);
638   GNUNET_DISK_directory_scan (networkIdDirectory,
639                               &hosts_directory_scan_callback, &dsc);
640   if ((0 == dsc.matched) && (0 == (++retries & 31)))
641     GNUNET_log (GNUNET_ERROR_TYPE_WARNING | GNUNET_ERROR_TYPE_BULK,
642                 _("Still no peers found in `%s'!\n"), networkIdDirectory);
643   GNUNET_SCHEDULER_add_delayed_with_priority (DATA_HOST_FREQ, 
644                                               GNUNET_SCHEDULER_PRIORITY_IDLE,
645                                               &cron_scan_directory_data_hosts,
646                                               NULL);
647 }
648
649
650 static struct GNUNET_HELLO_Message *
651 update_friend_hello (const struct GNUNET_HELLO_Message *hello,
652                      const struct GNUNET_HELLO_Message *friend_hello)
653 {
654   struct GNUNET_HELLO_Message * res;
655   struct GNUNET_HELLO_Message * tmp;
656   struct GNUNET_CRYPTO_EccPublicSignKey pk;
657   
658   if (NULL != friend_hello)
659   {
660     res = GNUNET_HELLO_merge (hello, friend_hello);
661     GNUNET_assert (GNUNET_YES == GNUNET_HELLO_is_friend_only (res));
662     return res;
663   }
664   
665   if (GNUNET_OK !=
666       GNUNET_HELLO_get_key (hello, &pk))
667   {
668     GNUNET_break (0);
669     return NULL;
670   }
671   tmp = GNUNET_HELLO_create (&pk, NULL, NULL, GNUNET_YES);
672   res = GNUNET_HELLO_merge (hello, tmp);
673   GNUNET_free (tmp);
674   GNUNET_assert (GNUNET_YES == GNUNET_HELLO_is_friend_only (res));
675   return res;
676 }
677
678
679 /**
680  * Bind a host address (hello) to a hostId.
681  *
682  * @param peer the peer for which this is a hello
683  * @param hello the verified (!) hello message
684  */
685 static void
686 update_hello (const struct GNUNET_PeerIdentity *peer,
687               const struct GNUNET_HELLO_Message *hello)
688 {
689   char *fn;
690   struct HostEntry *host;
691   struct GNUNET_HELLO_Message *mrg;
692   struct GNUNET_HELLO_Message **dest;
693   struct GNUNET_TIME_Absolute delta;
694   unsigned int cnt;
695   unsigned int size;
696   int friend_hello_type;
697   int store_hello;
698   int store_friend_hello;
699   int pos;
700   char *buffer;
701
702   host = GNUNET_CONTAINER_multihashmap_get (hostmap, &peer->hashPubKey);
703   GNUNET_assert (NULL != host);
704
705   friend_hello_type = GNUNET_HELLO_is_friend_only (hello);
706         GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Updating %s HELLO for `%s'\n",
707                         (GNUNET_YES == friend_hello_type) ? "friend-only" : "public",
708                         GNUNET_i2s (peer));
709
710   dest = NULL;
711   if (GNUNET_YES == friend_hello_type)
712   {
713     dest = &host->friend_only_hello;
714   }
715   else
716   {
717     dest = &host->hello;
718   }
719
720   if (NULL == (*dest))
721   {
722     (*dest) = GNUNET_malloc (GNUNET_HELLO_size (hello));
723     memcpy ((*dest), hello, GNUNET_HELLO_size (hello));
724   }
725   else
726   {
727     mrg = GNUNET_HELLO_merge ((*dest), hello);
728     delta = GNUNET_HELLO_equals (mrg, (*dest), GNUNET_TIME_absolute_get ());
729     if (delta.abs_value_us == GNUNET_TIME_UNIT_FOREVER_ABS.abs_value_us)
730     {
731       /* no differences, just ignore the update */
732         GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "No change in %s HELLO for `%s'\n",
733                         (GNUNET_YES == friend_hello_type) ? "friend-only" : "public",
734                         GNUNET_i2s (peer));
735       GNUNET_free (mrg);
736       return;
737     }
738     GNUNET_free ((*dest));
739     (*dest) = mrg;
740   }
741
742   if ((NULL != (host->hello)) && (GNUNET_NO == friend_hello_type))
743   {
744     /* Update friend only hello */
745     mrg = update_friend_hello (host->hello, host->friend_only_hello);
746     if (NULL != host->friend_only_hello)
747       GNUNET_free (host->friend_only_hello);
748     host->friend_only_hello = mrg;
749   }
750
751   if (NULL != host->hello)
752     GNUNET_assert ((GNUNET_NO == GNUNET_HELLO_is_friend_only (host->hello)));
753   if (NULL != host->friend_only_hello)
754     GNUNET_assert ((GNUNET_YES == GNUNET_HELLO_is_friend_only(host->friend_only_hello)));
755
756   fn = get_host_filename (peer);
757   if ( (NULL != fn) &&
758        (GNUNET_OK == GNUNET_DISK_directory_create_for_file (fn)) )
759   {
760     store_hello = GNUNET_NO;
761     size = 0;
762     cnt = 0;
763     if (NULL != host->hello)
764       (void) GNUNET_HELLO_iterate_addresses (host->hello,
765                                              GNUNET_NO, &count_addresses, &cnt);
766     if (cnt > 0)
767     {
768       store_hello = GNUNET_YES;
769       size += GNUNET_HELLO_size (host->hello);
770     }
771     cnt = 0;
772     if (NULL != host->friend_only_hello)
773       (void) GNUNET_HELLO_iterate_addresses (host->friend_only_hello, GNUNET_NO,
774                                              &count_addresses, &cnt);
775     store_friend_hello = GNUNET_NO;
776     if (0 < cnt)
777     {
778       store_friend_hello = GNUNET_YES;
779       size += GNUNET_HELLO_size (host->friend_only_hello);
780     }
781     
782     if ((GNUNET_NO == store_hello) && (GNUNET_NO == store_friend_hello))
783     {
784       /* no valid addresses, don't put HELLO on disk; in fact,
785          if one exists on disk, remove it */
786       (void) UNLINK (fn);
787     }
788     else
789     {
790       buffer = GNUNET_malloc (size);
791       pos = 0;
792       
793       if (GNUNET_YES == store_hello)
794       {
795         memcpy (buffer, host->hello, GNUNET_HELLO_size (host->hello));
796         pos += GNUNET_HELLO_size (host->hello);
797       }
798       if (GNUNET_YES == store_friend_hello)
799       {
800         memcpy (&buffer[pos], host->friend_only_hello, GNUNET_HELLO_size (host->friend_only_hello));
801         pos += GNUNET_HELLO_size (host->friend_only_hello);
802       }
803       GNUNET_assert (pos == size);
804       
805       if (GNUNET_SYSERR == GNUNET_DISK_fn_write (fn, buffer, size,
806                                                  GNUNET_DISK_PERM_USER_READ |
807                                                  GNUNET_DISK_PERM_USER_WRITE |
808                                                  GNUNET_DISK_PERM_GROUP_READ |
809                                                  GNUNET_DISK_PERM_OTHER_READ))
810         GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "write", fn);
811       else
812         GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Stored %s %s HELLO in %s  with total size %u\n",
813                     (GNUNET_YES == store_friend_hello) ? "friend-only": "",
814                     (GNUNET_YES == store_hello) ? "public": "",
815                     fn, size);
816       GNUNET_free (buffer);
817     }
818   }
819   GNUNET_free_non_null (fn);
820   notify_all (host);
821 }
822
823
824 /**
825  * Do transmit info about peer to given host.
826  *
827  * @param cls NULL to hit all hosts, otherwise specifies a particular target
828  * @param key hostID
829  * @param value information to transmit
830  * @return GNUNET_YES (continue to iterate)
831  */
832 static int
833 add_to_tc (void *cls, const struct GNUNET_HashCode * key, void *value)
834 {
835   struct TransmitContext *tc = cls;
836   struct HostEntry *pos = value;
837   struct InfoMessage *im;
838   uint16_t hs;
839   char buf[GNUNET_SERVER_MAX_MESSAGE_SIZE - 1] GNUNET_ALIGN;
840
841   hs = 0;
842   im = (struct InfoMessage *) buf;
843
844   if ((pos->hello != NULL) && (GNUNET_NO == tc->friend_only))
845   {
846         /* Copy public HELLO */
847     hs = GNUNET_HELLO_size (pos->hello);
848     GNUNET_assert (hs < GNUNET_SERVER_MAX_MESSAGE_SIZE -
849                    sizeof (struct InfoMessage));
850     memcpy (&im[1], pos->hello, hs);
851     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Sending public HELLO with size %u for peer `%4s'\n",
852                 hs, GNUNET_h2s (key));
853   }
854   else if ((pos->friend_only_hello != NULL) && (GNUNET_YES == tc->friend_only))
855   {
856         /* Copy friend only HELLO */
857     hs = GNUNET_HELLO_size (pos->friend_only_hello);
858     GNUNET_assert (hs < GNUNET_SERVER_MAX_MESSAGE_SIZE -
859                    sizeof (struct InfoMessage));
860     memcpy (&im[1], pos->friend_only_hello, hs);
861     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Sending friend-only HELLO with size %u for peer `%4s'\n",
862                 hs, GNUNET_h2s (key));
863   }
864   else
865   {
866       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Adding no HELLO for peer `%s'\n",
867                  GNUNET_h2s (key));
868   }
869
870   im->header.type = htons (GNUNET_MESSAGE_TYPE_PEERINFO_INFO);
871   im->header.size = htons (sizeof (struct InfoMessage) + hs);
872   im->reserved = htonl (0);
873   im->peer = pos->identity;
874   GNUNET_SERVER_transmit_context_append_message (tc->tc, &im->header);
875   return GNUNET_YES;
876 }
877
878
879 /**
880  * @brief delete expired HELLO entries in directory
881  *
882  * @param cls pointer to current time (struct GNUNET_TIME_Absolute)
883  * @param fn filename to test to see if the HELLO expired
884  * @return GNUNET_OK (continue iteration)
885  */
886 static int
887 discard_hosts_helper (void *cls, const char *fn)
888 {
889   struct GNUNET_TIME_Absolute *now = cls;
890   char buffer[GNUNET_SERVER_MAX_MESSAGE_SIZE - 1] GNUNET_ALIGN;
891   const struct GNUNET_HELLO_Message *hello;
892   struct GNUNET_HELLO_Message *new_hello;
893   int read_size;
894   unsigned int cur_hello_size;
895   unsigned int new_hello_size;
896   int read_pos;
897   int write_pos;
898   unsigned int cnt;
899   char *writebuffer;
900
901
902   read_size = GNUNET_DISK_fn_read (fn, buffer, sizeof (buffer));
903   if (read_size < sizeof (struct GNUNET_MessageHeader))
904   {
905     if (0 != UNLINK (fn))
906       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING |
907                                 GNUNET_ERROR_TYPE_BULK, "unlink", fn);
908     return GNUNET_OK;
909   }
910
911   writebuffer = GNUNET_malloc (read_size);
912   read_pos = 0;
913   write_pos = 0;
914   while (read_pos < read_size)
915   {
916     /* Check each HELLO */
917     hello = (const struct GNUNET_HELLO_Message *) &buffer[read_pos];
918     cur_hello_size = GNUNET_HELLO_size (hello);
919     if (0 == cur_hello_size)
920     {
921       /* Invalid data, discard */
922       if (0 != UNLINK (fn))
923         GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING |
924                                   GNUNET_ERROR_TYPE_BULK, "unlink", fn);
925       return GNUNET_OK;
926     }
927     new_hello = GNUNET_HELLO_iterate_addresses (hello, GNUNET_YES, &discard_expired, now);
928     cnt = 0;
929     if (NULL != new_hello)
930       (void) GNUNET_HELLO_iterate_addresses (hello, GNUNET_NO, &count_addresses, &cnt);
931     if ( (NULL != new_hello) && (0 < cnt) )
932     {
933       /* Store new HELLO to write it when done */
934       new_hello_size = GNUNET_HELLO_size (new_hello);
935       memcpy (&writebuffer[write_pos], new_hello, new_hello_size);
936       write_pos += new_hello_size;
937     }
938     read_pos += cur_hello_size;
939     GNUNET_free_non_null (new_hello);
940   }
941
942   if (0 < write_pos)
943   {
944       GNUNET_DISK_fn_write (fn, writebuffer,write_pos,
945                             GNUNET_DISK_PERM_USER_READ |
946                             GNUNET_DISK_PERM_USER_WRITE |
947                             GNUNET_DISK_PERM_GROUP_READ |
948                             GNUNET_DISK_PERM_OTHER_READ);
949   }
950   else if (0 != UNLINK (fn))
951     GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING |
952                               GNUNET_ERROR_TYPE_BULK, "unlink", fn);
953
954   GNUNET_free (writebuffer);
955   return GNUNET_OK;
956 }
957
958
959 /**
960  * Call this method periodically to scan peerinfo/ for ancient
961  * HELLOs to expire.
962  *
963  * @param cls unused
964  * @param tc scheduler context, aborted if reason is shutdown
965  */
966 static void
967 cron_clean_data_hosts (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
968 {
969   struct GNUNET_TIME_Absolute now;
970
971   if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN))
972     return;
973   now = GNUNET_TIME_absolute_get ();
974   GNUNET_log (GNUNET_ERROR_TYPE_INFO | GNUNET_ERROR_TYPE_BULK,
975               _("Cleaning up directory `%s'\n"), networkIdDirectory);
976   GNUNET_DISK_directory_scan (networkIdDirectory, &discard_hosts_helper, &now);
977   GNUNET_SCHEDULER_add_delayed (DATA_HOST_CLEAN_FREQ, &cron_clean_data_hosts,
978                                 NULL);
979 }
980
981
982 /**
983  * Handle HELLO-message.
984  *
985  * @param cls closure
986  * @param client identification of the client
987  * @param message the actual message
988  */
989 static void
990 handle_hello (void *cls, struct GNUNET_SERVER_Client *client,
991               const struct GNUNET_MessageHeader *message)
992 {
993   const struct GNUNET_HELLO_Message *hello;
994   struct GNUNET_PeerIdentity pid;
995
996   hello = (const struct GNUNET_HELLO_Message *) message;
997   if (GNUNET_OK != GNUNET_HELLO_get_id (hello, &pid))
998   {
999     GNUNET_break (0);
1000     GNUNET_SERVER_receive_done (client, GNUNET_SYSERR);
1001     return;
1002   }
1003   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "`%s' message received for peer `%4s'\n",
1004               "HELLO", GNUNET_i2s (&pid));
1005   add_host_to_known_hosts (&pid);
1006   update_hello (&pid, hello);
1007   GNUNET_SERVER_receive_done (client, GNUNET_OK);
1008 }
1009
1010
1011 /**
1012  * Handle GET-message.
1013  *
1014  * @param cls closure
1015  * @param client identification of the client
1016  * @param message the actual message
1017  */
1018 static void
1019 handle_get (void *cls, struct GNUNET_SERVER_Client *client,
1020             const struct GNUNET_MessageHeader *message)
1021 {
1022   const struct ListPeerMessage *lpm;
1023   struct TransmitContext tcx;
1024
1025   lpm = (const struct ListPeerMessage *) message;
1026   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "`%s' message received for peer `%4s'\n",
1027               "GET", GNUNET_i2s (&lpm->peer));
1028   tcx.friend_only = ntohl (lpm->include_friend_only);
1029   tcx.tc = GNUNET_SERVER_transmit_context_create (client);
1030   GNUNET_CONTAINER_multihashmap_get_multiple (hostmap, &lpm->peer.hashPubKey,
1031                                               &add_to_tc, &tcx);
1032   GNUNET_SERVER_transmit_context_append_data (tcx.tc, NULL, 0,
1033                                               GNUNET_MESSAGE_TYPE_PEERINFO_INFO_END);
1034   GNUNET_SERVER_transmit_context_run (tcx.tc, GNUNET_TIME_UNIT_FOREVER_REL);
1035 }
1036
1037
1038 /**
1039  * Handle GET-ALL-message.
1040  *
1041  * @param cls closure
1042  * @param client identification of the client
1043  * @param message the actual message
1044  */
1045 static void
1046 handle_get_all (void *cls, struct GNUNET_SERVER_Client *client,
1047                 const struct GNUNET_MessageHeader *message)
1048 {
1049   const struct ListAllPeersMessage *lapm;
1050   struct TransmitContext tcx;
1051
1052   lapm = (const struct ListAllPeersMessage *) message;
1053   tcx.friend_only = ntohl (lapm->include_friend_only);
1054   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "`%s' message received\n", "GET_ALL");
1055   tcx.tc = GNUNET_SERVER_transmit_context_create (client);
1056   GNUNET_CONTAINER_multihashmap_iterate (hostmap, &add_to_tc, &tcx);
1057   GNUNET_SERVER_transmit_context_append_data (tcx.tc, NULL, 0,
1058                                               GNUNET_MESSAGE_TYPE_PEERINFO_INFO_END);
1059   GNUNET_SERVER_transmit_context_run (tcx.tc, GNUNET_TIME_UNIT_FOREVER_REL);
1060 }
1061
1062
1063
1064 /**
1065  * Pass the given client the information we have in the respective
1066  * host entry; the client is already in the notification context.
1067  *
1068  * @param cls the 'struct GNUNET_SERVER_Client' to notify
1069  * @param key key for the value (unused)
1070  * @param value the 'struct HostEntry' to notify the client about
1071  * @return GNUNET_YES (always, continue to iterate)
1072  */
1073 static int
1074 do_notify_entry (void *cls, const struct GNUNET_HashCode * key, void *value)
1075 {
1076   struct NotificationContext *nc = cls;
1077   struct HostEntry *he = value;
1078   struct InfoMessage *msg;
1079
1080   if ((NULL == he->hello) && (GNUNET_NO == nc->include_friend_only))
1081   {
1082     /* We have no public hello  */
1083     return GNUNET_YES;
1084   }
1085   
1086   if ( (NULL == he->friend_only_hello) && 
1087        (GNUNET_YES == nc->include_friend_only) )
1088   {
1089     /* We have no friend hello */
1090     return GNUNET_YES;
1091   }
1092   
1093   msg = make_info_message (he, nc->include_friend_only);
1094   GNUNET_SERVER_notification_context_unicast (notify_list,
1095                                               nc->client,
1096                                               &msg->header,
1097                                               GNUNET_NO);
1098   GNUNET_free (msg);
1099   return GNUNET_YES;
1100 }
1101
1102
1103 /**
1104  * Handle NOTIFY-message.
1105  *
1106  * @param cls closure
1107  * @param client identification of the client
1108  * @param message the actual message
1109  */
1110 static void
1111 handle_notify (void *cls, struct GNUNET_SERVER_Client *client,
1112                const struct GNUNET_MessageHeader *message)
1113 {
1114   struct NotifyMessage *nm = (struct NotifyMessage *) message;
1115   struct NotificationContext *nc;
1116
1117   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 
1118               "`%s' message received\n", 
1119               "NOTIFY");
1120   nc = GNUNET_malloc (sizeof (struct NotificationContext));
1121   nc->client = client;
1122   nc->include_friend_only = ntohl (nm->include_friend_only);
1123   
1124   GNUNET_CONTAINER_DLL_insert (nc_head, nc_tail, nc);
1125   GNUNET_SERVER_client_mark_monitor (client);
1126         GNUNET_SERVER_notification_context_add (notify_list, client);
1127   GNUNET_CONTAINER_multihashmap_iterate (hostmap, &do_notify_entry, nc);
1128   GNUNET_SERVER_receive_done (client, GNUNET_OK);
1129 }
1130
1131
1132 /**
1133  * Client disconnect callback
1134  *
1135  * @param cls unused
1136  * @param client server client
1137  */
1138 static void 
1139 disconnect_cb (void *cls,struct GNUNET_SERVER_Client *client)
1140 {
1141   struct NotificationContext *cur;
1142
1143   for (cur = nc_head; NULL != cur; cur = cur->next)
1144     if (cur->client == client)
1145       break;
1146   if (NULL == cur)
1147     return;
1148   GNUNET_CONTAINER_DLL_remove (nc_head, nc_tail, cur);
1149   GNUNET_free (cur);
1150 }
1151
1152
1153 /**
1154  * Release memory taken by a host entry.
1155  *
1156  * @param cls NULL
1157  * @param key key of the host entry
1158  * @param value the 'struct HostEntry' to free
1159  * @return GNUNET_YES (continue to iterate)
1160  */
1161 static int
1162 free_host_entry (void *cls, const struct GNUNET_HashCode * key, void *value)
1163 {
1164   struct HostEntry *he = value;
1165
1166   GNUNET_free_non_null (he->hello);
1167   GNUNET_free_non_null (he->friend_only_hello);
1168   GNUNET_free (he);
1169   return GNUNET_YES;
1170 }
1171
1172
1173 /**
1174  * Clean up our state.  Called during shutdown.
1175  *
1176  * @param cls unused
1177  * @param tc scheduler task context, unused
1178  */
1179 static void
1180 shutdown_task (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
1181 {
1182   struct NotificationContext *cur;
1183   struct NotificationContext *next;
1184
1185   GNUNET_SERVER_notification_context_destroy (notify_list);
1186   notify_list = NULL;
1187
1188   for (cur = nc_head; NULL != cur; cur = next)
1189   {
1190     next = cur->next;
1191     GNUNET_CONTAINER_DLL_remove (nc_head, nc_tail, cur);
1192     GNUNET_free (cur);
1193   }
1194   GNUNET_CONTAINER_multihashmap_iterate (hostmap, &free_host_entry, NULL);
1195   GNUNET_CONTAINER_multihashmap_destroy (hostmap);
1196   if (NULL != stats)
1197   {
1198     GNUNET_STATISTICS_destroy (stats, GNUNET_NO);
1199     stats = NULL;
1200   }
1201 }
1202
1203
1204 /**
1205  * Start up peerinfo service.
1206  *
1207  * @param cls closure
1208  * @param server the initialized server
1209  * @param cfg configuration to use
1210  */
1211 static void
1212 run (void *cls, struct GNUNET_SERVER_Handle *server,
1213      const struct GNUNET_CONFIGURATION_Handle *cfg)
1214 {
1215   static const struct GNUNET_SERVER_MessageHandler handlers[] = {
1216     {&handle_hello, NULL, GNUNET_MESSAGE_TYPE_HELLO, 0},
1217     {&handle_get, NULL, GNUNET_MESSAGE_TYPE_PEERINFO_GET,
1218      sizeof (struct ListPeerMessage)},
1219     {&handle_get_all, NULL, GNUNET_MESSAGE_TYPE_PEERINFO_GET_ALL,
1220      sizeof (struct ListAllPeersMessage)},
1221     {&handle_notify, NULL, GNUNET_MESSAGE_TYPE_PEERINFO_NOTIFY,
1222      sizeof (struct NotifyMessage)},
1223     {NULL, NULL, 0, 0}
1224   };
1225   char *peerdir;
1226   char *ip;
1227   struct DirScanContext dsc;
1228   int noio;
1229   int use_included;
1230
1231   hostmap = GNUNET_CONTAINER_multihashmap_create (1024, GNUNET_YES);
1232   stats = GNUNET_STATISTICS_create ("peerinfo", cfg);
1233   notify_list = GNUNET_SERVER_notification_context_create (server, 0);
1234   noio = GNUNET_CONFIGURATION_get_value_yesno (cfg, "peerinfo", "NO_IO");
1235   use_included = GNUNET_CONFIGURATION_get_value_yesno (cfg, "peerinfo", "USE_INCLUDED_HELLOS");
1236   GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_FOREVER_REL, &shutdown_task,
1237                                 NULL);
1238   if (GNUNET_YES != noio)
1239   {
1240     GNUNET_assert (GNUNET_OK ==
1241                    GNUNET_CONFIGURATION_get_value_filename (cfg, "peerinfo",
1242                                                             "HOSTS",
1243                                                             &networkIdDirectory));
1244     if (GNUNET_OK !=
1245         GNUNET_DISK_directory_create (networkIdDirectory))
1246     {
1247       GNUNET_SCHEDULER_shutdown ();
1248       return;
1249     }
1250     
1251     GNUNET_SCHEDULER_add_with_priority (GNUNET_SCHEDULER_PRIORITY_IDLE,
1252                                         &cron_scan_directory_data_hosts, NULL);
1253
1254     GNUNET_SCHEDULER_add_with_priority (GNUNET_SCHEDULER_PRIORITY_IDLE,
1255                                         &cron_clean_data_hosts, NULL);
1256     if (GNUNET_YES == use_included)
1257     {
1258         ip = GNUNET_OS_installation_get_path (GNUNET_OS_IPK_DATADIR);
1259         GNUNET_asprintf (&peerdir,
1260                      "%shellos",
1261                      ip);
1262         GNUNET_free (ip);
1263
1264                         GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1265                         _("Importing HELLOs from `%s'\n"),
1266                         peerdir);
1267                         dsc.matched = 0;
1268                         dsc.remove_files = GNUNET_NO;
1269
1270                         GNUNET_DISK_directory_scan (peerdir,
1271                                         &hosts_directory_scan_callback, &dsc);
1272
1273                         GNUNET_free (peerdir);
1274     }
1275   }
1276   GNUNET_SERVER_add_handlers (server, handlers);
1277   GNUNET_SERVER_disconnect_notify (server, &disconnect_cb, NULL) ;
1278 }
1279
1280
1281 /**
1282  * The main function for the peerinfo service.
1283  *
1284  * @param argc number of arguments from the command line
1285  * @param argv command line arguments
1286  * @return 0 ok, 1 on error
1287  */
1288 int
1289 main (int argc, char *const *argv)
1290 {
1291   int ret;
1292
1293   ret =
1294       (GNUNET_OK ==
1295        GNUNET_SERVICE_run (argc, argv, "peerinfo", GNUNET_SERVICE_OPTION_NONE,
1296                            &run, NULL)) ? 0 : 1;
1297   GNUNET_free_non_null (networkIdDirectory);
1298   return ret;
1299 }
1300
1301
1302 /* end of gnunet-service-peerinfo.c */