- fix error messages
[oweals/gnunet.git] / src / transport / plugin_transport_http_client.c
1 /*
2      This file is part of GNUnet
3      (C) 2002-2013 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  */
26
27 #if BUILD_HTTPS
28 #define PLUGIN_NAME "https_client"
29 #define HTTP_STAT_STR_CONNECTIONS "# HTTPS client connections"
30 #define LIBGNUNET_PLUGIN_TRANSPORT_INIT libgnunet_plugin_transport_https_client_init
31 #define LIBGNUNET_PLUGIN_TRANSPORT_DONE libgnunet_plugin_transport_https_client_done
32 #else
33 #define PLUGIN_NAME "http_client"
34 #define HTTP_STAT_STR_CONNECTIONS "# HTTP client connections"
35 #define LIBGNUNET_PLUGIN_TRANSPORT_INIT libgnunet_plugin_transport_http_client_init
36 #define LIBGNUNET_PLUGIN_TRANSPORT_DONE libgnunet_plugin_transport_http_client_done
37 #endif
38
39 #define VERBOSE_CURL GNUNET_YES
40
41 #define PUT_DISCONNECT_TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 1)
42
43 #define ENABLE_PUT GNUNET_YES
44 #define ENABLE_GET GNUNET_YES
45
46 #include "platform.h"
47 #include "gnunet_util_lib.h"
48 #include "gnunet_protocols.h"
49 #include "gnunet_transport_plugin.h"
50 #include "plugin_transport_http_common.h"
51 #include <curl/curl.h>
52
53
54
55 /**
56  * Encapsulation of all of the state of the plugin.
57  */
58 struct HTTP_Client_Plugin;
59
60
61 /**
62  *  Message to send using http
63  */
64 struct HTTP_Message
65 {
66   /**
67    * next pointer for double linked list
68    */
69   struct HTTP_Message *next;
70
71   /**
72    * previous pointer for double linked list
73    */
74   struct HTTP_Message *prev;
75
76   /**
77    * buffer containing data to send
78    */
79   char *buf;
80
81   /**
82    * amount of data already sent
83    */
84   size_t pos;
85
86   /**
87    * buffer length
88    */
89   size_t size;
90
91   /**
92    * Continuation function to call once the transmission buffer
93    * has again space available.  NULL if there is no
94    * continuation to call.
95    */
96   GNUNET_TRANSPORT_TransmitContinuation transmit_cont;
97
98   /**
99    * Closure for transmit_cont.
100    */
101   void *transmit_cont_cls;
102 };
103
104
105 /**
106  * Session handle for connections.
107  */
108 struct Session;
109
110
111 /**
112  * A connection handle
113  *
114  */
115 struct ConnectionHandle
116 {
117   /**
118    * The curl easy handle
119    */
120   CURL *easyhandle;
121
122   /**
123    * The related session
124    */
125   struct Session *s;
126 };
127
128
129 /**
130  * Session handle for connections.
131  */
132 struct Session
133 {
134   /**
135    * To whom are we talking to (set to our identity
136    * if we are still waiting for the welcome message)
137    */
138   struct GNUNET_PeerIdentity target;
139
140   /**
141    * Stored in a linked list.
142    */
143   struct Session *next;
144
145   /**
146    * Stored in a linked list.
147    */
148   struct Session *prev;
149
150   /**
151    * The URL to connect to
152    */
153   char *url;
154
155   /**
156    * Address
157    */
158   struct HttpAddress *addr;
159
160   /**
161    * Address length
162    */
163   size_t addrlen;
164
165   /**
166    * ATS network type in NBO
167    */
168   uint32_t ats_address_network_type;
169
170   /**
171    * Pointer to the global plugin struct.
172    */
173   struct HTTP_Client_Plugin *plugin;
174
175   /**
176    * Client send handle
177    */
178   void *client_put;
179
180   struct ConnectionHandle put;
181   struct ConnectionHandle get;
182
183   /**
184    * Is the client PUT handle currently paused
185    */
186   int put_paused;
187
188   /**
189    * Is the client PUT handle disconnect in progress?
190    */
191   int put_tmp_disconnecting;
192
193   /**
194    * Is the client PUT handle temporarily disconnected?
195    */
196   int put_tmp_disconnected;
197
198   /**
199    * We received data to send while disconnecting, reconnect immediately
200    */
201   int put_reconnect_required;
202
203   /**
204    * Client receive handle
205    */
206   void *client_get;
207
208   /**
209    * Outbound overhead due to HTTP connection
210    * Add to next message of this session when calling callback
211    */
212   size_t overhead;
213
214   /**
215    * next pointer for double linked list
216    */
217   struct HTTP_Message *msg_head;
218
219   /**
220    * previous pointer for double linked list
221    */
222   struct HTTP_Message *msg_tail;
223
224   /**
225    * Message stream tokenizer for incoming data
226    */
227   struct GNUNET_SERVER_MessageStreamTokenizer *msg_tk;
228
229   /**
230    * Session timeout task
231    */
232   GNUNET_SCHEDULER_TaskIdentifier put_disconnect_task;
233
234   /**
235    * Session timeout task
236    */
237   GNUNET_SCHEDULER_TaskIdentifier timeout_task;
238
239   /**
240    * Task to wake up client receive handle when receiving is allowed again
241    */
242   GNUNET_SCHEDULER_TaskIdentifier recv_wakeup_task;
243
244   /**
245   * Absolute time when to receive data again
246   * Used for receive throttling
247   */
248   struct GNUNET_TIME_Absolute next_receive;
249 };
250
251
252 /**
253  * Encapsulation of all of the state of the plugin.
254  */
255 struct HTTP_Client_Plugin
256 {
257   /**
258    * Our environment.
259    */
260   struct GNUNET_TRANSPORT_PluginEnvironment *env;
261
262   /**
263    * Linked list head of open sessions.
264    */
265   struct Session *head;
266
267   /**
268    * Linked list tail of open sessions.
269    */
270   struct Session *tail;
271
272   /**
273    * Plugin name
274    */
275   char *name;
276
277   /**
278    * Protocol
279    */
280   char *protocol;
281
282   /**
283    * My options to be included in the address
284    */
285   uint32_t options;
286
287   /**
288    * Maximum number of sockets the plugin can use
289    * Each http inbound /outbound connections are two connections
290    */
291   unsigned int max_connections;
292
293   /**
294    * Current number of sockets the plugin can use
295    * Each http inbound /outbound connections are two connections
296    */
297   unsigned int cur_connections;
298
299   /**
300    * Last used unique HTTP connection tag
301    */
302   uint32_t last_tag;
303
304   /**
305    * use IPv6
306    */
307   uint16_t use_ipv6;
308
309   /**
310    * use IPv4
311    */
312   uint16_t use_ipv4;
313
314   /**
315    * cURL Multihandle
316    */
317   CURLM *curl_multi_handle;
318
319   /**
320    * curl perform task
321    */
322   GNUNET_SCHEDULER_TaskIdentifier client_perform_task;
323 };
324
325
326 /**
327  * Increment session timeout due to activity for a session
328  * @param s the session
329  */
330 static void
331 client_reschedule_session_timeout (struct Session *s);
332
333
334 /**
335  * Function setting up file descriptors and scheduling task to run
336  *
337  * @param  plugin plugin as closure
338  * @param now schedule task in 1ms, regardless of what curl may say
339  * @return GNUNET_SYSERR for hard failure, GNUNET_OK for ok
340  */
341 static int
342 client_schedule (struct HTTP_Client_Plugin *plugin, int now);
343
344
345 /**
346  * Connect a HTTP put connection
347  *
348  * @param s the session to connect
349  * @return #GNUNET_SYSERR for hard failure, #GNUNET_OK for success
350  */
351 static int
352 client_connect_put (struct Session *s);
353
354
355 /**
356  * Does a session s exists?
357  *
358  * @param plugin the plugin
359  * @param s desired session
360  * @return #GNUNET_YES or #GNUNET_NO
361  */
362 static int
363 client_exist_session (struct HTTP_Client_Plugin *plugin,
364                       struct Session *s)
365 {
366   struct Session * head;
367
368   for (head = plugin->head; head != NULL; head = head->next)
369     if (head == s)
370       return GNUNET_YES;
371   return GNUNET_NO;
372 }
373
374
375 /**
376  * Loggging function
377  *
378  * @param curl the curl easy handle
379  * @param type message type
380  * @param data data to log, NOT a 0-terminated string
381  * @param size data length
382  * @param cls the closure
383  * @return always 0
384  */
385 static int
386 client_log (CURL *curl, curl_infotype type,
387             const char *data, size_t size, void *cls)
388 {
389   struct ConnectionHandle *ch = cls;
390   const char *ttype = "UNSPECIFIED";
391
392   if ((type == CURLINFO_TEXT) || (type == CURLINFO_HEADER_IN) || (type == CURLINFO_HEADER_OUT))
393   {
394     char text[size + 2];
395
396     switch (type) {
397       case CURLINFO_TEXT:
398         ttype = "TEXT";
399         break;
400       case CURLINFO_HEADER_IN:
401         ttype = "HEADER_IN";
402         break;
403       case CURLINFO_HEADER_OUT:
404         ttype = "HEADER_OUT";
405         /* Overhead*/
406
407         GNUNET_assert (NULL != ch);
408         GNUNET_assert (NULL != ch->easyhandle);
409         GNUNET_assert (NULL != ch->s);
410         ch->s->overhead += size;
411         break;
412       default:
413         ttype = "UNSPECIFIED";
414         break;
415     }
416
417     memcpy (text, data, size);
418     if (text[size - 1] == '\n')
419       text[size] = '\0';
420     else
421     {
422       text[size] = '\n';
423       text[size + 1] = '\0';
424     }
425 #if BUILD_HTTPS
426     GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, "transport-https_client",
427                      "Connection %p %s: %s", ch->easyhandle, ttype, text);
428 #else
429     GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, "transport-http_client",
430                      "Connection %p %s: %s", ch->easyhandle, ttype, text);
431 #endif
432   }
433   return 0;
434 }
435
436
437 /**
438  * Function that can be used by the transport service to transmit
439  * a message using the plugin.   Note that in the case of a
440  * peer disconnecting, the continuation MUST be called
441  * prior to the disconnect notification itself.  This function
442  * will be called with this peer's HELLO message to initiate
443  * a fresh connection to another peer.
444  *
445  * @param cls closure
446  * @param s which session must be used
447  * @param msgbuf the message to transmit
448  * @param msgbuf_size number of bytes in 'msgbuf'
449  * @param priority how important is the message (most plugins will
450  *                 ignore message priority and just FIFO)
451  * @param to how long to wait at most for the transmission (does not
452  *                require plugins to discard the message after the timeout,
453  *                just advisory for the desired delay; most plugins will ignore
454  *                this as well)
455  * @param cont continuation to call once the message has
456  *        been transmitted (or if the transport is ready
457  *        for the next transmission call; or if the
458  *        peer disconnected...); can be NULL
459  * @param cont_cls closure for cont
460  * @return number of bytes used (on the physical network, with overheads);
461  *         -1 on hard errors (i.e. address invalid); 0 is a legal value
462  *         and does NOT mean that the message was not transmitted (DV)
463  */
464 static ssize_t
465 http_client_plugin_send (void *cls,
466                          struct Session *s,
467                          const char *msgbuf, size_t msgbuf_size,
468                          unsigned int priority,
469                          struct GNUNET_TIME_Relative to,
470                          GNUNET_TRANSPORT_TransmitContinuation cont,
471                          void *cont_cls)
472 {
473   struct HTTP_Client_Plugin *plugin = cls;
474   struct HTTP_Message *msg;
475   char *stat_txt;
476
477   /* lookup if session is really existing */
478   if (GNUNET_YES != client_exist_session (plugin, s))
479   {
480     GNUNET_break (0);
481     return GNUNET_SYSERR;
482   }
483
484   GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, s->plugin->name,
485                    "Session %p/connection %p: Sending message with %u to peer `%s' \n",
486                    s, s->client_put,
487                    msgbuf_size, GNUNET_i2s (&s->target));
488
489   /* create new message and schedule */
490   msg = GNUNET_malloc (sizeof (struct HTTP_Message) + msgbuf_size);
491   msg->next = NULL;
492   msg->size = msgbuf_size;
493   msg->pos = 0;
494   msg->buf = (char *) &msg[1];
495   msg->transmit_cont = cont;
496   msg->transmit_cont_cls = cont_cls;
497   memcpy (msg->buf, msgbuf, msgbuf_size);
498   GNUNET_CONTAINER_DLL_insert_tail (s->msg_head, s->msg_tail, msg);
499
500   GNUNET_asprintf (&stat_txt,
501                    "# bytes currently in %s_client buffers",
502                    plugin->protocol);
503   GNUNET_STATISTICS_update (plugin->env->stats,
504                             stat_txt, msgbuf_size, GNUNET_NO);
505   GNUNET_free (stat_txt);
506
507   if (GNUNET_YES == s->put_tmp_disconnecting)
508   {
509     /* PUT connection is currently getting disconnected */
510     s->put_reconnect_required = GNUNET_YES;
511     GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, s->plugin->name,
512                      "Session %p/connection %jp: currently disconnecting, reconnecting immediately\n",
513                      s, s->client_put);
514     return msgbuf_size;
515   }
516   else if (GNUNET_YES == s->put_paused)
517   {
518     /* PUT connection was paused, unpause */
519     GNUNET_assert (s->put_disconnect_task != GNUNET_SCHEDULER_NO_TASK);
520     GNUNET_SCHEDULER_cancel (s->put_disconnect_task);
521     s->put_disconnect_task = GNUNET_SCHEDULER_NO_TASK;
522     GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, s->plugin->name,
523                      "Session %p/connection %p: unpausing connection\n",
524                      s, s->client_put);
525     s->put_paused = GNUNET_NO;
526     if (NULL != s->client_put)
527       curl_easy_pause (s->client_put, CURLPAUSE_CONT);
528   }
529   else if (GNUNET_YES == s->put_tmp_disconnected)
530   {
531     /* PUT connection was disconnected, reconnect */
532     GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, s->plugin->name,
533                      "Session %p: Reconnecting PUT connection\n",
534                      s);
535     s->put_tmp_disconnected = GNUNET_NO;
536     GNUNET_break (s->client_put == NULL);
537     if (GNUNET_SYSERR == client_connect_put (s))
538     {
539       return GNUNET_SYSERR;
540     }
541   }
542
543   client_schedule (s->plugin, GNUNET_YES);
544   return msgbuf_size;
545 }
546
547
548 /**
549  * Delete session s
550  *
551  * @param s the session to delete
552  */
553 static void
554 client_delete_session (struct Session *s)
555 {
556   struct HTTP_Client_Plugin *plugin = s->plugin;
557   struct HTTP_Message *pos;
558   struct HTTP_Message *next;
559
560   if (GNUNET_SCHEDULER_NO_TASK != s->timeout_task)
561   {
562     GNUNET_SCHEDULER_cancel (s->timeout_task);
563     s->timeout_task = GNUNET_SCHEDULER_NO_TASK;
564   }
565   if (GNUNET_SCHEDULER_NO_TASK != s->put_disconnect_task)
566   {
567       GNUNET_SCHEDULER_cancel (s->put_disconnect_task);
568       s->put_disconnect_task = GNUNET_SCHEDULER_NO_TASK;
569   }
570
571   GNUNET_CONTAINER_DLL_remove (plugin->head, plugin->tail, s);
572
573   next = s->msg_head;
574   while (NULL != (pos = next))
575   {
576     next = pos->next;
577     GNUNET_CONTAINER_DLL_remove (s->msg_head, s->msg_tail, pos);
578     if (pos->transmit_cont != NULL)
579       pos->transmit_cont (pos->transmit_cont_cls, &s->target, GNUNET_SYSERR,
580                           pos->size, pos->pos + s->overhead);
581     s->overhead = 0;
582     GNUNET_free (pos);
583   }
584
585   if (s->msg_tk != NULL)
586   {
587     GNUNET_SERVER_mst_destroy (s->msg_tk);
588     s->msg_tk = NULL;
589   }
590   GNUNET_free (s->addr);
591   GNUNET_free (s->url);
592   GNUNET_free (s);
593 }
594
595
596 /**
597  * Disconnect a session
598  *
599  * @param cls the `struct HTTP_Client_Plugin`
600  * @param s session
601  * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
602  */
603 static int
604 http_client_session_disconnect (void *cls,
605                                 struct Session *s)
606 {
607   struct HTTP_Client_Plugin *plugin = cls;
608   struct HTTP_Message *msg;
609   struct HTTP_Message *t;
610   int res = GNUNET_OK;
611   CURLMcode mret;
612
613   if (GNUNET_YES != client_exist_session (plugin, s))
614   {
615     GNUNET_break (0);
616     return GNUNET_SYSERR;
617   }
618
619   if (NULL != s->client_put)
620   {
621     GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, plugin->name,
622                      "Session %p/connection %p: disconnecting PUT connection to peer `%s'\n",
623                      s, s->client_put, GNUNET_i2s (&s->target));
624
625     /* remove curl handle from multi handle */
626     mret = curl_multi_remove_handle (plugin->curl_multi_handle, s->client_put);
627     if (mret != CURLM_OK)
628     {
629       /* clean up easy handle, handle is now invalid and free'd */
630       res = GNUNET_SYSERR;
631       GNUNET_break (0);
632     }
633     curl_easy_cleanup (s->client_put);
634     s->client_put = NULL;
635   }
636
637
638   if (s->recv_wakeup_task != GNUNET_SCHEDULER_NO_TASK)
639   {
640     GNUNET_SCHEDULER_cancel (s->recv_wakeup_task);
641     s->recv_wakeup_task = GNUNET_SCHEDULER_NO_TASK;
642   }
643
644   if (NULL != s->client_get)
645   {
646     GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, plugin->name,
647                      "Session %p/connection %p: disconnecting GET connection to peer `%s'\n",
648                      s, s->client_get,
649                      GNUNET_i2s (&s->target));
650     /* remove curl handle from multi handle */
651     mret = curl_multi_remove_handle (plugin->curl_multi_handle, s->client_get);
652     if (mret != CURLM_OK)
653     {
654       /* clean up easy handle, handle is now invalid and free'd */
655       res = GNUNET_SYSERR;
656       GNUNET_break (0);
657     }
658     curl_easy_cleanup (s->client_get);
659     s->client_get = NULL;
660   }
661
662   msg = s->msg_head;
663   while (NULL != msg)
664   {
665     t = msg->next;
666     if (NULL != msg->transmit_cont)
667       msg->transmit_cont (msg->transmit_cont_cls, &s->target, GNUNET_SYSERR,
668                           msg->size, msg->pos + s->overhead);
669     s->overhead = 0;
670     GNUNET_CONTAINER_DLL_remove (s->msg_head, s->msg_tail, msg);
671     GNUNET_free (msg);
672     msg = t;
673   }
674
675   GNUNET_assert (plugin->cur_connections >= 2);
676   plugin->cur_connections -= 2;
677   GNUNET_STATISTICS_set (plugin->env->stats,
678                          HTTP_STAT_STR_CONNECTIONS,
679                          plugin->cur_connections,
680                          GNUNET_NO);
681   GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, plugin->name,
682                    "Session %p: notifying transport about ending session\n",s);
683
684   plugin->env->session_end (plugin->env->cls, &s->target, s);
685   client_delete_session (s);
686
687   /* Re-schedule since handles have changed */
688   if (plugin->client_perform_task != GNUNET_SCHEDULER_NO_TASK)
689   {
690     GNUNET_SCHEDULER_cancel (plugin->client_perform_task);
691     plugin->client_perform_task = GNUNET_SCHEDULER_NO_TASK;
692   }
693   client_schedule (plugin, GNUNET_YES);
694
695   return res;
696 }
697
698
699 /**
700  * Function that is called to get the keepalive factor.
701  * GNUNET_CONSTANTS_IDLE_CONNECTION_TIMEOUT is divided by this number to
702  * calculate the interval between keepalive packets.
703  *
704  * @param cls closure with the `struct Plugin`
705  * @return keepalive factor
706  */
707 static unsigned int
708 http_client_query_keepalive_factor (void *cls)
709 {
710   return 3;
711 }
712
713
714 /**
715  * Function that can be used to force the plugin to disconnect
716  * from the given peer and cancel all previous transmissions
717  * (and their continuationc).
718  *
719  * @param cls closure
720  * @param target peer from which to disconnect
721  */
722 static void
723 http_client_peer_disconnect (void *cls,
724                              const struct GNUNET_PeerIdentity *target)
725 {
726   struct HTTP_Client_Plugin *plugin = cls;
727   struct Session *next = NULL;
728   struct Session *pos = NULL;
729
730   GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, plugin->name,
731                    "Transport tells me to disconnect `%s'\n",
732                    GNUNET_i2s (target));
733
734   next = plugin->head;
735   while (NULL != (pos = next))
736   {
737     next = pos->next;
738     if (0 == memcmp (target, &pos->target, sizeof (struct GNUNET_PeerIdentity)))
739     {
740       GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, plugin->name,
741                        "Disconnecting session %p to `%pos'\n",
742                        pos, GNUNET_i2s (target));
743       GNUNET_assert (GNUNET_OK == http_client_session_disconnect (plugin,
744                                                                   pos));
745     }
746   }
747 }
748
749
750 /**
751  * Check if a sessions exists for an specific address
752  *
753  * @param plugin the plugin
754  * @param address the address
755  * @return the session or NULL
756  */
757 static struct Session *
758 client_lookup_session (struct HTTP_Client_Plugin *plugin,
759                        const struct GNUNET_HELLO_Address *address)
760 {
761   struct Session *pos;
762
763   for (pos = plugin->head; NULL != pos; pos = pos->next)
764     if ((0 == memcmp (&address->peer, &pos->target, sizeof (struct GNUNET_PeerIdentity))) &&
765         (address->address_length == pos->addrlen) &&
766         (0 == memcmp (address->address, pos->addr, pos->addrlen)))
767       return pos;
768   return NULL;
769 }
770
771
772 static void
773 client_put_disconnect (void *cls,
774                        const struct GNUNET_SCHEDULER_TaskContext *tc)
775 {
776   struct Session *s = cls;
777
778   s->put_disconnect_task = GNUNET_SCHEDULER_NO_TASK;
779   GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, s->plugin->name,
780                    "Session %p/connection %p: will be disconnected due to no activity\n",
781                    s, s->client_put);
782   s->put_paused = GNUNET_NO;
783   s->put_tmp_disconnecting = GNUNET_YES;
784   if (NULL != s->client_put)
785     curl_easy_pause (s->client_put, CURLPAUSE_CONT);
786   client_schedule (s->plugin, GNUNET_YES);
787 }
788
789
790 /**
791  * Callback method used with libcurl
792  * Method is called when libcurl needs to read data during sending
793  *
794  * @param stream pointer where to write data
795  * @param size size of an individual element
796  * @param nmemb count of elements that can be written to the buffer
797  * @param cls our `struct Session`
798  * @return bytes written to stream, returning 0 will terminate connection!
799  */
800 static size_t
801 client_send_cb (void *stream, size_t size, size_t nmemb, void *cls)
802 {
803   struct Session *s = cls;
804   struct HTTP_Client_Plugin *plugin = s->plugin;
805   struct HTTP_Message *msg = s->msg_head;
806   size_t len;
807   char *stat_txt;
808
809   if (GNUNET_YES != client_exist_session (plugin, s))
810   {
811     GNUNET_break (0);
812     return 0;
813   }
814   if (GNUNET_YES == s->put_tmp_disconnecting)
815   {
816
817       GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, s->plugin->name,
818                        "Session %p/connection %p: disconnect due to inactivity\n",
819                        s, s->client_put);
820       return 0;
821   }
822
823   if (NULL == msg)
824   {
825     GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, plugin->name,
826                      "Session %p/connection %p: nothing to send, suspending\n",
827                      s, s->client_put);
828     s->put_disconnect_task = GNUNET_SCHEDULER_add_delayed (PUT_DISCONNECT_TIMEOUT, &client_put_disconnect, s);
829     s->put_paused = GNUNET_YES;
830     return CURL_READFUNC_PAUSE;
831   }
832   /* data to send */
833   GNUNET_assert (msg->pos < msg->size);
834   /* calculate how much fits in buffer */
835   len = GNUNET_MIN (msg->size - msg->pos,
836                     size * nmemb);
837   memcpy (stream, &msg->buf[msg->pos], len);
838   msg->pos += len;
839   if (msg->pos == msg->size)
840   {
841     GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, plugin->name,
842                      "Session %p/connection %p: sent message with %u bytes sent, removing message from queue\n",
843                      s, s->client_put, msg->size, msg->pos);
844     /* Calling transmit continuation  */
845     GNUNET_CONTAINER_DLL_remove (s->msg_head, s->msg_tail, msg);
846     if (NULL != msg->transmit_cont)
847       msg->transmit_cont (msg->transmit_cont_cls, &s->target, GNUNET_OK,
848                           msg->size, msg->size + s->overhead);
849     s->overhead = 0;
850     GNUNET_free (msg);
851   }
852
853   GNUNET_asprintf (&stat_txt, "# bytes currently in %s_client buffers", plugin->protocol);
854   GNUNET_STATISTICS_update (plugin->env->stats,
855                             stat_txt, -len, GNUNET_NO);
856   GNUNET_free (stat_txt);
857
858   GNUNET_asprintf (&stat_txt, "# bytes transmitted via %s_client", plugin->protocol);
859   GNUNET_STATISTICS_update (plugin->env->stats,
860                             stat_txt, len, GNUNET_NO);
861   GNUNET_free (stat_txt);
862   return len;
863 }
864
865
866 /**
867  * Wake up a curl handle which was suspended
868  *
869  * @param cls the session
870  * @param tc task context
871  */
872 static void
873 client_wake_up (void *cls,
874                 const struct GNUNET_SCHEDULER_TaskContext *tc)
875 {
876   struct Session *s = cls;
877   struct HTTP_Client_Plugin *p = s->plugin;
878
879   if (GNUNET_YES != client_exist_session (p, s))
880   {
881     GNUNET_break (0);
882     return;
883   }
884   s->recv_wakeup_task = GNUNET_SCHEDULER_NO_TASK;
885   if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN))
886     return;
887   GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, s->plugin->name,
888                    "Session %p/connection %p: Waking up GET handle\n",
889                    s,
890                    s->client_get);
891   s->put_paused = GNUNET_NO;
892   if (NULL != s->client_get)
893     curl_easy_pause (s->client_get, CURLPAUSE_CONT);
894
895 }
896
897
898 /**
899  * Callback for message stream tokenizer
900  *
901  * @param cls the session
902  * @param client not used
903  * @param message the message received
904  * @return always #GNUNET_OK
905  */
906 static int
907 client_receive_mst_cb (void *cls, void *client,
908                        const struct GNUNET_MessageHeader *message)
909 {
910   struct Session *s = cls;
911   struct HTTP_Client_Plugin *plugin;
912   struct GNUNET_TIME_Relative delay;
913   struct GNUNET_ATS_Information atsi;
914   char *stat_txt;
915
916   plugin = s->plugin;
917   if (GNUNET_YES != client_exist_session (plugin, s))
918   {
919     GNUNET_break (0);
920     return GNUNET_OK;
921   }
922
923   atsi.type = htonl (GNUNET_ATS_NETWORK_TYPE);
924   atsi.value = s->ats_address_network_type;
925   GNUNET_break (s->ats_address_network_type != ntohl (GNUNET_ATS_NET_UNSPECIFIED));
926   delay = s->plugin->env->receive (plugin->env->cls, &s->target, message,
927                                    s, (const char *) s->addr, s->addrlen);
928
929   plugin->env->update_address_metrics (plugin->env->cls,
930                                        &s->target,
931                                        s->addr,
932                                        s->addrlen,
933                                        s,
934                                        &atsi, 1);
935
936   GNUNET_asprintf (&stat_txt,
937                    "# bytes received via %s_client",
938                    plugin->protocol);
939   GNUNET_STATISTICS_update (plugin->env->stats,
940                             stat_txt, ntohs(message->size), GNUNET_NO);
941   GNUNET_free (stat_txt);
942
943   s->next_receive =
944       GNUNET_TIME_absolute_add (GNUNET_TIME_absolute_get (), delay);
945
946   if (GNUNET_TIME_absolute_get ().abs_value_us < s->next_receive.abs_value_us)
947   {
948     GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, plugin->name,
949                      "Client: peer `%s' address `%s' next read delayed for %s\n",
950                      GNUNET_i2s (&s->target),
951                      http_common_plugin_address_to_string (NULL,
952                                                            s->plugin->protocol,
953                                                            s->addr, s->addrlen),
954                      GNUNET_STRINGS_relative_time_to_string (delay,
955                                                              GNUNET_YES));
956   }
957   client_reschedule_session_timeout (s);
958   return GNUNET_OK;
959 }
960
961
962 /**
963  * Callback method used with libcurl when data for a PUT connection are
964  * received. We do not expect data here, so we just dismiss it
965  *
966  * @param stream pointer where to write data
967  * @param size size of an individual element
968  * @param nmemb count of elements that can be written to the buffer
969  * @param cls destination pointer, passed to the libcurl handle
970  * @return bytes read from stream
971  */
972 static size_t
973 client_receive_put (void *stream, size_t size, size_t nmemb, void *cls)
974 {
975   return size * nmemb;
976 }
977
978
979 /**
980  * Callback method used with libcurl when data for a GET connection are
981  * received. Forward to MST
982  *
983  * @param stream pointer where to write data
984  * @param size size of an individual element
985  * @param nmemb count of elements that can be written to the buffer
986  * @param cls destination pointer, passed to the libcurl handle
987  * @return bytes read from stream
988  */
989 static size_t
990 client_receive (void *stream, size_t size, size_t nmemb, void *cls)
991 {
992   struct Session *s = cls;
993   struct GNUNET_TIME_Absolute now;
994   size_t len = size * nmemb;
995
996   GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, s->plugin->name,
997                    "Session %p / connection %p: Received %u bytes from peer `%s'\n",
998                    s, s->client_get,
999                    len, GNUNET_i2s (&s->target));
1000   now = GNUNET_TIME_absolute_get ();
1001   if (now.abs_value_us < s->next_receive.abs_value_us)
1002   {
1003     struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
1004     struct GNUNET_TIME_Relative delta =
1005         GNUNET_TIME_absolute_get_difference (now, s->next_receive);
1006     GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, s->plugin->name,
1007                      "Session %p / connection %p: No inbound bandwidth available! Next read was delayed for %s\n",
1008                      s, s->client_get,
1009                      GNUNET_STRINGS_relative_time_to_string (delta,
1010                                                              GNUNET_YES));
1011     if (s->recv_wakeup_task != GNUNET_SCHEDULER_NO_TASK)
1012     {
1013       GNUNET_SCHEDULER_cancel (s->recv_wakeup_task);
1014       s->recv_wakeup_task = GNUNET_SCHEDULER_NO_TASK;
1015     }
1016     s->recv_wakeup_task =
1017         GNUNET_SCHEDULER_add_delayed (delta, &client_wake_up, s);
1018     return CURL_WRITEFUNC_PAUSE;
1019   }
1020   if (NULL == s->msg_tk)
1021     s->msg_tk = GNUNET_SERVER_mst_create (&client_receive_mst_cb, s);
1022   GNUNET_SERVER_mst_receive (s->msg_tk, s, stream, len, GNUNET_NO, GNUNET_NO);
1023   return len;
1024 }
1025
1026
1027 /**
1028  * Task performing curl operations
1029  *
1030  * @param cls plugin as closure
1031  * @param tc gnunet scheduler task context
1032  */
1033 static void
1034 client_run (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc);
1035
1036
1037 /**
1038  * Function setting up file descriptors and scheduling task to run
1039  *
1040  * @param plugin the plugin as closure
1041  * @param now schedule task in 1ms, regardless of what curl may say
1042  * @return #GNUNET_SYSERR for hard failure, #GNUNET_OK for ok
1043  */
1044 static int
1045 client_schedule (struct HTTP_Client_Plugin *plugin, int now)
1046 {
1047   fd_set rs;
1048   fd_set ws;
1049   fd_set es;
1050   int max;
1051   struct GNUNET_NETWORK_FDSet *grs;
1052   struct GNUNET_NETWORK_FDSet *gws;
1053   long to;
1054   CURLMcode mret;
1055   struct GNUNET_TIME_Relative timeout;
1056
1057   /* Cancel previous scheduled task */
1058   if (plugin->client_perform_task != GNUNET_SCHEDULER_NO_TASK)
1059   {
1060     GNUNET_SCHEDULER_cancel (plugin->client_perform_task);
1061     plugin->client_perform_task = GNUNET_SCHEDULER_NO_TASK;
1062   }
1063   max = -1;
1064   FD_ZERO (&rs);
1065   FD_ZERO (&ws);
1066   FD_ZERO (&es);
1067   mret = curl_multi_fdset (plugin->curl_multi_handle, &rs, &ws, &es, &max);
1068   if (mret != CURLM_OK)
1069   {
1070     GNUNET_log (GNUNET_ERROR_TYPE_ERROR, _("%s failed at %s:%d: `%s'\n"),
1071                 "curl_multi_fdset", __FILE__, __LINE__,
1072                 curl_multi_strerror (mret));
1073     return GNUNET_SYSERR;
1074   }
1075   mret = curl_multi_timeout (plugin->curl_multi_handle, &to);
1076   if (to == -1)
1077     timeout = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 1);
1078   else
1079     timeout = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, to);
1080   if (now == GNUNET_YES)
1081     timeout = GNUNET_TIME_UNIT_MILLISECONDS;
1082
1083   if (mret != CURLM_OK)
1084   {
1085     GNUNET_log (GNUNET_ERROR_TYPE_ERROR, _("%s failed at %s:%d: `%s'\n"),
1086                 "curl_multi_timeout", __FILE__, __LINE__,
1087                 curl_multi_strerror (mret));
1088     return GNUNET_SYSERR;
1089   }
1090
1091   grs = GNUNET_NETWORK_fdset_create ();
1092   gws = GNUNET_NETWORK_fdset_create ();
1093   GNUNET_NETWORK_fdset_copy_native (grs, &rs, max + 1);
1094   GNUNET_NETWORK_fdset_copy_native (gws, &ws, max + 1);
1095
1096   plugin->client_perform_task =
1097       GNUNET_SCHEDULER_add_select (GNUNET_SCHEDULER_PRIORITY_DEFAULT,
1098                                    timeout, grs, gws,
1099                                    &client_run, plugin);
1100   GNUNET_NETWORK_fdset_destroy (gws);
1101   GNUNET_NETWORK_fdset_destroy (grs);
1102   return GNUNET_OK;
1103 }
1104
1105
1106 /**
1107  * Task performing curl operations
1108  *
1109  * @param cls plugin as closure
1110  * @param tc gnunet scheduler task context
1111  */
1112 static void
1113 client_run (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
1114 {
1115   struct HTTP_Client_Plugin *plugin = cls;
1116   int running;
1117   long http_statuscode;
1118   CURLMcode mret;
1119
1120   GNUNET_assert (cls != NULL);
1121
1122   plugin->client_perform_task = GNUNET_SCHEDULER_NO_TASK;
1123   if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN))
1124     return;
1125
1126   do
1127   {
1128     running = 0;
1129     mret = curl_multi_perform (plugin->curl_multi_handle, &running);
1130
1131     CURLMsg *msg;
1132     int msgs_left;
1133
1134     while ((msg = curl_multi_info_read (plugin->curl_multi_handle, &msgs_left)))
1135     {
1136       CURL *easy_h = msg->easy_handle;
1137       struct Session *s = NULL;
1138       char *d = (char *) s;
1139
1140       if (easy_h == NULL)
1141       {
1142         GNUNET_break (0);
1143         GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, plugin->name,
1144                          "Client: connection to ended with reason %i: `%s', %i handles running\n",
1145                          msg->data.result,
1146                          curl_easy_strerror (msg->data.result), running);
1147         continue;
1148       }
1149
1150       GNUNET_assert (CURLE_OK ==
1151                      curl_easy_getinfo (easy_h, CURLINFO_PRIVATE, &d));
1152       s = (struct Session *) d;
1153
1154       if (GNUNET_YES != client_exist_session(plugin, s))
1155       {
1156         GNUNET_break (0);
1157         return;
1158       }
1159
1160       GNUNET_assert (s != NULL);
1161       if (msg->msg == CURLMSG_DONE)
1162       {
1163         curl_easy_getinfo (easy_h, CURLINFO_RESPONSE_CODE, &http_statuscode);
1164         if (easy_h == s->client_put)
1165         {
1166             if  ((0 != msg->data.result) || (http_statuscode != 200))
1167             {
1168                 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, plugin->name,
1169                   "Session %p/connection %p: PUT connection to `%s' ended with status %i reason %i: `%s'\n",
1170                   s, msg->easy_handle, GNUNET_i2s (&s->target),
1171                   http_statuscode,
1172                   msg->data.result,
1173                   curl_easy_strerror (msg->data.result));
1174             }
1175             else
1176               GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, plugin->name,
1177                 "Session %p/connection %p: PUT connection to `%s' ended normal\n",
1178                 s, msg->easy_handle, GNUNET_i2s (&s->target));
1179             if (s->client_get == NULL)
1180             {
1181               /* Disconnect other transmission direction and tell transport */
1182             }
1183             curl_multi_remove_handle (plugin->curl_multi_handle, easy_h);
1184             curl_easy_cleanup (easy_h);
1185             s->put_tmp_disconnecting = GNUNET_NO;
1186             s->put_tmp_disconnected = GNUNET_YES;
1187             s->client_put = NULL;
1188             s->put.easyhandle = NULL;
1189             s->put.s = NULL;
1190
1191             /*
1192              * Handling a rare case:
1193              * plugin_send was called during temporary put disconnect,
1194              * reconnect required after connection was disconnected
1195              */
1196             if (GNUNET_YES == s->put_reconnect_required)
1197             {
1198                 s->put_reconnect_required = GNUNET_NO;
1199                 if (GNUNET_SYSERR == client_connect_put(s))
1200                 {
1201                     GNUNET_break (s->client_put == NULL);
1202                     GNUNET_break (s->put_tmp_disconnected == GNUNET_NO);
1203                 }
1204             }
1205         }
1206         if (easy_h == s->client_get)
1207         {
1208             if  ((0 != msg->data.result) || (http_statuscode != 200))
1209             {
1210               GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, plugin->name,
1211                 "Session %p/connection %p: GET connection to `%s' ended with status %i reason %i: `%s'\n",
1212                 s, msg->easy_handle, GNUNET_i2s (&s->target),
1213                 http_statuscode,
1214                 msg->data.result,
1215                 curl_easy_strerror (msg->data.result));
1216
1217             }
1218             else
1219               GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, plugin->name,
1220                 "Session %p/connection %p: GET connection to `%s' ended normal\n",
1221                 s, msg->easy_handle, GNUNET_i2s (&s->target));
1222             /* Disconnect other transmission direction and tell transport */
1223             s->get.easyhandle = NULL;
1224             s->get.s = NULL;
1225             http_client_session_disconnect (plugin, s);
1226         }
1227       }
1228     }
1229   }
1230   while (mret == CURLM_CALL_MULTI_PERFORM);
1231   client_schedule (plugin, GNUNET_NO);
1232 }
1233
1234
1235 /**
1236  * Connect GET connection for a session
1237  *
1238  * @param s the session to connect
1239  * @return #GNUNET_OK on success, #GNUNET_SYSERR otherwise
1240  */
1241 static int
1242 client_connect_get (struct Session *s)
1243 {
1244
1245   CURLMcode mret;
1246   /* create get connection */
1247   s->client_get = curl_easy_init ();
1248   s->get.s = s;
1249   s->get.easyhandle = s->client_get;
1250 #if VERBOSE_CURL
1251   curl_easy_setopt (s->client_get, CURLOPT_VERBOSE, 1L);
1252   curl_easy_setopt (s->client_get, CURLOPT_DEBUGFUNCTION, &client_log);
1253   curl_easy_setopt (s->client_get, CURLOPT_DEBUGDATA, &s->get);
1254 #endif
1255 #if BUILD_HTTPS
1256   curl_easy_setopt (s->client_get, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1);
1257   if (HTTP_OPTIONS_VERIFY_CERTIFICATE ==
1258       (ntohl (s->addr->options) & HTTP_OPTIONS_VERIFY_CERTIFICATE))
1259   {
1260     curl_easy_setopt (s->client_get, CURLOPT_SSL_VERIFYPEER, 1L);
1261     curl_easy_setopt (s->client_get, CURLOPT_SSL_VERIFYHOST, 2L);
1262   }
1263   else
1264   {
1265     curl_easy_setopt (s->client_get, CURLOPT_SSL_VERIFYPEER, 0);
1266     curl_easy_setopt (s->client_get, CURLOPT_SSL_VERIFYHOST, 0);
1267   }
1268   curl_easy_setopt (s->client_get, CURLOPT_PROTOCOLS, CURLPROTO_HTTPS);
1269   curl_easy_setopt (s->client_get, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTPS);
1270 #else
1271   curl_easy_setopt (s->client_get, CURLOPT_PROTOCOLS, CURLPROTO_HTTP);
1272   curl_easy_setopt (s->client_get, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP);
1273 #endif
1274
1275   curl_easy_setopt (s->client_get, CURLOPT_URL, s->url);
1276   //curl_easy_setopt (s->client_get, CURLOPT_HEADERFUNCTION, &curl_get_header_cb);
1277   //curl_easy_setopt (s->client_get, CURLOPT_WRITEHEADER, ps);
1278   curl_easy_setopt (s->client_get, CURLOPT_READFUNCTION, client_send_cb);
1279   curl_easy_setopt (s->client_get, CURLOPT_READDATA, s);
1280   curl_easy_setopt (s->client_get, CURLOPT_WRITEFUNCTION, client_receive);
1281   curl_easy_setopt (s->client_get, CURLOPT_WRITEDATA, s);
1282   /* No timeout by default, timeout done with session timeout */
1283   curl_easy_setopt (s->client_get, CURLOPT_TIMEOUT, 0);
1284   curl_easy_setopt (s->client_get, CURLOPT_PRIVATE, s);
1285   curl_easy_setopt (s->client_get, CURLOPT_CONNECTTIMEOUT_MS,
1286                     (long) (HTTP_CLIENT_NOT_VALIDATED_TIMEOUT.rel_value_us / 1000LL));
1287   curl_easy_setopt (s->client_get, CURLOPT_BUFFERSIZE,
1288                     2 * GNUNET_SERVER_MAX_MESSAGE_SIZE);
1289 #if CURL_TCP_NODELAY
1290   curl_easy_setopt (ps->recv_endpoint, CURLOPT_TCP_NODELAY, 1);
1291 #endif
1292   curl_easy_setopt (s->client_get, CURLOPT_FOLLOWLOCATION, 0);
1293
1294   mret = curl_multi_add_handle (s->plugin->curl_multi_handle, s->client_get);
1295   if (mret != CURLM_OK)
1296   {
1297     GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR, s->plugin->name,
1298                      "Session %p : Failed to add GET handle to multihandle: `%s'\n",
1299                      s,
1300                      curl_multi_strerror (mret));
1301     curl_easy_cleanup (s->client_get);
1302     s->client_get = NULL;
1303     s->get.s = NULL;
1304     s->get.easyhandle = NULL;
1305     GNUNET_break (0);
1306     return GNUNET_SYSERR;
1307   }
1308
1309   return GNUNET_OK;
1310 }
1311
1312
1313 /**
1314  * Connect a HTTP put connection
1315  *
1316  * @param s the session to connect
1317  * @return #GNUNET_SYSERR for hard failure, #GNUNET_OK for ok
1318  */
1319 static int
1320 client_connect_put (struct Session *s)
1321 {
1322   CURLMcode mret;
1323
1324   /* create put connection */
1325   GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, s->plugin->name,
1326                    "Session %p : Init PUT handle\n", s);
1327   s->client_put = curl_easy_init ();
1328   s->put.s = s;
1329   s->put.easyhandle = s->client_put;
1330 #if VERBOSE_CURL
1331   curl_easy_setopt (s->client_put, CURLOPT_VERBOSE, 1L);
1332   curl_easy_setopt (s->client_put, CURLOPT_DEBUGFUNCTION, &client_log);
1333   curl_easy_setopt (s->client_put, CURLOPT_DEBUGDATA, &s->put);
1334 #endif
1335 #if BUILD_HTTPS
1336   curl_easy_setopt (s->client_put, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1);
1337   if (HTTP_OPTIONS_VERIFY_CERTIFICATE ==
1338       (ntohl (s->addr->options) & HTTP_OPTIONS_VERIFY_CERTIFICATE))
1339   {
1340     curl_easy_setopt (s->client_put, CURLOPT_SSL_VERIFYPEER, 1L);
1341     curl_easy_setopt (s->client_put, CURLOPT_SSL_VERIFYHOST, 2L);
1342   }
1343   else
1344   {
1345     curl_easy_setopt (s->client_put, CURLOPT_SSL_VERIFYPEER, 0);
1346     curl_easy_setopt (s->client_put, CURLOPT_SSL_VERIFYHOST, 0);
1347   }
1348   curl_easy_setopt (s->client_get, CURLOPT_PROTOCOLS, CURLPROTO_HTTPS);
1349   curl_easy_setopt (s->client_get, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTPS);
1350 #else
1351   curl_easy_setopt (s->client_get, CURLOPT_PROTOCOLS, CURLPROTO_HTTP);
1352   curl_easy_setopt (s->client_get, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP);
1353 #endif
1354   curl_easy_setopt (s->client_put, CURLOPT_URL, s->url);
1355   curl_easy_setopt (s->client_put, CURLOPT_UPLOAD, 1L);
1356   //curl_easy_setopt (s->client_put, CURLOPT_HEADERFUNCTION, &client_curl_header);
1357   //curl_easy_setopt (s->client_put, CURLOPT_WRITEHEADER, ps);
1358   curl_easy_setopt (s->client_put, CURLOPT_READFUNCTION, client_send_cb);
1359   curl_easy_setopt (s->client_put, CURLOPT_READDATA, s);
1360   curl_easy_setopt (s->client_put, CURLOPT_WRITEFUNCTION, client_receive_put);
1361   curl_easy_setopt (s->client_put, CURLOPT_WRITEDATA, s);
1362   /* No timeout by default, timeout done with session timeout */
1363   curl_easy_setopt (s->client_put, CURLOPT_TIMEOUT, 0);
1364   curl_easy_setopt (s->client_put, CURLOPT_PRIVATE, s);
1365   curl_easy_setopt (s->client_put, CURLOPT_CONNECTTIMEOUT_MS,
1366                     (long) (HTTP_CLIENT_NOT_VALIDATED_TIMEOUT.rel_value_us / 1000LL));
1367   curl_easy_setopt (s->client_put, CURLOPT_BUFFERSIZE,
1368                     2 * GNUNET_SERVER_MAX_MESSAGE_SIZE);
1369 #if CURL_TCP_NODELAY
1370   curl_easy_setopt (s->client_put, CURLOPT_TCP_NODELAY, 1);
1371 #endif
1372   mret = curl_multi_add_handle (s->plugin->curl_multi_handle, s->client_put);
1373   if (mret != CURLM_OK)
1374   {
1375     GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR, s->plugin->name,
1376                      "Session %p : Failed to add PUT handle to multihandle: `%s'\n",
1377                      s,
1378                      curl_multi_strerror (mret));
1379     curl_easy_cleanup (s->client_put);
1380     s->client_put = NULL;
1381     s->put.easyhandle = NULL;
1382     s->put.s = NULL;
1383     s->put_tmp_disconnected = GNUNET_YES;
1384     return GNUNET_SYSERR;
1385   }
1386   s->put_tmp_disconnected = GNUNET_NO;
1387   return GNUNET_OK;
1388 }
1389
1390
1391 /**
1392  * Connect both PUT and GET connection for a session
1393  *
1394  * @param s the session to connect
1395  * @return #GNUNET_OK on success, #GNUNET_SYSERR otherwise
1396  */
1397 static int
1398 client_connect (struct Session *s)
1399 {
1400
1401   struct HTTP_Client_Plugin *plugin = s->plugin;
1402   int res = GNUNET_OK;
1403
1404   /* create url */
1405   if (NULL == http_common_plugin_address_to_string (NULL,
1406                                                     plugin->protocol,
1407                                                     s->addr, s->addrlen))
1408   {
1409     GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG,
1410                      plugin->name,
1411                      "Invalid address peer `%s'\n",
1412                      GNUNET_i2s (&s->target));
1413     return GNUNET_SYSERR;
1414   }
1415
1416   GNUNET_asprintf (&s->url, "%s/%s;%u",
1417                    http_common_plugin_address_to_url (NULL, s->addr, s->addrlen),
1418                    GNUNET_i2s_full (plugin->env->my_identity),
1419                    plugin->last_tag);
1420
1421   plugin->last_tag++;
1422
1423   GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, plugin->name,
1424                    "Initiating outbound session peer `%s' using address `%s'\n",
1425                    GNUNET_i2s (&s->target), s->url);
1426
1427   if ((GNUNET_SYSERR == client_connect_get (s)) ||
1428       (GNUNET_SYSERR == client_connect_put (s)))
1429   {
1430     GNUNET_break (0);
1431     return GNUNET_SYSERR;
1432   }
1433
1434   GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG,
1435                    plugin->name,
1436                    "Session %p: connected with connections GET %p and PUT %p\n",
1437                    s, s->client_get, s->client_put);
1438
1439   /* Perform connect */
1440   plugin->cur_connections += 2;
1441   GNUNET_STATISTICS_set (plugin->env->stats,
1442                          HTTP_STAT_STR_CONNECTIONS,
1443                          plugin->cur_connections,
1444                          GNUNET_NO);
1445
1446   /* Re-schedule since handles have changed */
1447   if (plugin->client_perform_task != GNUNET_SCHEDULER_NO_TASK)
1448   {
1449     GNUNET_SCHEDULER_cancel (plugin->client_perform_task);
1450     plugin->client_perform_task = GNUNET_SCHEDULER_NO_TASK;
1451   }
1452   plugin->client_perform_task = GNUNET_SCHEDULER_add_now (client_run, plugin);
1453   return res;
1454 }
1455
1456
1457 /**
1458  * Function obtain the network type for a session
1459  *
1460  * @param cls closure (`struct Plugin*`)
1461  * @param session the session
1462  * @return the network type
1463  */
1464 static enum GNUNET_ATS_Network_Type
1465 http_client_get_network (void *cls,
1466                          struct Session *session)
1467 {
1468   return ntohl (session->ats_address_network_type);
1469 }
1470
1471
1472 /**
1473  * Session was idle, so disconnect it
1474  *
1475  * @param cls the `struct Session` of the idle session
1476  * @param tc scheduler context
1477  */
1478 static void
1479 client_session_timeout (void *cls,
1480                         const struct GNUNET_SCHEDULER_TaskContext *tc)
1481 {
1482   struct Session *s = cls;
1483
1484   s->timeout_task = GNUNET_SCHEDULER_NO_TASK;
1485   GNUNET_log (TIMEOUT_LOG,
1486               "Session %p was idle for %s, disconnecting\n",
1487               s,
1488               GNUNET_STRINGS_relative_time_to_string (HTTP_CLIENT_SESSION_TIMEOUT,
1489                                                       GNUNET_YES));
1490
1491   /* call session destroy function */
1492   GNUNET_assert (GNUNET_OK == http_client_session_disconnect (s->plugin,
1493                                                               s));
1494 }
1495
1496
1497 /**
1498  * Creates a new outbound session the transport service will use to
1499  * send data to the peer
1500  *
1501  * @param cls the plugin
1502  * @param address the address
1503  * @return the session or NULL of max connections exceeded
1504  */
1505 static struct Session *
1506 http_client_plugin_get_session (void *cls,
1507                                 const struct GNUNET_HELLO_Address *address)
1508 {
1509   struct HTTP_Client_Plugin *plugin = cls;
1510   struct Session * s = NULL;
1511   struct sockaddr *sa;
1512   struct GNUNET_ATS_Information ats;
1513   size_t salen = 0;
1514   int res;
1515
1516   GNUNET_assert (address->address != NULL);
1517
1518   /* find existing session */
1519   s = client_lookup_session (plugin, address);
1520   if (s != NULL)
1521     return s;
1522
1523   if (plugin->max_connections <= plugin->cur_connections)
1524   {
1525     GNUNET_log_from (GNUNET_ERROR_TYPE_WARNING, plugin->name,
1526                      "Maximum number of connections (%u) reached: "
1527                      "cannot connect to peer `%s'\n",
1528                      plugin->max_connections,
1529                      GNUNET_i2s (&address->peer));
1530     return NULL;
1531   }
1532
1533   /* Determine network location */
1534   ats.type = htonl (GNUNET_ATS_NETWORK_TYPE);
1535   ats.value = htonl (GNUNET_ATS_NET_UNSPECIFIED);
1536   sa = http_common_socket_from_address (address->address, address->address_length, &res);
1537   if (GNUNET_SYSERR == res)
1538   {
1539     return NULL;
1540   }
1541   else if (GNUNET_YES == res)
1542   {
1543     GNUNET_assert (NULL != sa);
1544     if (AF_INET == sa->sa_family)
1545     {
1546       salen = sizeof (struct sockaddr_in);
1547     }
1548     else if (AF_INET6 == sa->sa_family)
1549     {
1550       salen = sizeof (struct sockaddr_in6);
1551     }
1552     ats = plugin->env->get_address_type (plugin->env->cls, sa, salen);
1553     //fprintf (stderr, "Address %s is in %s\n", GNUNET_a2s (sa,salen), GNUNET_ATS_print_network_type(ntohl(ats.value)));
1554     GNUNET_free (sa);
1555   }
1556   else if (GNUNET_NO == res)
1557   {
1558     /* Cannot convert to sockaddr -> is external hostname */
1559     ats.value = htonl (GNUNET_ATS_NET_WAN);
1560   }
1561   if (GNUNET_ATS_NET_UNSPECIFIED == ntohl (ats.value))
1562   {
1563     GNUNET_break (0);
1564     return NULL;
1565   }
1566
1567   s = GNUNET_new (struct Session);
1568   s->target = address->peer;
1569   s->plugin = plugin;
1570   s->addr = GNUNET_malloc (address->address_length);
1571   memcpy (s->addr, address->address, address->address_length);
1572   s->addrlen = address->address_length;
1573   s->ats_address_network_type = ats.value;
1574   s->put_paused = GNUNET_NO;
1575   s->put_tmp_disconnecting = GNUNET_NO;
1576   s->put_tmp_disconnected = GNUNET_NO;
1577   s->timeout_task =  GNUNET_SCHEDULER_add_delayed (HTTP_CLIENT_SESSION_TIMEOUT,
1578                                                    &client_session_timeout,
1579                                                    s);
1580   GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, plugin->name,
1581                    "Created new session %p for `%s' address `%s''\n",
1582                    s,
1583                    http_common_plugin_address_to_string (NULL, plugin->protocol, s->addr, s->addrlen),
1584                    GNUNET_i2s (&s->target));
1585
1586   /* add new session */
1587   GNUNET_CONTAINER_DLL_insert (plugin->head, plugin->tail, s);
1588
1589   /* initiate new connection */
1590   if (GNUNET_SYSERR == client_connect (s))
1591   {
1592     GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR, plugin->name,
1593                      "Cannot connect to peer `%s' address `%s''\n",
1594                      http_common_plugin_address_to_string (NULL, plugin->protocol, s->addr, s->addrlen),
1595                      GNUNET_i2s (&s->target));
1596     client_delete_session (s);
1597     return NULL;
1598   }
1599   return s;
1600 }
1601
1602
1603 /**
1604  * Setup http_client plugin
1605  *
1606  * @param plugin the plugin handle
1607  * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
1608  */
1609 static int
1610 client_start (struct HTTP_Client_Plugin *plugin)
1611 {
1612   curl_global_init (CURL_GLOBAL_ALL);
1613   plugin->curl_multi_handle = curl_multi_init ();
1614
1615   if (NULL == plugin->curl_multi_handle)
1616   {
1617     GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR, plugin->name,
1618                      _("Could not initialize curl multi handle, failed to start %s plugin!\n"),
1619                      plugin->name);
1620     return GNUNET_SYSERR;
1621   }
1622   return GNUNET_OK;
1623 }
1624
1625
1626 /**
1627  * Increment session timeout due to activity for session s
1628  *
1629  * param s the session
1630  */
1631 static void
1632 client_reschedule_session_timeout (struct Session *s)
1633 {
1634   GNUNET_assert (GNUNET_SCHEDULER_NO_TASK != s->timeout_task);
1635   GNUNET_SCHEDULER_cancel (s->timeout_task);
1636   s->timeout_task =  GNUNET_SCHEDULER_add_delayed (HTTP_CLIENT_SESSION_TIMEOUT,
1637                                                    &client_session_timeout,
1638                                                    s);
1639   GNUNET_log (TIMEOUT_LOG,
1640               "Timeout rescheduled for session %p set to %s\n",
1641               s,
1642               GNUNET_STRINGS_relative_time_to_string (HTTP_CLIENT_SESSION_TIMEOUT,
1643                                                       GNUNET_YES));
1644 }
1645
1646
1647 /**
1648  * Another peer has suggested an address for this
1649  * peer and transport plugin.  Check that this could be a valid
1650  * address.  If so, consider adding it to the list
1651  * of addresses.
1652  *
1653  * @param cls closure with the `struct Plugin`
1654  * @param addr pointer to the address
1655  * @param addrlen length of @a addr
1656  * @return #GNUNET_OK if this is a plausible address for this peer
1657  *         and transport; always returns #GNUNET_NO (this is the client!)
1658  */
1659 static int
1660 http_client_plugin_address_suggested (void *cls,
1661                                       const void *addr,
1662                                       size_t addrlen)
1663 {
1664   /* struct Plugin *plugin = cls; */
1665
1666   /* A HTTP/S client does not have any valid address so:*/
1667   return GNUNET_NO;
1668 }
1669
1670
1671 /**
1672  * Exit point from the plugin.
1673  *
1674  * @param cls api as closure
1675  * @return NULL
1676  */
1677 void *
1678 LIBGNUNET_PLUGIN_TRANSPORT_DONE (void *cls)
1679 {
1680   struct GNUNET_TRANSPORT_PluginFunctions *api = cls;
1681   struct HTTP_Client_Plugin *plugin = api->cls;
1682   struct Session *pos;
1683   struct Session *next;
1684
1685   if (NULL == api->cls)
1686   {
1687     /* Stub shutdown */
1688     GNUNET_free (api);
1689     return NULL;
1690   }
1691
1692   GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, plugin->name,
1693                    _("Shutting down plugin `%s'\n"),
1694                    plugin->name);
1695
1696
1697   next = plugin->head;
1698   while (NULL != (pos = next))
1699   {
1700     next = pos->next;
1701     http_client_session_disconnect (plugin, pos);
1702   }
1703   if (GNUNET_SCHEDULER_NO_TASK != plugin->client_perform_task)
1704   {
1705     GNUNET_SCHEDULER_cancel (plugin->client_perform_task);
1706     plugin->client_perform_task = GNUNET_SCHEDULER_NO_TASK;
1707   }
1708
1709
1710   if (NULL != plugin->curl_multi_handle)
1711   {
1712     curl_multi_cleanup (plugin->curl_multi_handle);
1713     plugin->curl_multi_handle = NULL;
1714   }
1715   curl_global_cleanup ();
1716
1717   GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, plugin->name,
1718                    _("Shutdown for plugin `%s' complete\n"),
1719                    plugin->name);
1720
1721   GNUNET_free (plugin);
1722   GNUNET_free (api);
1723   return NULL;
1724 }
1725
1726
1727 /**
1728  * Configure plugin
1729  *
1730  * @param plugin the plugin handle
1731  * @return #GNUNET_OK on success, #GNUNET_SYSERR on failure
1732  */
1733 static int
1734 client_configure_plugin (struct HTTP_Client_Plugin *plugin)
1735 {
1736   unsigned long long max_connections;
1737
1738   /* Optional parameters */
1739   if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_number (plugin->env->cfg,
1740                       plugin->name,
1741                       "MAX_CONNECTIONS", &max_connections))
1742     max_connections = 128;
1743   plugin->max_connections = max_connections;
1744
1745   GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, plugin->name,
1746                    _("Maximum number of connections is %u\n"),
1747                    plugin->max_connections);
1748   return GNUNET_OK;
1749 }
1750
1751
1752 static const char *
1753 http_plugin_address_to_string (void *cls,
1754                                const void *addr,
1755                                size_t addrlen)
1756 {
1757   return http_common_plugin_address_to_string (cls, PLUGIN_NAME, addr, addrlen);
1758 }
1759
1760
1761 static void
1762 http_client_plugin_update_session_timeout (void *cls,
1763                                   const struct GNUNET_PeerIdentity *peer,
1764                                   struct Session *session)
1765 {
1766   struct HTTP_Client_Plugin *plugin = cls;
1767
1768   /* lookup if session is really existing */
1769   if (GNUNET_YES != client_exist_session (plugin, session))
1770   {
1771     GNUNET_break (0);
1772     return;
1773   }
1774   client_reschedule_session_timeout (session);
1775 }
1776
1777 /**
1778  * Entry point for the plugin.
1779  */
1780 void *
1781 LIBGNUNET_PLUGIN_TRANSPORT_INIT (void *cls)
1782 {
1783   struct GNUNET_TRANSPORT_PluginEnvironment *env = cls;
1784   struct GNUNET_TRANSPORT_PluginFunctions *api;
1785   struct HTTP_Client_Plugin *plugin;
1786
1787   if (NULL == env->receive)
1788   {
1789     /* run in 'stub' mode (i.e. as part of gnunet-peerinfo), don't fully
1790        initialze the plugin or the API */
1791     api = GNUNET_new (struct GNUNET_TRANSPORT_PluginFunctions);
1792     api->cls = NULL;
1793     api->address_to_string = &http_plugin_address_to_string;
1794     api->string_to_address = &http_common_plugin_string_to_address;
1795     api->address_pretty_printer = &http_common_plugin_address_pretty_printer;
1796     return api;
1797   }
1798
1799   plugin = GNUNET_new (struct HTTP_Client_Plugin);
1800   plugin->env = env;
1801   api = GNUNET_new (struct GNUNET_TRANSPORT_PluginFunctions);
1802   api->cls = plugin;
1803   api->send = &http_client_plugin_send;
1804   api->disconnect_session = &http_client_session_disconnect;
1805   api->query_keepalive_factor = &http_client_query_keepalive_factor;
1806   api->disconnect_peer = &http_client_peer_disconnect;
1807   api->check_address = &http_client_plugin_address_suggested;
1808   api->get_session = &http_client_plugin_get_session;
1809   api->address_to_string = &http_plugin_address_to_string;
1810   api->string_to_address = &http_common_plugin_string_to_address;
1811   api->address_pretty_printer = &http_common_plugin_address_pretty_printer;
1812   api->get_network = &http_client_get_network;
1813   api->update_session_timeout = &http_client_plugin_update_session_timeout;
1814
1815 #if BUILD_HTTPS
1816   plugin->name = "transport-https_client";
1817   plugin->protocol = "https";
1818 #else
1819   plugin->name = "transport-http_client";
1820   plugin->protocol = "http";
1821 #endif
1822   plugin->last_tag = 1;
1823   plugin->options = 0; /* Setup options */
1824
1825   if (GNUNET_SYSERR == client_configure_plugin (plugin))
1826   {
1827     LIBGNUNET_PLUGIN_TRANSPORT_DONE (api);
1828     return NULL;
1829   }
1830
1831   /* Start client */
1832   if (GNUNET_SYSERR == client_start (plugin))
1833   {
1834     LIBGNUNET_PLUGIN_TRANSPORT_DONE (api);
1835     return NULL;
1836   }
1837   return api;
1838 }
1839
1840 /* end of plugin_transport_http_client.c */