From 71433c7fbfd26a517309b6fa375edc4856573d2f Mon Sep 17 00:00:00 2001
From: Fabian Frank <fabian@pagefault.de>
Date: Wed, 29 Jan 2014 21:28:50 -0800
Subject: [PATCH 2/2] http2: switch into http2 mode if NPN indicates

Check the NPN result before preparing an HTTP request and
switch into HTTP/2.0 mode if necessary. This is a work in
progress, the actual code to prepare and send the request
using nghttp2 is still missing from Curl_http2_send_request().

also propose to rename Curl_http2_request() to
Curl_http2_upgrade() or similar, to better reflect its
purpose in its name.
---
 lib/http.c      |  15 ++
 lib/http2.c     |  37 +++--
 lib/http2.c.bak | 458 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 lib/http2.h     |   4 +
 4 files changed, 505 insertions(+), 9 deletions(-)
 create mode 100644 lib/http2.c.bak

diff --git a/lib/http.c b/lib/http.c
index 8df0105..eaccc92 100644
--- a/lib/http.c
+++ b/lib/http.c
@@ -1668,6 +1668,21 @@ CURLcode Curl_http(struct connectdata *conn, bool *done)
      the rest of the request in the PERFORM phase. */
   *done = TRUE;
 
+  switch (conn->negnpn) {
+    case NPN_HTTP2_DRAFT09:
+      infof(data, "http, we have to use HTTP-draft-09/2\n");
+      Curl_http2_init(conn);
+      Curl_http2_switched(conn);
+      Curl_http2_send_request(conn);
+      break;
+    case NPN_HTTP1_1:
+      /* continue with HTTP/1.1 when explicitly requested */
+      break;
+    default:
+      /* and as fallback */
+      break;
+  }
+
   http = data->req.protop;
 
   if(!data->state.this_is_a_follow) {
diff --git a/lib/http2.c b/lib/http2.c
index c3ed3c6..4df74d1 100644
--- a/lib/http2.c
+++ b/lib/http2.c
@@ -114,6 +114,9 @@ static ssize_t recv_callback(nghttp2_session *h2,
                              void *userp)
 {
   struct connectdata *conn = (struct connectdata *)userp;
+
+  infof(conn->data, "recv_callback() was called with length %d\n", length);
+
   ssize_t nread;
   CURLcode rc = Curl_read_plain(conn->sock[FIRSTSOCKET], (char *)buf, length,
                                 &nread);
@@ -286,6 +289,30 @@ static nghttp2_settings_entry settings[] = {
 };
 
 /*
+ * Initialize nghttp2 for a Curl connection
+ */
+CURLcode Curl_http2_init(struct connectdata *conn) {
+  if(!conn->proto.httpc.h2) {
+    /* The nghttp2 session is not yet setup, do it */
+    int rc = nghttp2_session_client_new(&conn->proto.httpc.h2,
+                                        &callbacks, conn);
+    if(rc) {
+      failf(conn->data, "Couldn't initialize nghttp2!");
+      return CURLE_OUT_OF_MEMORY; /* most likely at least */
+    }
+  }
+  return CURLE_OK;
+}
+
+/*
+ * Send a request using http2
+ */
+CURLcode Curl_http2_send_request(struct connectdata *conn)
+{
+  return CURLE_OK;
+}
+
+/*
  * Append headers to ask for a HTTP1.1 to HTTP2 upgrade.
  */
 CURLcode Curl_http2_request(Curl_send_buffer *req,
@@ -298,15 +325,7 @@ CURLcode Curl_http2_request(Curl_send_buffer *req,
   size_t blen;
   struct SingleRequest *k = &conn->data->req;
 
-  if(!conn->proto.httpc.h2) {
-    /* The nghttp2 session is not yet setup, do it */
-    int rc = nghttp2_session_client_new(&conn->proto.httpc.h2,
-                                        &callbacks, conn);
-    if(rc) {
-      failf(conn->data, "Couldn't initialize nghttp2!");
-      return CURLE_OUT_OF_MEMORY; /* most likely at least */
-    }
-  }
+  Curl_http2_init(conn);
 
   /* As long as we have a fixed set of settings, we don't have to dynamically
    * figure out the base64 strings since it'll always be the same. However,
diff --git a/lib/http2.c.bak b/lib/http2.c.bak
new file mode 100644
index 0000000..ea804a1
--- /dev/null
+++ b/lib/http2.c.bak
@@ -0,0 +1,458 @@
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 1998 - 2014, Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at http://curl.haxx.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ***************************************************************************/
+
+#include "curl_setup.h"
+
+#ifdef USE_NGHTTP2
+#define _MPRINTF_REPLACE
+#include <curl/mprintf.h>
+
+#include <nghttp2/nghttp2.h>
+#include "urldata.h"
+#include "http2.h"
+#include "http.h"
+#include "sendf.h"
+#include "curl_base64.h"
+#include "curl_memory.h"
+
+/* include memdebug.h last */
+#include "memdebug.h"
+
+#if (NGHTTP2_VERSION_NUM < 0x000300)
+#error too old nghttp2 version, upgrade!
+#endif
+
+/*
+ * HTTP2 handler interface. This isn't added to the general list of protocols
+ * but will be used at run-time when the protocol is dynamically switched from
+ * HTTP to HTTP2.
+ */
+const struct Curl_handler Curl_handler_http2 = {
+  "HTTP2",                              /* scheme */
+  ZERO_NULL,                            /* setup_connection */
+  ZERO_NULL,                            /* do_it */
+  ZERO_NULL     ,                       /* done */
+  ZERO_NULL,                            /* do_more */
+  ZERO_NULL,                            /* connect_it */
+  ZERO_NULL,                            /* connecting */
+  ZERO_NULL,                            /* doing */
+  ZERO_NULL,                            /* proto_getsock */
+  ZERO_NULL,                            /* doing_getsock */
+  ZERO_NULL,                            /* domore_getsock */
+  ZERO_NULL,                            /* perform_getsock */
+  ZERO_NULL,                            /* disconnect */
+  ZERO_NULL,                            /* readwrite */
+  PORT_HTTP,                            /* defport */
+  0,                                    /* protocol */
+  PROTOPT_NONE                          /* flags */
+};
+
+
+/*
+ * Store nghttp2 version info in this buffer, Prefix with a space.  Return
+ * total length written.
+ */
+int Curl_http2_ver(char *p, size_t len)
+{
+  nghttp2_info *h2 = nghttp2_version(0);
+  return snprintf(p, len, " nghttp2/%s", h2->version_str);
+}
+
+/*
+ * The implementation of nghttp2_send_callback type. Here we write |data| with
+ * size |length| to the network and return the number of bytes actually
+ * written. See the documentation of nghttp2_send_callback for the details.
+ */
+static ssize_t send_callback(nghttp2_session *h2,
+                             const uint8_t *data, size_t length, int flags,
+                             void *userp)
+{
+  struct connectdata *conn = (struct connectdata *)userp;
+  ssize_t written;
+  CURLcode rc =
+    Curl_write(conn, conn->sock[FIRSTSOCKET], data, length, &written);
+  (void)h2;
+  (void)flags;
+
+  if(rc) {
+    failf(conn->data, "Failed sending HTTP2 data");
+    return NGHTTP2_ERR_CALLBACK_FAILURE;
+  }
+  else if(!written)
+    return NGHTTP2_ERR_WOULDBLOCK;
+
+  return written;
+}
+
+/*
+ * The implementation of nghttp2_recv_callback type. Here we read data from
+ * the network and write them in |buf|. The capacity of |buf| is |length|
+ * bytes. Returns the number of bytes stored in |buf|. See the documentation
+ * of nghttp2_recv_callback for the details.
+ */
+static ssize_t recv_callback(nghttp2_session *h2,
+                             uint8_t *buf, size_t length, int flags,
+                             void *userp)
+{
+  struct connectdata *conn = (struct connectdata *)userp;
+
+  infof(conn->data, "recv_callback() was called with length %d\n", length);
+
+  ssize_t nread;
+  CURLcode rc = Curl_read_plain(conn->sock[FIRSTSOCKET], (char *)buf, length,
+                                &nread);
+  (void)h2;
+  (void)flags;
+
+  if(rc) {
+    failf(conn->data, "Failed receiving HTTP2 data");
+    return NGHTTP2_ERR_CALLBACK_FAILURE;
+  }
+  if(!nread)
+    return NGHTTP2_ERR_WOULDBLOCK;
+
+  return nread;
+}
+
+static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame,
+                         void *userp)
+{
+  struct connectdata *conn = (struct connectdata *)userp;
+  (void)session;
+  (void)frame;
+  infof(conn->data, "on_frame_recv() was called with header %x\n",
+        frame->hd.type);
+  return 0;
+}
+
+static int on_invalid_frame_recv(nghttp2_session *session,
+                                 const nghttp2_frame *frame,
+                                 nghttp2_error_code error_code, void *userp)
+{
+  struct connectdata *conn = (struct connectdata *)userp;
+  (void)session;
+  (void)frame;
+  infof(conn->data, "on_invalid_frame_recv() was called, error_code = %d\n",
+        error_code);
+  return 0;
+}
+
+static int on_data_chunk_recv(nghttp2_session *session, uint8_t flags,
+                              int32_t stream_id,
+                              const uint8_t *data, size_t len, void *userp)
+{
+  struct connectdata *conn = (struct connectdata *)userp;
+  (void)session;
+  (void)flags;
+  (void)stream_id;
+  (void)data;
+  infof(conn->data, "on_data_chunk_recv() was called with len = %u\n", len);
+  return 0;
+}
+
+static int before_frame_send(nghttp2_session *session,
+                             const nghttp2_frame *frame,
+                             void *userp)
+{
+  struct connectdata *conn = (struct connectdata *)userp;
+  (void)session;
+  (void)frame;
+  infof(conn->data, "before_frame_send() was called\n");
+  return 0;
+}
+static int on_frame_send(nghttp2_session *session,
+                         const nghttp2_frame *frame,
+                         void *userp)
+{
+  struct connectdata *conn = (struct connectdata *)userp;
+  (void)session;
+  (void)frame;
+  infof(conn->data, "on_frame_send() was called\n");
+  return 0;
+}
+static int on_frame_not_send(nghttp2_session *session,
+                             const nghttp2_frame *frame,
+                             int lib_error_code, void *userp)
+{
+  struct connectdata *conn = (struct connectdata *)userp;
+  (void)session;
+  (void)frame;
+  infof(conn->data, "on_frame_not_send() was called, lib_error_code = %d\n",
+        lib_error_code);
+  return 0;
+}
+static int on_stream_close(nghttp2_session *session, int32_t stream_id,
+                           nghttp2_error_code error_code, void *userp)
+{
+  struct connectdata *conn = (struct connectdata *)userp;
+  (void)session;
+  (void)stream_id;
+  infof(conn->data, "on_stream_close() was called, error_code = %d\n",
+        error_code);
+  return 0;
+}
+
+static int on_unknown_frame_recv(nghttp2_session *session,
+                                 const uint8_t *head, size_t headlen,
+                                 const uint8_t *payload, size_t payloadlen,
+                                 void *userp)
+{
+  struct connectdata *conn = (struct connectdata *)userp;
+  (void)session;
+  (void)head;
+  (void)headlen;
+  (void)payload;
+  (void)payloadlen;
+  infof(conn->data, "on_unknown_frame_recv() was called\n");
+  return 0;
+}
+static int on_begin_headers(nghttp2_session *session,
+                            const nghttp2_frame *frame, void *userp)
+{
+  struct connectdata *conn = (struct connectdata *)userp;
+  (void)session;
+  (void)frame;
+  infof(conn->data, "on_begin_headers() was called\n");
+  return 0;
+}
+
+/* frame->hd.type is either NGHTTP2_HEADERS or NGHTTP2_PUSH_PROMISE */
+static int on_header(nghttp2_session *session, const nghttp2_frame *frame,
+                      const uint8_t *name, size_t namelen,
+                      const uint8_t *value, size_t valuelen,
+                      void *userp)
+{
+  struct connectdata *conn = (struct connectdata *)userp;
+  (void)session;
+  (void)frame;
+
+  if(namelen + valuelen < 200) {
+    char buffer[256];
+    memcpy(buffer, name, namelen);
+    buffer[namelen]=':';
+    memcpy(&buffer[namelen+1], value, valuelen);
+    buffer[namelen + valuelen + 1]=0;
+    infof(conn->data, "Got '%s'\n", buffer);
+    /* TODO: the headers need to be passed to the http parser */
+  }
+  else {
+    infof(conn->data, "Got header with no name or too long\n",
+          namelen, name, valuelen, value);
+  }
+
+  return 0; /* 0 is successful */
+}
+
+/*
+ * This is all callbacks nghttp2 calls
+ */
+static const nghttp2_session_callbacks callbacks = {
+  send_callback,         /* nghttp2_send_callback */
+  recv_callback,         /* nghttp2_recv_callback */
+  on_frame_recv,         /* nghttp2_on_frame_recv_callback */
+  on_invalid_frame_recv, /* nghttp2_on_invalid_frame_recv_callback */
+  on_data_chunk_recv,    /* nghttp2_on_data_chunk_recv_callback */
+  before_frame_send,     /* nghttp2_before_frame_send_callback */
+  on_frame_send,         /* nghttp2_on_frame_send_callback */
+  on_frame_not_send,     /* nghttp2_on_frame_not_send_callback */
+  on_stream_close,       /* nghttp2_on_stream_close_callback */
+  on_unknown_frame_recv, /* nghttp2_on_unknown_frame_recv_callback */
+  on_begin_headers,      /* nghttp2_on_begin_headers_callback */
+  on_header              /* nghttp2_on_header_callback */
+};
+
+/*
+ * The HTTP2 settings we send in the Upgrade request
+ */
+static nghttp2_settings_entry settings[] = {
+  { NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100 },
+  { NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE, NGHTTP2_INITIAL_WINDOW_SIZE },
+};
+
+/*
+ * Initialize nghttp2 for a Curl connection
+ */
+CURLcode Curl_http2_init(struct connectdata *conn) {
+  if(!conn->proto.httpc.h2) {
+    /* The nghttp2 session is not yet setup, do it */
+    int rc = nghttp2_session_client_new(&conn->proto.httpc.h2,
+                                        &callbacks, conn);
+    if(rc) {
+      failf(conn->data, "Couldn't initialize nghttp2!");
+      return CURLE_OUT_OF_MEMORY; /* most likely at least */
+    }
+  }
+}
+
+
+static void print_header(FILE *f,
+                         const uint8_t *name, size_t namelen,
+                         const uint8_t *value, size_t valuelen)
+{
+  fwrite(name, namelen, 1, f);
+  fprintf(f, ": ");
+  fwrite(value, valuelen, 1, f);
+  fprintf(f, "\n");
+}
+
+/* Print HTTP headers to |f|. Please note that this function does not
+ take into account that header name and value are sequence of
+ octets, therefore they may contain non-printable characters. */
+static void print_headers(FILE *f, nghttp2_nv *nva, size_t nvlen)
+{
+  size_t i;
+  for(i = 0; i < nvlen; ++i) {
+    print_header(f,
+                 nva[i].name, nva[i].namelen,
+                 nva[i].value, nva[i].valuelen);
+  }
+  fprintf(f, "\n");
+}
+
+/*
+ * Make a request using http2
+ */
+#define MAKE_NV(NAME, VALUE, VALUELEN)                                  \
+{ (uint8_t*)NAME, (uint8_t*)VALUE, sizeof(NAME) - 1, VALUELEN }
+
+#define MAKE_NV2(NAME, VALUE)                                           \
+{ (uint8_t*)NAME, (uint8_t*)VALUE, sizeof(NAME) - 1, sizeof(VALUE) - 1 }
+#define ARRLEN(x) (sizeof(x)/sizeof(x[0]))
+CURLcode Curl_http2_send_request(struct connectdata *conn)
+{
+  int ret;
+  nghttp2_settings_entry iv[1] = {
+    { NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100 }
+  };
+
+  nghttp2_nv hdrs[] = {
+    MAKE_NV2(":method", "GET"),
+    MAKE_NV(":scheme", "https", 4),
+    MAKE_NV(":authority", "www.twitter.com", 15),
+    MAKE_NV(":path", "/", 1)
+  };
+
+  ret = nghttp2_submit_settings(conn->proto.httpc.h2, NGHTTP2_FLAG_NONE,
+                          iv, ARRLEN(iv));
+    infof(conn->data, "nghttp2_submit_settings() returned %d\n", ret);
+
+  fprintf(stderr, "Request headers:\n");
+  print_headers(stderr, hdrs, ARRLEN(hdrs));
+
+  ret = nghttp2_submit_request(conn->proto.httpc.h2, NGHTTP2_PRI_DEFAULT,
+                              hdrs, ARRLEN(hdrs), NULL, NULL);
+  infof(conn->data, "nghttp2_submit_request() returned %d\n", ret);
+}
+
+/*
+ * Append headers to ask for a HTTP1.1 to HTTP2 upgrade.
+ */
+CURLcode Curl_http2_request(Curl_send_buffer *req,
+                            struct connectdata *conn)
+{
+  uint8_t binsettings[80];
+  CURLcode result;
+  ssize_t binlen;
+  char *base64;
+  size_t blen;
+  struct SingleRequest *k = &conn->data->req;
+
+  Curl_http2_init(conn);
+
+  /* As long as we have a fixed set of settings, we don't have to dynamically
+   * figure out the base64 strings since it'll always be the same. However,
+   * the settings will likely not be fixed every time in the future.
+   */
+
+  /* this returns number of bytes it wrote */
+  binlen = nghttp2_pack_settings_payload(binsettings,
+                                         sizeof(binsettings),
+                                         settings,
+                                         sizeof(settings)/sizeof(settings[0]));
+  if(!binlen) {
+    failf(conn->data, "nghttp2 unexpectedly failed on pack_settings_payload");
+    return CURLE_FAILED_INIT;
+  }
+
+  result = Curl_base64_encode(conn->data, (const char *)binsettings, binlen,
+                              &base64, &blen);
+  if(result)
+    return result;
+
+  result = Curl_add_bufferf(req,
+                            "Connection: Upgrade, HTTP2-Settings\r\n"
+                            "Upgrade: %s\r\n"
+                            "HTTP2-Settings: %s\r\n",
+                            NGHTTP2_PROTO_VERSION_ID, base64);
+  free(base64);
+
+  k->upgr101 = UPGR101_REQUESTED;
+
+  return result;
+}
+
+/*
+ * If the read would block (EWOULDBLOCK) we return -1. Otherwise we return
+ * a regular CURLcode value.
+ */
+static ssize_t http2_recv(struct connectdata *conn, int sockindex,
+                          char *mem, size_t len, CURLcode *err)
+{
+  int rc;
+  (void)sockindex; /* we always do HTTP2 on sockindex 0 */
+
+  conn->proto.httpc.mem = mem;
+  conn->proto.httpc.size = len;
+
+  rc = nghttp2_session_recv(conn->proto.httpc.h2);
+
+  if(rc < 0) {
+    failf(conn->data, "nghttp2_session_recv() returned %d\n",
+          rc);
+    *err = CURLE_RECV_ERROR;
+  }
+  return 0;
+}
+
+/* return number of received (decrypted) bytes */
+static ssize_t http2_send(struct connectdata *conn, int sockindex,
+                          const void *mem, size_t len, CURLcode *err)
+{
+  /* TODO: proper implementation */
+  (void)conn;
+  (void)sockindex;
+  (void)mem;
+  (void)len;
+  (void)err;
+  return 0;
+}
+
+void Curl_http2_switched(struct connectdata *conn)
+{
+  /* we are switched! */
+  conn->handler = &Curl_handler_http2;
+  conn->recv[FIRSTSOCKET] = http2_recv;
+  conn->send[FIRSTSOCKET] = http2_send;
+  infof(conn->data, "We have switched to HTTP2\n");
+}
+
+#endif
diff --git a/lib/http2.h b/lib/http2.h
index ffe1682..90e64ec 100644
--- a/lib/http2.h
+++ b/lib/http2.h
@@ -32,6 +32,10 @@
  */
 int Curl_http2_ver(char *p, size_t len);
 
+CURLcode Curl_http2_init(struct connectdata *conn);
+CURLcode Curl_http2_send_request(struct connectdata *conn);
+
+// propose to rename to Curl_http2_request_upgrade
 CURLcode Curl_http2_request(Curl_send_buffer *req,
                             struct connectdata *conn);
 void Curl_http2_switched(struct connectdata *conn);
-- 
1.8.3.2


