handle NULL better
[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        (linked_list_size == 0) )
384   {
385     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
386                 "Using preconfigured bootstrap server\n");
387     use_preconfigured_list = GNUNET_NO;
388     return get_bootstrap_url();
389   }
390   index = GNUNET_CRYPTO_random_u32 ( GNUNET_CRYPTO_QUALITY_WEAK, linked_list_size);
391   counter = 0;
392   pos = linked_list_head;
393   while ( counter < index )
394     {
395       pos = pos->next;
396       counter ++;
397     }
398   use_preconfigured_list = GNUNET_YES;
399   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
400               "Using learned hostlist `%s'\n", pos->hostlist_uri);
401   return strdup(pos->hostlist_uri);
402 }
403
404
405 #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);
406
407
408 /**
409  * Schedule the background task that will (possibly)
410  * download a hostlist.
411  */
412 static void
413 schedule_hostlist_task (void);
414
415 /**
416  * Method to load persistent hostlist file during hostlist client shutdown
417  * @param shutdown set if called because of shutdown, entries in linked list will be destroyed
418  */
419 static void save_hostlist_file ( int shutdown );
420
421 /**
422  * Clean up the state from the task that downloaded the
423  * hostlist and schedule the next task.
424  */
425 static void 
426 clean_up ()
427 {
428   CURLMcode mret;
429
430   if (multi != NULL)
431     {
432       mret = curl_multi_remove_handle (multi, curl);
433       if (mret != CURLM_OK)
434         {
435           GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
436                       _("%s failed at %s:%d: `%s'\n"),
437                       "curl_multi_remove_handle", __FILE__, __LINE__,
438                       curl_multi_strerror (mret));
439         }
440       mret = curl_multi_cleanup (multi);
441       if (mret != CURLM_OK)
442         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
443                     _("%s failed at %s:%d: `%s'\n"),
444                     "curl_multi_cleanup", __FILE__, __LINE__,
445                     curl_multi_strerror (mret));
446       multi = NULL;
447     }
448   if (curl != NULL)
449     {
450       curl_easy_cleanup (curl);
451       curl = NULL;
452     }  
453   GNUNET_free_non_null (current_url);
454   current_url = NULL;
455   schedule_hostlist_task ();
456 }
457
458
459 /**
460  * Ask CURL for the select set and then schedule the
461  * receiving task with the scheduler.
462  */
463 static void
464 run_multi (void);
465
466
467 /**
468  * Task that is run when we are ready to receive more data from the hostlist
469  * server. 
470  *
471  * @param cls closure, unused
472  * @param tc task context, unused
473  */
474 static void
475 multi_ready (void *cls,
476              const struct GNUNET_SCHEDULER_TaskContext *tc)
477 {
478   int running;
479   struct CURLMsg *msg;
480   CURLMcode mret;
481   
482   if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN))
483     {
484 #if DEBUG_HOSTLIST_CLIENT
485       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
486                   "Shutdown requested while trying to download hostlist from `%s'\n",
487                   current_url);
488 #endif
489       clean_up ();
490       return;
491     }
492   if (GNUNET_TIME_absolute_get_remaining (end_time).value == 0)
493     {
494       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
495                   _("Timeout trying to download hostlist from `%s'\n"),
496                   current_url);
497       clean_up ();
498       return;
499     }
500 #if DEBUG_HOSTLIST_CLIENT
501   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
502               "Ready for processing hostlist client request\n");
503 #endif
504   do 
505     {
506       running = 0;
507       mret = curl_multi_perform (multi, &running);
508       if (running == 0)
509         {
510           do
511             {
512               msg = curl_multi_info_read (multi, &running);
513               GNUNET_break (msg != NULL);
514               if (msg == NULL)
515                 break;
516               switch (msg->msg)
517                 {
518                 case CURLMSG_DONE:
519                   if ( (msg->data.result != CURLE_OK) &&
520                        (msg->data.result != CURLE_GOT_NOTHING) )                       
521                     GNUNET_log(GNUNET_ERROR_TYPE_INFO,
522                                _("%s failed for `%s' at %s:%d: `%s'\n"),
523                                "curl_multi_perform", 
524                                current_url,
525                                __FILE__,
526                                __LINE__,
527                                curl_easy_strerror (msg->data.result));            
528                   else
529                     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
530                                 _("Download of hostlist `%s' completed.\n"),
531                                 current_url);
532                   clean_up ();
533                   return;
534                 default:
535                   break;
536                 }
537             }
538           while (running > 0);
539         }
540     }
541   while (mret == CURLM_CALL_MULTI_PERFORM);
542   if (mret != CURLM_OK)
543     {
544       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
545                   _("%s failed at %s:%d: `%s'\n"),
546                   "curl_multi_perform", __FILE__, __LINE__,
547                   curl_multi_strerror (mret));
548       clean_up ();
549     }
550   run_multi ();
551 }
552
553
554 /**
555  * Ask CURL for the select set and then schedule the
556  * receiving task with the scheduler.
557  */
558 static void
559 run_multi () 
560 {
561   CURLMcode mret;
562   fd_set rs;
563   fd_set ws;
564   fd_set es;
565   int max;
566   struct GNUNET_NETWORK_FDSet *grs;
567   struct GNUNET_NETWORK_FDSet *gws;
568   long timeout;
569   struct GNUNET_TIME_Relative rtime;
570   
571   max = -1;
572   FD_ZERO (&rs);
573   FD_ZERO (&ws);
574   FD_ZERO (&es);
575   mret = curl_multi_fdset (multi, &rs, &ws, &es, &max);
576   if (mret != CURLM_OK)
577     {
578       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
579                   _("%s failed at %s:%d: `%s'\n"),
580                   "curl_multi_fdset", __FILE__, __LINE__,
581                   curl_multi_strerror (mret));
582       clean_up ();
583       return;
584     }
585   mret = curl_multi_timeout (multi, &timeout);
586   if (mret != CURLM_OK)
587     {
588       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
589                   _("%s failed at %s:%d: `%s'\n"),
590                   "curl_multi_timeout", __FILE__, __LINE__,
591                   curl_multi_strerror (mret));
592       clean_up ();
593       return;
594     }
595   rtime = GNUNET_TIME_relative_min (GNUNET_TIME_absolute_get_remaining (end_time),
596                                     GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS,
597                                                                    timeout));
598   grs = GNUNET_NETWORK_fdset_create ();
599   gws = GNUNET_NETWORK_fdset_create ();
600   GNUNET_NETWORK_fdset_copy_native (grs, &rs, max + 1);
601   GNUNET_NETWORK_fdset_copy_native (gws, &ws, max + 1);  
602 #if DEBUG_HOSTLIST_CLIENT
603   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
604               "Scheduling task for hostlist download using cURL\n");
605 #endif
606   current_task 
607     = GNUNET_SCHEDULER_add_select (sched,
608                                    GNUNET_SCHEDULER_PRIORITY_DEFAULT,
609                                    GNUNET_SCHEDULER_NO_TASK,
610                                    rtime,
611                                    grs,
612                                    gws,
613                                    &multi_ready,
614                                    multi);
615   GNUNET_NETWORK_fdset_destroy (gws);
616   GNUNET_NETWORK_fdset_destroy (grs);
617 }
618
619
620 /**
621  * Main function that will download a hostlist and process its
622  * data.
623  */
624 static void
625 download_hostlist () 
626 {
627   CURLcode ret;
628   CURLMcode mret;
629
630   current_url = get_list_url ();
631   if (current_url == NULL)
632     return;
633   curl = curl_easy_init ();
634   multi = NULL;
635   if (curl == NULL)
636     {
637       GNUNET_break (0);
638       clean_up ();
639       return;
640     }
641   GNUNET_log (GNUNET_ERROR_TYPE_INFO | GNUNET_ERROR_TYPE_BULK,
642               _("Bootstrapping using hostlist at `%s'.\n"), 
643               current_url);
644   GNUNET_STATISTICS_update (stats, 
645                             gettext_noop ("# hostlist downloads initiated"), 
646                             1, 
647                             GNUNET_NO);  
648   if (proxy != NULL)
649     CURL_EASY_SETOPT (curl, CURLOPT_PROXY, proxy);    
650   download_pos = 0;
651   bogus_url = 0;
652   CURL_EASY_SETOPT (curl,
653                     CURLOPT_WRITEFUNCTION, 
654                     &download_hostlist_processor);
655   if (ret != CURLE_OK)
656     {
657       clean_up ();
658       return;
659     }
660   CURL_EASY_SETOPT (curl,
661                     CURLOPT_WRITEDATA, 
662                     NULL);
663   if (ret != CURLE_OK)
664     {
665       clean_up ();
666       return;
667     }
668   CURL_EASY_SETOPT (curl, CURLOPT_FOLLOWLOCATION, 1);
669   CURL_EASY_SETOPT (curl, CURLOPT_MAXREDIRS, 4);
670   /* no need to abort if the above failed */
671   CURL_EASY_SETOPT (curl, 
672                     CURLOPT_URL, 
673                     current_url);
674   if (ret != CURLE_OK)
675     {
676       clean_up ();
677       return;
678     }
679   CURL_EASY_SETOPT (curl, 
680                     CURLOPT_FAILONERROR, 
681                     1);
682 #if 0
683   CURL_EASY_SETOPT (curl, 
684                     CURLOPT_VERBOSE, 
685                     1);
686 #endif
687   CURL_EASY_SETOPT (curl, 
688                     CURLOPT_BUFFERSIZE, 
689                     GNUNET_SERVER_MAX_MESSAGE_SIZE);
690   if (0 == strncmp (current_url, "http", 4))
691     CURL_EASY_SETOPT (curl, CURLOPT_USERAGENT, "GNUnet");
692   CURL_EASY_SETOPT (curl, 
693                     CURLOPT_CONNECTTIMEOUT, 
694                     60L);
695   CURL_EASY_SETOPT (curl, 
696                     CURLOPT_TIMEOUT, 
697                     60L);
698 #if 0
699   /* this should no longer be needed; we're now single-threaded! */
700   CURL_EASY_SETOPT (curl,
701                     CURLOPT_NOSIGNAL, 
702                     1);
703 #endif
704   multi = curl_multi_init ();
705   if (multi == NULL)
706     {
707       GNUNET_break (0);
708       clean_up ();
709       return;
710     }
711   mret = curl_multi_add_handle (multi, curl);
712   if (mret != CURLM_OK)
713     {
714       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
715                   _("%s failed at %s:%d: `%s'\n"),
716                   "curl_multi_add_handle", __FILE__, __LINE__,
717                   curl_multi_strerror (mret));
718       mret = curl_multi_cleanup (multi);
719       if (mret != CURLM_OK)
720         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
721                     _("%s failed at %s:%d: `%s'\n"),
722                     "curl_multi_cleanup", __FILE__, __LINE__,
723                     curl_multi_strerror (mret));
724       multi = NULL;
725       clean_up ();
726       return;
727     }
728   end_time = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_MINUTES);
729   run_multi ();
730 }  
731
732
733 /**
734  * Task that checks if we should try to download a hostlist.
735  * If so, we initiate the download, otherwise we schedule
736  * this task again for a later time.
737  */
738 static void
739 check_task (void *cls,
740             const struct GNUNET_SCHEDULER_TaskContext *tc)
741 {
742   current_task = GNUNET_SCHEDULER_NO_TASK;
743   if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN))
744     return;
745   if (connection_count < MIN_CONNECTIONS)
746     download_hostlist ();
747   else
748     schedule_hostlist_task ();
749 }
750
751
752 /**
753  * Compute when we should check the next time about downloading
754  * a hostlist; then schedule the task accordingly.
755  */
756 static void
757 schedule_hostlist_task ()
758 {
759   static int once;
760   struct GNUNET_TIME_Relative delay;
761
762   if (stats == NULL)
763     {
764       curl_global_cleanup ();
765       return; /* in shutdown */
766     }
767   delay = hostlist_delay;
768   if (hostlist_delay.value == 0)
769     hostlist_delay = GNUNET_TIME_UNIT_SECONDS;
770   else
771     hostlist_delay = GNUNET_TIME_relative_multiply (hostlist_delay, 2);
772   if (hostlist_delay.value > GNUNET_TIME_UNIT_HOURS.value * (1 + connection_count))
773     hostlist_delay = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_HOURS,
774                                                     (1 + connection_count));
775   GNUNET_STATISTICS_set (stats,
776                          gettext_noop("# seconds between hostlist downloads"),
777                          hostlist_delay.value,
778                          GNUNET_YES);
779   if (0 == once)
780     {
781       delay = GNUNET_TIME_UNIT_ZERO;
782       once = 1;
783     }  
784   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
785               _("Have %u/%u connections.  Will consider downloading hostlist in %llums\n"),
786               connection_count,
787               MIN_CONNECTIONS,
788               (unsigned long long) delay.value);
789   current_task = GNUNET_SCHEDULER_add_delayed (sched,
790                                                delay,
791                                                &check_task,
792                                                NULL);
793 }
794
795 /**
796  * Task that writes hostlist entries to a file on a regular base
797  * cls closure
798  * tc TaskContext
799  */
800 static void
801 hostlist_saving_task (void *cls,
802             const struct GNUNET_SCHEDULER_TaskContext *tc)
803 {
804   saving_task = GNUNET_SCHEDULER_NO_TASK;
805   if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN))
806     return;
807   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
808               _("Scheduled saving of hostlists\n"));
809   save_hostlist_file ( GNUNET_NO );
810
811   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
812               _("Hostlists will be saved to file again in %llums\n"),
813               (unsigned long long) SAVING_INTERVALL.value);
814   saving_task = GNUNET_SCHEDULER_add_delayed (sched,
815                                                SAVING_INTERVALL,
816                                                &hostlist_saving_task,
817                                                NULL);
818 }
819
820 /**
821  * Method called whenever a given peer connects.
822  *
823  * @param cls closure
824  * @param peer peer identity this notification is about
825  * @param latency reported latency of the connection with 'other'
826  * @param distance reported distance (DV) to 'other' 
827  */
828 static void
829 connect_handler (void *cls,
830                  const struct
831                  GNUNET_PeerIdentity * peer,
832                  struct GNUNET_TIME_Relative latency,
833                  uint32_t distance)
834 {
835   connection_count++;
836   GNUNET_STATISTICS_update (stats, 
837                             gettext_noop ("# active connections"), 
838                             1, 
839                             GNUNET_NO);  
840 }
841
842
843 /**
844  * Method called whenever a given peer disconnects.
845  *
846  * @param cls closure
847  * @param peer peer identity this notification is about
848  */
849 static void
850 disconnect_handler (void *cls,
851                     const struct
852                     GNUNET_PeerIdentity * peer)
853 {
854   connection_count--;
855   GNUNET_STATISTICS_update (stats, 
856                             gettext_noop ("# active connections"), 
857                             -1, 
858                             GNUNET_NO);  
859 }
860
861
862 /**
863  * Method to check if URI is in hostlist linked list
864  * @param uri uri to check
865  * @return GNUNET_YES if existing in linked list, GNUNET_NO if not
866  */
867 static int 
868 linked_list_contains (const char * uri)
869 {
870   struct Hostlist * pos;
871
872   pos = linked_list_head;
873   while (pos != NULL)
874     {
875       if (0 == strcmp(pos->hostlist_uri, uri) ) 
876         return GNUNET_YES;
877       pos = pos->next;
878     }
879   return GNUNET_NO;
880 }
881
882
883 /* linked_list_? */
884 static struct Hostlist *
885 linked_list_get_lowest_quality ( )
886 {
887   struct Hostlist * pos;
888   struct Hostlist * lowest;
889
890   if (linked_list_size == 0)
891     return NULL;
892   lowest = linked_list_head;
893   pos = linked_list_head->next;
894   while (pos != NULL)
895     {
896       if (pos->quality < lowest->quality) 
897         lowest = pos;
898       pos = pos->next;
899     }
900   return lowest;
901 }
902
903
904 /**
905  * Method called whenever an advertisement message arrives.
906  *
907  * @param cls closure (always NULL)
908  * @param client identification of the client
909  * @param message the actual message
910  * @return GNUNET_OK to keep the connection open,
911  *         GNUNET_SYSERR to close it (signal serious error)
912  */
913 static int
914 advertisement_handler (void *cls,
915     const struct GNUNET_PeerIdentity * peer,
916     const struct GNUNET_MessageHeader * message,
917     struct GNUNET_TIME_Relative latency,
918     uint32_t distance)
919 {
920   size_t size;
921   size_t uri_size;
922   const struct GNUNET_MessageHeader * incoming;
923   const char *uri;
924   struct Hostlist * hostlist;
925
926   GNUNET_assert (ntohs (message->type) == GNUNET_MESSAGE_TYPE_HOSTLIST_ADVERTISEMENT);
927   size = ntohs (message->size);
928   if (size <= sizeof(struct GNUNET_MessageHeader))
929     {
930       GNUNET_break_op (0);
931       return GNUNET_SYSERR;
932     }
933   incoming = (const struct GNUNET_MessageHeader *) message;
934   uri = (const char*) &incoming[1];
935   uri_size = size - sizeof (struct GNUNET_MessageHeader);
936   if (uri [uri_size - 1] != '\0')
937     {
938       GNUNET_break_op (0);
939       return GNUNET_SYSERR;
940     }
941   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
942               "Hostlist client recieved advertisement from `%s' containing URI `%s'\n", 
943               GNUNET_i2s (peer), 
944               uri);
945   if (GNUNET_NO != linked_list_contains (uri))
946     {
947       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
948                 "URI `%s' is already known\n",
949                 uri);
950       return GNUNET_OK;
951     }
952   hostlist = GNUNET_malloc (sizeof (struct Hostlist) + uri_size);
953   hostlist->hostlist_uri = (const char*) &hostlist[1];
954   memcpy (&hostlist[1], uri, uri_size);
955   hostlist->time_creation = GNUNET_TIME_absolute_get();
956   hostlist->time_last_usage = GNUNET_TIME_absolute_get_zero();
957   hostlist->quality = HOSTLIST_INITIAL;  
958
959   GNUNET_CONTAINER_DLL_insert(linked_list_head, linked_list_tail, hostlist);
960   linked_list_size++;
961   
962   if (MAX_NUMBER_HOSTLISTS >= linked_list_size)
963     return GNUNET_OK;
964
965   /* No free entries available, replace existing entry  */
966   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
967               "Removing lowest quality entry\n" );  
968   struct Hostlist * lowest_quality = linked_list_get_lowest_quality();
969   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
970               "Hostlist with URI `%s' has the worst quality of all with value %llu\n", 
971               lowest_quality->hostlist_uri,
972               (unsigned long long) lowest_quality->quality);
973   GNUNET_CONTAINER_DLL_remove (linked_list_head, linked_list_tail, lowest_quality);
974   linked_list_size--;
975   GNUNET_free (lowest_quality);
976   return GNUNET_OK;
977 }
978
979
980 /**
981  * Continuation called by the statistics code once 
982  * we go the stat.  Initiates hostlist download scheduling.
983  *
984  * @param cls closure
985  * @param success GNUNET_OK if statistics were
986  *        successfully obtained, GNUNET_SYSERR if not.
987  */
988 static void
989 primary_task (void *cls, int success)
990 {
991   if (stats == NULL)
992     return; /* in shutdown */
993 #if DEBUG_HOSTLIST_CLIENT
994   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
995               "Statistics request done, scheduling hostlist download\n");
996 #endif
997   schedule_hostlist_task ();
998 }
999
1000
1001 static int
1002 process_stat (void *cls,
1003               const char *subsystem,
1004               const char *name,
1005               uint64_t value,
1006               int is_persistent)
1007 {
1008   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1009               _("Initial time between hostlist downloads is %llums\n"),
1010               (unsigned long long) value);
1011   hostlist_delay.value = value;
1012   return GNUNET_OK;
1013 }
1014
1015 /**
1016  * Method to load persistent hostlist file during hostlist client startup
1017  */
1018 static void 
1019 load_hostlist_file ()
1020 {
1021   char *filename;
1022   char *uri;
1023   char *emsg;
1024   struct Hostlist * hostlist;
1025   uri = NULL;
1026   uint32_t times_used;
1027   uint32_t hellos_returned;
1028   uint64_t quality;
1029   uint64_t last_used;
1030   uint64_t created;
1031   uint32_t counter;
1032
1033   if (GNUNET_OK !=
1034       GNUNET_CONFIGURATION_get_value_string (cfg,
1035                                              "HOSTLIST",
1036                                              "HOSTLISTFILE",
1037                                              &filename))
1038     {
1039       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
1040                   _("No `%s' specified in `%s' configuration, cannot load hostlists from file.\n"),
1041                   "HOSTLISTFILE", "HOSTLIST");
1042       return;
1043     }
1044
1045   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1046               _("Loading saved hostlist entries from file `%s' \n"), filename);
1047
1048   struct GNUNET_BIO_ReadHandle * rh = GNUNET_BIO_read_open (filename);
1049   if (NULL == rh)
1050     {
1051       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1052                   _("Could not open file `%s' for reading to load hostlists: %s\n"), 
1053                   filename,
1054                   STRERROR (errno));
1055       GNUNET_free (filename);
1056       return;
1057     }
1058
1059   counter = 0;
1060   while ( (GNUNET_OK == GNUNET_BIO_read_string (rh, "url" , &uri, MAX_URL_LEN)) &&
1061           (GNUNET_OK == GNUNET_BIO_read_int32 (rh, &times_used)) &&
1062           (GNUNET_OK == GNUNET_BIO_read_int64 (rh, &quality)) &&
1063           (GNUNET_OK == GNUNET_BIO_read_int64 (rh, &last_used)) &&
1064           (GNUNET_OK == GNUNET_BIO_read_int64 (rh, &created)) &&
1065           (GNUNET_OK == GNUNET_BIO_read_int32 (rh, &hellos_returned)) )
1066     {
1067       hostlist = GNUNET_malloc (sizeof (struct Hostlist) + strlen (uri) + 1);
1068       hostlist->hello_count = hellos_returned;
1069       hostlist->hostlist_uri = (const char *) &hostlist[1];
1070       memcpy (&hostlist[1], uri, strlen(uri)+1);
1071       hostlist->quality = quality;
1072       hostlist->time_creation.value = created;
1073       hostlist->time_last_usage.value = last_used;
1074       GNUNET_CONTAINER_DLL_insert(linked_list_head, linked_list_tail, hostlist);
1075       linked_list_size++;
1076       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1077                   "Added hostlist entry eith URI `%s' \n", hostlist->hostlist_uri);
1078       GNUNET_free (uri);
1079       uri = NULL;
1080       counter++;
1081       if ( counter >= MAX_NUMBER_HOSTLISTS ) break;
1082     }
1083
1084   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1085               _("%u hostlist URIs loaded from file\n"), counter);
1086   GNUNET_STATISTICS_set (stats,
1087                          gettext_noop("# hostlis URIs read from file"),
1088                          counter,
1089                          GNUNET_YES);
1090
1091   GNUNET_free_non_null (uri);
1092   emsg = NULL;
1093   GNUNET_BIO_read_close (rh, &emsg);
1094   if (emsg != NULL)
1095     GNUNET_free (emsg);
1096   GNUNET_free (filename);
1097 }
1098
1099
1100 /**
1101  * Method to load persistent hostlist file during hostlist client shutdown
1102  * @param shutdown set if called because of shutdown, entries in linked list will be destroyed
1103  */
1104 static void save_hostlist_file ( int shutdown )
1105 {
1106   char *filename;
1107   struct Hostlist *pos;
1108   struct GNUNET_BIO_WriteHandle * wh;
1109   int ok;
1110   uint32_t counter;
1111
1112   if (GNUNET_OK !=
1113       GNUNET_CONFIGURATION_get_value_string (cfg,
1114                                              "HOSTLIST",
1115                                              "HOSTLISTFILE",
1116                                              &filename))
1117     {
1118       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
1119                   _("No `%s' specified in `%s' configuration, cannot save hostlists to file.\n"),
1120                   "HOSTLISTFILE", "HOSTLIST");
1121                   GNUNET_free (filename);
1122       return;
1123     }
1124   wh = GNUNET_BIO_write_open (filename);
1125   if ( NULL == wh)
1126     {
1127       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
1128                   _("Could not open file `%s' for writing to save hostlists: %s\n"),
1129                   filename,
1130                   STRERROR (errno));
1131                   GNUNET_free (filename);
1132       return;
1133     }
1134   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1135               _("Writing %u hostlist URIs to `%s'\n" ),
1136               linked_list_size, filename);
1137
1138   /* add code to write hostlists to file using bio */
1139   ok = GNUNET_YES;
1140   counter = 0;
1141   while (NULL != (pos = linked_list_head))
1142     {
1143       if ( GNUNET_YES == shutdown)
1144       {
1145         GNUNET_CONTAINER_DLL_remove (linked_list_head, linked_list_tail, pos);
1146         linked_list_size--;
1147       }
1148       if (GNUNET_YES == ok)
1149         {
1150           if ( (GNUNET_OK !=
1151                 GNUNET_BIO_write_string (wh, pos->hostlist_uri)) ||
1152                (GNUNET_OK !=
1153                 GNUNET_BIO_write_int32 (wh, pos->times_used)) ||
1154                (GNUNET_OK !=
1155                 GNUNET_BIO_write_int64 (wh, pos->quality)) ||
1156                (GNUNET_OK !=
1157                 GNUNET_BIO_write_int64 (wh, pos->time_last_usage.value)) ||
1158                (GNUNET_OK !=
1159                 GNUNET_BIO_write_int64 (wh, pos->time_creation.value)) ||
1160                (GNUNET_OK !=
1161                 GNUNET_BIO_write_int32 (wh, pos->hello_count)))
1162             {
1163               GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
1164                           _("Error writing hostlist URIs to file `%s'\n"),
1165                           filename);
1166               ok = GNUNET_NO;
1167             }
1168         }
1169
1170       if ( GNUNET_YES == shutdown)
1171         GNUNET_free (pos);
1172       counter ++;
1173       if ( counter >= MAX_NUMBER_HOSTLISTS) break;
1174     }  
1175   GNUNET_STATISTICS_set (stats,
1176                          gettext_noop("# hostlist URIs written to file"),
1177                          counter,
1178                          GNUNET_YES);
1179
1180   if ( GNUNET_OK != GNUNET_BIO_write_close ( wh ) )
1181     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
1182                 _("Error writing hostlist URIs to file `%s'\n"),
1183                 filename);
1184   GNUNET_free (filename);
1185 }
1186
1187 /**
1188  * Start downloading hostlists from hostlist servers as necessary.
1189  */
1190 int
1191 GNUNET_HOSTLIST_client_start (const struct GNUNET_CONFIGURATION_Handle *c,
1192                               struct GNUNET_SCHEDULER_Handle *s,
1193                               struct GNUNET_STATISTICS_Handle *st,
1194                               GNUNET_CORE_ConnectEventHandler *ch,
1195                               GNUNET_CORE_DisconnectEventHandler *dh,
1196                               GNUNET_CORE_MessageCallback *msgh,
1197                               int learn)
1198 {
1199   if (0 != curl_global_init (CURL_GLOBAL_WIN32))
1200     {
1201       GNUNET_break (0);
1202       return GNUNET_SYSERR;
1203     }
1204   transport = GNUNET_TRANSPORT_connect (s, c, NULL, NULL, NULL, NULL);
1205   if (NULL == transport)
1206     {
1207       curl_global_cleanup ();
1208       return GNUNET_SYSERR;
1209     }
1210   cfg = c;
1211   sched = s;
1212   stats = st;
1213   if (GNUNET_OK !=
1214       GNUNET_CONFIGURATION_get_value_string (cfg,
1215                                              "HOSTLIST",
1216                                              "HTTP-PROXY", 
1217                                              &proxy))
1218     proxy = NULL;
1219   *ch = &connect_handler;
1220   *dh = &disconnect_handler;
1221   if (learn)
1222     *msgh = &advertisement_handler;
1223   else
1224     *msgh = NULL;
1225   linked_list_head = NULL;
1226   linked_list_tail = NULL;
1227   use_preconfigured_list = GNUNET_YES;
1228   load_hostlist_file ();
1229
1230   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1231               _("Hostlists will be saved to file again in  %llums\n"),
1232               (unsigned long long) SAVING_INTERVALL.value);
1233   saving_task = GNUNET_SCHEDULER_add_delayed (sched,
1234                                                SAVING_INTERVALL,
1235                                                &hostlist_saving_task,
1236                                                NULL);
1237
1238   GNUNET_STATISTICS_get (stats,
1239                          "hostlist",
1240                          gettext_noop("# seconds between hostlist downloads"),
1241                          GNUNET_TIME_UNIT_MINUTES,
1242                          &primary_task,
1243                          &process_stat,
1244                          NULL);
1245   return GNUNET_OK;
1246 }
1247
1248
1249 /**
1250  * Stop downloading hostlists from hostlist servers as necessary.
1251  */
1252 void
1253 GNUNET_HOSTLIST_client_stop ()
1254 {
1255 #if DEBUG_HOSTLIST_CLIENT
1256   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1257               "Hostlist client shutdown\n");
1258 #endif
1259   save_hostlist_file ( GNUNET_YES );
1260
1261   if (current_task != GNUNET_SCHEDULER_NO_TASK)
1262     {
1263       GNUNET_SCHEDULER_cancel (sched,
1264                                current_task);
1265       curl_global_cleanup ();
1266     }
1267   if (transport != NULL)
1268     {
1269       GNUNET_TRANSPORT_disconnect (transport);
1270       transport = NULL;
1271     }
1272   GNUNET_assert (NULL == transport);
1273   GNUNET_free_non_null (proxy);
1274   proxy = NULL;
1275   cfg = NULL;
1276   sched = NULL;
1277 }
1278
1279 /* end of hostlist-client.c */