-doxygen, indentation
[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   GNUNET_assert (plugin != NULL);
478   GNUNET_assert (s != NULL);
479
480   /* lookup if session is really existing */
481   if (GNUNET_YES != client_exist_session (plugin, s))
482   {
483     GNUNET_break (0);
484     return GNUNET_SYSERR;
485   }
486
487   GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, s->plugin->name,
488                    "Session %p/connection %p: Sending message with %u to peer `%s' \n",
489                    s, s->client_put,
490                    msgbuf_size, GNUNET_i2s (&s->target));
491
492   /* create new message and schedule */
493   msg = GNUNET_malloc (sizeof (struct HTTP_Message) + msgbuf_size);
494   msg->next = NULL;
495   msg->size = msgbuf_size;
496   msg->pos = 0;
497   msg->buf = (char *) &msg[1];
498   msg->transmit_cont = cont;
499   msg->transmit_cont_cls = cont_cls;
500   memcpy (msg->buf, msgbuf, msgbuf_size);
501   GNUNET_CONTAINER_DLL_insert_tail (s->msg_head, s->msg_tail, msg);
502
503   GNUNET_asprintf (&stat_txt,
504                    "# bytes currently in %s_client buffers",
505                    plugin->protocol);
506   GNUNET_STATISTICS_update (plugin->env->stats,
507                             stat_txt, msgbuf_size, GNUNET_NO);
508   GNUNET_free (stat_txt);
509
510   if (GNUNET_YES == s->put_tmp_disconnecting)
511   {
512     /* PUT connection is currently getting disconnected */
513     s->put_reconnect_required = GNUNET_YES;
514     GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, s->plugin->name,
515                      "Session %p/connection %jp: currently disconnecting, reconnecting immediately\n",
516                      s, s->client_put);
517     return msgbuf_size;
518   }
519   else if (GNUNET_YES == s->put_paused)
520   {
521     /* PUT connection was paused, unpause */
522     GNUNET_assert (s->put_disconnect_task != GNUNET_SCHEDULER_NO_TASK);
523     GNUNET_SCHEDULER_cancel (s->put_disconnect_task);
524     s->put_disconnect_task = GNUNET_SCHEDULER_NO_TASK;
525     GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, s->plugin->name,
526                      "Session %p/connection %p: unpausing connection\n",
527                      s, s->client_put);
528     s->put_paused = GNUNET_NO;
529     curl_easy_pause (s->client_put, CURLPAUSE_CONT);
530   }
531   else if (GNUNET_YES == s->put_tmp_disconnected)
532   {
533     /* PUT connection was disconnected, reconnect */
534     GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, s->plugin->name,
535                      "Session %p: Reconnecting PUT connection\n",
536                      s);
537     s->put_tmp_disconnected = GNUNET_NO;
538     GNUNET_break (s->client_put == NULL);
539     if (GNUNET_SYSERR == client_connect_put (s))
540     {
541       return GNUNET_SYSERR;
542     }
543   }
544
545   client_schedule (s->plugin, GNUNET_YES);
546   client_reschedule_session_timeout (s);
547   return msgbuf_size;
548 }
549
550
551 /**
552  * Delete session s
553  *
554  * @param s the session to delete
555  */
556 static void
557 client_delete_session (struct Session *s)
558 {
559   struct HTTP_Client_Plugin *plugin = s->plugin;
560   struct HTTP_Message *pos;
561   struct HTTP_Message *next;
562
563   if (GNUNET_SCHEDULER_NO_TASK != s->timeout_task)
564   {
565     GNUNET_SCHEDULER_cancel (s->timeout_task);
566     s->timeout_task = GNUNET_SCHEDULER_NO_TASK;
567   }
568   if (GNUNET_SCHEDULER_NO_TASK != s->put_disconnect_task)
569   {
570       GNUNET_SCHEDULER_cancel (s->put_disconnect_task);
571       s->put_disconnect_task = GNUNET_SCHEDULER_NO_TASK;
572   }
573
574   GNUNET_CONTAINER_DLL_remove (plugin->head, plugin->tail, s);
575
576   next = s->msg_head;
577   while (NULL != (pos = next))
578   {
579     next = pos->next;
580     GNUNET_CONTAINER_DLL_remove (s->msg_head, s->msg_tail, pos);
581     if (pos->transmit_cont != NULL)
582       pos->transmit_cont (pos->transmit_cont_cls, &s->target, GNUNET_SYSERR,
583                           pos->size, pos->pos + s->overhead);
584     s->overhead = 0;
585     GNUNET_free (pos);
586   }
587
588   if (s->msg_tk != NULL)
589   {
590     GNUNET_SERVER_mst_destroy (s->msg_tk);
591     s->msg_tk = NULL;
592   }
593   GNUNET_free (s->addr);
594   GNUNET_free (s->url);
595   GNUNET_free (s);
596 }
597
598
599 /**
600  * Disconnect a session
601  *
602  * @param cls the `struct HTTP_Client_Plugin`
603  * @param s session
604  * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
605  */
606 static int
607 http_client_session_disconnect (void *cls,
608                                 struct Session *s)
609 {
610   struct HTTP_Client_Plugin *plugin = cls;
611   struct HTTP_Message *msg;
612   struct HTTP_Message *t;
613   int res = GNUNET_OK;
614   CURLMcode mret;
615
616   if (GNUNET_YES != client_exist_session (plugin, s))
617   {
618     GNUNET_break (0);
619     return GNUNET_SYSERR;
620   }
621
622   if (s->client_put != NULL)
623   {
624     GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, plugin->name,
625                      "Session %p/connection %p: disconnecting PUT connection to peer `%s'\n",
626                      s, s->client_put, GNUNET_i2s (&s->target));
627
628     /* remove curl handle from multi handle */
629     mret = curl_multi_remove_handle (plugin->curl_multi_handle, s->client_put);
630     if (mret != CURLM_OK)
631     {
632       /* clean up easy handle, handle is now invalid and free'd */
633       res = GNUNET_SYSERR;
634       GNUNET_break (0);
635     }
636     curl_easy_cleanup (s->client_put);
637     s->client_put = NULL;
638   }
639
640
641   if (s->recv_wakeup_task != GNUNET_SCHEDULER_NO_TASK)
642   {
643     GNUNET_SCHEDULER_cancel (s->recv_wakeup_task);
644     s->recv_wakeup_task = GNUNET_SCHEDULER_NO_TASK;
645   }
646
647   if (s->client_get != NULL)
648   {
649       GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, plugin->name,
650                        "Session %p/connection %p: disconnecting GET connection to peer `%s'\n",
651                        s, s->client_get, GNUNET_i2s (&s->target));
652
653     /* remove curl handle from multi handle */
654     mret = curl_multi_remove_handle (plugin->curl_multi_handle, s->client_get);
655     if (mret != CURLM_OK)
656     {
657       /* clean up easy handle, handle is now invalid and free'd */
658       res = GNUNET_SYSERR;
659       GNUNET_break (0);
660     }
661     curl_easy_cleanup (s->client_get);
662     s->client_get = NULL;
663   }
664
665   msg = s->msg_head;
666   while (msg != NULL)
667   {
668     t = msg->next;
669     if (NULL != msg->transmit_cont)
670       msg->transmit_cont (msg->transmit_cont_cls, &s->target, GNUNET_SYSERR,
671                           msg->size, msg->pos + s->overhead);
672     s->overhead = 0;
673     GNUNET_CONTAINER_DLL_remove (s->msg_head, s->msg_tail, msg);
674     GNUNET_free (msg);
675     msg = t;
676   }
677
678   GNUNET_assert (plugin->cur_connections >= 2);
679   plugin->cur_connections -= 2;
680   GNUNET_STATISTICS_set (plugin->env->stats,
681                          HTTP_STAT_STR_CONNECTIONS,
682                          plugin->cur_connections,
683                          GNUNET_NO);
684   GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, plugin->name,
685                    "Session %p: notifying transport about ending session\n",s);
686
687   plugin->env->session_end (plugin->env->cls, &s->target, s);
688   client_delete_session (s);
689
690   /* Re-schedule since handles have changed */
691   if (plugin->client_perform_task != GNUNET_SCHEDULER_NO_TASK)
692   {
693     GNUNET_SCHEDULER_cancel (plugin->client_perform_task);
694     plugin->client_perform_task = GNUNET_SCHEDULER_NO_TASK;
695   }
696   client_schedule (plugin, GNUNET_YES);
697
698   return res;
699 }
700
701
702 /**
703  * Function that can be used to force the plugin to disconnect
704  * from the given peer and cancel all previous transmissions
705  * (and their continuationc).
706  *
707  * @param cls closure
708  * @param target peer from which to disconnect
709  */
710 static void
711 http_client_peer_disconnect (void *cls,
712                              const struct GNUNET_PeerIdentity *target)
713 {
714   struct HTTP_Client_Plugin *plugin = cls;
715   struct Session *next = NULL;
716   struct Session *pos = NULL;
717
718   GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, plugin->name,
719                    "Transport tells me to disconnect `%s'\n",
720                    GNUNET_i2s (target));
721
722   next = plugin->head;
723   while (NULL != (pos = next))
724   {
725     next = pos->next;
726     if (0 == memcmp (target, &pos->target, sizeof (struct GNUNET_PeerIdentity)))
727     {
728       GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, plugin->name,
729                        "Disconnecting session %p to `%pos'\n",
730                        pos, GNUNET_i2s (target));
731       GNUNET_assert (GNUNET_OK == http_client_session_disconnect (plugin,
732                                                                   pos));
733     }
734   }
735 }
736
737
738 /**
739  * Check if a sessions exists for an specific address
740  *
741  * @param plugin the plugin
742  * @param address the address
743  * @return the session or NULL
744  */
745 static struct Session *
746 client_lookup_session (struct HTTP_Client_Plugin *plugin,
747                        const struct GNUNET_HELLO_Address *address)
748 {
749   struct Session *pos;
750
751   for (pos = plugin->head; NULL != pos; pos = pos->next)
752     if ((0 == memcmp (&address->peer, &pos->target, sizeof (struct GNUNET_PeerIdentity))) &&
753         (address->address_length == pos->addrlen) &&
754         (0 == memcmp (address->address, pos->addr, pos->addrlen)))
755       return pos;
756   return NULL;
757 }
758
759
760 static void
761 client_put_disconnect (void *cls,
762                        const struct GNUNET_SCHEDULER_TaskContext *tc)
763 {
764   struct Session *s = cls;
765
766   s->put_disconnect_task = GNUNET_SCHEDULER_NO_TASK;
767   GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, s->plugin->name,
768                    "Session %p/connection %p: will be disconnected due to no activity\n",
769                    s, s->client_put);
770   s->put_paused = GNUNET_NO;
771   s->put_tmp_disconnecting = GNUNET_YES;
772   curl_easy_pause (s->client_put, CURLPAUSE_CONT);
773   client_schedule (s->plugin, GNUNET_YES);
774 }
775
776
777 /**
778  * Callback method used with libcurl
779  * Method is called when libcurl needs to read data during sending
780  *
781  * @param stream pointer where to write data
782  * @param size size of an individual element
783  * @param nmemb count of elements that can be written to the buffer
784  * @param cls our `struct Session`
785  * @return bytes written to stream, returning 0 will terminate connection!
786  */
787 static size_t
788 client_send_cb (void *stream, size_t size, size_t nmemb, void *cls)
789 {
790   struct Session *s = cls;
791   struct HTTP_Client_Plugin *plugin = s->plugin;
792   struct HTTP_Message *msg = s->msg_head;
793   size_t len;
794   char *stat_txt;
795
796   if (GNUNET_YES != client_exist_session (plugin, s))
797   {
798     GNUNET_break (0);
799     return 0;
800   }
801   if (GNUNET_YES == s->put_tmp_disconnecting)
802   {
803
804       GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, s->plugin->name,
805                        "Session %p/connection %p: disconnect due to inactivity\n",
806                        s, s->client_put);
807       return 0;
808   }
809
810   if (NULL == msg)
811   {
812     GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, plugin->name,
813                      "Session %p/connection %p: nothing to send, suspending\n",
814                      s, s->client_put);
815     s->put_disconnect_task = GNUNET_SCHEDULER_add_delayed (PUT_DISCONNECT_TIMEOUT, &client_put_disconnect, s);
816     s->put_paused = GNUNET_YES;
817     return CURL_READFUNC_PAUSE;
818   }
819   /* data to send */
820   GNUNET_assert (msg->pos < msg->size);
821   /* calculate how much fits in buffer */
822   len = GNUNET_MIN (msg->size - msg->pos,
823                     size * nmemb);
824   memcpy (stream, &msg->buf[msg->pos], len);
825   msg->pos += len;
826   if (msg->pos == msg->size)
827   {
828     GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, plugin->name,
829                      "Session %p/connection %p: sent message with %u bytes sent, removing message from queue\n",
830                      s, s->client_put, msg->size, msg->pos);
831     /* Calling transmit continuation  */
832     GNUNET_CONTAINER_DLL_remove (s->msg_head, s->msg_tail, msg);
833     if (NULL != msg->transmit_cont)
834       msg->transmit_cont (msg->transmit_cont_cls, &s->target, GNUNET_OK,
835                           msg->size, msg->size + s->overhead);
836     s->overhead = 0;
837     GNUNET_free (msg);
838   }
839
840   GNUNET_asprintf (&stat_txt, "# bytes currently in %s_client buffers", plugin->protocol);
841   GNUNET_STATISTICS_update (plugin->env->stats,
842                             stat_txt, -len, GNUNET_NO);
843   GNUNET_free (stat_txt);
844
845   GNUNET_asprintf (&stat_txt, "# bytes transmitted via %s_client", plugin->protocol);
846   GNUNET_STATISTICS_update (plugin->env->stats,
847                             stat_txt, len, GNUNET_NO);
848   GNUNET_free (stat_txt);
849
850   client_reschedule_session_timeout (s);
851   return len;
852 }
853
854
855 /**
856  * Wake up a curl handle which was suspended
857  *
858  * @param cls the session
859  * @param tc task context
860  */
861 static void
862 client_wake_up (void *cls,
863                 const struct GNUNET_SCHEDULER_TaskContext *tc)
864 {
865   struct Session *s = cls;
866   struct HTTP_Client_Plugin *p = s->plugin;
867
868   if (GNUNET_YES != client_exist_session (p, s))
869   {
870     GNUNET_break (0);
871     return;
872   }
873   s->recv_wakeup_task = GNUNET_SCHEDULER_NO_TASK;
874   if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN))
875     return;
876   GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, s->plugin->name,
877                    "Session %p/connection %p: Waking up GET handle\n",
878                    s,
879                    s->client_get);
880   if (s->client_get != NULL)
881     curl_easy_pause (s->client_get, CURLPAUSE_CONT);
882 }
883
884
885 /**
886  * Callback for message stream tokenizer
887  *
888  * @param cls the session
889  * @param client not used
890  * @param message the message received
891  * @return always #GNUNET_OK
892  */
893 static int
894 client_receive_mst_cb (void *cls, void *client,
895                        const struct GNUNET_MessageHeader *message)
896 {
897   struct Session *s = cls;
898   struct HTTP_Client_Plugin *plugin;
899   struct GNUNET_TIME_Relative delay;
900   struct GNUNET_ATS_Information atsi;
901   char *stat_txt;
902
903   plugin = s->plugin;
904   if (GNUNET_YES != client_exist_session (plugin, s))
905   {
906     GNUNET_break (0);
907     return GNUNET_OK;
908   }
909
910   atsi.type = htonl (GNUNET_ATS_NETWORK_TYPE);
911   atsi.value = s->ats_address_network_type;
912   GNUNET_break (s->ats_address_network_type != ntohl (GNUNET_ATS_NET_UNSPECIFIED));
913   delay = s->plugin->env->receive (plugin->env->cls, &s->target, message,
914                                    s, (const char *) s->addr, s->addrlen);
915
916   plugin->env->update_address_metrics (plugin->env->cls,
917                                        &s->target,
918                                        s->addr,
919                                        s->addrlen,
920                                        s,
921                                        &atsi, 1);
922
923   GNUNET_asprintf (&stat_txt,
924                    "# bytes received via %s_client",
925                    plugin->protocol);
926   GNUNET_STATISTICS_update (plugin->env->stats,
927                             stat_txt, ntohs(message->size), GNUNET_NO);
928   GNUNET_free (stat_txt);
929
930   s->next_receive =
931       GNUNET_TIME_absolute_add (GNUNET_TIME_absolute_get (), delay);
932
933   if (GNUNET_TIME_absolute_get ().abs_value_us < s->next_receive.abs_value_us)
934   {
935     GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, plugin->name,
936                      "Client: peer `%s' address `%s' next read delayed for %s\n",
937                      GNUNET_i2s (&s->target),
938                      http_common_plugin_address_to_string (NULL,
939                                                            s->plugin->protocol,
940                                                            s->addr, s->addrlen),
941                      GNUNET_STRINGS_relative_time_to_string (delay,
942                                                              GNUNET_YES));
943   }
944   client_reschedule_session_timeout (s);
945   return GNUNET_OK;
946 }
947
948
949 /**
950  * Callback method used with libcurl when data for a PUT connection are
951  * received. We do not expect data here, so we just dismiss it
952  *
953  * @param stream pointer where to write data
954  * @param size size of an individual element
955  * @param nmemb count of elements that can be written to the buffer
956  * @param cls destination pointer, passed to the libcurl handle
957  * @return bytes read from stream
958  */
959 static size_t
960 client_receive_put (void *stream, size_t size, size_t nmemb, void *cls)
961 {
962   return size * nmemb;
963 }
964
965
966 /**
967  * Callback method used with libcurl when data for a GET connection are
968  * received. Forward to MST
969  *
970  * @param stream pointer where to write data
971  * @param size size of an individual element
972  * @param nmemb count of elements that can be written to the buffer
973  * @param cls destination pointer, passed to the libcurl handle
974  * @return bytes read from stream
975  */
976 static size_t
977 client_receive (void *stream, size_t size, size_t nmemb, void *cls)
978 {
979   struct Session *s = cls;
980   struct GNUNET_TIME_Absolute now;
981   size_t len = size * nmemb;
982
983   GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, s->plugin->name,
984                    "Session %p / connection %p: Received %u bytes from peer `%s'\n",
985                    s, s->client_get,
986                    len, GNUNET_i2s (&s->target));
987   now = GNUNET_TIME_absolute_get ();
988   if (now.abs_value_us < s->next_receive.abs_value_us)
989   {
990     struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
991     struct GNUNET_TIME_Relative delta =
992         GNUNET_TIME_absolute_get_difference (now, s->next_receive);
993     GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, s->plugin->name,
994                      "Session %p / connection %p: No inbound bandwidth available! Next read was delayed for %s\n",
995                      s, s->client_get,
996                      GNUNET_STRINGS_relative_time_to_string (delta,
997                                                              GNUNET_YES));
998     if (s->recv_wakeup_task != GNUNET_SCHEDULER_NO_TASK)
999     {
1000       GNUNET_SCHEDULER_cancel (s->recv_wakeup_task);
1001       s->recv_wakeup_task = GNUNET_SCHEDULER_NO_TASK;
1002     }
1003     s->recv_wakeup_task =
1004         GNUNET_SCHEDULER_add_delayed (delta, &client_wake_up, s);
1005     return CURL_WRITEFUNC_PAUSE;
1006   }
1007   if (NULL == s->msg_tk)
1008     s->msg_tk = GNUNET_SERVER_mst_create (&client_receive_mst_cb, s);
1009   GNUNET_SERVER_mst_receive (s->msg_tk, s, stream, len, GNUNET_NO, GNUNET_NO);
1010   return len;
1011 }
1012
1013
1014 /**
1015  * Task performing curl operations
1016  *
1017  * @param cls plugin as closure
1018  * @param tc gnunet scheduler task context
1019  */
1020 static void
1021 client_run (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc);
1022
1023
1024 /**
1025  * Function setting up file descriptors and scheduling task to run
1026  *
1027  * @param plugin the plugin as closure
1028  * @param now schedule task in 1ms, regardless of what curl may say
1029  * @return #GNUNET_SYSERR for hard failure, #GNUNET_OK for ok
1030  */
1031 static int
1032 client_schedule (struct HTTP_Client_Plugin *plugin, int now)
1033 {
1034   fd_set rs;
1035   fd_set ws;
1036   fd_set es;
1037   int max;
1038   struct GNUNET_NETWORK_FDSet *grs;
1039   struct GNUNET_NETWORK_FDSet *gws;
1040   long to;
1041   CURLMcode mret;
1042   struct GNUNET_TIME_Relative timeout;
1043
1044   /* Cancel previous scheduled task */
1045   if (plugin->client_perform_task != GNUNET_SCHEDULER_NO_TASK)
1046   {
1047     GNUNET_SCHEDULER_cancel (plugin->client_perform_task);
1048     plugin->client_perform_task = GNUNET_SCHEDULER_NO_TASK;
1049   }
1050   max = -1;
1051   FD_ZERO (&rs);
1052   FD_ZERO (&ws);
1053   FD_ZERO (&es);
1054   mret = curl_multi_fdset (plugin->curl_multi_handle, &rs, &ws, &es, &max);
1055   if (mret != CURLM_OK)
1056   {
1057     GNUNET_log (GNUNET_ERROR_TYPE_ERROR, _("%s failed at %s:%d: `%s'\n"),
1058                 "curl_multi_fdset", __FILE__, __LINE__,
1059                 curl_multi_strerror (mret));
1060     return GNUNET_SYSERR;
1061   }
1062   mret = curl_multi_timeout (plugin->curl_multi_handle, &to);
1063   if (to == -1)
1064     timeout = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 1);
1065   else
1066     timeout = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, to);
1067   if (now == GNUNET_YES)
1068     timeout = GNUNET_TIME_UNIT_MILLISECONDS;
1069
1070   if (mret != CURLM_OK)
1071   {
1072     GNUNET_log (GNUNET_ERROR_TYPE_ERROR, _("%s failed at %s:%d: `%s'\n"),
1073                 "curl_multi_timeout", __FILE__, __LINE__,
1074                 curl_multi_strerror (mret));
1075     return GNUNET_SYSERR;
1076   }
1077
1078   grs = GNUNET_NETWORK_fdset_create ();
1079   gws = GNUNET_NETWORK_fdset_create ();
1080   GNUNET_NETWORK_fdset_copy_native (grs, &rs, max + 1);
1081   GNUNET_NETWORK_fdset_copy_native (gws, &ws, max + 1);
1082
1083   plugin->client_perform_task =
1084       GNUNET_SCHEDULER_add_select (GNUNET_SCHEDULER_PRIORITY_DEFAULT,
1085                                    timeout, grs, gws,
1086                                    &client_run, plugin);
1087   GNUNET_NETWORK_fdset_destroy (gws);
1088   GNUNET_NETWORK_fdset_destroy (grs);
1089   return GNUNET_OK;
1090 }
1091
1092
1093 /**
1094  * Task performing curl operations
1095  *
1096  * @param cls plugin as closure
1097  * @param tc gnunet scheduler task context
1098  */
1099 static void
1100 client_run (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
1101 {
1102   struct HTTP_Client_Plugin *plugin = cls;
1103   int running;
1104   long http_statuscode;
1105   CURLMcode mret;
1106
1107   GNUNET_assert (cls != NULL);
1108
1109   plugin->client_perform_task = GNUNET_SCHEDULER_NO_TASK;
1110   if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN))
1111     return;
1112
1113   do
1114   {
1115     running = 0;
1116     mret = curl_multi_perform (plugin->curl_multi_handle, &running);
1117
1118     CURLMsg *msg;
1119     int msgs_left;
1120
1121     while ((msg = curl_multi_info_read (plugin->curl_multi_handle, &msgs_left)))
1122     {
1123       CURL *easy_h = msg->easy_handle;
1124       struct Session *s = NULL;
1125       char *d = (char *) s;
1126
1127       if (easy_h == NULL)
1128       {
1129         GNUNET_break (0);
1130         GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, plugin->name,
1131                          "Client: connection to ended with reason %i: `%s', %i handles running\n",
1132                          msg->data.result,
1133                          curl_easy_strerror (msg->data.result), running);
1134         continue;
1135       }
1136
1137       GNUNET_assert (CURLE_OK ==
1138                      curl_easy_getinfo (easy_h, CURLINFO_PRIVATE, &d));
1139       s = (struct Session *) d;
1140
1141       if (GNUNET_YES != client_exist_session(plugin, s))
1142       {
1143         GNUNET_break (0);
1144         return;
1145       }
1146
1147       GNUNET_assert (s != NULL);
1148       if (msg->msg == CURLMSG_DONE)
1149       {
1150         curl_easy_getinfo (easy_h, CURLINFO_RESPONSE_CODE, &http_statuscode);
1151         if (easy_h == s->client_put)
1152         {
1153             if  ((0 != msg->data.result) || (http_statuscode != 200))
1154             {
1155                 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, plugin->name,
1156                   "Session %p/connection %p: PUT connection to `%s' ended with status %i reason %i: `%s'\n",
1157                   s, msg->easy_handle, GNUNET_i2s (&s->target),
1158                   http_statuscode,
1159                   msg->data.result,
1160                   curl_easy_strerror (msg->data.result));
1161             }
1162             else
1163               GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, plugin->name,
1164                 "Session %p/connection %p: PUT connection to `%s' ended normal\n",
1165                 s, msg->easy_handle, GNUNET_i2s (&s->target));
1166             if (s->client_get == NULL)
1167             {
1168               /* Disconnect other transmission direction and tell transport */
1169             }
1170             curl_multi_remove_handle (plugin->curl_multi_handle, easy_h);
1171             curl_easy_cleanup (easy_h);
1172             s->put_tmp_disconnecting = GNUNET_NO;
1173             s->put_tmp_disconnected = GNUNET_YES;
1174             s->client_put = NULL;
1175             s->put.easyhandle = NULL;
1176             s->put.s = NULL;
1177
1178             /*
1179              * Handling a rare case:
1180              * plugin_send was called during temporary put disconnect,
1181              * reconnect required after connection was disconnected
1182              */
1183             if (GNUNET_YES == s->put_reconnect_required)
1184             {
1185                 s->put_reconnect_required = GNUNET_NO;
1186                 if (GNUNET_SYSERR == client_connect_put(s))
1187                 {
1188                     GNUNET_break (s->client_put == NULL);
1189                     GNUNET_break (s->put_tmp_disconnected == GNUNET_NO);
1190                 }
1191             }
1192         }
1193         if (easy_h == s->client_get)
1194         {
1195             if  ((0 != msg->data.result) || (http_statuscode != 200))
1196             {
1197               GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, plugin->name,
1198                 "Session %p/connection %p: GET connection to `%s' ended with status %i reason %i: `%s'\n",
1199                 s, msg->easy_handle, GNUNET_i2s (&s->target),
1200                 http_statuscode,
1201                 msg->data.result,
1202                 curl_easy_strerror (msg->data.result));
1203
1204             }
1205             else
1206               GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, plugin->name,
1207                 "Session %p/connection %p: GET connection to `%s' ended normal\n",
1208                 s, msg->easy_handle, GNUNET_i2s (&s->target));
1209             /* Disconnect other transmission direction and tell transport */
1210             s->get.easyhandle = NULL;
1211             s->get.s = NULL;
1212             http_client_session_disconnect (plugin, s);
1213         }
1214       }
1215     }
1216   }
1217   while (mret == CURLM_CALL_MULTI_PERFORM);
1218   client_schedule (plugin, GNUNET_NO);
1219 }
1220
1221
1222 /**
1223  * Connect GET connection for a session
1224  *
1225  * @param s the session to connect
1226  * @return #GNUNET_OK on success, #GNUNET_SYSERR otherwise
1227  */
1228 static int
1229 client_connect_get (struct Session *s)
1230 {
1231
1232   CURLMcode mret;
1233   /* create get connection */
1234   s->client_get = curl_easy_init ();
1235   s->get.s = s;
1236   s->get.easyhandle = s->client_get;
1237 #if VERBOSE_CURL
1238   curl_easy_setopt (s->client_get, CURLOPT_VERBOSE, 1L);
1239   curl_easy_setopt (s->client_get, CURLOPT_DEBUGFUNCTION, &client_log);
1240   curl_easy_setopt (s->client_get, CURLOPT_DEBUGDATA, &s->get);
1241 #endif
1242 #if BUILD_HTTPS
1243   curl_easy_setopt (s->client_get, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1);
1244   if (HTTP_OPTIONS_VERIFY_CERTIFICATE ==
1245       (ntohl (s->addr->options) & HTTP_OPTIONS_VERIFY_CERTIFICATE))
1246   {
1247     curl_easy_setopt (s->client_get, CURLOPT_SSL_VERIFYPEER, 1L);
1248     curl_easy_setopt (s->client_get, CURLOPT_SSL_VERIFYHOST, 2L);
1249   }
1250   else
1251   {
1252     curl_easy_setopt (s->client_get, CURLOPT_SSL_VERIFYPEER, 0);
1253     curl_easy_setopt (s->client_get, CURLOPT_SSL_VERIFYHOST, 0);
1254   }
1255   curl_easy_setopt (s->client_get, CURLOPT_PROTOCOLS, CURLPROTO_HTTPS);
1256   curl_easy_setopt (s->client_get, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTPS);
1257 #else
1258   curl_easy_setopt (s->client_get, CURLOPT_PROTOCOLS, CURLPROTO_HTTP);
1259   curl_easy_setopt (s->client_get, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP);
1260 #endif
1261
1262   curl_easy_setopt (s->client_get, CURLOPT_URL, s->url);
1263   //curl_easy_setopt (s->client_get, CURLOPT_HEADERFUNCTION, &curl_get_header_cb);
1264   //curl_easy_setopt (s->client_get, CURLOPT_WRITEHEADER, ps);
1265   curl_easy_setopt (s->client_get, CURLOPT_READFUNCTION, client_send_cb);
1266   curl_easy_setopt (s->client_get, CURLOPT_READDATA, s);
1267   curl_easy_setopt (s->client_get, CURLOPT_WRITEFUNCTION, client_receive);
1268   curl_easy_setopt (s->client_get, CURLOPT_WRITEDATA, s);
1269   /* No timeout by default, timeout done with session timeout */
1270   curl_easy_setopt (s->client_get, CURLOPT_TIMEOUT, 0);
1271   curl_easy_setopt (s->client_get, CURLOPT_PRIVATE, s);
1272   curl_easy_setopt (s->client_get, CURLOPT_CONNECTTIMEOUT_MS,
1273                     (long) HTTP_CLIENT_NOT_VALIDATED_TIMEOUT.rel_value_us / 1000LL);
1274   curl_easy_setopt (s->client_get, CURLOPT_BUFFERSIZE,
1275                     2 * GNUNET_SERVER_MAX_MESSAGE_SIZE);
1276 #if CURL_TCP_NODELAY
1277   curl_easy_setopt (ps->recv_endpoint, CURLOPT_TCP_NODELAY, 1);
1278 #endif
1279   curl_easy_setopt (s->client_get, CURLOPT_FOLLOWLOCATION, 0);
1280
1281   mret = curl_multi_add_handle (s->plugin->curl_multi_handle, s->client_get);
1282   if (mret != CURLM_OK)
1283   {
1284     GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR, s->plugin->name,
1285                      "Session %p : Failed to add GET handle to multihandle: `%s'\n",
1286                      s,
1287                      curl_multi_strerror (mret));
1288     curl_easy_cleanup (s->client_get);
1289     s->client_get = NULL;
1290     s->get.s = NULL;
1291     s->get.easyhandle = NULL;
1292     GNUNET_break (0);
1293     return GNUNET_SYSERR;
1294   }
1295
1296   return GNUNET_OK;
1297 }
1298
1299
1300 /**
1301  * Connect a HTTP put connection
1302  *
1303  * @param s the session to connect
1304  * @return #GNUNET_SYSERR for hard failure, #GNUNET_OK for ok
1305  */
1306 static int
1307 client_connect_put (struct Session *s)
1308 {
1309   CURLMcode mret;
1310
1311   /* create put connection */
1312   GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, s->plugin->name,
1313                    "Session %p : Init PUT handle\n", s);
1314   s->client_put = curl_easy_init ();
1315   s->put.s = s;
1316   s->put.easyhandle = s->client_put;
1317 #if VERBOSE_CURL
1318   curl_easy_setopt (s->client_put, CURLOPT_VERBOSE, 1L);
1319   curl_easy_setopt (s->client_put, CURLOPT_DEBUGFUNCTION, &client_log);
1320   curl_easy_setopt (s->client_put, CURLOPT_DEBUGDATA, &s->put);
1321 #endif
1322 #if BUILD_HTTPS
1323   curl_easy_setopt (s->client_put, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1);
1324   if (HTTP_OPTIONS_VERIFY_CERTIFICATE ==
1325       (ntohl (s->addr->options) & HTTP_OPTIONS_VERIFY_CERTIFICATE))
1326   {
1327     curl_easy_setopt (s->client_put, CURLOPT_SSL_VERIFYPEER, 1L);
1328     curl_easy_setopt (s->client_put, CURLOPT_SSL_VERIFYHOST, 2L);
1329   }
1330   else
1331   {
1332     curl_easy_setopt (s->client_put, CURLOPT_SSL_VERIFYPEER, 0);
1333     curl_easy_setopt (s->client_put, CURLOPT_SSL_VERIFYHOST, 0);
1334   }
1335   curl_easy_setopt (s->client_get, CURLOPT_PROTOCOLS, CURLPROTO_HTTPS);
1336   curl_easy_setopt (s->client_get, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTPS);
1337 #else
1338   curl_easy_setopt (s->client_get, CURLOPT_PROTOCOLS, CURLPROTO_HTTP);
1339   curl_easy_setopt (s->client_get, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP);
1340 #endif
1341   curl_easy_setopt (s->client_put, CURLOPT_URL, s->url);
1342   curl_easy_setopt (s->client_put, CURLOPT_UPLOAD, 1L);
1343   //curl_easy_setopt (s->client_put, CURLOPT_HEADERFUNCTION, &client_curl_header);
1344   //curl_easy_setopt (s->client_put, CURLOPT_WRITEHEADER, ps);
1345   curl_easy_setopt (s->client_put, CURLOPT_READFUNCTION, client_send_cb);
1346   curl_easy_setopt (s->client_put, CURLOPT_READDATA, s);
1347   curl_easy_setopt (s->client_put, CURLOPT_WRITEFUNCTION, client_receive_put);
1348   curl_easy_setopt (s->client_put, CURLOPT_WRITEDATA, s);
1349   /* No timeout by default, timeout done with session timeout */
1350   curl_easy_setopt (s->client_put, CURLOPT_TIMEOUT, 0);
1351   curl_easy_setopt (s->client_put, CURLOPT_PRIVATE, s);
1352   curl_easy_setopt (s->client_put, CURLOPT_CONNECTTIMEOUT_MS,
1353                     (long) HTTP_CLIENT_NOT_VALIDATED_TIMEOUT.rel_value_us / 1000LL);
1354   curl_easy_setopt (s->client_put, CURLOPT_BUFFERSIZE,
1355                     2 * GNUNET_SERVER_MAX_MESSAGE_SIZE);
1356 #if CURL_TCP_NODELAY
1357   curl_easy_setopt (s->client_put, CURLOPT_TCP_NODELAY, 1);
1358 #endif
1359   mret = curl_multi_add_handle (s->plugin->curl_multi_handle, s->client_put);
1360   if (mret != CURLM_OK)
1361   {
1362     GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR, s->plugin->name,
1363                      "Session %p : Failed to add PUT handle to multihandle: `%s'\n",
1364                      s,
1365                      curl_multi_strerror (mret));
1366     curl_easy_cleanup (s->client_put);
1367     s->client_put = NULL;
1368     s->put.easyhandle = NULL;
1369     s->put.s = NULL;
1370     s->put_tmp_disconnected = GNUNET_YES;
1371     return GNUNET_SYSERR;
1372   }
1373   s->put_tmp_disconnected = GNUNET_NO;
1374   return GNUNET_OK;
1375 }
1376
1377
1378 /**
1379  * Connect both PUT and GET connection for a session
1380  *
1381  * @param s the session to connect
1382  * @return #GNUNET_OK on success, #GNUNET_SYSERR otherwise
1383  */
1384 static int
1385 client_connect (struct Session *s)
1386 {
1387
1388   struct HTTP_Client_Plugin *plugin = s->plugin;
1389   int res = GNUNET_OK;
1390
1391   /* create url */
1392   if (NULL == http_common_plugin_address_to_string (NULL,
1393                                                     plugin->protocol,
1394                                                     s->addr, s->addrlen))
1395   {
1396     GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG,
1397                      plugin->name,
1398                      "Invalid address peer `%s'\n",
1399                      GNUNET_i2s (&s->target));
1400     return GNUNET_SYSERR;
1401   }
1402
1403   GNUNET_asprintf (&s->url, "%s/%s;%u",
1404                    http_common_plugin_address_to_url (NULL, s->addr, s->addrlen),
1405                    GNUNET_i2s_full (plugin->env->my_identity),
1406                    plugin->last_tag);
1407
1408   plugin->last_tag++;
1409
1410   GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, plugin->name,
1411                    "Initiating outbound session peer `%s' using address `%s'\n",
1412                    GNUNET_i2s (&s->target), s->url);
1413
1414   if ((GNUNET_SYSERR == client_connect_get (s)) ||
1415       (GNUNET_SYSERR == client_connect_put (s)))
1416   {
1417     GNUNET_break (0);
1418     return GNUNET_SYSERR;
1419   }
1420
1421   GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG,
1422                    plugin->name,
1423                    "Session %p: connected with connections GET %p and PUT %p\n",
1424                    s, s->client_get, s->client_put);
1425
1426   /* Perform connect */
1427   plugin->cur_connections += 2;
1428   GNUNET_STATISTICS_set (plugin->env->stats,
1429                          HTTP_STAT_STR_CONNECTIONS,
1430                          plugin->cur_connections,
1431                          GNUNET_NO);
1432
1433   /* Re-schedule since handles have changed */
1434   if (plugin->client_perform_task != GNUNET_SCHEDULER_NO_TASK)
1435   {
1436     GNUNET_SCHEDULER_cancel (plugin->client_perform_task);
1437     plugin->client_perform_task = GNUNET_SCHEDULER_NO_TASK;
1438   }
1439   plugin->client_perform_task = GNUNET_SCHEDULER_add_now (client_run, plugin);
1440   return res;
1441 }
1442
1443
1444 /**
1445  * Function obtain the network type for a session
1446  *
1447  * @param cls closure (`struct Plugin*`)
1448  * @param session the session
1449  * @return the network type
1450  */
1451 static enum GNUNET_ATS_Network_Type
1452 http_client_get_network (void *cls,
1453                          struct Session *session)
1454 {
1455   return ntohl (session->ats_address_network_type);
1456 }
1457
1458
1459 /**
1460  * Session was idle, so disconnect it
1461  *
1462  * @param cls the `struct Session` of the idle session
1463  * @param tc scheduler context
1464  */
1465 static void
1466 client_session_timeout (void *cls,
1467                         const struct GNUNET_SCHEDULER_TaskContext *tc)
1468 {
1469   struct Session *s = cls;
1470
1471   s->timeout_task = GNUNET_SCHEDULER_NO_TASK;
1472   GNUNET_log (TIMEOUT_LOG,
1473               "Session %p was idle for %s, disconnecting\n",
1474               s,
1475               GNUNET_STRINGS_relative_time_to_string (HTTP_CLIENT_SESSION_TIMEOUT,
1476                                                       GNUNET_YES));
1477
1478   /* call session destroy function */
1479   GNUNET_assert (GNUNET_OK == http_client_session_disconnect (s->plugin,
1480                                                               s));
1481 }
1482
1483
1484 /**
1485  * Creates a new outbound session the transport service will use to
1486  * send data to the peer
1487  *
1488  * @param cls the plugin
1489  * @param address the address
1490  * @return the session or NULL of max connections exceeded
1491  */
1492 static struct Session *
1493 http_client_plugin_get_session (void *cls,
1494                                 const struct GNUNET_HELLO_Address *address)
1495 {
1496   struct HTTP_Client_Plugin *plugin = cls;
1497   struct Session * s = NULL;
1498   struct sockaddr *sa;
1499   struct GNUNET_ATS_Information ats;
1500   size_t salen = 0;
1501   int res;
1502
1503   GNUNET_assert (address->address != NULL);
1504
1505   /* find existing session */
1506   s = client_lookup_session (plugin, address);
1507   if (s != NULL)
1508     return s;
1509
1510   if (plugin->max_connections <= plugin->cur_connections)
1511   {
1512     GNUNET_log_from (GNUNET_ERROR_TYPE_WARNING, plugin->name,
1513                      "Maximum number of connections (%u) reached: "
1514                      "cannot connect to peer `%s'\n",
1515                      plugin->max_connections,
1516                      GNUNET_i2s (&address->peer));
1517     return NULL;
1518   }
1519
1520   /* Determine network location */
1521   ats.type = htonl (GNUNET_ATS_NETWORK_TYPE);
1522   ats.value = htonl (GNUNET_ATS_NET_UNSPECIFIED);
1523   sa = http_common_socket_from_address (address->address, address->address_length, &res);
1524   if (GNUNET_SYSERR == res)
1525   {
1526     return NULL;
1527   }
1528   else if (GNUNET_YES == res)
1529   {
1530     GNUNET_assert (NULL != sa);
1531     if (AF_INET == sa->sa_family)
1532     {
1533       salen = sizeof (struct sockaddr_in);
1534     }
1535     else if (AF_INET6 == sa->sa_family)
1536     {
1537       salen = sizeof (struct sockaddr_in6);
1538     }
1539     ats = plugin->env->get_address_type (plugin->env->cls, sa, salen);
1540     //fprintf (stderr, "Address %s is in %s\n", GNUNET_a2s (sa,salen), GNUNET_ATS_print_network_type(ntohl(ats.value)));
1541     GNUNET_free (sa);
1542   }
1543   else if (GNUNET_NO == res)
1544   {
1545     /* Cannot convert to sockaddr -> is external hostname */
1546     ats.value = htonl (GNUNET_ATS_NET_WAN);
1547   }
1548   if (GNUNET_ATS_NET_UNSPECIFIED == ntohl (ats.value))
1549   {
1550     GNUNET_break (0);
1551     return NULL;
1552   }
1553
1554   s = GNUNET_new (struct Session);
1555   s->target = address->peer;
1556   s->plugin = plugin;
1557   s->addr = GNUNET_malloc (address->address_length);
1558   memcpy (s->addr, address->address, address->address_length);
1559   s->addrlen = address->address_length;
1560   s->ats_address_network_type = ats.value;
1561   s->put_paused = GNUNET_NO;
1562   s->put_tmp_disconnecting = GNUNET_NO;
1563   s->put_tmp_disconnected = GNUNET_NO;
1564   s->timeout_task =  GNUNET_SCHEDULER_add_delayed (HTTP_CLIENT_SESSION_TIMEOUT,
1565                                                    &client_session_timeout,
1566                                                    s);
1567   GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, plugin->name,
1568                    "Created new session %p for `%s' address `%s''\n",
1569                    s,
1570                    http_common_plugin_address_to_string (NULL, plugin->protocol, s->addr, s->addrlen),
1571                    GNUNET_i2s (&s->target));
1572
1573   /* add new session */
1574   GNUNET_CONTAINER_DLL_insert (plugin->head, plugin->tail, s);
1575
1576   /* initiate new connection */
1577   if (GNUNET_SYSERR == client_connect (s))
1578   {
1579     GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR, plugin->name,
1580                      "Cannot connect to peer `%s' address `%s''\n",
1581                      http_common_plugin_address_to_string (NULL, plugin->protocol, s->addr, s->addrlen),
1582                      GNUNET_i2s (&s->target));
1583     client_delete_session (s);
1584     return NULL;
1585   }
1586   return s;
1587 }
1588
1589
1590 /**
1591  * Setup http_client plugin
1592  *
1593  * @param plugin the plugin handle
1594  * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
1595  */
1596 static int
1597 client_start (struct HTTP_Client_Plugin *plugin)
1598 {
1599   curl_global_init (CURL_GLOBAL_ALL);
1600   plugin->curl_multi_handle = curl_multi_init ();
1601
1602   if (NULL == plugin->curl_multi_handle)
1603   {
1604     GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR, plugin->name,
1605                      _("Could not initialize curl multi handle, failed to start %s plugin!\n"),
1606                      plugin->name);
1607     return GNUNET_SYSERR;
1608   }
1609   return GNUNET_OK;
1610 }
1611
1612
1613 /**
1614  * Increment session timeout due to activity for session s
1615  *
1616  * param s the session
1617  */
1618 static void
1619 client_reschedule_session_timeout (struct Session *s)
1620 {
1621   GNUNET_assert (GNUNET_SCHEDULER_NO_TASK != s->timeout_task);
1622   GNUNET_SCHEDULER_cancel (s->timeout_task);
1623   s->timeout_task =  GNUNET_SCHEDULER_add_delayed (HTTP_CLIENT_SESSION_TIMEOUT,
1624                                                    &client_session_timeout,
1625                                                    s);
1626   GNUNET_log (TIMEOUT_LOG,
1627               "Timeout rescheduled for session %p set to %s\n",
1628               s,
1629               GNUNET_STRINGS_relative_time_to_string (HTTP_CLIENT_SESSION_TIMEOUT,
1630                                                       GNUNET_YES));
1631 }
1632
1633
1634 /**
1635  * Another peer has suggested an address for this
1636  * peer and transport plugin.  Check that this could be a valid
1637  * address.  If so, consider adding it to the list
1638  * of addresses.
1639  *
1640  * @param cls closure with the `struct Plugin`
1641  * @param addr pointer to the address
1642  * @param addrlen length of @a addr
1643  * @return #GNUNET_OK if this is a plausible address for this peer
1644  *         and transport; always returns #GNUNET_NO (this is the client!)
1645  */
1646 static int
1647 http_client_plugin_address_suggested (void *cls,
1648                                       const void *addr,
1649                                       size_t addrlen)
1650 {
1651   /* struct Plugin *plugin = cls; */
1652
1653   /* A HTTP/S client does not have any valid address so:*/
1654   return GNUNET_NO;
1655 }
1656
1657
1658 /**
1659  * Exit point from the plugin.
1660  *
1661  * @param cls api as closure
1662  * @return NULL
1663  */
1664 void *
1665 LIBGNUNET_PLUGIN_TRANSPORT_DONE (void *cls)
1666 {
1667   struct GNUNET_TRANSPORT_PluginFunctions *api = cls;
1668   struct HTTP_Client_Plugin *plugin = api->cls;
1669   struct Session *pos;
1670   struct Session *next;
1671
1672   if (NULL == api->cls)
1673   {
1674     /* Stub shutdown */
1675     GNUNET_free (api);
1676     return NULL;
1677   }
1678
1679   GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, plugin->name,
1680                    _("Shutting down plugin `%s'\n"),
1681                    plugin->name);
1682
1683
1684   next = plugin->head;
1685   while (NULL != (pos = next))
1686   {
1687     next = pos->next;
1688     http_client_session_disconnect (plugin, pos);
1689   }
1690   if (GNUNET_SCHEDULER_NO_TASK != plugin->client_perform_task)
1691   {
1692     GNUNET_SCHEDULER_cancel (plugin->client_perform_task);
1693     plugin->client_perform_task = GNUNET_SCHEDULER_NO_TASK;
1694   }
1695
1696
1697   if (NULL != plugin->curl_multi_handle)
1698   {
1699     curl_multi_cleanup (plugin->curl_multi_handle);
1700     plugin->curl_multi_handle = NULL;
1701   }
1702   curl_global_cleanup ();
1703
1704   GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, plugin->name,
1705                    _("Shutdown for plugin `%s' complete\n"),
1706                    plugin->name);
1707
1708   GNUNET_free (plugin);
1709   GNUNET_free (api);
1710   return NULL;
1711 }
1712
1713
1714 /**
1715  * Configure plugin
1716  *
1717  * @param plugin the plugin handle
1718  * @return #GNUNET_OK on success, #GNUNET_SYSERR on failure
1719  */
1720 static int
1721 client_configure_plugin (struct HTTP_Client_Plugin *plugin)
1722 {
1723   unsigned long long max_connections;
1724
1725   /* Optional parameters */
1726   if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_number (plugin->env->cfg,
1727                       plugin->name,
1728                       "MAX_CONNECTIONS", &max_connections))
1729     max_connections = 128;
1730   plugin->max_connections = max_connections;
1731
1732   GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, plugin->name,
1733                    _("Maximum number of connections is %u\n"),
1734                    plugin->max_connections);
1735   return GNUNET_OK;
1736 }
1737
1738
1739 static const char *
1740 http_plugin_address_to_string (void *cls,
1741                                const void *addr,
1742                                size_t addrlen)
1743 {
1744   return http_common_plugin_address_to_string (cls, PLUGIN_NAME, addr, addrlen);
1745 }
1746
1747
1748 /**
1749  * Entry point for the plugin.
1750  */
1751 void *
1752 LIBGNUNET_PLUGIN_TRANSPORT_INIT (void *cls)
1753 {
1754   struct GNUNET_TRANSPORT_PluginEnvironment *env = cls;
1755   struct GNUNET_TRANSPORT_PluginFunctions *api;
1756   struct HTTP_Client_Plugin *plugin;
1757
1758   if (NULL == env->receive)
1759   {
1760     /* run in 'stub' mode (i.e. as part of gnunet-peerinfo), don't fully
1761        initialze the plugin or the API */
1762     api = GNUNET_new (struct GNUNET_TRANSPORT_PluginFunctions);
1763     api->cls = NULL;
1764     api->address_to_string = &http_plugin_address_to_string;
1765     api->string_to_address = &http_common_plugin_string_to_address;
1766     api->address_pretty_printer = &http_common_plugin_address_pretty_printer;
1767     return api;
1768   }
1769
1770   plugin = GNUNET_new (struct HTTP_Client_Plugin);
1771   plugin->env = env;
1772   api = GNUNET_new (struct GNUNET_TRANSPORT_PluginFunctions);
1773   api->cls = plugin;
1774   api->send = &http_client_plugin_send;
1775   api->disconnect_session = &http_client_session_disconnect;
1776   api->disconnect_peer = &http_client_peer_disconnect;
1777   api->check_address = &http_client_plugin_address_suggested;
1778   api->get_session = &http_client_plugin_get_session;
1779   api->address_to_string = &http_plugin_address_to_string;
1780   api->string_to_address = &http_common_plugin_string_to_address;
1781   api->address_pretty_printer = &http_common_plugin_address_pretty_printer;
1782   api->get_network = &http_client_get_network;
1783
1784 #if BUILD_HTTPS
1785   plugin->name = "transport-https_client";
1786   plugin->protocol = "https";
1787 #else
1788   plugin->name = "transport-http_client";
1789   plugin->protocol = "http";
1790 #endif
1791   plugin->last_tag = 1;
1792   plugin->options = 0; /* Setup options */
1793
1794   if (GNUNET_SYSERR == client_configure_plugin (plugin))
1795   {
1796     LIBGNUNET_PLUGIN_TRANSPORT_DONE (api);
1797     return NULL;
1798   }
1799
1800   /* Start client */
1801   if (GNUNET_SYSERR == client_start (plugin))
1802   {
1803     LIBGNUNET_PLUGIN_TRANSPORT_DONE (api);
1804     return NULL;
1805   }
1806   return api;
1807 }
1808
1809 /* end of plugin_transport_http_client.c */