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