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