-simplify logic
[oweals/gnunet.git] / src / transport / plugin_transport_http_client.c
1 /*
2      This file is part of GNUnet
3      Copyright (C) 2002-2014 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 3, or (at your
8      option) any later version.
9
10      GNUnet is distributed in the hope that it will be useful, but
11      WITHOUT ANY WARRANTY; without even the implied warranty of
12      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13      General Public License for more details.
14
15      You should have received a copy of the GNU General Public License
16      along with GNUnet; see the file COPYING.  If not, write to the
17      Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18      Boston, MA 02111-1307, USA.
19 */
20
21 /**
22  * @file transport/plugin_transport_http_client.c
23  * @brief HTTP/S client transport plugin
24  * @author Matthias Wachs
25  * @author Christian Grothoff
26  */
27
28 #if BUILD_HTTPS
29 #define PLUGIN_NAME "https_client"
30 #define HTTP_STAT_STR_CONNECTIONS "# HTTPS client connections"
31 #define LIBGNUNET_PLUGIN_TRANSPORT_INIT libgnunet_plugin_transport_https_client_init
32 #define LIBGNUNET_PLUGIN_TRANSPORT_DONE libgnunet_plugin_transport_https_client_done
33 #else
34 #define PLUGIN_NAME "http_client"
35 #define HTTP_STAT_STR_CONNECTIONS "# HTTP client connections"
36 #define LIBGNUNET_PLUGIN_TRANSPORT_INIT libgnunet_plugin_transport_http_client_init
37 #define LIBGNUNET_PLUGIN_TRANSPORT_DONE libgnunet_plugin_transport_http_client_done
38 #endif
39
40 #define VERBOSE_CURL GNUNET_NO
41
42 #define PUT_DISCONNECT_TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 1)
43
44 #define ENABLE_PUT GNUNET_YES
45 #define ENABLE_GET GNUNET_YES
46
47 #include "platform.h"
48 #include "gnunet_util_lib.h"
49 #include "gnunet_protocols.h"
50 #include "gnunet_transport_plugin.h"
51 #include "plugin_transport_http_common.h"
52 #include <curl/curl.h>
53
54
55 #define LOG(kind,...) GNUNET_log_from(kind, PLUGIN_NAME, __VA_ARGS__)
56
57 /**
58  * Encapsulation of all of the state of the plugin.
59  */
60 struct HTTP_Client_Plugin;
61
62 /**
63  * State of a HTTP PUT request
64  */
65 enum HTTP_PUT_REQUEST_STATE
66 {
67   /**
68    *  Just created, not yet connected
69    */
70   H_NOT_CONNECTED,
71
72   /**
73    *  Connected
74    */
75   H_CONNECTED,
76
77   /**
78    *  Paused, nothing to send
79    */
80   H_PAUSED,
81
82   /**
83    * Temporary disconnect in progress due to inactivity
84    */
85   H_TMP_DISCONNECTING,
86
87   /**
88    * Send request while temporary disconnect, reconnect
89    */
90   H_TMP_RECONNECT_REQUIRED,
91
92   /**
93    * Temporarily disconnected
94    */
95   H_TMP_DISCONNECTED,
96
97   /**
98    * Disconnected
99    */
100   H_DISCONNECTED
101 };
102
103 /**
104  *  Message to send using http
105  */
106 struct HTTP_Message
107 {
108   /**
109    * next pointer for double linked list
110    */
111   struct HTTP_Message *next;
112
113   /**
114    * previous pointer for double linked list
115    */
116   struct HTTP_Message *prev;
117
118   /**
119    * buffer containing data to send
120    */
121   char *buf;
122
123   /**
124    * Continuation function to call once the transmission buffer
125    * has again space available.  NULL if there is no
126    * continuation to call.
127    */
128   GNUNET_TRANSPORT_TransmitContinuation transmit_cont;
129
130   /**
131    * Closure for @e transmit_cont.
132    */
133   void *transmit_cont_cls;
134
135   /**
136    * amount of data already sent
137    */
138   size_t pos;
139
140   /**
141    * buffer length
142    */
143   size_t size;
144
145 };
146
147
148 /**
149  * Session handle for HTTP(S) connections.
150  */
151 struct Session;
152
153
154 /**
155  * A request handle
156  *
157  */
158 struct RequestHandle
159 {
160   /**
161    * Current state of this request
162    */
163   enum HTTP_PUT_REQUEST_STATE state;
164
165   /**
166    * The curl easy handle
167    */
168   CURL *easyhandle;
169
170   /**
171    * The related session
172    */
173   struct Session *s;
174 };
175
176
177 /**
178  * Session handle for connections.
179  */
180 struct Session
181 {
182   /**
183    * The URL to connect to
184    */
185   char *url;
186
187   /**
188    * Address
189    */
190   struct GNUNET_HELLO_Address *address;
191
192   /**
193    * Pointer to the global plugin struct.
194    */
195   struct HTTP_Client_Plugin *plugin;
196
197   /**
198    * Handle for the HTTP PUT request.
199    */
200   struct RequestHandle put;
201
202   /**
203    * Handle for the HTTP GET request.
204    */
205   struct RequestHandle get;
206
207   /**
208    * next pointer for double linked list
209    */
210   struct HTTP_Message *msg_head;
211
212   /**
213    * previous pointer for double linked list
214    */
215   struct HTTP_Message *msg_tail;
216
217   /**
218    * Message stream tokenizer for incoming data
219    */
220   struct GNUNET_SERVER_MessageStreamTokenizer *msg_tk;
221
222   /**
223    * Session timeout task
224    */
225   struct GNUNET_SCHEDULER_Task * put_disconnect_task;
226
227   /**
228    * Session timeout task
229    */
230   struct GNUNET_SCHEDULER_Task * timeout_task;
231
232   /**
233    * Task to wake up client receive handle when receiving is allowed again
234    */
235   struct GNUNET_SCHEDULER_Task * recv_wakeup_task;
236
237   /**
238    * Absolute time when to receive data again.
239    * Used for receive throttling.
240    */
241   struct GNUNET_TIME_Absolute next_receive;
242
243   /**
244    * When does this session time out.
245    */
246   struct GNUNET_TIME_Absolute timeout;
247
248   /**
249    * Number of bytes waiting for transmission to this peer.
250    */
251   unsigned long long bytes_in_queue;
252
253   /**
254    * Outbound overhead due to HTTP connection
255    * Add to next message of this session when calling callback
256    */
257   size_t overhead;
258
259   /**
260    * Number of messages waiting for transmission to this peer.
261    */
262   unsigned int msgs_in_queue;
263
264   /**
265    * ATS network type.
266    */
267   enum GNUNET_ATS_Network_Type scope;
268 };
269
270
271 /**
272  * Encapsulation of all of the state of the plugin.
273  */
274 struct HTTP_Client_Plugin
275 {
276   /**
277    * Our environment.
278    */
279   struct GNUNET_TRANSPORT_PluginEnvironment *env;
280
281   /**
282    * Open sessions.
283    */
284   struct GNUNET_CONTAINER_MultiPeerMap *sessions;
285
286   /**
287    * Function to call about session status changes.
288    */
289   GNUNET_TRANSPORT_SessionInfoCallback sic;
290
291   /**
292    * Closure for @e sic.
293    */
294   void *sic_cls;
295
296   /**
297    * Plugin name
298    */
299   char *name;
300
301   /**
302    * Protocol
303    */
304   char *protocol;
305
306   /**
307    * Proxy configuration: hostname or ip of the proxy server
308    */
309   char *proxy_hostname;
310
311   /**
312    * Username for the proxy server
313    */
314   char *proxy_username;
315
316   /**
317    * Password for the proxy server
318    */
319   char *proxy_password;
320
321   /**
322    * cURL Multihandle
323    */
324   CURLM *curl_multi_handle;
325
326   /**
327    * curl perform task
328    */
329   struct GNUNET_SCHEDULER_Task * client_perform_task;
330
331   /**
332    * Type of proxy server:
333    *
334    * Valid values as supported by curl:
335    * CURLPROXY_HTTP, CURLPROXY_HTTP_1_0 CURLPROXY_SOCKS4, CURLPROXY_SOCKS5,
336    * CURLPROXY_SOCKS4A, CURLPROXY_SOCKS5_HOSTNAME
337    */
338   curl_proxytype proxytype;
339
340   /**
341    * Use proxy tunneling:
342    * Tunnel all operations through a given HTTP instead of have the proxy
343    * evaluate the HTTP request
344    *
345    * Default: #GNUNET_NO, #GNUNET_YES experimental
346    */
347   int proxy_use_httpproxytunnel;
348
349   /**
350    * My options to be included in the address
351    */
352   uint32_t options;
353
354   /**
355    * Maximum number of sockets the plugin can use
356    * Each http connections are two requests
357    */
358   unsigned int max_requests;
359
360   /**
361    * Current number of sockets the plugin can use
362    * Each http connections are two requests
363    */
364   unsigned int cur_requests;
365
366   /**
367    * Last used unique HTTP connection tag
368    */
369   uint32_t last_tag;
370
371   /**
372    * use IPv6
373    */
374   uint16_t use_ipv6;
375
376   /**
377    * use IPv4
378    */
379   uint16_t use_ipv4;
380
381   /**
382    * Should we emulate an XHR client for testing?
383    */
384   int emulate_xhr;
385 };
386
387
388 /**
389  * Disconnect a session
390  *
391  * @param cls the `struct HTTP_Client_Plugin *`
392  * @param s session
393  * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
394  */
395 static int
396 http_client_plugin_session_disconnect (void *cls, struct Session *s);
397
398
399 /**
400  * If a session monitor is attached, notify it about the new
401  * session state.
402  *
403  * @param plugin our plugin
404  * @param session session that changed state
405  * @param state new state of the session
406  */
407 static void
408 notify_session_monitor (struct HTTP_Client_Plugin *plugin,
409                         struct Session *session,
410                         enum GNUNET_TRANSPORT_SessionState state)
411 {
412   struct GNUNET_TRANSPORT_SessionInfo info;
413
414   if (NULL == plugin->sic)
415     return;
416   memset (&info, 0, sizeof (info));
417   info.state = state;
418   info.is_inbound = GNUNET_NO;
419   info.num_msg_pending = session->msgs_in_queue;
420   info.num_bytes_pending = session->bytes_in_queue;
421   info.receive_delay = session->next_receive;
422   info.session_timeout = session->timeout;
423   info.address = session->address;
424   plugin->sic (plugin->sic_cls,
425                session,
426                &info);
427 }
428
429
430 /**
431  * Delete session @a s.
432  *
433  * @param s the session to delete
434  */
435 static void
436 client_delete_session (struct Session *s)
437 {
438   struct HTTP_Client_Plugin *plugin = s->plugin;
439   struct HTTP_Message *pos;
440   struct HTTP_Message *next;
441   CURLMcode mret;
442
443   if (NULL != s->timeout_task)
444   {
445     GNUNET_SCHEDULER_cancel (s->timeout_task);
446     s->timeout_task = NULL;
447     s->timeout = GNUNET_TIME_UNIT_ZERO_ABS;
448   }
449   if (NULL != s->put_disconnect_task)
450   {
451     GNUNET_SCHEDULER_cancel (s->put_disconnect_task);
452     s->put_disconnect_task = NULL;
453   }
454   if (NULL != s->recv_wakeup_task)
455   {
456     GNUNET_SCHEDULER_cancel (s->recv_wakeup_task);
457     s->recv_wakeup_task = NULL;
458   }
459   GNUNET_assert (GNUNET_OK ==
460                  GNUNET_CONTAINER_multipeermap_remove (plugin->sessions,
461                                                        &s->address->peer,
462                                                        s));
463   if (NULL != s->put.easyhandle)
464   {
465     LOG (GNUNET_ERROR_TYPE_DEBUG,
466          "Session %p/request %p: disconnecting PUT request to peer `%s'\n",
467          s,
468          s->put.easyhandle,
469          GNUNET_i2s (&s->address->peer));
470
471     /* remove curl handle from multi handle */
472     mret = curl_multi_remove_handle (plugin->curl_multi_handle,
473                                      s->put.easyhandle);
474     GNUNET_break (CURLM_OK == mret);
475     curl_easy_cleanup (s->put.easyhandle);
476     GNUNET_assert (plugin->cur_requests > 0);
477     plugin->cur_requests--;
478     s->put.easyhandle = NULL;
479   }
480   if (NULL != s->get.easyhandle)
481   {
482     LOG (GNUNET_ERROR_TYPE_DEBUG,
483          "Session %p/request %p: disconnecting GET request to peer `%s'\n",
484          s, s->get.easyhandle,
485          GNUNET_i2s (&s->address->peer));
486     /* remove curl handle from multi handle */
487     mret = curl_multi_remove_handle (plugin->curl_multi_handle,
488                                      s->get.easyhandle);
489     GNUNET_break (CURLM_OK == mret);
490     curl_easy_cleanup (s->get.easyhandle);
491     GNUNET_assert (plugin->cur_requests > 0);
492     plugin->cur_requests--;
493     s->get.easyhandle = NULL;
494   }
495
496   GNUNET_STATISTICS_set (plugin->env->stats,
497                          HTTP_STAT_STR_CONNECTIONS,
498                          plugin->cur_requests,
499                          GNUNET_NO);
500   next = s->msg_head;
501   while (NULL != (pos = next))
502   {
503     next = pos->next;
504     GNUNET_CONTAINER_DLL_remove (s->msg_head,
505                                  s->msg_tail,
506                                  pos);
507     GNUNET_assert (0 < s->msgs_in_queue);
508     s->msgs_in_queue--;
509     GNUNET_assert (pos->size <= s->bytes_in_queue);
510     s->bytes_in_queue -= pos->size;
511     if (NULL != pos->transmit_cont)
512       pos->transmit_cont (pos->transmit_cont_cls,
513                           &s->address->peer,
514                           GNUNET_SYSERR,
515                           pos->size,
516                           pos->pos + s->overhead);
517     s->overhead = 0;
518     GNUNET_free (pos);
519   }
520   GNUNET_assert (0 == s->msgs_in_queue);
521   GNUNET_assert (0 == s->bytes_in_queue);
522   notify_session_monitor (plugin,
523                           s,
524                           GNUNET_TRANSPORT_SS_DONE);
525   if (NULL != s->msg_tk)
526   {
527     GNUNET_SERVER_mst_destroy (s->msg_tk);
528     s->msg_tk = NULL;
529   }
530   GNUNET_HELLO_address_free (s->address);
531   GNUNET_free (s->url);
532   GNUNET_free (s);
533 }
534
535
536 /**
537  * Increment session timeout due to activity for session @a s.
538  *
539  * @param s the session
540  */
541 static void
542 client_reschedule_session_timeout (struct Session *s)
543 {
544   GNUNET_assert (NULL != s->timeout_task);
545   s->timeout = GNUNET_TIME_relative_to_absolute (GNUNET_CONSTANTS_IDLE_CONNECTION_TIMEOUT);
546 }
547
548
549 /**
550  * Task performing curl operations
551  *
552  * @param cls plugin as closure
553  * @param tc gnunet scheduler task context
554  */
555 static void
556 client_run (void *cls,
557             const struct GNUNET_SCHEDULER_TaskContext *tc);
558
559
560 /**
561  * Function setting up file descriptors and scheduling task to run
562  *
563  * @param plugin the plugin as closure
564  * @param now schedule task in 1ms, regardless of what curl may say
565  * @return #GNUNET_SYSERR for hard failure, #GNUNET_OK for ok
566  */
567 static int
568 client_schedule (struct HTTP_Client_Plugin *plugin,
569                  int now)
570 {
571   fd_set rs;
572   fd_set ws;
573   fd_set es;
574   int max;
575   struct GNUNET_NETWORK_FDSet *grs;
576   struct GNUNET_NETWORK_FDSet *gws;
577   long to;
578   CURLMcode mret;
579   struct GNUNET_TIME_Relative timeout;
580
581   /* Cancel previous scheduled task */
582   if (plugin->client_perform_task != NULL)
583   {
584     GNUNET_SCHEDULER_cancel (plugin->client_perform_task);
585     plugin->client_perform_task = NULL;
586   }
587   max = -1;
588   FD_ZERO (&rs);
589   FD_ZERO (&ws);
590   FD_ZERO (&es);
591   mret = curl_multi_fdset (plugin->curl_multi_handle, &rs, &ws, &es, &max);
592   if (mret != CURLM_OK)
593   {
594     LOG (GNUNET_ERROR_TYPE_ERROR,
595          _("%s failed at %s:%d: `%s'\n"),
596          "curl_multi_fdset",
597          __FILE__,
598          __LINE__,
599          curl_multi_strerror (mret));
600     return GNUNET_SYSERR;
601   }
602   mret = curl_multi_timeout (plugin->curl_multi_handle, &to);
603   if (-1 == to)
604     timeout = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 1);
605   else
606     timeout = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, to);
607   if (now == GNUNET_YES)
608     timeout = GNUNET_TIME_UNIT_MILLISECONDS;
609
610   if (CURLM_OK != mret)
611   {
612     LOG (GNUNET_ERROR_TYPE_ERROR,
613                 _("%s failed at %s:%d: `%s'\n"),
614                 "curl_multi_timeout", __FILE__, __LINE__,
615                 curl_multi_strerror (mret));
616     return GNUNET_SYSERR;
617   }
618
619   grs = GNUNET_NETWORK_fdset_create ();
620   gws = GNUNET_NETWORK_fdset_create ();
621   GNUNET_NETWORK_fdset_copy_native (grs, &rs, max + 1);
622   GNUNET_NETWORK_fdset_copy_native (gws, &ws, max + 1);
623
624   /* Schedule task to run when select is ready to read or write */
625   plugin->client_perform_task =
626       GNUNET_SCHEDULER_add_select (GNUNET_SCHEDULER_PRIORITY_DEFAULT,
627                                    timeout, grs, gws,
628                                    &client_run, plugin);
629   GNUNET_NETWORK_fdset_destroy (gws);
630   GNUNET_NETWORK_fdset_destroy (grs);
631   return GNUNET_OK;
632 }
633
634
635 #if VERBOSE_CURL
636 /**
637  * Loggging function
638  *
639  * @param curl the curl easy handle
640  * @param type message type
641  * @param data data to log, NOT a 0-terminated string
642  * @param size data length
643  * @param cls the closure
644  * @return always 0
645  */
646 static int
647 client_log (CURL *curl,
648             curl_infotype type,
649             const char *data,
650             size_t size,
651             void *cls)
652 {
653   struct RequestHandle *ch = cls;
654   const char *ttype = "UNSPECIFIED";
655   char text[size + 2];
656
657   if (! ((CURLINFO_TEXT == type) ||
658          (CURLINFO_HEADER_IN == type) ||
659          (CURLINFO_HEADER_OUT == type)))
660     return 0;
661   switch (type)
662   {
663   case CURLINFO_TEXT:
664     ttype = "TEXT";
665     break;
666   case CURLINFO_HEADER_IN:
667     ttype = "HEADER_IN";
668     break;
669   case CURLINFO_HEADER_OUT:
670     ttype = "HEADER_OUT";
671     /* Overhead*/
672     GNUNET_assert (NULL != ch);
673     GNUNET_assert (NULL != ch->easyhandle);
674     GNUNET_assert (NULL != ch->s);
675     ch->s->overhead += size;
676     break;
677   default:
678     ttype = "UNSPECIFIED";
679     break;
680   }
681   memcpy (text, data, size);
682   if (text[size - 1] == '\n')
683   {
684     text[size] = '\0';
685   }
686   else
687   {
688     text[size] = '\n';
689     text[size + 1] = '\0';
690   }
691   LOG (GNUNET_ERROR_TYPE_DEBUG,
692        "Request %p %s: %s",
693        ch->easyhandle,
694        ttype,
695        text);
696   return 0;
697 }
698 #endif
699
700 /**
701  * Connect GET request
702  *
703  * @param s the session to connect
704  * @return #GNUNET_OK on success, #GNUNET_SYSERR otherwise
705  */
706 static int
707 client_connect_get (struct Session *s);
708
709
710 /**
711  * Connect a HTTP put request
712  *
713  * @param s the session to connect
714  * @return #GNUNET_SYSERR for hard failure, #GNUNET_OK for success
715  */
716 static int
717 client_connect_put (struct Session *s);
718
719
720 /**
721  * Function that can be used by the transport service to transmit
722  * a message using the plugin.   Note that in the case of a
723  * peer disconnecting, the continuation MUST be called
724  * prior to the disconnect notification itself.  This function
725  * will be called with this peer's HELLO message to initiate
726  * a fresh connection to another peer.
727  *
728  * @param cls closure
729  * @param s which session must be used
730  * @param msgbuf the message to transmit
731  * @param msgbuf_size number of bytes in @a msgbuf
732  * @param priority how important is the message (most plugins will
733  *                 ignore message priority and just FIFO)
734  * @param to how long to wait at most for the transmission (does not
735  *                require plugins to discard the message after the timeout,
736  *                just advisory for the desired delay; most plugins will ignore
737  *                this as well)
738  * @param cont continuation to call once the message has
739  *        been transmitted (or if the transport is ready
740  *        for the next transmission call; or if the
741  *        peer disconnected...); can be NULL
742  * @param cont_cls closure for @a cont
743  * @return number of bytes used (on the physical network, with overheads);
744  *         -1 on hard errors (i.e. address invalid); 0 is a legal value
745  *         and does NOT mean that the message was not transmitted (DV)
746  */
747 static ssize_t
748 http_client_plugin_send (void *cls,
749                          struct Session *s,
750                          const char *msgbuf,
751                          size_t msgbuf_size,
752                          unsigned int priority,
753                          struct GNUNET_TIME_Relative to,
754                          GNUNET_TRANSPORT_TransmitContinuation cont,
755                          void *cont_cls)
756 {
757   struct HTTP_Client_Plugin *plugin = cls;
758   struct HTTP_Message *msg;
759   char *stat_txt;
760
761   LOG (GNUNET_ERROR_TYPE_DEBUG,
762        "Session %p/request %p: Sending message with %u to peer `%s' \n",
763        s,
764        s->put.easyhandle,
765        msgbuf_size,
766        GNUNET_i2s (&s->address->peer));
767
768   /* create new message and schedule */
769   msg = GNUNET_malloc (sizeof (struct HTTP_Message) + msgbuf_size);
770   msg->size = msgbuf_size;
771   msg->buf = (char *) &msg[1];
772   msg->transmit_cont = cont;
773   msg->transmit_cont_cls = cont_cls;
774   memcpy (msg->buf,
775           msgbuf,
776           msgbuf_size);
777   GNUNET_CONTAINER_DLL_insert_tail (s->msg_head,
778                                     s->msg_tail,
779                                     msg);
780   s->msgs_in_queue++;
781   s->bytes_in_queue += msg->size;
782
783   GNUNET_asprintf (&stat_txt,
784                    "# bytes currently in %s_client buffers",
785                    plugin->protocol);
786   GNUNET_STATISTICS_update (plugin->env->stats,
787                             stat_txt, msgbuf_size, GNUNET_NO);
788   GNUNET_free (stat_txt);
789   notify_session_monitor (plugin,
790                           s,
791                           GNUNET_TRANSPORT_SS_UPDATE);
792   if (H_TMP_DISCONNECTING == s->put.state)
793   {
794     /* PUT request is currently getting disconnected */
795     s->put.state = H_TMP_RECONNECT_REQUIRED;
796     LOG (GNUNET_ERROR_TYPE_DEBUG,
797          "Session %p/request %p: currently disconnecting, reconnecting immediately\n",
798          s,
799          s->put.easyhandle);
800     return msgbuf_size;
801   }
802   if (H_PAUSED == s->put.state)
803   {
804     /* PUT request was paused, unpause */
805     GNUNET_assert (s->put_disconnect_task != NULL);
806     GNUNET_SCHEDULER_cancel (s->put_disconnect_task);
807     s->put_disconnect_task = NULL;
808     LOG (GNUNET_ERROR_TYPE_DEBUG,
809          "Session %p/request %p: unpausing request\n",
810          s, s->put.easyhandle);
811     s->put.state = H_CONNECTED;
812     if (NULL != s->put.easyhandle)
813       curl_easy_pause (s->put.easyhandle, CURLPAUSE_CONT);
814   }
815   else if (H_TMP_DISCONNECTED == s->put.state)
816   {
817     /* PUT request was disconnected, reconnect */
818     LOG (GNUNET_ERROR_TYPE_DEBUG, "Session %p: Reconnecting PUT request\n", s);
819     GNUNET_break (NULL == s->put.easyhandle);
820     if (GNUNET_SYSERR == client_connect_put (s))
821     {
822       /* Could not reconnect */
823       http_client_plugin_session_disconnect (plugin, s);
824       return GNUNET_SYSERR;
825     }
826   }
827   client_schedule (s->plugin, GNUNET_YES);
828   return msgbuf_size;
829 }
830
831
832 /**
833  * Disconnect a session
834  *
835  * @param cls the `struct HTTP_Client_Plugin *`
836  * @param s session
837  * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
838  */
839 static int
840 http_client_plugin_session_disconnect (void *cls,
841                                        struct Session *s)
842 {
843   struct HTTP_Client_Plugin *plugin = cls;
844
845   LOG (GNUNET_ERROR_TYPE_DEBUG,
846        "Session %p: notifying transport about ending session\n",
847        s);
848   plugin->env->session_end (plugin->env->cls,
849                             s->address,
850                             s);
851   client_delete_session (s);
852
853   /* Re-schedule since handles have changed */
854   if (NULL != plugin->client_perform_task)
855   {
856     GNUNET_SCHEDULER_cancel (plugin->client_perform_task);
857     plugin->client_perform_task = NULL;
858   }
859   client_schedule (plugin, GNUNET_YES);
860
861   return GNUNET_OK;
862 }
863
864
865 /**
866  * Function that is called to get the keepalive factor.
867  * #GNUNET_CONSTANTS_IDLE_CONNECTION_TIMEOUT is divided by this number to
868  * calculate the interval between keepalive packets.
869  *
870  * @param cls closure with the `struct Plugin`
871  * @return keepalive factor
872  */
873 static unsigned int
874 http_client_query_keepalive_factor (void *cls)
875 {
876   return 3;
877 }
878
879
880 /**
881  * Callback to destroys all sessions on exit.
882  *
883  * @param cls the `struct HTTP_Client_Plugin *`
884  * @param peer identity of the peer
885  * @param value the `struct Session *`
886  * @return #GNUNET_OK (continue iterating)
887  */
888 static int
889 destroy_session_cb (void *cls,
890                     const struct GNUNET_PeerIdentity *peer,
891                     void *value)
892 {
893   struct HTTP_Client_Plugin *plugin = cls;
894   struct Session *session = value;
895
896   http_client_plugin_session_disconnect (plugin, session);
897   return GNUNET_OK;
898 }
899
900
901 /**
902  * Function that can be used to force the plugin to disconnect
903  * from the given peer and cancel all previous transmissions
904  * (and their continuationc).
905  *
906  * @param cls closure
907  * @param target peer from which to disconnect
908  */
909 static void
910 http_client_plugin_peer_disconnect (void *cls,
911                                     const struct GNUNET_PeerIdentity *target)
912 {
913   struct HTTP_Client_Plugin *plugin = cls;
914
915   LOG (GNUNET_ERROR_TYPE_DEBUG,
916        "Transport tells me to disconnect `%s'\n",
917        GNUNET_i2s (target));
918   GNUNET_CONTAINER_multipeermap_get_multiple (plugin->sessions,
919                                               target,
920                                               &destroy_session_cb,
921                                               plugin);
922 }
923
924
925 /**
926  * Closure for #session_lookup_client_by_address().
927  */
928 struct SessionClientCtx
929 {
930   /**
931    * Address we are looking for.
932    */
933   const struct GNUNET_HELLO_Address *address;
934
935   /**
936    * Session that was found.
937    */
938   struct Session *ret;
939 };
940
941
942 /**
943  * Locate the seession object for a given address.
944  *
945  * @param cls the `struct SessionClientCtx *`
946  * @param key peer identity
947  * @param value the `struct Session` to check
948  * @return #GNUNET_NO if found, #GNUNET_OK if not
949  */
950 static int
951 session_lookup_client_by_address (void *cls,
952                                   const struct GNUNET_PeerIdentity *key,
953                                   void *value)
954 {
955   struct SessionClientCtx *sc_ctx = cls;
956   struct Session *s = value;
957
958   if (0 == GNUNET_HELLO_address_cmp (sc_ctx->address,
959                                      s->address))
960   {
961     sc_ctx->ret = s;
962     return GNUNET_NO;
963   }
964   return GNUNET_YES;
965 }
966
967
968 /**
969  * Check if a sessions exists for an specific address
970  *
971  * @param plugin the plugin
972  * @param address the address
973  * @return the session or NULL
974  */
975 static struct Session *
976 client_lookup_session (struct HTTP_Client_Plugin *plugin,
977                        const struct GNUNET_HELLO_Address *address)
978 {
979   struct SessionClientCtx sc_ctx;
980
981   sc_ctx.address = address;
982   sc_ctx.ret = NULL;
983   GNUNET_CONTAINER_multipeermap_iterate (plugin->sessions,
984                                          &session_lookup_client_by_address,
985                                          &sc_ctx);
986   return sc_ctx.ret;
987 }
988
989
990 /**
991  * When we have nothing to transmit, we pause the HTTP PUT
992  * after a while (so that gnurl stops asking).  This task
993  * is the delayed task that actually disconnects the PUT.
994  *
995  * @param cls the `struct Session *` with the put
996  * @param tc scheduler context
997  */
998 static void
999 client_put_disconnect (void *cls,
1000                        const struct GNUNET_SCHEDULER_TaskContext *tc)
1001 {
1002   struct Session *s = cls;
1003
1004   s->put_disconnect_task = NULL;
1005   LOG (GNUNET_ERROR_TYPE_DEBUG,
1006        "Session %p/request %p: will be disconnected due to no activity\n",
1007        s, s->put.easyhandle);
1008   s->put.state = H_TMP_DISCONNECTING;
1009   if (NULL != s->put.easyhandle)
1010     curl_easy_pause (s->put.easyhandle,
1011                      CURLPAUSE_CONT);
1012   client_schedule (s->plugin, GNUNET_YES);
1013 }
1014
1015
1016 /**
1017  * Callback method used with libcurl
1018  * Method is called when libcurl needs to read data during sending
1019  *
1020  * @param stream pointer where to write data
1021  * @param size size of an individual element
1022  * @param nmemb count of elements that can be written to the buffer
1023  * @param cls our `struct Session`
1024  * @return bytes written to stream, returning 0 will terminate request!
1025  */
1026 static size_t
1027 client_send_cb (void *stream,
1028                 size_t size,
1029                 size_t nmemb,
1030                 void *cls)
1031 {
1032   struct Session *s = cls;
1033   struct HTTP_Client_Plugin *plugin = s->plugin;
1034   struct HTTP_Message *msg = s->msg_head;
1035   size_t len;
1036   char *stat_txt;
1037
1038   if (H_TMP_DISCONNECTING == s->put.state)
1039   {
1040     LOG (GNUNET_ERROR_TYPE_DEBUG,
1041          "Session %p/request %p: disconnect due to inactivity\n",
1042          s, s->put.easyhandle);
1043     return 0;
1044   }
1045
1046   if (NULL == msg)
1047   {
1048     if (GNUNET_YES == plugin->emulate_xhr)
1049     {
1050       LOG (GNUNET_ERROR_TYPE_DEBUG,
1051            "Session %p/request %p: PUT request finished\n",
1052            s,
1053            s->put.easyhandle);
1054       s->put.state = H_TMP_DISCONNECTING;
1055       return 0;
1056     }
1057
1058     /* We have nothing to send, so pause PUT request */
1059     LOG (GNUNET_ERROR_TYPE_DEBUG,
1060          "Session %p/request %p: nothing to send, suspending\n",
1061          s,
1062          s->put.easyhandle);
1063     s->put_disconnect_task = GNUNET_SCHEDULER_add_delayed (PUT_DISCONNECT_TIMEOUT,
1064         &client_put_disconnect, s);
1065     s->put.state = H_PAUSED;
1066     return CURL_READFUNC_PAUSE;
1067   }
1068   /* data to send */
1069   GNUNET_assert (msg->pos < msg->size);
1070   /* calculate how much fits in buffer */
1071   len = GNUNET_MIN (msg->size - msg->pos,
1072                     size * nmemb);
1073   memcpy (stream, &msg->buf[msg->pos], len);
1074   msg->pos += len;
1075   if (msg->pos == msg->size)
1076   {
1077     LOG (GNUNET_ERROR_TYPE_DEBUG,
1078          "Session %p/request %p: sent message with %u bytes sent, removing message from queue\n",
1079          s,
1080          s->put.easyhandle,
1081          msg->size,
1082          msg->pos);
1083     /* Calling transmit continuation  */
1084     GNUNET_CONTAINER_DLL_remove (s->msg_head,
1085                                  s->msg_tail,
1086                                  msg);
1087     GNUNET_assert (0 < s->msgs_in_queue);
1088     s->msgs_in_queue--;
1089     GNUNET_assert (msg->size <= s->bytes_in_queue);
1090     s->bytes_in_queue -= msg->size;
1091     if (NULL != msg->transmit_cont)
1092       msg->transmit_cont (msg->transmit_cont_cls,
1093                           &s->address->peer,
1094                           GNUNET_OK,
1095                           msg->size,
1096                           msg->size + s->overhead);
1097     s->overhead = 0;
1098     GNUNET_free (msg);
1099   }
1100   notify_session_monitor (plugin,
1101                           s,
1102                           GNUNET_TRANSPORT_SS_UPDATE);
1103   GNUNET_asprintf (&stat_txt,
1104                    "# bytes currently in %s_client buffers",
1105                    plugin->protocol);
1106   GNUNET_STATISTICS_update (plugin->env->stats,
1107                             stat_txt,
1108                             - len,
1109                             GNUNET_NO);
1110   GNUNET_free (stat_txt);
1111   GNUNET_asprintf (&stat_txt,
1112                    "# bytes transmitted via %s_client",
1113                    plugin->protocol);
1114   GNUNET_STATISTICS_update (plugin->env->stats,
1115                             stat_txt,
1116                             len,
1117                             GNUNET_NO);
1118   GNUNET_free (stat_txt);
1119   return len;
1120 }
1121
1122
1123 /**
1124  * Wake up a curl handle which was suspended
1125  *
1126  * @param cls the session
1127  * @param tc task context
1128  */
1129 static void
1130 client_wake_up (void *cls,
1131                 const struct GNUNET_SCHEDULER_TaskContext *tc)
1132 {
1133   struct Session *s = cls;
1134
1135   s->recv_wakeup_task = NULL;
1136   if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN))
1137     return;
1138   LOG (GNUNET_ERROR_TYPE_DEBUG,
1139        "Session %p/request %p: Waking up GET handle\n",
1140        s, s->get.easyhandle);
1141   if (H_PAUSED == s->put.state)
1142   {
1143     /* PUT request was paused, unpause */
1144     GNUNET_assert (s->put_disconnect_task != NULL);
1145     GNUNET_SCHEDULER_cancel (s->put_disconnect_task);
1146     s->put_disconnect_task = NULL;
1147     s->put.state = H_CONNECTED;
1148     if (NULL != s->put.easyhandle)
1149       curl_easy_pause (s->put.easyhandle, CURLPAUSE_CONT);
1150   }
1151   if (NULL != s->get.easyhandle)
1152     curl_easy_pause (s->get.easyhandle, CURLPAUSE_CONT);
1153 }
1154
1155
1156 /**
1157  * Callback for message stream tokenizer
1158  *
1159  * @param cls the session
1160  * @param client not used
1161  * @param message the message received
1162  * @return always #GNUNET_OK
1163  */
1164 static int
1165 client_receive_mst_cb (void *cls,
1166                        void *client,
1167                        const struct GNUNET_MessageHeader *message)
1168 {
1169   struct Session *s = cls;
1170   struct HTTP_Client_Plugin *plugin;
1171   struct GNUNET_TIME_Relative delay;
1172   char *stat_txt;
1173
1174   plugin = s->plugin;
1175   delay = s->plugin->env->receive (plugin->env->cls,
1176                                    s->address,
1177                                    s,
1178                                    message);
1179   GNUNET_asprintf (&stat_txt,
1180                    "# bytes received via %s_client",
1181                    plugin->protocol);
1182   GNUNET_STATISTICS_update (plugin->env->stats,
1183                             stat_txt,
1184                             ntohs (message->size),
1185                             GNUNET_NO);
1186   GNUNET_free (stat_txt);
1187
1188   s->next_receive = GNUNET_TIME_relative_to_absolute (delay);
1189   if (GNUNET_TIME_absolute_get ().abs_value_us < s->next_receive.abs_value_us)
1190   {
1191     LOG (GNUNET_ERROR_TYPE_DEBUG,
1192          "Client: peer `%s' address `%s' next read delayed for %s\n",
1193          GNUNET_i2s (&s->address->peer),
1194          http_common_plugin_address_to_string (s->plugin->protocol,
1195                                                s->address->address,
1196                                                s->address->address_length),
1197          GNUNET_STRINGS_relative_time_to_string (delay,
1198                                                  GNUNET_YES));
1199   }
1200   client_reschedule_session_timeout (s);
1201   return GNUNET_OK;
1202 }
1203
1204
1205 /**
1206  * Callback method used with libcurl when data for a PUT request are
1207  * received.  We do not expect data here, so we just discard it.
1208  *
1209  * @param stream pointer where to write data
1210  * @param size size of an individual element
1211  * @param nmemb count of elements that can be written to the buffer
1212  * @param cls destination pointer, passed to the libcurl handle
1213  * @return bytes read from stream
1214  */
1215 static size_t
1216 client_receive_put (void *stream,
1217                     size_t size,
1218                     size_t nmemb,
1219                     void *cls)
1220 {
1221   return size * nmemb;
1222 }
1223
1224
1225 /**
1226  * Callback method used with libcurl when data for a GET request are
1227  * received. Forward to MST
1228  *
1229  * @param stream pointer where to write data
1230  * @param size size of an individual element
1231  * @param nmemb count of elements that can be written to the buffer
1232  * @param cls destination pointer, passed to the libcurl handle
1233  * @return bytes read from stream
1234  */
1235 static size_t
1236 client_receive (void *stream,
1237                 size_t size,
1238                 size_t nmemb,
1239                 void *cls)
1240 {
1241   struct Session *s = cls;
1242   struct GNUNET_TIME_Absolute now;
1243   size_t len = size * nmemb;
1244
1245   LOG (GNUNET_ERROR_TYPE_DEBUG,
1246        "Session %p / request %p: Received %u bytes from peer `%s'\n",
1247        s,
1248        s->get.easyhandle,
1249        len,
1250        GNUNET_i2s (&s->address->peer));
1251   now = GNUNET_TIME_absolute_get ();
1252   if (now.abs_value_us < s->next_receive.abs_value_us)
1253   {
1254     struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
1255     struct GNUNET_TIME_Relative delta
1256       = GNUNET_TIME_absolute_get_difference (now, s->next_receive);
1257
1258     LOG (GNUNET_ERROR_TYPE_DEBUG,
1259          "Session %p / request %p: No inbound bandwidth available! Next read was delayed for %s\n",
1260          s,
1261          s->get.easyhandle,
1262          GNUNET_STRINGS_relative_time_to_string (delta,
1263                                                  GNUNET_YES));
1264     if (s->recv_wakeup_task != NULL)
1265     {
1266       GNUNET_SCHEDULER_cancel (s->recv_wakeup_task);
1267       s->recv_wakeup_task = NULL;
1268     }
1269     s->recv_wakeup_task
1270       = GNUNET_SCHEDULER_add_delayed (delta,
1271                                       &client_wake_up,
1272                                       s);
1273     return CURL_WRITEFUNC_PAUSE;
1274   }
1275   if (NULL == s->msg_tk)
1276     s->msg_tk = GNUNET_SERVER_mst_create (&client_receive_mst_cb,
1277                                           s);
1278   GNUNET_SERVER_mst_receive (s->msg_tk,
1279                              s,
1280                              stream,
1281                              len,
1282                              GNUNET_NO,
1283                              GNUNET_NO);
1284   return len;
1285 }
1286
1287
1288 /**
1289  * Task performing curl operations
1290  *
1291  * @param cls plugin as closure
1292  * @param tc scheduler task context
1293  */
1294 static void
1295 client_run (void *cls,
1296             const struct GNUNET_SCHEDULER_TaskContext *tc)
1297 {
1298   struct HTTP_Client_Plugin *plugin = cls;
1299   int running;
1300   long http_statuscode;
1301   CURLMcode mret;
1302   CURLMsg *msg;
1303   int put_request; /* GNUNET_YES if easy handle is put, GNUNET_NO for get */
1304   int msgs_left;
1305
1306   plugin->client_perform_task = NULL;
1307   if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN))
1308     return;
1309
1310   /* While data are available or timeouts occured */
1311   do
1312   {
1313     running = 0;
1314     /* Perform operations for all handles */
1315     mret = curl_multi_perform (plugin->curl_multi_handle, &running);
1316
1317     /* Get additional information for all handles */
1318     while (NULL != (msg = curl_multi_info_read (plugin->curl_multi_handle, &msgs_left)))
1319     {
1320       CURL *easy_h = msg->easy_handle;
1321       struct Session *s = NULL;
1322       char *d = NULL; /* curl requires 'd' to be a 'char *' */
1323
1324       GNUNET_assert (NULL != easy_h);
1325
1326       /* Obtain session from easy handle */
1327       GNUNET_assert (CURLE_OK == curl_easy_getinfo (easy_h, CURLINFO_PRIVATE, &d));
1328       s = (struct Session *) d;
1329       GNUNET_assert (NULL != s);
1330
1331       if (msg->msg != CURLMSG_DONE)
1332         continue; /* This should not happen */
1333
1334       /* Get HTTP response code */
1335       GNUNET_break (CURLE_OK == curl_easy_getinfo (easy_h,
1336           CURLINFO_RESPONSE_CODE, &http_statuscode));
1337
1338       if (easy_h == s->put.easyhandle)
1339         put_request = GNUNET_YES;
1340       else
1341         put_request = GNUNET_NO;
1342
1343       /* Log status of terminated request */
1344       if  ((0 != msg->data.result) || (http_statuscode != 200))
1345         LOG (GNUNET_ERROR_TYPE_DEBUG,
1346              "Session %p/request %p: %s request to `%s' ended with status %i reason %i: `%s'\n",
1347              s, msg->easy_handle,
1348              (GNUNET_YES == put_request) ? "PUT" : "GET",
1349              GNUNET_i2s (&s->address->peer),
1350              http_statuscode,
1351              msg->data.result,
1352              curl_easy_strerror (msg->data.result));
1353       else
1354         LOG (GNUNET_ERROR_TYPE_DEBUG,
1355              "Session %p/request %p: %s request to `%s' ended normal\n",
1356              s, msg->easy_handle,
1357              (GNUNET_YES == put_request) ? "PUT" : "GET",
1358              GNUNET_i2s (&s->address->peer));
1359
1360       /* Remove easy handle from multi handle */
1361       curl_multi_remove_handle (plugin->curl_multi_handle, easy_h);
1362
1363       /* Clean up easy handle */
1364       curl_easy_cleanup (easy_h);
1365
1366       /* Remove information */
1367       GNUNET_assert (plugin->cur_requests > 0);
1368       plugin->cur_requests--;
1369       LOG  (GNUNET_ERROR_TYPE_INFO,
1370           "%s request to %s done, number of requests decreased to %u\n",
1371           (GNUNET_YES == put_request) ? "PUT" : "GET",
1372           s->url,
1373           plugin->cur_requests);
1374
1375       if (GNUNET_YES == put_request)
1376       {
1377         /* Clean up a PUT request */
1378         s->put.easyhandle = NULL;
1379         s->put.s = NULL;
1380
1381         switch (s->put.state) {
1382           case H_NOT_CONNECTED:
1383           case H_DISCONNECTED:
1384           case H_TMP_DISCONNECTED:
1385             /* This must not happen */
1386             GNUNET_break (0);
1387             break;
1388           case H_TMP_RECONNECT_REQUIRED:
1389             /* Transport called send while disconnect in progess, reconnect */
1390             if (GNUNET_SYSERR == client_connect_put (s))
1391             {
1392               /* Reconnect failed, disconnect session */
1393               http_client_plugin_session_disconnect (plugin, s);
1394             }
1395             break;
1396           case H_TMP_DISCONNECTING:
1397             /* PUT gets temporarily disconnected */
1398             s->put.state = H_TMP_DISCONNECTED;
1399             break;
1400           case H_PAUSED:
1401           case H_CONNECTED:
1402             /* PUT gets permanently disconnected */
1403             s->put.state = H_DISCONNECTED;
1404             http_client_plugin_session_disconnect (plugin, s);
1405             break;
1406           default:
1407             GNUNET_break (0);
1408             break;
1409         }
1410       }
1411       else if (GNUNET_NO == put_request)
1412       {
1413         /* Clean up a GET request */
1414         s->get.easyhandle = NULL;
1415         s->get.s = NULL;
1416
1417         /* If we are emulating an XHR client we need to make another GET
1418          * request.
1419          */
1420         if (GNUNET_YES == plugin->emulate_xhr)
1421         {
1422           if (GNUNET_SYSERR == client_connect_get (s))
1423             http_client_plugin_session_disconnect (plugin, s);
1424         }
1425         else
1426         {
1427           /* GET request was terminated, so disconnect session */
1428           http_client_plugin_session_disconnect (plugin, s);
1429         }
1430       }
1431       else
1432         GNUNET_break (0); /* Must not happen */
1433
1434       GNUNET_STATISTICS_set (plugin->env->stats,
1435                              HTTP_STAT_STR_CONNECTIONS,
1436                              plugin->cur_requests,
1437                              GNUNET_NO);
1438     }
1439   }
1440   while (mret == CURLM_CALL_MULTI_PERFORM);
1441   client_schedule (plugin, GNUNET_NO);
1442 }
1443
1444
1445 #ifdef TCP_STEALTH
1446 /**
1447  * Open TCP socket with TCP STEALTH enabled.
1448  *
1449  * @param clientp our `struct Session *`
1450  * @param purpose why does curl want to open a socket
1451  * @param address what kind of socket does curl want to have opened?
1452  * @return opened socket
1453  */
1454 static curl_socket_t
1455 open_tcp_stealth_socket_cb (void *clientp,
1456                             curlsocktype purpose,
1457                             struct curl_sockaddr *address)
1458 {
1459   struct Session *s = clientp;
1460   int ret;
1461
1462   switch (purpose)
1463   {
1464   case CURLSOCKTYPE_IPCXN:
1465     ret = socket (address->family,
1466                   address->socktype,
1467                   address->protocol);
1468     if (-1 == ret)
1469       return CURL_SOCKET_BAD;
1470     if ( ( (SOCK_STREAM != address->socktype) ||
1471            ( (0 != address->protocol) &&
1472              (IPPROTO_TCP != address->protocol))) )
1473       return (curl_socket_t) ret;
1474     if ( (0 != setsockopt (ret,
1475                            IPPROTO_TCP,
1476                            TCP_STEALTH,
1477                            &s->address->peer,
1478                            sizeof (struct GNUNET_PeerIdentity))) )
1479     {
1480       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1481                   _("TCP_STEALTH not supported on this platform.\n"));
1482       (void) close (ret);
1483       return CURL_SOCKET_BAD;
1484     }
1485     return (curl_socket_t) ret;
1486   case CURLSOCKTYPE_ACCEPT:
1487     GNUNET_break (0);
1488     return CURL_SOCKET_BAD;
1489     break;
1490   case CURLSOCKTYPE_LAST:
1491     GNUNET_break (0);
1492     return CURL_SOCKET_BAD;
1493   default:
1494     GNUNET_break (0);
1495     return CURL_SOCKET_BAD;
1496   }
1497 }
1498 #endif
1499
1500
1501 /**
1502  * Connect GET request for a session
1503  *
1504  * @param s the session to connect
1505  * @return #GNUNET_OK on success, #GNUNET_SYSERR otherwise
1506  */
1507 static int
1508 client_connect_get (struct Session *s)
1509 {
1510   CURLMcode mret;
1511   struct HttpAddress *ha;
1512   uint32_t options;
1513
1514   ha = (struct HttpAddress *) s->address->address;
1515   options = ntohl (ha->options);
1516   /* create get request */
1517   s->get.easyhandle = curl_easy_init ();
1518   s->get.s = s;
1519   if (0 != (options & HTTP_OPTIONS_TCP_STEALTH))
1520   {
1521 #ifdef TCP_STEALTH
1522     curl_easy_setopt (s->get.easyhandle,
1523                       CURLOPT_OPENSOCKETFUNCTION,
1524                       &open_tcp_stealth_socket_cb);
1525     curl_easy_setopt (s->get.easyhandle,
1526                       CURLOPT_OPENSOCKETDATA,
1527                       s);
1528 #else
1529     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1530                 "Cannot connect, TCP STEALTH needed and not supported by kernel.\n");
1531     curl_easy_cleanup (s->get.easyhandle);
1532     s->get.easyhandle = NULL;
1533     s->get.s = NULL;
1534     return GNUNET_SYSERR;
1535 #endif
1536   }
1537
1538 #if VERBOSE_CURL
1539   curl_easy_setopt (s->get.easyhandle,
1540                     CURLOPT_VERBOSE,
1541                     1L);
1542   curl_easy_setopt (s->get.easyhandle,
1543                     CURLOPT_DEBUGFUNCTION,
1544                     &client_log);
1545   curl_easy_setopt (s->get.easyhandle,
1546                     CURLOPT_DEBUGDATA,
1547                     &s->get);
1548 #endif
1549 #if BUILD_HTTPS
1550   curl_easy_setopt (s->get.easyhandle, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1);
1551   {
1552     if (HTTP_OPTIONS_VERIFY_CERTIFICATE ==
1553         (options & HTTP_OPTIONS_VERIFY_CERTIFICATE))
1554     {
1555       curl_easy_setopt (s->get.easyhandle,
1556                         CURLOPT_SSL_VERIFYPEER, 1L);
1557       curl_easy_setopt (s->get.easyhandle,
1558                         CURLOPT_SSL_VERIFYHOST,
1559                         2L);
1560     }
1561     else
1562     {
1563       curl_easy_setopt (s->get.easyhandle,
1564                         CURLOPT_SSL_VERIFYPEER,
1565                         0L);
1566       curl_easy_setopt (s->get.easyhandle,
1567                         CURLOPT_SSL_VERIFYHOST,
1568                         0L);
1569     }
1570   }
1571   curl_easy_setopt (s->get.easyhandle,
1572                     CURLOPT_PROTOCOLS,
1573                     CURLPROTO_HTTPS);
1574   curl_easy_setopt (s->get.easyhandle,
1575                     CURLOPT_REDIR_PROTOCOLS,
1576                     CURLPROTO_HTTPS);
1577 #else
1578   curl_easy_setopt (s->get.easyhandle,
1579                     CURLOPT_PROTOCOLS,
1580                     CURLPROTO_HTTP);
1581   curl_easy_setopt (s->get.easyhandle,
1582                     CURLOPT_REDIR_PROTOCOLS,
1583                     CURLPROTO_HTTP);
1584 #endif
1585
1586   if (NULL != s->plugin->proxy_hostname)
1587   {
1588     curl_easy_setopt (s->get.easyhandle,
1589                       CURLOPT_PROXY,
1590                       s->plugin->proxy_hostname);
1591     curl_easy_setopt (s->get.easyhandle,
1592                       CURLOPT_PROXYTYPE,
1593                       s->plugin->proxytype);
1594     if (NULL != s->plugin->proxy_username)
1595       curl_easy_setopt (s->get.easyhandle,
1596                         CURLOPT_PROXYUSERNAME,
1597                         s->plugin->proxy_username);
1598     if (NULL != s->plugin->proxy_password)
1599       curl_easy_setopt (s->get.easyhandle,
1600                         CURLOPT_PROXYPASSWORD,
1601                         s->plugin->proxy_password);
1602     if (GNUNET_YES == s->plugin->proxy_use_httpproxytunnel)
1603       curl_easy_setopt (s->get.easyhandle,
1604                         CURLOPT_HTTPPROXYTUNNEL,
1605                         s->plugin->proxy_use_httpproxytunnel);
1606   }
1607
1608   if (GNUNET_YES == s->plugin->emulate_xhr)
1609   {
1610     char *url;
1611
1612     GNUNET_asprintf (&url,
1613                      "%s,1",
1614                      s->url);
1615     curl_easy_setopt (s->get.easyhandle,
1616                       CURLOPT_URL,
1617                       url);
1618     GNUNET_free(url);
1619   }
1620   else
1621   {
1622     curl_easy_setopt (s->get.easyhandle,
1623                       CURLOPT_URL,
1624                       s->url);
1625   }
1626   curl_easy_setopt (s->get.easyhandle,
1627                     CURLOPT_READFUNCTION,
1628                     &client_send_cb);
1629   curl_easy_setopt (s->get.easyhandle,
1630                     CURLOPT_READDATA,
1631                     s);
1632   curl_easy_setopt (s->get.easyhandle,
1633                     CURLOPT_WRITEFUNCTION,
1634                     &client_receive);
1635   curl_easy_setopt (s->get.easyhandle,
1636                     CURLOPT_WRITEDATA,
1637                     s);
1638   /* No timeout by default, timeout done with session timeout */
1639   curl_easy_setopt (s->get.easyhandle,
1640                     CURLOPT_TIMEOUT,
1641                     0L);
1642   curl_easy_setopt (s->get.easyhandle,
1643                     CURLOPT_PRIVATE, s);
1644   curl_easy_setopt (s->get.easyhandle,
1645                     CURLOPT_CONNECTTIMEOUT_MS,
1646                     (long) (HTTP_CLIENT_NOT_VALIDATED_TIMEOUT.rel_value_us / 1000LL));
1647   curl_easy_setopt (s->get.easyhandle, CURLOPT_BUFFERSIZE,
1648                     2 * GNUNET_SERVER_MAX_MESSAGE_SIZE);
1649 #if CURL_TCP_NODELAY
1650   curl_easy_setopt (ps->recv_endpoint,
1651                     CURLOPT_TCP_NODELAY,
1652                     1L);
1653 #endif
1654   curl_easy_setopt (s->get.easyhandle,
1655                     CURLOPT_FOLLOWLOCATION,
1656                     0L);
1657
1658   mret = curl_multi_add_handle (s->plugin->curl_multi_handle,
1659                                 s->get.easyhandle);
1660   if (CURLM_OK != mret)
1661   {
1662     LOG (GNUNET_ERROR_TYPE_ERROR,
1663          "Session %p : Failed to add GET handle to multihandle: `%s'\n",
1664          s,
1665          curl_multi_strerror (mret));
1666     curl_easy_cleanup (s->get.easyhandle);
1667     s->get.easyhandle = NULL;
1668     s->get.s = NULL;
1669     GNUNET_break (0);
1670     return GNUNET_SYSERR;
1671   }
1672   s->plugin->cur_requests++;
1673   LOG (GNUNET_ERROR_TYPE_INFO,
1674        "GET request `%s' established, number of requests increased to %u\n",
1675        s->url,
1676        s->plugin->cur_requests);
1677   return GNUNET_OK;
1678 }
1679
1680
1681 /**
1682  * Connect a HTTP put request
1683  *
1684  * @param s the session to connect
1685  * @return #GNUNET_SYSERR for hard failure, #GNUNET_OK for ok
1686  */
1687 static int
1688 client_connect_put (struct Session *s)
1689 {
1690   CURLMcode mret;
1691   struct HttpAddress *ha;
1692   uint32_t options;
1693
1694   ha = (struct HttpAddress *) s->address->address;
1695   options = ntohl (ha->options);
1696   /* create put request */
1697   LOG (GNUNET_ERROR_TYPE_DEBUG,
1698        "Session %p: Init PUT handle\n",
1699        s);
1700   s->put.easyhandle = curl_easy_init ();
1701   s->put.s = s;
1702 #if VERBOSE_CURL
1703   curl_easy_setopt (s->put.easyhandle,
1704                     CURLOPT_VERBOSE,
1705                     1L);
1706   curl_easy_setopt (s->put.easyhandle,
1707                     CURLOPT_DEBUGFUNCTION,
1708                     &client_log);
1709   curl_easy_setopt (s->put.easyhandle,
1710                     CURLOPT_DEBUGDATA,
1711                     &s->put);
1712 #endif
1713   if (0 != (options & HTTP_OPTIONS_TCP_STEALTH))
1714   {
1715 #ifdef TCP_STEALTH
1716     curl_easy_setopt (s->put.easyhandle,
1717                       CURLOPT_OPENSOCKETFUNCTION,
1718                       &open_tcp_stealth_socket_cb);
1719     curl_easy_setopt (s->put.easyhandle,
1720                       CURLOPT_OPENSOCKETDATA,
1721                       s);
1722 #else
1723     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1724                 "Cannot connect, TCP STEALTH needed and not supported by kernel.\n");
1725     curl_easy_cleanup (s->put.easyhandle);
1726     s->put.easyhandle = NULL;
1727     s->put.s = NULL;
1728     s->put.state = H_DISCONNECTED;
1729     return GNUNET_SYSERR;
1730 #endif
1731   }
1732 #if BUILD_HTTPS
1733   curl_easy_setopt (s->put.easyhandle,
1734                     CURLOPT_SSLVERSION,
1735                     CURL_SSLVERSION_TLSv1);
1736   {
1737     struct HttpAddress *ha;
1738     ha = (struct HttpAddress *) s->address->address;
1739
1740     if (HTTP_OPTIONS_VERIFY_CERTIFICATE ==
1741         (ntohl (ha->options) & HTTP_OPTIONS_VERIFY_CERTIFICATE))
1742     {
1743       curl_easy_setopt (s->put.easyhandle,
1744                         CURLOPT_SSL_VERIFYPEER,
1745                         1L);
1746       curl_easy_setopt (s->put.easyhandle,
1747                         CURLOPT_SSL_VERIFYHOST,
1748                         2L);
1749     }
1750     else
1751     {
1752       curl_easy_setopt (s->put.easyhandle,
1753                         CURLOPT_SSL_VERIFYPEER,
1754                         0L);
1755       curl_easy_setopt (s->put.easyhandle,
1756                         CURLOPT_SSL_VERIFYHOST,
1757                         0L);
1758     }
1759   }
1760   curl_easy_setopt (s->put.easyhandle,
1761                     CURLOPT_PROTOCOLS,
1762                     CURLPROTO_HTTPS);
1763   curl_easy_setopt (s->put.easyhandle,
1764                     CURLOPT_REDIR_PROTOCOLS,
1765                     CURLPROTO_HTTPS);
1766 #else
1767   curl_easy_setopt (s->put.easyhandle,
1768                     CURLOPT_PROTOCOLS,
1769                     CURLPROTO_HTTP);
1770   curl_easy_setopt (s->put.easyhandle,
1771                     CURLOPT_REDIR_PROTOCOLS,
1772                     CURLPROTO_HTTP);
1773 #endif
1774   if (NULL != s->plugin->proxy_hostname)
1775   {
1776     curl_easy_setopt (s->put.easyhandle,
1777                       CURLOPT_PROXY,
1778                       s->plugin->proxy_hostname);
1779     curl_easy_setopt (s->put.easyhandle,
1780                       CURLOPT_PROXYTYPE,
1781                       s->plugin->proxytype);
1782     if (NULL != s->plugin->proxy_username)
1783       curl_easy_setopt (s->put.easyhandle,
1784                         CURLOPT_PROXYUSERNAME,
1785                         s->plugin->proxy_username);
1786     if (NULL != s->plugin->proxy_password)
1787       curl_easy_setopt (s->put.easyhandle,
1788                         CURLOPT_PROXYPASSWORD,
1789                         s->plugin->proxy_password);
1790     if (GNUNET_YES == s->plugin->proxy_use_httpproxytunnel)
1791       curl_easy_setopt (s->put.easyhandle,
1792                         CURLOPT_HTTPPROXYTUNNEL,
1793                         s->plugin->proxy_use_httpproxytunnel);
1794   }
1795
1796   curl_easy_setopt (s->put.easyhandle,
1797                     CURLOPT_URL,
1798                     s->url);
1799   curl_easy_setopt (s->put.easyhandle,
1800                     CURLOPT_UPLOAD,
1801                     1L);
1802   curl_easy_setopt (s->put.easyhandle,
1803                     CURLOPT_READFUNCTION,
1804                     &client_send_cb);
1805   curl_easy_setopt (s->put.easyhandle,
1806                     CURLOPT_READDATA,
1807                     s);
1808   curl_easy_setopt (s->put.easyhandle,
1809                     CURLOPT_WRITEFUNCTION,
1810                     &client_receive_put);
1811   curl_easy_setopt (s->put.easyhandle,
1812                     CURLOPT_WRITEDATA,
1813                     s);
1814   /* No timeout by default, timeout done with session timeout */
1815   curl_easy_setopt (s->put.easyhandle,
1816                     CURLOPT_TIMEOUT,
1817                     0L);
1818   curl_easy_setopt (s->put.easyhandle,
1819                     CURLOPT_PRIVATE,
1820                     s);
1821   curl_easy_setopt (s->put.easyhandle,
1822                     CURLOPT_CONNECTTIMEOUT_MS,
1823                     (long) (HTTP_CLIENT_NOT_VALIDATED_TIMEOUT.rel_value_us / 1000LL));
1824   curl_easy_setopt (s->put.easyhandle, CURLOPT_BUFFERSIZE,
1825                     2 * GNUNET_SERVER_MAX_MESSAGE_SIZE);
1826 #if CURL_TCP_NODELAY
1827   curl_easy_setopt (s->put.easyhandle, CURLOPT_TCP_NODELAY, 1);
1828 #endif
1829   mret = curl_multi_add_handle (s->plugin->curl_multi_handle,
1830                                 s->put.easyhandle);
1831   if (CURLM_OK != mret)
1832   {
1833     LOG (GNUNET_ERROR_TYPE_ERROR,
1834          "Session %p : Failed to add PUT handle to multihandle: `%s'\n",
1835          s, curl_multi_strerror (mret));
1836     curl_easy_cleanup (s->put.easyhandle);
1837     s->put.easyhandle = NULL;
1838     s->put.s = NULL;
1839     s->put.state = H_DISCONNECTED;
1840     return GNUNET_SYSERR;
1841   }
1842   s->put.state = H_CONNECTED;
1843   s->plugin->cur_requests++;
1844
1845   LOG  (GNUNET_ERROR_TYPE_INFO,
1846       "PUT request `%s' established, number of requests increased to %u\n",
1847       s->url, s->plugin->cur_requests);
1848
1849   return GNUNET_OK;
1850 }
1851
1852
1853 /**
1854  * Connect both PUT and GET request for a session
1855  *
1856  * @param s the session to connect
1857  * @return #GNUNET_OK on success, #GNUNET_SYSERR otherwise
1858  */
1859 static int
1860 client_connect (struct Session *s)
1861 {
1862   struct HTTP_Client_Plugin *plugin = s->plugin;
1863   int res = GNUNET_OK;
1864
1865   /* create url */
1866   if (NULL ==
1867       http_common_plugin_address_to_string(plugin->protocol,
1868                                            s->address->address,
1869                                            s->address->address_length))
1870   {
1871     LOG (GNUNET_ERROR_TYPE_DEBUG,
1872          "Invalid address peer `%s'\n",
1873          GNUNET_i2s(&s->address->peer));
1874     return GNUNET_SYSERR;
1875   }
1876
1877   GNUNET_asprintf (&s->url,
1878                    "%s/%s;%u",
1879                    http_common_plugin_address_to_url (NULL,
1880                                                       s->address->address,
1881                                                       s->address->address_length),
1882                    GNUNET_i2s_full (plugin->env->my_identity),
1883                    plugin->last_tag);
1884
1885   plugin->last_tag++;
1886   LOG (GNUNET_ERROR_TYPE_DEBUG,
1887        "Initiating outbound session peer `%s' using address `%s'\n",
1888        GNUNET_i2s (&s->address->peer), s->url);
1889
1890   if (GNUNET_SYSERR == client_connect_get (s))
1891     return GNUNET_SYSERR;
1892   /* If we are emulating an XHR client then delay sending a PUT request until
1893    * there is something to send.
1894    */
1895   if (GNUNET_YES == plugin->emulate_xhr)
1896   {
1897     s->put.state = H_TMP_DISCONNECTED;
1898   }
1899   else if (GNUNET_SYSERR == client_connect_put (s))
1900     return GNUNET_SYSERR;
1901
1902   LOG (GNUNET_ERROR_TYPE_DEBUG,
1903        "Session %p: connected with GET %p and PUT %p\n",
1904        s, s->get.easyhandle,
1905        s->put.easyhandle);
1906   /* Perform connect */
1907   GNUNET_STATISTICS_set (plugin->env->stats,
1908                          HTTP_STAT_STR_CONNECTIONS,
1909                          plugin->cur_requests,
1910                          GNUNET_NO);
1911   /* Re-schedule since handles have changed */
1912   if (NULL != plugin->client_perform_task)
1913   {
1914     GNUNET_SCHEDULER_cancel (plugin->client_perform_task);
1915     plugin->client_perform_task = NULL;
1916   }
1917
1918   /* Schedule task to run immediately */
1919   plugin->client_perform_task = GNUNET_SCHEDULER_add_now (client_run,
1920                                                           plugin);
1921   return res;
1922 }
1923
1924
1925 /**
1926  * Function obtain the network type for a session
1927  *
1928  * @param cls closure (`struct Plugin*`)
1929  * @param session the session
1930  * @return the network type
1931  */
1932 static enum GNUNET_ATS_Network_Type
1933 http_client_plugin_get_network (void *cls,
1934                                 struct Session *session)
1935 {
1936   return session->scope;
1937 }
1938
1939
1940 /**
1941  * Session was idle, so disconnect it
1942  *
1943  * @param cls the `struct Session` of the idle session
1944  * @param tc scheduler context
1945  */
1946 static void
1947 client_session_timeout (void *cls,
1948                         const struct GNUNET_SCHEDULER_TaskContext *tc)
1949 {
1950   struct Session *s = cls;
1951   struct GNUNET_TIME_Relative left;
1952
1953   s->timeout_task = NULL;
1954   left = GNUNET_TIME_absolute_get_remaining (s->timeout);
1955   if (0 != left.rel_value_us)
1956   {
1957     /* not actually our turn yet, but let's at least update
1958        the monitor, it may think we're about to die ... */
1959     notify_session_monitor (s->plugin,
1960                             s,
1961                             GNUNET_TRANSPORT_SS_UPDATE);
1962     s->timeout_task = GNUNET_SCHEDULER_add_delayed (left,
1963                                                     &client_session_timeout,
1964                                                     s);
1965     return;
1966   }
1967   LOG (TIMEOUT_LOG,
1968        "Session %p was idle for %s, disconnecting\n",
1969        s,
1970        GNUNET_STRINGS_relative_time_to_string (HTTP_CLIENT_SESSION_TIMEOUT,
1971                                                GNUNET_YES));
1972   GNUNET_assert (GNUNET_OK ==
1973                  http_client_plugin_session_disconnect (s->plugin,
1974                                                  s));
1975 }
1976
1977
1978 /**
1979  * Creates a new outbound session the transport service will use to
1980  * send data to the peer
1981  *
1982  * @param cls the plugin
1983  * @param address the address
1984  * @return the session or NULL of max connections exceeded
1985  */
1986 static struct Session *
1987 http_client_plugin_get_session (void *cls,
1988                                 const struct GNUNET_HELLO_Address *address)
1989 {
1990   struct HTTP_Client_Plugin *plugin = cls;
1991   struct Session *s;
1992   struct sockaddr *sa;
1993   enum GNUNET_ATS_Network_Type net_type;
1994   size_t salen = 0;
1995   int res;
1996
1997   GNUNET_assert (NULL != address->address);
1998
1999   /* find existing session */
2000   s = client_lookup_session (plugin, address);
2001   if (NULL != s)
2002     return s;
2003
2004   /* create a new session */
2005   if (plugin->max_requests <= plugin->cur_requests)
2006   {
2007     LOG (GNUNET_ERROR_TYPE_WARNING,
2008          "Maximum number of requests (%u) reached: "
2009          "cannot connect to peer `%s'\n",
2010          plugin->max_requests,
2011          GNUNET_i2s (&address->peer));
2012     return NULL;
2013   }
2014
2015   /* Determine network location */
2016   net_type = GNUNET_ATS_NET_UNSPECIFIED;
2017   sa = http_common_socket_from_address (address->address,
2018                                         address->address_length,
2019                                         &res);
2020   if (GNUNET_SYSERR == res)
2021     return NULL;
2022   if (GNUNET_YES == res)
2023   {
2024     GNUNET_assert (NULL != sa);
2025     if (AF_INET == sa->sa_family)
2026     {
2027       salen = sizeof (struct sockaddr_in);
2028     }
2029     else if (AF_INET6 == sa->sa_family)
2030     {
2031       salen = sizeof (struct sockaddr_in6);
2032     }
2033     net_type = plugin->env->get_address_type (plugin->env->cls, sa, salen);
2034     GNUNET_free (sa);
2035   }
2036   else if (GNUNET_NO == res)
2037   {
2038     /* Cannot convert to sockaddr -> is external hostname */
2039     net_type = GNUNET_ATS_NET_WAN;
2040   }
2041   if (GNUNET_ATS_NET_UNSPECIFIED == net_type)
2042   {
2043     GNUNET_break (0);
2044     return NULL;
2045   }
2046
2047   s = GNUNET_new (struct Session);
2048   s->plugin = plugin;
2049   s->address = GNUNET_HELLO_address_copy (address);
2050   s->scope = net_type;
2051
2052   s->put.state = H_NOT_CONNECTED;
2053   s->timeout = GNUNET_TIME_relative_to_absolute (HTTP_CLIENT_SESSION_TIMEOUT);
2054   s->timeout_task =  GNUNET_SCHEDULER_add_delayed (HTTP_CLIENT_SESSION_TIMEOUT,
2055                                                    &client_session_timeout,
2056                                                    s);
2057   LOG (GNUNET_ERROR_TYPE_DEBUG,
2058        "Created new session %p for `%s' address `%s''\n",
2059        s,
2060        http_common_plugin_address_to_string (plugin->protocol,
2061                                              s->address->address,
2062                                              s->address->address_length),
2063        GNUNET_i2s (&s->address->peer));
2064
2065   /* add new session */
2066   (void) GNUNET_CONTAINER_multipeermap_put (plugin->sessions,
2067                                             &s->address->peer,
2068                                             s,
2069                                             GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
2070   /* initiate new connection */
2071   if (GNUNET_SYSERR == client_connect (s))
2072   {
2073     LOG (GNUNET_ERROR_TYPE_ERROR,
2074          "Cannot connect to peer `%s' address `%s''\n",
2075          http_common_plugin_address_to_string (plugin->protocol,
2076              s->address->address, s->address->address_length),
2077              GNUNET_i2s (&s->address->peer));
2078     client_delete_session (s);
2079     return NULL;
2080   }
2081   notify_session_monitor (plugin,
2082                           s,
2083                           GNUNET_TRANSPORT_SS_INIT);
2084   notify_session_monitor (plugin,
2085                           s,
2086                           GNUNET_TRANSPORT_SS_UP); /* or handshake? */
2087   return s;
2088 }
2089
2090
2091 /**
2092  * Setup http_client plugin
2093  *
2094  * @param plugin the plugin handle
2095  * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
2096  */
2097 static int
2098 client_start (struct HTTP_Client_Plugin *plugin)
2099 {
2100   curl_global_init (CURL_GLOBAL_ALL);
2101   plugin->curl_multi_handle = curl_multi_init ();
2102
2103   if (NULL == plugin->curl_multi_handle)
2104   {
2105     LOG (GNUNET_ERROR_TYPE_ERROR,
2106          _("Could not initialize curl multi handle, failed to start %s plugin!\n"),
2107          plugin->name);
2108     return GNUNET_SYSERR;
2109   }
2110   return GNUNET_OK;
2111 }
2112
2113
2114 /**
2115  * Another peer has suggested an address for this
2116  * peer and transport plugin.  Check that this could be a valid
2117  * address.  If so, consider adding it to the list
2118  * of addresses.
2119  *
2120  * @param cls closure with the `struct Plugin`
2121  * @param addr pointer to the address
2122  * @param addrlen length of @a addr
2123  * @return #GNUNET_OK if this is a plausible address for this peer
2124  *         and transport; always returns #GNUNET_NO (this is the client!)
2125  */
2126 static int
2127 http_client_plugin_address_suggested (void *cls,
2128                                       const void *addr,
2129                                       size_t addrlen)
2130 {
2131   /* A HTTP/S client does not have any valid address so:*/
2132   return GNUNET_NO;
2133 }
2134
2135
2136 /**
2137  * Exit point from the plugin.
2138  *
2139  * @param cls api as closure
2140  * @return NULL
2141  */
2142 void *
2143 LIBGNUNET_PLUGIN_TRANSPORT_DONE (void *cls)
2144 {
2145   struct GNUNET_TRANSPORT_PluginFunctions *api = cls;
2146   struct HTTP_Client_Plugin *plugin = api->cls;
2147
2148   if (NULL == api->cls)
2149   {
2150     /* Stub shutdown */
2151     GNUNET_free (api);
2152     return NULL;
2153   }
2154   LOG (GNUNET_ERROR_TYPE_DEBUG,
2155        _("Shutting down plugin `%s'\n"),
2156        plugin->name);
2157   GNUNET_CONTAINER_multipeermap_iterate (plugin->sessions,
2158                                          &destroy_session_cb,
2159                                          plugin);
2160   if (NULL != plugin->client_perform_task)
2161   {
2162     GNUNET_SCHEDULER_cancel (plugin->client_perform_task);
2163     plugin->client_perform_task = NULL;
2164   }
2165   if (NULL != plugin->curl_multi_handle)
2166   {
2167     curl_multi_cleanup (plugin->curl_multi_handle);
2168     plugin->curl_multi_handle = NULL;
2169   }
2170   curl_global_cleanup ();
2171   LOG (GNUNET_ERROR_TYPE_DEBUG,
2172        _("Shutdown for plugin `%s' complete\n"),
2173        plugin->name);
2174   GNUNET_CONTAINER_multipeermap_destroy (plugin->sessions);
2175   GNUNET_free_non_null (plugin->proxy_hostname);
2176   GNUNET_free_non_null (plugin->proxy_username);
2177   GNUNET_free_non_null (plugin->proxy_password);
2178   GNUNET_free (plugin);
2179   GNUNET_free (api);
2180   return NULL;
2181 }
2182
2183
2184 /**
2185  * Configure plugin
2186  *
2187  * @param plugin the plugin handle
2188  * @return #GNUNET_OK on success, #GNUNET_SYSERR on failure
2189  */
2190 static int
2191 client_configure_plugin (struct HTTP_Client_Plugin *plugin)
2192 {
2193   unsigned long long max_requests;
2194   char *proxy_type;
2195
2196   /* Optional parameters */
2197   if (GNUNET_OK !=
2198       GNUNET_CONFIGURATION_get_value_number (plugin->env->cfg,
2199                                              plugin->name,
2200                                              "MAX_CONNECTIONS",
2201                                              &max_requests))
2202     max_requests = 128;
2203   plugin->max_requests = max_requests;
2204
2205   LOG (GNUNET_ERROR_TYPE_DEBUG,
2206        _("Maximum number of requests is %u\n"),
2207        plugin->max_requests);
2208
2209   /* Read proxy configuration */
2210   if (GNUNET_OK ==
2211       GNUNET_CONFIGURATION_get_value_string (plugin->env->cfg,
2212                                              plugin->name,
2213                                              "PROXY",
2214                                              &plugin->proxy_hostname))
2215   {
2216     LOG (GNUNET_ERROR_TYPE_DEBUG,
2217          "Found proxy host: `%s'\n",
2218          plugin->proxy_hostname);
2219     /* proxy username */
2220     if (GNUNET_OK ==
2221         GNUNET_CONFIGURATION_get_value_string (plugin->env->cfg,
2222                                                plugin->name,
2223                                                "PROXY_USERNAME",
2224                                                &plugin->proxy_username))
2225     {
2226       LOG (GNUNET_ERROR_TYPE_DEBUG,
2227            "Found proxy username name: `%s'\n",
2228            plugin->proxy_username);
2229     }
2230
2231     /* proxy password */
2232     if (GNUNET_OK ==
2233         GNUNET_CONFIGURATION_get_value_string (plugin->env->cfg,
2234                                                plugin->name,
2235                                                "PROXY_PASSWORD",
2236                                                &plugin->proxy_password))
2237     {
2238       LOG (GNUNET_ERROR_TYPE_DEBUG,
2239            "Found proxy password name: `%s'\n",
2240            plugin->proxy_password);
2241     }
2242
2243     /* proxy type */
2244     if (GNUNET_OK ==
2245         GNUNET_CONFIGURATION_get_value_string (plugin->env->cfg,
2246                                                plugin->name,
2247                                                "PROXY_TYPE",
2248                                                &proxy_type))
2249     {
2250       GNUNET_STRINGS_utf8_toupper (proxy_type, proxy_type);
2251
2252       if (0 == strcmp(proxy_type, "HTTP"))
2253         plugin->proxytype = CURLPROXY_HTTP;
2254       else if (0 == strcmp(proxy_type, "SOCKS4"))
2255         plugin->proxytype = CURLPROXY_SOCKS4;
2256       else if (0 == strcmp(proxy_type, "SOCKS5"))
2257         plugin->proxytype = CURLPROXY_SOCKS5;
2258       else if (0 == strcmp(proxy_type, "SOCKS4A"))
2259         plugin->proxytype = CURLPROXY_SOCKS4A;
2260       else if (0 == strcmp(proxy_type, "SOCKS5_HOSTNAME "))
2261         plugin->proxytype = CURLPROXY_SOCKS5_HOSTNAME ;
2262       else
2263       {
2264         LOG (GNUNET_ERROR_TYPE_ERROR,
2265              _("Invalid proxy type: `%s', disabling proxy! Check configuration!\n"),
2266              proxy_type);
2267
2268         GNUNET_free (proxy_type);
2269         GNUNET_free (plugin->proxy_hostname);
2270         plugin->proxy_hostname = NULL;
2271         GNUNET_free_non_null (plugin->proxy_username);
2272         plugin->proxy_username = NULL;
2273         GNUNET_free_non_null (plugin->proxy_password);
2274         plugin->proxy_password = NULL;
2275
2276         return GNUNET_SYSERR;
2277       }
2278
2279       LOG (GNUNET_ERROR_TYPE_DEBUG,
2280            "Found proxy type: `%s'\n",
2281            proxy_type);
2282     }
2283
2284     /* proxy http tunneling */
2285     plugin->proxy_use_httpproxytunnel
2286       = GNUNET_CONFIGURATION_get_value_yesno (plugin->env->cfg,
2287                                               plugin->name,
2288                                               "PROXY_HTTP_TUNNELING");
2289     if (GNUNET_SYSERR == plugin->proxy_use_httpproxytunnel)
2290       plugin->proxy_use_httpproxytunnel = GNUNET_NO;
2291
2292     GNUNET_free_non_null (proxy_type);
2293   }
2294
2295   /* Should we emulate an XHR client for testing? */
2296   plugin->emulate_xhr
2297     = GNUNET_CONFIGURATION_get_value_yesno (plugin->env->cfg,
2298                                             plugin->name,
2299                                             "EMULATE_XHR");
2300   return GNUNET_OK;
2301 }
2302
2303
2304 /**
2305  * Function to convert an address to a human-readable string.
2306  *
2307  * @param cls closure
2308  * @param addr address to convert
2309  * @param addrlen address length
2310  * @return res string if conversion was successful, NULL otherwise
2311  */
2312 static const char *
2313 http_client_plugin_address_to_string (void *cls,
2314                                       const void *addr,
2315                                       size_t addrlen)
2316 {
2317   return http_common_plugin_address_to_string (PLUGIN_NAME,
2318                                                addr,
2319                                                addrlen);
2320 }
2321
2322
2323 /**
2324  * Function that will be called whenever the transport service wants to
2325  * notify the plugin that a session is still active and in use and
2326  * therefore the session timeout for this session has to be updated
2327  *
2328  * @param cls closure
2329  * @param peer which peer was the session for
2330  * @param session which session is being updated
2331  */
2332 static void
2333 http_client_plugin_update_session_timeout (void *cls,
2334                                            const struct GNUNET_PeerIdentity *peer,
2335                                            struct Session *session)
2336 {
2337   client_reschedule_session_timeout (session);
2338 }
2339
2340
2341 /**
2342  * Function that will be called whenever the transport service wants to
2343  * notify the plugin that the inbound quota changed and that the plugin
2344  * should update it's delay for the next receive value
2345  *
2346  * @param cls closure
2347  * @param peer which peer was the session for
2348  * @param s which session is being updated
2349  * @param delay new delay to use for receiving
2350  */
2351 static void
2352 http_client_plugin_update_inbound_delay (void *cls,
2353                                          const struct GNUNET_PeerIdentity *peer,
2354                                          struct Session *s,
2355                                          struct GNUNET_TIME_Relative delay)
2356 {
2357   s->next_receive = GNUNET_TIME_relative_to_absolute (delay);
2358   LOG (GNUNET_ERROR_TYPE_DEBUG,
2359        "New inbound delay %s\n",
2360        GNUNET_STRINGS_relative_time_to_string (delay,
2361                                                GNUNET_NO));
2362   if (s->recv_wakeup_task != NULL)
2363   {
2364     GNUNET_SCHEDULER_cancel (s->recv_wakeup_task);
2365     s->recv_wakeup_task = GNUNET_SCHEDULER_add_delayed (delay,
2366         &client_wake_up, s);
2367   }
2368 }
2369
2370
2371 /**
2372  * Return information about the given session to the
2373  * monitor callback.
2374  *
2375  * @param cls the `struct Plugin` with the monitor callback (`sic`)
2376  * @param peer peer we send information about
2377  * @param value our `struct Session` to send information about
2378  * @return #GNUNET_OK (continue to iterate)
2379  */
2380 static int
2381 send_session_info_iter (void *cls,
2382                         const struct GNUNET_PeerIdentity *peer,
2383                         void *value)
2384 {
2385   struct HTTP_Client_Plugin *plugin = cls;
2386   struct Session *session = value;
2387
2388   notify_session_monitor (plugin,
2389                           session,
2390                           GNUNET_TRANSPORT_SS_INIT);
2391   notify_session_monitor (plugin,
2392                           session,
2393                           GNUNET_TRANSPORT_SS_UP); /* FIXME: or handshake? */
2394   return GNUNET_OK;
2395 }
2396
2397
2398 /**
2399  * Begin monitoring sessions of a plugin.  There can only
2400  * be one active monitor per plugin (i.e. if there are
2401  * multiple monitors, the transport service needs to
2402  * multiplex the generated events over all of them).
2403  *
2404  * @param cls closure of the plugin
2405  * @param sic callback to invoke, NULL to disable monitor;
2406  *            plugin will being by iterating over all active
2407  *            sessions immediately and then enter monitor mode
2408  * @param sic_cls closure for @a sic
2409  */
2410 static void
2411 http_client_plugin_setup_monitor (void *cls,
2412                                   GNUNET_TRANSPORT_SessionInfoCallback sic,
2413                                   void *sic_cls)
2414 {
2415   struct HTTP_Client_Plugin *plugin = cls;
2416
2417   plugin->sic = sic;
2418   plugin->sic_cls = sic_cls;
2419   if (NULL != sic)
2420   {
2421     GNUNET_CONTAINER_multipeermap_iterate (plugin->sessions,
2422                                            &send_session_info_iter,
2423                                            plugin);
2424     /* signal end of first iteration */
2425     sic (sic_cls, NULL, NULL);
2426   }
2427 }
2428
2429
2430 /**
2431  * Entry point for the plugin.
2432  */
2433 void *
2434 LIBGNUNET_PLUGIN_TRANSPORT_INIT (void *cls)
2435 {
2436   struct GNUNET_TRANSPORT_PluginEnvironment *env = cls;
2437   struct GNUNET_TRANSPORT_PluginFunctions *api;
2438   struct HTTP_Client_Plugin *plugin;
2439
2440   if (NULL == env->receive)
2441   {
2442     /* run in 'stub' mode (i.e. as part of gnunet-peerinfo), don't fully
2443        initialze the plugin or the API */
2444     api = GNUNET_new (struct GNUNET_TRANSPORT_PluginFunctions);
2445     api->cls = NULL;
2446     api->address_to_string = &http_client_plugin_address_to_string;
2447     api->string_to_address = &http_common_plugin_string_to_address;
2448     api->address_pretty_printer = &http_common_plugin_address_pretty_printer;
2449     return api;
2450   }
2451
2452   plugin = GNUNET_new (struct HTTP_Client_Plugin);
2453   plugin->env = env;
2454   plugin->sessions = GNUNET_CONTAINER_multipeermap_create (128,
2455                                                            GNUNET_YES);
2456   api = GNUNET_new (struct GNUNET_TRANSPORT_PluginFunctions);
2457   api->cls = plugin;
2458   api->send = &http_client_plugin_send;
2459   api->disconnect_session = &http_client_plugin_session_disconnect;
2460   api->query_keepalive_factor = &http_client_query_keepalive_factor;
2461   api->disconnect_peer = &http_client_plugin_peer_disconnect;
2462   api->check_address = &http_client_plugin_address_suggested;
2463   api->get_session = &http_client_plugin_get_session;
2464   api->address_to_string = &http_client_plugin_address_to_string;
2465   api->string_to_address = &http_common_plugin_string_to_address;
2466   api->address_pretty_printer = &http_common_plugin_address_pretty_printer;
2467   api->get_network = &http_client_plugin_get_network;
2468   api->update_session_timeout = &http_client_plugin_update_session_timeout;
2469   api->update_inbound_delay = &http_client_plugin_update_inbound_delay;
2470   api->setup_monitor = &http_client_plugin_setup_monitor;
2471 #if BUILD_HTTPS
2472   plugin->name = "transport-https_client";
2473   plugin->protocol = "https";
2474 #else
2475   plugin->name = "transport-http_client";
2476   plugin->protocol = "http";
2477 #endif
2478   plugin->last_tag = 1;
2479
2480   if (GNUNET_SYSERR == client_configure_plugin (plugin))
2481   {
2482     LIBGNUNET_PLUGIN_TRANSPORT_DONE (api);
2483     return NULL;
2484   }
2485
2486   /* Start client */
2487   if (GNUNET_SYSERR == client_start (plugin))
2488   {
2489     LIBGNUNET_PLUGIN_TRANSPORT_DONE (api);
2490     return NULL;
2491   }
2492   return api;
2493 }
2494
2495 /* end of plugin_transport_http_client.c */