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