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