fc100a84ca992d784511ccd852c75c0b8a80b256
[oweals/gnunet.git] / src / hostlist / hostlist-client.c
1 /*
2      This file is part of GNUnet.
3      (C) 2001, 2002, 2003, 2004, 2005, 2006, 2009, 2010 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 2, 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 hostlist/hostlist-client.c
23  * @brief hostlist support.  Downloads HELLOs via HTTP.
24  * @author Christian Grothoff
25  * @author Matthias Wachs
26  */
27
28 #include "platform.h"
29 #include "hostlist-client.h"
30 #include "gnunet_core_service.h"
31 #include "gnunet_hello_lib.h"
32 #include "gnunet_statistics_service.h"
33 #include "gnunet_transport_service.h"
34 #include "gnunet-daemon-hostlist.h"
35 #include <curl/curl.h>
36 #include "gnunet_common.h"
37 #include "gnunet_bio_lib.h"
38
39 #define DEBUG_HOSTLIST_CLIENT GNUNET_YES
40
41 #define MAX_URL_LEN 1000
42
43 /**
44  * Number of connections that we must have to NOT download
45  * hostlists anymore.
46  */
47 #define MIN_CONNECTIONS 4
48
49 /**
50  * A single hostlist obtained by hostlist advertisements
51  */
52 struct Hostlist
53 {
54   struct Hostlist * prev;
55
56   struct Hostlist * next;
57
58   /**
59    * URI where hostlist can be obtained
60    */
61   const char *hostlist_uri;
62
63   /**
64    * Peer offering the hostlist.  TO BE REMOVED.
65    */
66   struct GNUNET_PeerIdentity peer;
67
68   /**
69    * Value describing the quality of the hostlist, the bigger the better but (should) never < 0
70    * used for deciding which hostlist is replaced if MAX_NUMBER_HOSTLISTS in data structure is reached
71    * intial value = HOSTLIST_INITIAL
72    * increased every successful download by HOSTLIST_SUCCESSFULL_DOWNLOAD
73    * increased every successful download by number of obtained HELLO messages
74    * decreased every failed download by HOSTLIST_SUCCESSFULL_DOWNLOAD
75    */
76   uint64_t quality;
77
78   /**
79    * Time the hostlist advertisement was recieved and the entry was created
80    */
81   struct GNUNET_TIME_Absolute   time_creation;
82
83   /**
84    * Last time the hostlist was obtained
85    */
86   struct GNUNET_TIME_Absolute   time_last_usage;
87
88   /**
89    * Number of HELLO messages obtained during last download
90    */
91   uint32_t                 hello_count;
92
93   /**
94    * Number of times the hostlist was obtained
95    */
96   uint32_t                 times_used;
97
98 };
99
100
101 /**
102  * Our configuration.
103  */
104 static const struct GNUNET_CONFIGURATION_Handle *cfg;
105
106 /**
107  * Our scheduler.
108  */
109 static struct GNUNET_SCHEDULER_Handle *sched;
110
111 /**
112  * Statistics handle.
113  */
114 struct GNUNET_STATISTICS_Handle *stats; 
115
116 /**
117  * Transport handle.
118  */
119 struct GNUNET_TRANSPORT_Handle *transport;
120                        
121 /**
122  * Proxy that we are using (can be NULL).
123  */
124 static char *proxy;
125
126 /**
127  * Buffer for data downloaded via HTTP.
128  */
129 static char download_buffer[GNUNET_SERVER_MAX_MESSAGE_SIZE];
130
131 /**
132  * Number of bytes valid in 'download_buffer'.
133  */
134 static size_t download_pos;
135
136 /**
137  * Current URL that we are using.
138  */
139 static char *current_url;
140
141 /**
142  * Current CURL handle.
143  */
144 static CURL *curl;
145
146 /**
147  * Current multi-CURL handle.
148  */
149 static CURLM *multi;
150
151 /**
152  * ID of the current task scheduled.
153  */
154 static GNUNET_SCHEDULER_TaskIdentifier current_task;
155
156 /**
157  * Amount of time we wait between hostlist downloads.
158  */
159 static struct GNUNET_TIME_Relative hostlist_delay;
160
161 /**
162  * Set to GNUNET_YES if the current URL had some problems.
163  */ 
164 static int bogus_url;
165
166 /**
167  * Number of active connections (according to core service).
168  */
169 static unsigned int connection_count;
170
171 /**
172  * At what time MUST the current hostlist request be done?
173  */
174 static struct GNUNET_TIME_Absolute end_time;
175
176 /* DLL_? */
177 static struct Hostlist * dll_head;
178
179 /* DLL_? */
180 static struct Hostlist * dll_tail;
181
182 /* DLL_? */
183 static unsigned int dll_size;
184
185 /**
186  * Process downloaded bits by calling callback on each HELLO.
187  *
188  * @param ptr buffer with downloaded data
189  * @param size size of a record
190  * @param nmemb number of records downloaded
191  * @param ctx unused
192  * @return number of bytes that were processed (always size*nmemb)
193  */
194 static size_t
195 download_hostlist_processor (void *ptr, 
196                              size_t size, 
197                              size_t nmemb, 
198                              void *ctx)
199 {
200   const char * cbuf = ptr;
201   const struct GNUNET_MessageHeader *msg;
202   size_t total;
203   size_t cpy;
204   size_t left;
205   uint16_t msize;
206
207   total = size * nmemb;
208   if ( (total == 0) || (bogus_url) )
209     {
210       return total;  /* ok, no data or bogus data */
211     }
212   GNUNET_STATISTICS_update (stats, 
213                             gettext_noop ("# bytes downloaded from hostlist servers"), 
214                             (int64_t) total, 
215                             GNUNET_NO);  
216   left = total;
217   while ( (left > 0) ||
218           (download_pos > 0) )
219     {
220       cpy = GNUNET_MIN (left, GNUNET_SERVER_MAX_MESSAGE_SIZE - download_pos);
221       memcpy (&download_buffer[download_pos],
222               cbuf,
223               cpy);      
224       cbuf += cpy;
225       download_pos += cpy;
226       left -= cpy;
227       if (download_pos < sizeof(struct GNUNET_MessageHeader))
228         {
229           GNUNET_assert (left == 0);
230           break;
231         }
232       msg = (const struct GNUNET_MessageHeader *) download_buffer;
233       msize = ntohs(msg->size);
234       if (msize < sizeof(struct GNUNET_MessageHeader))
235         {        
236           GNUNET_STATISTICS_update (stats, 
237                                     gettext_noop ("# invalid HELLOs downloaded from hostlist servers"), 
238                                     1, 
239                                     GNUNET_NO);  
240           GNUNET_log (GNUNET_ERROR_TYPE_INFO,
241                       _("Invalid `%s' message received from hostlist at `%s'\n"),
242                       "HELLO",
243                       current_url); 
244           bogus_url = 1;
245           return total;
246         }
247       if (download_pos < msize)
248         {
249           GNUNET_assert (left == 0);
250           break;
251         }
252       if (GNUNET_HELLO_size ((const struct GNUNET_HELLO_Message*)msg) == msize)
253         {
254 #if DEBUG_HOSTLIST_CLIENT
255           GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
256                       "Received valid `%s' message from hostlist server.\n",
257                       "HELLO");
258 #endif
259           GNUNET_STATISTICS_update (stats, 
260                                     gettext_noop ("# valid HELLOs downloaded from hostlist servers"), 
261                                     1, 
262                                     GNUNET_NO);  
263           GNUNET_TRANSPORT_offer_hello (transport, msg);
264         }
265       else
266         {
267           GNUNET_STATISTICS_update (stats, 
268                                     gettext_noop ("# invalid HELLOs downloaded from hostlist servers"), 
269                                     1, 
270                                     GNUNET_NO);  
271           GNUNET_log (GNUNET_ERROR_TYPE_INFO,
272                       _("Invalid `%s' message received from hostlist at `%s'\n"),
273                       "HELLO",
274                       current_url);
275           bogus_url = GNUNET_YES;
276           return total;
277         }
278       memmove (download_buffer,
279                &download_buffer[msize],
280                download_pos - msize);
281       download_pos -= msize;
282     }
283   return total;
284 }
285
286
287 /**
288  * Obtain a hostlist URL that we should use.
289  *
290  * @return NULL if there is no URL available
291  */
292 static char *
293 get_url ()
294 {
295   char *servers;
296   char *ret;
297   size_t urls;
298   size_t pos;
299
300   if (GNUNET_OK != 
301       GNUNET_CONFIGURATION_get_value_string (cfg,
302                                              "HOSTLIST",
303                                              "SERVERS",
304                                              &servers))
305     {
306       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
307                   _("No `%s' specified in `%s' configuration, will not bootstrap.\n"),
308                   "SERVERS", "HOSTLIST");
309       return NULL;
310     }
311
312   urls = 0;
313   if (strlen (servers) > 0)
314     {
315       urls++;
316       pos = strlen (servers) - 1;
317       while (pos > 0)
318         {
319           if (servers[pos] == ' ')
320             urls++;
321           pos--;
322         }
323     }
324   if (urls == 0)
325     {
326       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
327                   _("No `%s' specified in `%s' configuration, will not bootstrap.\n"),
328                   "SERVERS", "HOSTLIST");
329       GNUNET_free (servers);
330       return NULL;
331     }
332
333   urls = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, urls) + 1;
334   pos = strlen (servers) - 1;
335   while (pos > 0)
336     {
337       if (servers[pos] == ' ')
338         {
339           urls--;
340           servers[pos] = '\0';
341         }
342       if (urls == 0)
343         {
344           pos++;
345           break;
346         }
347       pos--;    
348     }
349   ret = GNUNET_strdup (&servers[pos]);
350   GNUNET_free (servers);
351   return ret;
352 }
353
354
355 #define CURL_EASY_SETOPT(c, a, b) do { ret = curl_easy_setopt(c, a, b); if (ret != CURLE_OK) GNUNET_log(GNUNET_ERROR_TYPE_WARNING, _("%s failed at %s:%d: `%s'\n"), "curl_easy_setopt", __FILE__, __LINE__, curl_easy_strerror(ret)); } while (0);
356
357
358 /**
359  * Schedule the background task that will (possibly)
360  * download a hostlist.
361  */
362 static void
363 schedule_hostlist_task (void);
364
365
366 /**
367  * Clean up the state from the task that downloaded the
368  * hostlist and schedule the next task.
369  */
370 static void 
371 clean_up ()
372 {
373   CURLMcode mret;
374
375   if (multi != NULL)
376     {
377       mret = curl_multi_remove_handle (multi, curl);
378       if (mret != CURLM_OK)
379         {
380           GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
381                       _("%s failed at %s:%d: `%s'\n"),
382                       "curl_multi_remove_handle", __FILE__, __LINE__,
383                       curl_multi_strerror (mret));
384         }
385       mret = curl_multi_cleanup (multi);
386       if (mret != CURLM_OK)
387         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
388                     _("%s failed at %s:%d: `%s'\n"),
389                     "curl_multi_cleanup", __FILE__, __LINE__,
390                     curl_multi_strerror (mret));
391       multi = NULL;
392     }
393   if (curl != NULL)
394     {
395       curl_easy_cleanup (curl);
396       curl = NULL;
397     }  
398   GNUNET_free_non_null (current_url);
399   current_url = NULL;
400   schedule_hostlist_task ();
401 }
402
403
404 /**
405  * Ask CURL for the select set and then schedule the
406  * receiving task with the scheduler.
407  */
408 static void
409 run_multi (void);
410
411
412 /**
413  * Task that is run when we are ready to receive more data from the hostlist
414  * server. 
415  *
416  * @param cls closure, unused
417  * @param tc task context, unused
418  */
419 static void
420 multi_ready (void *cls,
421              const struct GNUNET_SCHEDULER_TaskContext *tc)
422 {
423   int running;
424   struct CURLMsg *msg;
425   CURLMcode mret;
426   
427   if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN))
428     {
429 #if DEBUG_HOSTLIST_CLIENT
430       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
431                   "Shutdown requested while trying to download hostlist from `%s'\n",
432                   current_url);
433 #endif
434       clean_up ();
435       return;
436     }
437   if (GNUNET_TIME_absolute_get_remaining (end_time).value == 0)
438     {
439       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
440                   _("Timeout trying to download hostlist from `%s'\n"),
441                   current_url);
442       clean_up ();
443       return;
444     }
445 #if DEBUG_HOSTLIST_CLIENT
446   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
447               "Ready for processing hostlist client request\n");
448 #endif
449   do 
450     {
451       running = 0;
452       mret = curl_multi_perform (multi, &running);
453       if (running == 0)
454         {
455           do
456             {
457               msg = curl_multi_info_read (multi, &running);
458               GNUNET_break (msg != NULL);
459               if (msg == NULL)
460                 break;
461               switch (msg->msg)
462                 {
463                 case CURLMSG_DONE:
464                   if ( (msg->data.result != CURLE_OK) &&
465                        (msg->data.result != CURLE_GOT_NOTHING) )                       
466                     GNUNET_log(GNUNET_ERROR_TYPE_INFO,
467                                _("%s failed for `%s' at %s:%d: `%s'\n"),
468                                "curl_multi_perform", 
469                                current_url,
470                                __FILE__,
471                                __LINE__,
472                                curl_easy_strerror (msg->data.result));            
473                   else
474                     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
475                                 _("Download of hostlist `%s' completed.\n"),
476                                 current_url);
477                   clean_up ();
478                   return;
479                 default:
480                   break;
481                 }
482             }
483           while (running > 0);
484         }
485     }
486   while (mret == CURLM_CALL_MULTI_PERFORM);
487   if (mret != CURLM_OK)
488     {
489       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
490                   _("%s failed at %s:%d: `%s'\n"),
491                   "curl_multi_perform", __FILE__, __LINE__,
492                   curl_multi_strerror (mret));
493       clean_up ();
494     }
495   run_multi ();
496 }
497
498
499 /**
500  * Ask CURL for the select set and then schedule the
501  * receiving task with the scheduler.
502  */
503 static void
504 run_multi () 
505 {
506   CURLMcode mret;
507   fd_set rs;
508   fd_set ws;
509   fd_set es;
510   int max;
511   struct GNUNET_NETWORK_FDSet *grs;
512   struct GNUNET_NETWORK_FDSet *gws;
513   long timeout;
514   struct GNUNET_TIME_Relative rtime;
515   
516   max = -1;
517   FD_ZERO (&rs);
518   FD_ZERO (&ws);
519   FD_ZERO (&es);
520   mret = curl_multi_fdset (multi, &rs, &ws, &es, &max);
521   if (mret != CURLM_OK)
522     {
523       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
524                   _("%s failed at %s:%d: `%s'\n"),
525                   "curl_multi_fdset", __FILE__, __LINE__,
526                   curl_multi_strerror (mret));
527       clean_up ();
528       return;
529     }
530   mret = curl_multi_timeout (multi, &timeout);
531   if (mret != CURLM_OK)
532     {
533       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
534                   _("%s failed at %s:%d: `%s'\n"),
535                   "curl_multi_timeout", __FILE__, __LINE__,
536                   curl_multi_strerror (mret));
537       clean_up ();
538       return;
539     }
540   rtime = GNUNET_TIME_relative_min (GNUNET_TIME_absolute_get_remaining (end_time),
541                                     GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS,
542                                                                    timeout));
543   grs = GNUNET_NETWORK_fdset_create ();
544   gws = GNUNET_NETWORK_fdset_create ();
545   GNUNET_NETWORK_fdset_copy_native (grs, &rs, max + 1);
546   GNUNET_NETWORK_fdset_copy_native (gws, &ws, max + 1);  
547 #if DEBUG_HOSTLIST_CLIENT
548   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
549               "Scheduling task for hostlist download using cURL\n");
550 #endif
551   current_task 
552     = GNUNET_SCHEDULER_add_select (sched,
553                                    GNUNET_SCHEDULER_PRIORITY_DEFAULT,
554                                    GNUNET_SCHEDULER_NO_TASK,
555                                    rtime,
556                                    grs,
557                                    gws,
558                                    &multi_ready,
559                                    multi);
560   GNUNET_NETWORK_fdset_destroy (gws);
561   GNUNET_NETWORK_fdset_destroy (grs);
562 }
563
564
565 /**
566  * Main function that will download a hostlist and process its
567  * data.
568  */
569 static void
570 download_hostlist () 
571 {
572   CURLcode ret;
573   CURLMcode mret;
574
575   curl = curl_easy_init ();
576   multi = NULL;
577   if (curl == NULL)
578     {
579       GNUNET_break (0);
580       clean_up ();
581       return;
582     }
583   current_url = get_url ();
584   GNUNET_log (GNUNET_ERROR_TYPE_INFO | GNUNET_ERROR_TYPE_BULK,
585               _("Bootstrapping using hostlist at `%s'.\n"), 
586               current_url);
587   GNUNET_STATISTICS_update (stats, 
588                             gettext_noop ("# hostlist downloads initiated"), 
589                             1, 
590                             GNUNET_NO);  
591   if (proxy != NULL)
592     CURL_EASY_SETOPT (curl, CURLOPT_PROXY, proxy);    
593   download_pos = 0;
594   bogus_url = 0;
595   CURL_EASY_SETOPT (curl,
596                     CURLOPT_WRITEFUNCTION, 
597                     &download_hostlist_processor);
598   if (ret != CURLE_OK)
599     {
600       clean_up ();
601       return;
602     }
603   CURL_EASY_SETOPT (curl,
604                     CURLOPT_WRITEDATA, 
605                     NULL);
606   if (ret != CURLE_OK)
607     {
608       clean_up ();
609       return;
610     }
611   CURL_EASY_SETOPT (curl, CURLOPT_FOLLOWLOCATION, 1);
612   CURL_EASY_SETOPT (curl, CURLOPT_MAXREDIRS, 4);
613   /* no need to abort if the above failed */
614   CURL_EASY_SETOPT (curl, 
615                     CURLOPT_URL, 
616                     current_url);
617   if (ret != CURLE_OK)
618     {
619       clean_up ();
620       return;
621     }
622   CURL_EASY_SETOPT (curl, 
623                     CURLOPT_FAILONERROR, 
624                     1);
625 #if 0
626   CURL_EASY_SETOPT (curl, 
627                     CURLOPT_VERBOSE, 
628                     1);
629 #endif
630   CURL_EASY_SETOPT (curl, 
631                     CURLOPT_BUFFERSIZE, 
632                     GNUNET_SERVER_MAX_MESSAGE_SIZE);
633   if (0 == strncmp (current_url, "http", 4))
634     CURL_EASY_SETOPT (curl, CURLOPT_USERAGENT, "GNUnet");
635   CURL_EASY_SETOPT (curl, 
636                     CURLOPT_CONNECTTIMEOUT, 
637                     60L);
638   CURL_EASY_SETOPT (curl, 
639                     CURLOPT_TIMEOUT, 
640                     60L);
641 #if 0
642   /* this should no longer be needed; we're now single-threaded! */
643   CURL_EASY_SETOPT (curl,
644                     CURLOPT_NOSIGNAL, 
645                     1);
646 #endif
647   multi = curl_multi_init ();
648   if (multi == NULL)
649     {
650       GNUNET_break (0);
651       clean_up ();
652       return;
653     }
654   mret = curl_multi_add_handle (multi, curl);
655   if (mret != CURLM_OK)
656     {
657       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
658                   _("%s failed at %s:%d: `%s'\n"),
659                   "curl_multi_add_handle", __FILE__, __LINE__,
660                   curl_multi_strerror (mret));
661       mret = curl_multi_cleanup (multi);
662       if (mret != CURLM_OK)
663         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
664                     _("%s failed at %s:%d: `%s'\n"),
665                     "curl_multi_cleanup", __FILE__, __LINE__,
666                     curl_multi_strerror (mret));
667       multi = NULL;
668       clean_up ();
669       return;
670     }
671   end_time = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_MINUTES);
672   run_multi ();
673 }  
674
675
676 /**
677  * Task that checks if we should try to download a hostlist.
678  * If so, we initiate the download, otherwise we schedule
679  * this task again for a later time.
680  */
681 static void
682 check_task (void *cls,
683             const struct GNUNET_SCHEDULER_TaskContext *tc)
684 {
685   current_task = GNUNET_SCHEDULER_NO_TASK;
686   if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN))
687     return;
688   if (connection_count < MIN_CONNECTIONS)
689     download_hostlist ();
690   else
691     schedule_hostlist_task ();
692 }
693
694
695 /**
696  * Compute when we should check the next time about downloading
697  * a hostlist; then schedule the task accordingly.
698  */
699 static void
700 schedule_hostlist_task ()
701 {
702   static int once;
703   struct GNUNET_TIME_Relative delay;
704
705   if (stats == NULL)
706     {
707       curl_global_cleanup ();
708       return; /* in shutdown */
709     }
710   delay = hostlist_delay;
711   if (hostlist_delay.value == 0)
712     hostlist_delay = GNUNET_TIME_UNIT_SECONDS;
713   else
714     hostlist_delay = GNUNET_TIME_relative_multiply (hostlist_delay, 2);
715   if (hostlist_delay.value > GNUNET_TIME_UNIT_HOURS.value * (1 + connection_count))
716     hostlist_delay = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_HOURS,
717                                                     (1 + connection_count));
718   GNUNET_STATISTICS_set (stats,
719                          gettext_noop("# seconds between hostlist downloads"),
720                          hostlist_delay.value,
721                          GNUNET_YES);
722   if (0 == once)
723     {
724       delay = GNUNET_TIME_UNIT_ZERO;
725       once = 1;
726     }  
727   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
728               _("Have %u/%u connections.  Will consider downloading hostlist in %llums\n"),
729               connection_count,
730               MIN_CONNECTIONS,
731               (unsigned long long) delay.value);
732   current_task = GNUNET_SCHEDULER_add_delayed (sched,
733                                                delay,
734                                                &check_task,
735                                                NULL);
736 }
737
738
739 /**
740  * Method called whenever a given peer connects.
741  *
742  * @param cls closure
743  * @param peer peer identity this notification is about
744  * @param latency reported latency of the connection with 'other'
745  * @param distance reported distance (DV) to 'other' 
746  */
747 static void
748 connect_handler (void *cls,
749                  const struct
750                  GNUNET_PeerIdentity * peer,
751                  struct GNUNET_TIME_Relative latency,
752                  uint32_t distance)
753 {
754   connection_count++;
755   GNUNET_STATISTICS_update (stats, 
756                             gettext_noop ("# active connections"), 
757                             1, 
758                             GNUNET_NO);  
759 }
760
761
762 /**
763  * Method called whenever a given peer disconnects.
764  *
765  * @param cls closure
766  * @param peer peer identity this notification is about
767  */
768 static void
769 disconnect_handler (void *cls,
770                     const struct
771                     GNUNET_PeerIdentity * peer)
772 {
773   connection_count--;
774   GNUNET_STATISTICS_update (stats, 
775                             gettext_noop ("# active connections"), 
776                             -1, 
777                             GNUNET_NO);  
778 }
779
780
781 /* DLL_? */
782 static int 
783 dll_contains (const char * uri)
784 {
785   struct Hostlist * pos;
786
787   pos = dll_head;
788   while (pos != NULL)
789     {
790       if (0 == strcmp(pos->hostlist_uri, uri) ) 
791         return GNUNET_YES;
792       pos = pos->next;
793     }
794   return GNUNET_NO;
795 }
796
797
798 /* DLL_? */
799 static struct Hostlist *
800 dll_get_lowest_quality ( )
801 {
802   struct Hostlist * pos;
803   struct Hostlist * lowest;
804
805   if (dll_size == 0)
806     return NULL;
807   lowest = dll_head;
808   pos = dll_head->next;
809   while (pos != NULL)
810     {
811       if (pos->quality < lowest->quality) 
812         lowest = pos;
813       pos = pos->next;
814     }
815   return lowest;
816 }
817
818
819 #if DUMMY
820 /* TO BE REMOVED later */
821 static void dll_insert ( struct Hostlist *hostlist)
822 {
823   GNUNET_CONTAINER_DLL_insert(dll_head, dll_tail, hostlist);
824   dll_size++;
825 }
826
827 static void create_dummy_entries ()
828 {
829
830   /* test */
831   struct Hostlist * hostlist1;
832   hostlist1 = GNUNET_malloc ( sizeof (struct Hostlist) );
833   char * str = "uri_1";
834
835   GNUNET_CRYPTO_hash_create_random ( GNUNET_CRYPTO_QUALITY_WEAK , &hostlist1->peer.hashPubKey);
836   hostlist1->hello_count = 0;
837   hostlist1->hostlist_uri = GNUNET_malloc ( strlen(str) +1 );
838   strcpy(hostlist1->hostlist_uri,str);
839   hostlist1->time_creation = GNUNET_TIME_absolute_get();
840   hostlist1->time_last_usage = GNUNET_TIME_absolute_get_zero();
841   hostlist1->quality = HOSTLIST_INITIAL - 100;
842   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
843       "Adding test peer '%s' with URI %s and quality %u to dll \n", GNUNET_h2s (&hostlist1->peer.hashPubKey) , hostlist1->hostlist_uri, hostlist1->quality);
844   dll_insert (hostlist1);
845
846   struct Hostlist * hostlist2;
847   hostlist2 = GNUNET_malloc ( sizeof (struct Hostlist) );
848   char * str2 = "uri_2";
849
850   GNUNET_CRYPTO_hash_create_random ( GNUNET_CRYPTO_QUALITY_WEAK , &hostlist2->peer.hashPubKey);
851   hostlist2->hello_count = 0;
852   hostlist2->hostlist_uri = GNUNET_malloc ( strlen(str2) +1 );
853   strcpy(hostlist2->hostlist_uri,str2);
854   hostlist2->time_creation = GNUNET_TIME_absolute_get();
855   hostlist2->time_last_usage = GNUNET_TIME_absolute_get_zero();
856   hostlist2->quality = HOSTLIST_INITIAL - 200;
857   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
858       "Adding test peer '%s' with URI %s and quality %u to dll \n", GNUNET_h2s (&hostlist2->peer.hashPubKey) , hostlist2->hostlist_uri, hostlist2->quality);
859   dll_insert (hostlist2);
860
861   struct Hostlist * hostlist3;
862   hostlist3 = GNUNET_malloc ( sizeof (struct Hostlist) );
863   char * str3 = "uri_3";
864
865   GNUNET_CRYPTO_hash_create_random ( GNUNET_CRYPTO_QUALITY_WEAK , &hostlist3->peer.hashPubKey);
866   hostlist3->hello_count = 0;
867   hostlist3->hostlist_uri = GNUNET_malloc ( strlen(str3) +1 );
868   strcpy(hostlist3->hostlist_uri,str3);
869   hostlist3->time_creation = GNUNET_TIME_absolute_get();
870   hostlist3->time_last_usage = GNUNET_TIME_absolute_get_zero();
871   hostlist3->quality = HOSTLIST_INITIAL - 300;
872   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
873             "Adding test peer '%s' with URI %s and quality %u to dll \n", GNUNET_h2s (&hostlist3->peer.hashPubKey) , hostlist3->hostlist_uri, hostlist3->quality);
874   dll_insert (hostlist3);
875
876
877   struct Hostlist * hostlist4;
878   hostlist4 = GNUNET_malloc ( sizeof (struct Hostlist) );
879   char * str4 = "uri_4";
880
881   GNUNET_CRYPTO_hash_create_random ( GNUNET_CRYPTO_QUALITY_WEAK , &hostlist4->peer.hashPubKey);
882   hostlist4->hello_count = 0;
883   hostlist4->hostlist_uri = GNUNET_malloc ( strlen(str4) +1 );
884   strcpy(hostlist4->hostlist_uri,str4);
885   hostlist4->time_creation = GNUNET_TIME_absolute_get();
886   hostlist4->time_last_usage = GNUNET_TIME_absolute_get_zero();
887   hostlist4->quality = HOSTLIST_INITIAL - 400;
888   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
889       "Adding test peer '%s' with URI %s and quality %u to dll \n", GNUNET_h2s (&hostlist4->peer.hashPubKey) , hostlist4->hostlist_uri, hostlist4->quality);
890   dll_insert (hostlist4);
891 }
892 #endif
893
894 /**
895  * Method called whenever an advertisement message arrives.
896  *
897  * @param cls closure (always NULL)
898  * @param client identification of the client
899  * @param message the actual message
900  * @return GNUNET_OK to keep the connection open,
901  *         GNUNET_SYSERR to close it (signal serious error)
902  */
903 static int
904 advertisement_handler (void *cls,
905     const struct GNUNET_PeerIdentity * peer,
906     const struct GNUNET_MessageHeader * message,
907     struct GNUNET_TIME_Relative latency,
908     uint32_t distance)
909 {
910   size_t size;
911   size_t uri_size;
912   const struct GNUNET_MessageHeader * incoming;
913   const char *uri;
914   struct Hostlist * hostlist;
915
916   GNUNET_assert (ntohs (message->type) == GNUNET_MESSAGE_TYPE_HOSTLIST_ADVERTISEMENT);
917   size = ntohs (message->size);
918   if (size <= sizeof(struct GNUNET_MessageHeader))
919     {
920       GNUNET_break_op (0);
921       return GNUNET_SYSERR;
922     }
923   incoming = (const struct GNUNET_MessageHeader *) message;
924   uri = (const char*) &incoming[1];
925   uri_size = size - sizeof (struct GNUNET_MessageHeader);
926   if (uri [uri_size - 1] != '\0')
927     {
928       GNUNET_break_op (0);
929       return GNUNET_SYSERR;
930     }
931   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
932               "Hostlist client recieved advertisement from `%s' containing URI `%s'\n", 
933               GNUNET_i2s (peer), 
934               uri);
935   if (GNUNET_YES != dll_contains (uri))
936     return GNUNET_OK;
937   hostlist = GNUNET_malloc (sizeof (struct Hostlist) + uri_size);
938   hostlist->peer = *peer;
939   hostlist->hostlist_uri = (const char*) &hostlist[1];
940   memcpy (&hostlist[1], uri, uri_size);
941   hostlist->time_creation = GNUNET_TIME_absolute_get();
942   hostlist->time_last_usage = GNUNET_TIME_absolute_get_zero();
943   hostlist->quality = HOSTLIST_INITIAL;  
944 #if DUMMY
945   create_dummy_entries(); /* FIXME: remove later... */
946 #endif
947   GNUNET_CONTAINER_DLL_insert(dll_head, dll_tail, hostlist);
948   dll_size++;
949   
950   if (MAX_NUMBER_HOSTLISTS >= dll_size)
951     return GNUNET_OK;
952
953   /* No free entries available, replace existing entry  */
954   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
955               "Removing lowest quality entry\n" );  
956   struct Hostlist * lowest_quality = dll_get_lowest_quality();
957   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
958               "Hostlist with URI `%s' has the worst quality of all with value %llu\n", 
959               lowest_quality->hostlist_uri,
960               (unsigned long long) lowest_quality->quality);
961   GNUNET_CONTAINER_DLL_remove (dll_head, dll_tail, lowest_quality);
962   dll_size--;
963   GNUNET_free (lowest_quality);
964   return GNUNET_OK;
965 }
966
967
968 /**
969  * Continuation called by the statistics code once 
970  * we go the stat.  Initiates hostlist download scheduling.
971  *
972  * @param cls closure
973  * @param success GNUNET_OK if statistics were
974  *        successfully obtained, GNUNET_SYSERR if not.
975  */
976 static void
977 primary_task (void *cls, int success)
978 {
979   if (stats == NULL)
980     return; /* in shutdown */
981 #if DEBUG_HOSTLIST_CLIENT
982   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
983               "Statistics request done, scheduling hostlist download\n");
984 #endif
985   schedule_hostlist_task ();
986 }
987
988
989 static int
990 process_stat (void *cls,
991               const char *subsystem,
992               const char *name,
993               uint64_t value,
994               int is_persistent)
995 {
996   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
997               _("Initial time between hostlist downloads is %llums\n"),
998               (unsigned long long) value);
999   hostlist_delay.value = value;
1000   return GNUNET_OK;
1001 }
1002
1003 /**
1004  * Method to load persistent hostlist file during hostlist client startup
1005  */
1006 static void 
1007 load_hostlist_file ()
1008 {
1009   char *filename;
1010   char *uri;
1011   char *emsg;
1012   struct Hostlist * hostlist;
1013
1014   if (GNUNET_OK !=
1015       GNUNET_CONFIGURATION_get_value_string (cfg,
1016                                              "HOSTLIST",
1017                                              "HOSTLISTFILE",
1018                                              &filename))
1019     {
1020       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
1021                   _("No `%s' specified in `%s' configuration, cannot load hostlists from file.\n"),
1022                   "HOSTLISTFILE", "HOSTLIST");
1023       return;
1024     }
1025
1026   struct GNUNET_BIO_ReadHandle * rh = GNUNET_BIO_read_open (filename);
1027   if (NULL == rh)
1028     {
1029       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1030                   _("Could not open file `%s' for reading to load hostlists: %s\n"), 
1031                   filename,
1032                   STRERROR (errno));
1033       GNUNET_free (filename);
1034       return;
1035     }
1036
1037   /* add code to read hostlists to file using bio */
1038   uri = NULL;
1039   uint32_t times_used;
1040   uint32_t hellos_returned;
1041   uint64_t quality;
1042   uint64_t last_used;
1043   uint64_t created;
1044
1045   while ( (GNUNET_OK == GNUNET_BIO_read_string (rh, "url" , &uri, MAX_URL_LEN)) &&
1046           (GNUNET_OK == GNUNET_BIO_read_int32 (rh, &times_used)) &&
1047           (GNUNET_OK == GNUNET_BIO_read_int64 (rh, &quality)) &&
1048           (GNUNET_OK == GNUNET_BIO_read_int64 (rh, &last_used)) &&
1049           (GNUNET_OK == GNUNET_BIO_read_int64 (rh, &created)) &&
1050           (GNUNET_OK == GNUNET_BIO_read_int32 (rh, &hellos_returned)) )
1051     {
1052       hostlist = GNUNET_malloc ( sizeof (struct Hostlist));
1053       hostlist->hello_count = hellos_returned;
1054       strcpy(hostlist->hostlist_uri, uri);
1055       hostlist->quality = quality;
1056       hostlist->time_creation.value = created;
1057       hostlist->time_last_usage.value = last_used;
1058       GNUNET_CONTAINER_DLL_insert(dll_head, dll_tail, hostlist);
1059       dll_size++;
1060       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1061                   "Added hostlist entry eith URI `%s' \n", hostlist->hostlist_uri);
1062       uri = NULL;
1063     }
1064   GNUNET_free_non_null (uri);
1065   emsg = NULL;
1066   GNUNET_BIO_read_close (rh, &emsg);
1067   if (emsg != NULL)
1068     GNUNET_free (emsg);
1069   GNUNET_free (filename);
1070 }
1071
1072
1073 /**
1074  * Method to load persistent hostlist file during hostlist client shutdown
1075  */
1076 static void save_hostlist_file ()
1077 {
1078   char *filename;
1079   struct Hostlist *pos;
1080   struct GNUNET_BIO_WriteHandle * wh;
1081   int ok;
1082
1083   if (GNUNET_OK !=
1084       GNUNET_CONFIGURATION_get_value_string (cfg,
1085                                              "HOSTLIST",
1086                                              "HOSTLISTFILE",
1087                                              &filename))
1088     {
1089       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
1090                   _("No `%s' specified in `%s' configuration, cannot save hostlists to file.\n"),
1091                   "HOSTLISTFILE", "HOSTLIST");
1092       return;
1093     }
1094   wh = GNUNET_BIO_write_open (filename);
1095   if ( NULL == wh)
1096     {
1097       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
1098                   _("Could not open file `%s' for writing to save hostlists: %s\n"),
1099                   filename,
1100                   STRERROR (errno));
1101       return;
1102     }
1103   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1104               _("Writing hostlist URIs to `%s'\n"),
1105               filename);
1106
1107   /* add code to write hostlists to file using bio */
1108   ok = GNUNET_YES;
1109   while (NULL != (pos = dll_head))
1110     {
1111       GNUNET_CONTAINER_DLL_remove (dll_head, dll_tail, pos);
1112       dll_size--;
1113       if (GNUNET_YES == ok)
1114         {
1115           if ( (GNUNET_OK !=
1116                 GNUNET_BIO_write_string (wh, pos->hostlist_uri)) ||
1117                (GNUNET_OK !=
1118                 GNUNET_BIO_write_int32 (wh, pos->times_used)) ||
1119                (GNUNET_OK !=
1120                 GNUNET_BIO_write_int64 (wh, pos->quality)) ||
1121                (GNUNET_OK !=
1122                 GNUNET_BIO_write_int64 (wh, pos->time_last_usage.value)) ||
1123                (GNUNET_OK !=
1124                 GNUNET_BIO_write_int64 (wh, pos->time_creation.value)) ||
1125                (GNUNET_OK !=
1126                 GNUNET_BIO_write_int32 (wh, pos->hello_count)))
1127             {
1128               GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
1129                           _("Error writing hostlist URIs to file `%s'\n"),
1130                           filename);
1131               ok = GNUNET_NO;
1132             }
1133         }
1134       GNUNET_free (pos);
1135     }  
1136   if ( GNUNET_OK != GNUNET_BIO_write_close ( wh ) )
1137     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
1138                 _("Error writing hostlist URIs to file `%s'\n"),
1139                 filename);
1140   GNUNET_free (filename);
1141 }
1142
1143 /**
1144  * Start downloading hostlists from hostlist servers as necessary.
1145  */
1146 int
1147 GNUNET_HOSTLIST_client_start (const struct GNUNET_CONFIGURATION_Handle *c,
1148                               struct GNUNET_SCHEDULER_Handle *s,
1149                               struct GNUNET_STATISTICS_Handle *st,
1150                               GNUNET_CORE_ConnectEventHandler *ch,
1151                               GNUNET_CORE_DisconnectEventHandler *dh,
1152                               GNUNET_CORE_MessageCallback *msgh,
1153                               int learn)
1154 {
1155   if (0 != curl_global_init (CURL_GLOBAL_WIN32))
1156     {
1157       GNUNET_break (0);
1158       return GNUNET_SYSERR;
1159     }
1160   transport = GNUNET_TRANSPORT_connect (s, c, NULL, NULL, NULL, NULL);
1161   if (NULL == transport)
1162     {
1163       curl_global_cleanup ();
1164       return GNUNET_SYSERR;
1165     }
1166   cfg = c;
1167   sched = s;
1168   stats = st;
1169   if (GNUNET_OK !=
1170       GNUNET_CONFIGURATION_get_value_string (cfg,
1171                                              "HOSTLIST",
1172                                              "HTTP-PROXY", 
1173                                              &proxy))
1174     proxy = NULL;
1175   *ch = &connect_handler;
1176   *dh = &disconnect_handler;
1177   if (learn)
1178     *msgh = &advertisement_handler;
1179   else
1180     *msgh = NULL;
1181   dll_head = NULL;
1182   dll_tail = NULL;
1183   load_hostlist_file ();
1184
1185   GNUNET_STATISTICS_get (stats,
1186                          "hostlist",
1187                          gettext_noop("# seconds between hostlist downloads"),
1188                          GNUNET_TIME_UNIT_MINUTES,
1189                          &primary_task,
1190                          &process_stat,
1191                          NULL);
1192   return GNUNET_OK;
1193 }
1194
1195
1196 /**
1197  * Stop downloading hostlists from hostlist servers as necessary.
1198  */
1199 void
1200 GNUNET_HOSTLIST_client_stop ()
1201 {
1202 #if DEBUG_HOSTLIST_CLIENT
1203   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1204               "Hostlist client shutdown\n");
1205 #endif
1206   save_hostlist_file ();
1207
1208   if (current_task != GNUNET_SCHEDULER_NO_TASK)
1209     {
1210       GNUNET_SCHEDULER_cancel (sched,
1211                                current_task);
1212       curl_global_cleanup ();
1213     }
1214   if (transport != NULL)
1215     {
1216       GNUNET_TRANSPORT_disconnect (transport);
1217       transport = NULL;
1218     }
1219   GNUNET_assert (NULL == transport);
1220   GNUNET_free_non_null (proxy);
1221   proxy = NULL;
1222   cfg = NULL;
1223   sched = NULL;
1224 }
1225
1226 /* end of hostlist-client.c */