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