http2: Use nghttp2_session_mem_recv and nghttp2_session_upgrade
[platform/upstream/curl.git] / lib / http2.c
1 /***************************************************************************
2  *                                  _   _ ____  _
3  *  Project                     ___| | | |  _ \| |
4  *                             / __| | | | |_) | |
5  *                            | (__| |_| |  _ <| |___
6  *                             \___|\___/|_| \_\_____|
7  *
8  * Copyright (C) 1998 - 2014, Daniel Stenberg, <daniel@haxx.se>, et al.
9  *
10  * This software is licensed as described in the file COPYING, which
11  * you should have received as part of this distribution. The terms
12  * are also available at http://curl.haxx.se/docs/copyright.html.
13  *
14  * You may opt to use, copy, modify, merge, publish, distribute and/or sell
15  * copies of the Software, and permit persons to whom the Software is
16  * furnished to do so, under the terms of the COPYING file.
17  *
18  * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19  * KIND, either express or implied.
20  *
21  ***************************************************************************/
22
23 #include "curl_setup.h"
24
25 #ifdef USE_NGHTTP2
26 #define _MPRINTF_REPLACE
27 #include <curl/mprintf.h>
28
29 #include <nghttp2/nghttp2.h>
30 #include "urldata.h"
31 #include "http2.h"
32 #include "http.h"
33 #include "sendf.h"
34 #include "curl_base64.h"
35 #include "curl_memory.h"
36
37 /* include memdebug.h last */
38 #include "memdebug.h"
39
40 #if (NGHTTP2_VERSION_NUM < 0x000300)
41 #error too old nghttp2 version, upgrade!
42 #endif
43
44 /*
45  * HTTP2 handler interface. This isn't added to the general list of protocols
46  * but will be used at run-time when the protocol is dynamically switched from
47  * HTTP to HTTP2.
48  */
49 const struct Curl_handler Curl_handler_http2 = {
50   "HTTP2",                              /* scheme */
51   ZERO_NULL,                            /* setup_connection */
52   ZERO_NULL,                            /* do_it */
53   ZERO_NULL     ,                       /* done */
54   ZERO_NULL,                            /* do_more */
55   ZERO_NULL,                            /* connect_it */
56   ZERO_NULL,                            /* connecting */
57   ZERO_NULL,                            /* doing */
58   ZERO_NULL,                            /* proto_getsock */
59   ZERO_NULL,                            /* doing_getsock */
60   ZERO_NULL,                            /* domore_getsock */
61   ZERO_NULL,                            /* perform_getsock */
62   ZERO_NULL,                            /* disconnect */
63   ZERO_NULL,                            /* readwrite */
64   PORT_HTTP,                            /* defport */
65   CURLPROTO_HTTP,                       /* protocol */
66   PROTOPT_NONE                          /* flags */
67 };
68
69
70 /*
71  * Store nghttp2 version info in this buffer, Prefix with a space.  Return
72  * total length written.
73  */
74 int Curl_http2_ver(char *p, size_t len)
75 {
76   nghttp2_info *h2 = nghttp2_version(0);
77   return snprintf(p, len, " nghttp2/%s", h2->version_str);
78 }
79
80 /*
81  * The implementation of nghttp2_send_callback type. Here we write |data| with
82  * size |length| to the network and return the number of bytes actually
83  * written. See the documentation of nghttp2_send_callback for the details.
84  */
85 static ssize_t send_callback(nghttp2_session *h2,
86                              const uint8_t *data, size_t length, int flags,
87                              void *userp)
88 {
89   struct connectdata *conn = (struct connectdata *)userp;
90   ssize_t written;
91   CURLcode rc =
92     Curl_write(conn, conn->sock[FIRSTSOCKET], data, length, &written);
93   (void)h2;
94   (void)flags;
95
96   if(rc) {
97     failf(conn->data, "Failed sending HTTP2 data");
98     return NGHTTP2_ERR_CALLBACK_FAILURE;
99   }
100   else if(!written)
101     return NGHTTP2_ERR_WOULDBLOCK;
102
103   return written;
104 }
105
106 static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame,
107                          void *userp)
108 {
109   struct connectdata *conn = (struct connectdata *)userp;
110   (void)session;
111   (void)frame;
112   infof(conn->data, "on_frame_recv() was called with header %x\n",
113         frame->hd.type);
114   return 0;
115 }
116
117 static int on_invalid_frame_recv(nghttp2_session *session,
118                                  const nghttp2_frame *frame,
119                                  nghttp2_error_code error_code, void *userp)
120 {
121   struct connectdata *conn = (struct connectdata *)userp;
122   (void)session;
123   (void)frame;
124   infof(conn->data, "on_invalid_frame_recv() was called, error_code = %d\n",
125         error_code);
126   return 0;
127 }
128
129 static int on_data_chunk_recv(nghttp2_session *session, uint8_t flags,
130                               int32_t stream_id,
131                               const uint8_t *data, size_t len, void *userp)
132 {
133   struct connectdata *conn = (struct connectdata *)userp;
134   struct http_conn *c = &conn->proto.httpc;
135   (void)session;
136   (void)flags;
137   (void)data;
138   infof(conn->data, "on_data_chunk_recv() "
139         "len = %u, stream = %x\n", len, stream_id);
140
141   if(len < c->len) {
142     memcpy(c->mem, data, len);
143     c->mem += len;
144     c->len -= len;
145   }
146   else {
147     infof(conn->data, "EEEEEEK\n");
148   }
149
150   return 0;
151 }
152
153 static int before_frame_send(nghttp2_session *session,
154                              const nghttp2_frame *frame,
155                              void *userp)
156 {
157   struct connectdata *conn = (struct connectdata *)userp;
158   (void)session;
159   (void)frame;
160   infof(conn->data, "before_frame_send() was called\n");
161   return 0;
162 }
163 static int on_frame_send(nghttp2_session *session,
164                          const nghttp2_frame *frame,
165                          void *userp)
166 {
167   struct connectdata *conn = (struct connectdata *)userp;
168   (void)session;
169   (void)frame;
170   infof(conn->data, "on_frame_send() was called\n");
171   return 0;
172 }
173 static int on_frame_not_send(nghttp2_session *session,
174                              const nghttp2_frame *frame,
175                              int lib_error_code, void *userp)
176 {
177   struct connectdata *conn = (struct connectdata *)userp;
178   (void)session;
179   (void)frame;
180   infof(conn->data, "on_frame_not_send() was called, lib_error_code = %d\n",
181         lib_error_code);
182   return 0;
183 }
184 static int on_stream_close(nghttp2_session *session, int32_t stream_id,
185                            nghttp2_error_code error_code, void *userp)
186 {
187   struct connectdata *conn = (struct connectdata *)userp;
188   (void)session;
189   (void)stream_id;
190   infof(conn->data, "on_stream_close() was called, error_code = %d\n",
191         error_code);
192   return 0;
193 }
194
195 static int on_unknown_frame_recv(nghttp2_session *session,
196                                  const uint8_t *head, size_t headlen,
197                                  const uint8_t *payload, size_t payloadlen,
198                                  void *userp)
199 {
200   struct connectdata *conn = (struct connectdata *)userp;
201   (void)session;
202   (void)head;
203   (void)headlen;
204   (void)payload;
205   (void)payloadlen;
206   infof(conn->data, "on_unknown_frame_recv() was called\n");
207   return 0;
208 }
209 static int on_begin_headers(nghttp2_session *session,
210                             const nghttp2_frame *frame, void *userp)
211 {
212   struct connectdata *conn = (struct connectdata *)userp;
213   (void)session;
214   (void)frame;
215   infof(conn->data, "on_begin_headers() was called\n");
216   return 0;
217 }
218
219 /* frame->hd.type is either NGHTTP2_HEADERS or NGHTTP2_PUSH_PROMISE */
220 static int on_header(nghttp2_session *session, const nghttp2_frame *frame,
221                       const uint8_t *name, size_t namelen,
222                       const uint8_t *value, size_t valuelen,
223                       void *userp)
224 {
225   struct connectdata *conn = (struct connectdata *)userp;
226   struct http_conn *c = &conn->proto.httpc;
227   size_t hlen = namelen + valuelen + 3; /* colon + CRLF == 3 bytes */
228
229   (void)session;
230   (void)frame;
231
232   if(namelen && (name[0] == ':')) {
233     /* special case */
234     hlen = snprintf(c->mem, c->len, "HTTP/2.0 %s\r\n", value);
235   }
236   else if(hlen + 1 < c->len) { /* hlen + a zero byte */
237     /* convert to a HTTP1-style header */
238     memcpy(c->mem, name, namelen);
239     c->mem[namelen]=':';
240     memcpy(&c->mem[namelen+1], value, valuelen);
241     c->mem[namelen + valuelen + 1]='\r';
242     c->mem[namelen + valuelen + 2]='\n';
243     c->mem[namelen + valuelen + 3]=0; /* to display this easier */
244   }
245   infof(conn->data, "Got %s", c->mem);
246   c->mem += hlen;
247   c->len -= hlen;
248
249   return 0; /* 0 is successful */
250 }
251
252 /*
253  * This is all callbacks nghttp2 calls
254  */
255 static const nghttp2_session_callbacks callbacks = {
256   send_callback,         /* nghttp2_send_callback */
257   NULL,                  /* nghttp2_recv_callback */
258   on_frame_recv,         /* nghttp2_on_frame_recv_callback */
259   on_invalid_frame_recv, /* nghttp2_on_invalid_frame_recv_callback */
260   on_data_chunk_recv,    /* nghttp2_on_data_chunk_recv_callback */
261   before_frame_send,     /* nghttp2_before_frame_send_callback */
262   on_frame_send,         /* nghttp2_on_frame_send_callback */
263   on_frame_not_send,     /* nghttp2_on_frame_not_send_callback */
264   on_stream_close,       /* nghttp2_on_stream_close_callback */
265   on_unknown_frame_recv, /* nghttp2_on_unknown_frame_recv_callback */
266   on_begin_headers,      /* nghttp2_on_begin_headers_callback */
267   on_header              /* nghttp2_on_header_callback */
268 };
269
270 /*
271  * The HTTP2 settings we send in the Upgrade request
272  */
273 static nghttp2_settings_entry settings[] = {
274   { NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100 },
275   { NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE, NGHTTP2_INITIAL_WINDOW_SIZE },
276 };
277
278 /*
279  * Initialize nghttp2 for a Curl connection
280  */
281 CURLcode Curl_http2_init(struct connectdata *conn) {
282   if(!conn->proto.httpc.h2) {
283     /* The nghttp2 session is not yet setup, do it */
284     int rc = nghttp2_session_client_new(&conn->proto.httpc.h2,
285                                         &callbacks, conn);
286     if(rc) {
287       failf(conn->data, "Couldn't initialize nghttp2!");
288       return CURLE_OUT_OF_MEMORY; /* most likely at least */
289     }
290   }
291   return CURLE_OK;
292 }
293
294 /*
295  * Send a request using http2
296  */
297 CURLcode Curl_http2_send_request(struct connectdata *conn)
298 {
299   (void)conn;
300   return CURLE_OK;
301 }
302
303 /*
304  * Append headers to ask for a HTTP1.1 to HTTP2 upgrade.
305  */
306 CURLcode Curl_http2_request_upgrade(Curl_send_buffer *req,
307                                     struct connectdata *conn)
308 {
309   CURLcode result;
310   ssize_t binlen;
311   char *base64;
312   size_t blen;
313   struct SingleRequest *k = &conn->data->req;
314   uint8_t *binsettings = conn->proto.httpc.binsettings;
315
316   Curl_http2_init(conn);
317
318   /* As long as we have a fixed set of settings, we don't have to dynamically
319    * figure out the base64 strings since it'll always be the same. However,
320    * the settings will likely not be fixed every time in the future.
321    */
322
323   /* this returns number of bytes it wrote */
324   binlen = nghttp2_pack_settings_payload(binsettings, H2_BINSETTINGS_LEN,
325                                          settings,
326                                          sizeof(settings)/sizeof(settings[0]));
327   if(!binlen) {
328     failf(conn->data, "nghttp2 unexpectedly failed on pack_settings_payload");
329     return CURLE_FAILED_INIT;
330   }
331   conn->proto.httpc.binlen = binlen;
332
333   result = Curl_base64_encode(conn->data, (const char *)binsettings, binlen,
334                               &base64, &blen);
335   if(result)
336     return result;
337
338   result = Curl_add_bufferf(req,
339                             "Connection: Upgrade, HTTP2-Settings\r\n"
340                             "Upgrade: %s\r\n"
341                             "HTTP2-Settings: %s\r\n",
342                             NGHTTP2_PROTO_VERSION_ID, base64);
343   free(base64);
344
345   k->upgr101 = UPGR101_REQUESTED;
346
347   return result;
348 }
349
350 #define H2_BUFSIZE 4096
351
352 /*
353  * If the read would block (EWOULDBLOCK) we return -1. Otherwise we return
354  * a regular CURLcode value.
355  */
356 static ssize_t http2_recv(struct connectdata *conn, int sockindex,
357                           char *mem, size_t len, CURLcode *err)
358 {
359   CURLcode rc;
360   ssize_t rv;
361   ssize_t nread;
362   char inbuf[H2_BUFSIZE];
363
364   (void)sockindex; /* we always do HTTP2 on sockindex 0 */
365
366   conn->proto.httpc.mem = mem;
367   conn->proto.httpc.len = len;
368
369   infof(conn->data, "http2_recv\n");
370
371   for(;;) {
372     rc = Curl_read_plain(conn->sock[FIRSTSOCKET], inbuf, H2_BUFSIZE, &nread);
373
374     if(rc == CURLE_AGAIN) {
375       *err = rc;
376       return -1;
377     }
378     if(rc) {
379       failf(conn->data, "Failed receiving HTTP2 data");
380       *err = CURLE_RECV_ERROR;
381       return 0;
382     }
383     infof(conn->data, "nread=%zd\n", nread);
384     if(!nread) {
385       *err = CURLE_RECV_ERROR;
386       return 0; /* TODO EOF? */
387     }
388     rv = nghttp2_session_mem_recv(conn->proto.httpc.h2,
389                                   (const uint8_t *)inbuf, nread);
390
391     if(nghttp2_is_fatal((int)rv)) {
392       failf(conn->data, "nghttp2_session_mem_recv() returned %d:%s\n",
393             rv, nghttp2_strerror((int)rv));
394       *err = CURLE_RECV_ERROR;
395       return 0;
396     }
397     if(rv < nread) {
398       /* Happens when NGHTTP2_ERR_PAUSE is returned from user callback */
399       break;
400     }
401   }
402   return len - conn->proto.httpc.len;
403 }
404
405 /* return number of received (decrypted) bytes */
406 static ssize_t http2_send(struct connectdata *conn, int sockindex,
407                           const void *mem, size_t len, CURLcode *err)
408 {
409   /* TODO: proper implementation */
410   (void)conn;
411   (void)sockindex;
412   (void)mem;
413   (void)len;
414   (void)err;
415   return 0;
416 }
417
418 int Curl_http2_switched(struct connectdata *conn)
419 {
420   int rc;
421   struct http_conn *httpc = &conn->proto.httpc;
422   /* we are switched! */
423   conn->handler = &Curl_handler_http2;
424   conn->recv[FIRSTSOCKET] = http2_recv;
425   conn->send[FIRSTSOCKET] = http2_send;
426   infof(conn->data, "We have switched to HTTP2\n");
427
428   /* send the SETTINGS frame (again) */
429   rc = nghttp2_session_upgrade(httpc->h2, httpc->binsettings, httpc->binlen,
430                                conn);
431   return rc;
432 }
433
434 #endif